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:
Dalton Hubble
2016-06-22 11:04:19 -07:00
parent 481ebfd525
commit 235ebf6f62
2 changed files with 32 additions and 116 deletions

View File

@@ -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")
}

View File

@@ -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)