mirror of
https://github.com/outbackdingo/matchbox.git
synced 2026-01-28 02:19:35 +00:00
bootcfg/http: Upgrade from Ignition v1 to 2.0.0 and Fuze
* By default, templates for Ignition are rendered and parsed as github.com/coreos/fuze YAML configs, which formalize the transform from YAML to Ignition JSON * Ignition files (.ign/.ignition) should be validated and served directly, without template rendering * Remove support for Ignition v1 * This change is breaking! Users need to tweak any YAML Ignition templates to the very similar Fuze YAML format
This commit is contained in:
@@ -6,21 +6,19 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
fuze "github.com/coreos/fuze/config"
|
||||
ignition "github.com/coreos/ignition/config"
|
||||
ignitionTypes "github.com/coreos/ignition/config/types"
|
||||
ignitionV1 "github.com/coreos/ignition/config/v1"
|
||||
ignitionV1Types "github.com/coreos/ignition/config/v1/types"
|
||||
"golang.org/x/net/context"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/coreos/coreos-baremetal/bootcfg/server"
|
||||
pb "github.com/coreos/coreos-baremetal/bootcfg/server/serverpb"
|
||||
)
|
||||
|
||||
// ignitionHandler returns a handler that responds with the Ignition config
|
||||
// for the requester. The Ignition file referenced in the Profile is rendered
|
||||
// with metadata and parsed and validated as either YAML or JSON based on the
|
||||
// extension. The Ignition config is served as an HTTP JSON response.
|
||||
// for the requester. The Ignition file referenced in the Profile is parsed
|
||||
// as raw Ignition (for .ign/.ignition) or rendered to a Fuze config (YAML)
|
||||
// and converted to Ignition. Ignition configs are served as HTTP JSON
|
||||
// responses.
|
||||
func ignitionHandler(srv server.Server) ContextHandler {
|
||||
fn := func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
group, err := groupFromContext(ctx)
|
||||
@@ -39,6 +37,20 @@ func ignitionHandler(srv server.Server) ContextHandler {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip rendering if raw Ignition JSON is provided
|
||||
if isIgnition(profile.IgnitionId) {
|
||||
cfg, err := ignition.Parse([]byte(contents))
|
||||
if err != nil {
|
||||
log.Errorf("error parsing Ignition JSON: %v", err)
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
renderJSON(w, cfg)
|
||||
return
|
||||
}
|
||||
|
||||
// Fuze Config template
|
||||
|
||||
// collect data for rendering Ignition Config
|
||||
data := make(map[string]interface{})
|
||||
if group.Metadata != nil {
|
||||
@@ -62,17 +74,10 @@ func ignitionHandler(srv server.Server) ContextHandler {
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal YAML or JSON to Ignition V2
|
||||
cfg, err := parseToV2(buf.Bytes())
|
||||
// Parse fuze config into an Ignition config
|
||||
config, err := fuze.ParseAsV2_0_0(buf.Bytes())
|
||||
if err == nil {
|
||||
renderJSON(w, cfg)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal YAML or JSON to Ignition V1
|
||||
oldCfg, err := parseToV1(buf.Bytes())
|
||||
if err == nil {
|
||||
renderJSON(w, oldCfg)
|
||||
renderJSON(w, config)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,48 +88,7 @@ func ignitionHandler(srv server.Server) ContextHandler {
|
||||
return ContextHandlerFunc(fn)
|
||||
}
|
||||
|
||||
// parseToV2 parses raw JSON in Ignition v2 format and returns the
|
||||
// Ignition v2 Config struct.
|
||||
func parseToV2(data []byte) (cfg ignitionTypes.Config, err error) {
|
||||
// parse JSON v2 to Ignition
|
||||
cfg, err = ignition.ParseFromLatest(data)
|
||||
if err == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
if majorVersion(data) == 2 {
|
||||
err = yaml.Unmarshal(data, &cfg)
|
||||
}
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// parseToV1 parses raw JSON or YAML in Ignition v1 format and returns the
|
||||
// Ignition v1 Config struct.
|
||||
func parseToV1(data []byte) (cfg ignitionV1Types.Config, err error) {
|
||||
// parse JSON v1 to Ignition
|
||||
cfg, err = ignitionV1.Parse(data)
|
||||
if err == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
// unmarshal YAML v1 to Ignition
|
||||
err = yaml.Unmarshal(data, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func majorVersion(data []byte) int64 {
|
||||
var composite struct {
|
||||
Version *int `json:"ignitionVersion" yaml:"ignition_version"`
|
||||
Ignition struct {
|
||||
Version *string `json:"version" yaml:"version"`
|
||||
} `json:"ignition" yaml:"ignition"`
|
||||
}
|
||||
if yaml.Unmarshal(data, &composite) != nil {
|
||||
return 0
|
||||
}
|
||||
var major int64
|
||||
if composite.Ignition.Version != nil && *composite.Ignition.Version == "2.0.0" {
|
||||
major = 2
|
||||
} else if composite.Version != nil {
|
||||
major = int64(*composite.Version)
|
||||
}
|
||||
return major
|
||||
// isIgnition returns true if the file should be treated as plain Ignition.
|
||||
func isIgnition(filename string) bool {
|
||||
return strings.HasSuffix(filename, ".ign") || strings.HasSuffix(filename, ".ignition")
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ var (
|
||||
)
|
||||
|
||||
func TestIgnitionHandler_V2JSON(t *testing.T) {
|
||||
content := `{"ignition":{"version":"2.0.0","config":{}},"systemd":{"units":[{"name":"{{.service_name}}.service","enable":true},{"name":"{{.uuid}}.service","enable":true}]}}`
|
||||
content := `{"ignition":{"version":"2.0.0","config":{}},"systemd":{"units":[{"name":"etcd2.service","enable":true},{"name":"a1b2c3d4.service","enable":true}]}}`
|
||||
profile := &storagepb.Profile{
|
||||
Id: fake.Group.Profile,
|
||||
IgnitionId: "file.ign",
|
||||
}
|
||||
store := &fake.FixedStore{
|
||||
Profiles: map[string]*storagepb.Profile{fake.Group.Profile: fake.Profile},
|
||||
IgnitionConfigs: map[string]string{fake.Profile.IgnitionId: content},
|
||||
Profiles: map[string]*storagepb.Profile{fake.Group.Profile: profile},
|
||||
IgnitionConfigs: map[string]string{"file.ign": content},
|
||||
}
|
||||
srv := server.NewServer(&server.Config{Store: store})
|
||||
h := ignitionHandler(srv)
|
||||
@@ -39,31 +43,8 @@ func TestIgnitionHandler_V2JSON(t *testing.T) {
|
||||
assert.Equal(t, expectedIgnitionV2, w.Body.String())
|
||||
}
|
||||
|
||||
func TestIgnitionHandler_V1JSON(t *testing.T) {
|
||||
content := `{"ignitionVersion": 1,"systemd":{"units":[{"name":"{{.service_name}}.service","enable":true},{"name":"{{.uuid}}.service","enable":true}]}}`
|
||||
store := &fake.FixedStore{
|
||||
Profiles: map[string]*storagepb.Profile{fake.Group.Profile: fake.Profile},
|
||||
IgnitionConfigs: map[string]string{fake.Profile.IgnitionId: content},
|
||||
}
|
||||
srv := server.NewServer(&server.Config{Store: store})
|
||||
h := ignitionHandler(srv)
|
||||
ctx := withGroup(context.Background(), fake.Group)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
h.ServeHTTP(ctx, w, req)
|
||||
// assert that:
|
||||
// - Ignition template is rendered with Group metadata and selectors
|
||||
// - Rendered Ignition template is parsed as JSON
|
||||
// - Ignition Config served as JSON
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, jsonContentType, w.HeaderMap.Get(contentType))
|
||||
assert.Equal(t, expectedIgnitionV1, w.Body.String())
|
||||
}
|
||||
|
||||
func TestIgnitionHandler_V2YAML(t *testing.T) {
|
||||
content := `
|
||||
ignition:
|
||||
version: 2.0.0
|
||||
systemd:
|
||||
units:
|
||||
- name: {{.service_name}}.service
|
||||
@@ -90,35 +71,6 @@ systemd:
|
||||
assert.Equal(t, expectedIgnitionV2, w.Body.String())
|
||||
}
|
||||
|
||||
func TestIgnitionHandler_V1YAML(t *testing.T) {
|
||||
content := `
|
||||
ignition_version: 1
|
||||
systemd:
|
||||
units:
|
||||
- name: {{.service_name}}.service
|
||||
enable: true
|
||||
- name: {{.uuid}}.service
|
||||
enable: true
|
||||
`
|
||||
store := &fake.FixedStore{
|
||||
Profiles: map[string]*storagepb.Profile{fake.Group.Profile: testProfileIgnitionYAML},
|
||||
IgnitionConfigs: map[string]string{testProfileIgnitionYAML.IgnitionId: content},
|
||||
}
|
||||
srv := server.NewServer(&server.Config{Store: store})
|
||||
h := ignitionHandler(srv)
|
||||
ctx := withGroup(context.Background(), fake.Group)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
h.ServeHTTP(ctx, w, req)
|
||||
// assert that:
|
||||
// - Ignition template is rendered with Group metadata and selectors
|
||||
// - Rendered Ignition template is parsed as YAML
|
||||
// - Ignition Config served as JSON
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, jsonContentType, w.HeaderMap.Get(contentType))
|
||||
assert.Equal(t, expectedIgnitionV1, w.Body.String())
|
||||
}
|
||||
|
||||
func TestIgnitionHandler_MissingCtxProfile(t *testing.T) {
|
||||
srv := server.NewServer(&server.Config{Store: &fake.EmptyStore{}})
|
||||
h := ignitionHandler(srv)
|
||||
|
||||
Reference in New Issue
Block a user