diff --git a/bootcfg/http/ignition.go b/bootcfg/http/ignition.go index 41182f7c..069cfca3 100644 --- a/bootcfg/http/ignition.go +++ b/bootcfg/http/ignition.go @@ -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") } diff --git a/bootcfg/http/ignition_test.go b/bootcfg/http/ignition_test.go index 6ef8f300..69f39a69 100644 --- a/bootcfg/http/ignition_test.go +++ b/bootcfg/http/ignition_test.go @@ -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)