diff --git a/metadata/app/schema.go b/metadata/app/schema.go index c3c720e9b6584631c0cf199567133461a96e5851..1db429f89aa41fc462d73ef3238b662c75fcf830 100644 --- a/metadata/app/schema.go +++ b/metadata/app/schema.go @@ -29,8 +29,14 @@ const ( DefaultVersion = "0.0.1" ) -var ErrRegistryNameInvalid = fmt.Errorf("Registry name is invalid") -var ErrRegistryExists = fmt.Errorf("Registry with name already exists") +var ( + ErrRegistryNameInvalid = fmt.Errorf("Registry name is invalid") + ErrRegistryExists = fmt.Errorf("Registry with name already exists") + // ErrEnvironmentNameInvalid is the error where an environment name is invalid. + ErrEnvironmentNameInvalid = fmt.Errorf("Environment name is invalid") + // ErrEnvironmentExists is the error when trying to create an environment that already exists. + ErrEnvironmentExists = fmt.Errorf("Environment with name already exists") +) type Spec struct { APIVersion string `json:"apiVersion,omitempty"` @@ -44,10 +50,76 @@ type Spec struct { Bugs string `json:"bugs,omitempty"` Keywords []string `json:"keywords,omitempty"` Registries RegistryRefSpecs `json:"registries,omitempty"` + Environments EnvironmentSpecs `json:"environments,omitempty"` Libraries LibraryRefSpecs `json:"libraries,omitempty"` License string `json:"license,omitempty"` } +type RepositorySpec struct { + Type string `json:"type"` + URI string `json:"uri"` +} + +type RegistryRefSpec struct { + Name string `json:"-"` + Protocol string `json:"protocol"` + URI string `json:"uri"` + GitVersion *GitVersionSpec `json:"gitVersion"` +} + +type RegistryRefSpecs map[string]*RegistryRefSpec + +// EnvironmentSpecs contains one or more EnvironmentSpec. +type EnvironmentSpecs map[string]*EnvironmentSpec + +// EnvironmentSpec contains the specification for ksonnet environments. +// +// KubernetesVersion: The Kubernetes version the target cluster is running on. +// Destinations: One or more cluster addresses that this environment +// points to. +// Targets: The relative component paths that this environment wishes +// to deploy onto it's destinations. +type EnvironmentSpec struct { + Name string `json:"-"` + KubernetesVersion string `json:"k8sVersion"` + Destinations EnvironmentDestinationSpecs `json:"destinations"` + Targets []string `json:"targets"` +} + +// EnvironmentDestinationSpecs contains one or more EnvironmentDestinationSpec. +type EnvironmentDestinationSpecs []*EnvironmentDestinationSpec + +// EnvironmentDestinationSpec contains the specification for the cluster +// addresses that the environment points to. +// +// Server: The Kubernetes server that the cluster is running on. +// Namespace: The namespace of the Kubernetes server that targets should +// be deployed to. This is "default", by default. +type EnvironmentDestinationSpec struct { + Server string `json:"server"` + Namespace string `json:"namespace"` +} + +type LibraryRefSpec struct { + Name string `json:"name"` + Registry string `json:"registry"` + GitVersion *GitVersionSpec `json:"gitVersion"` +} + +type GitVersionSpec struct { + RefSpec string `json:"refSpec"` + CommitSHA string `json:"commitSha"` +} + +type LibraryRefSpecs map[string]*LibraryRefSpec + +type ContributorSpec struct { + Name string `json:"name"` + Email string `json:"email"` +} + +type ContributorSpecs []*ContributorSpec + func Unmarshal(bytes []byte) (*Spec, error) { schema := Spec{} err := yaml.Unmarshal(bytes, &schema) @@ -105,36 +177,33 @@ func (s *Spec) validate() error { return nil } -type RepositorySpec struct { - Type string `json:"type"` - URI string `json:"uri"` -} - -type RegistryRefSpec struct { - Name string `json:"-"` - Protocol string `json:"protocol"` - URI string `json:"uri"` - GitVersion *GitVersionSpec `json:"gitVersion"` +// GetEnvironmentSpec returns the environment specification for the environment. +func (s *Spec) GetEnvironmentSpec(name string) (*EnvironmentSpec, bool) { + environmentSpec, ok := s.Environments[name] + if ok { + environmentSpec.Name = name + } + return environmentSpec, ok } -type RegistryRefSpecs map[string]*RegistryRefSpec +// AddEnvironmentSpec adds an EnvironmentSpec to the list of EnvironmentSpecs. +// This is equivalent to registering the environment for a ksonnet app. +func (s *Spec) AddEnvironmentSpec(spec *EnvironmentSpec) error { + if spec.Name == "" { + return ErrEnvironmentNameInvalid + } -type LibraryRefSpec struct { - Name string `json:"name"` - Registry string `json:"registry"` - GitVersion *GitVersionSpec `json:"gitVersion"` -} + _, environmentSpecExists := s.Environments[spec.Name] + if environmentSpecExists { + return ErrEnvironmentExists + } -type GitVersionSpec struct { - RefSpec string `json:"refSpec"` - CommitSHA string `json:"commitSha"` + s.Environments[spec.Name] = spec + return nil } -type LibraryRefSpecs map[string]*LibraryRefSpec - -type ContributorSpec struct { - Name string `json:"name"` - Email string `json:"email"` +// DeleteEnvironmentSpec removes the environment specification from the app spec. +func (s *Spec) DeleteEnvironmentSpec(name string) error { + delete(s.Environments, name) + return nil } - -type ContributorSpecs []*ContributorSpec diff --git a/metadata/app/schema_test.go b/metadata/app/schema_test.go index c56112d48565e3d488fd0860ae8fe9eeeafbc6b1..b0036777a1a6d6bdd506bd96c8e6ff4ce5341a14 100644 --- a/metadata/app/schema_test.go +++ b/metadata/app/schema_test.go @@ -16,7 +16,6 @@ package app import ( - "fmt" "testing" "github.com/blang/semver" @@ -34,6 +33,19 @@ func makeSimpleRefSpec(name, protocol, uri, version string) *RegistryRefSpec { } } +func makeSimpleEnvironmentSpec(name, namespace, server, k8sVersion string) *EnvironmentSpec { + return &EnvironmentSpec{ + Name: name, + Destinations: EnvironmentDestinationSpecs{ + &EnvironmentDestinationSpec{ + Namespace: namespace, + Server: server, + }, + }, + KubernetesVersion: k8sVersion, + } +} + func TestApiVersionValidate(t *testing.T) { type spec struct { spec string @@ -87,7 +99,6 @@ func TestGetRegistryRefSuccess(t *testing.T) { } r1, ok := example1.GetRegistryRef("simple1") - fmt.Println(r1) if r1 == nil || !ok { t.Error("Expected registry to contain 'simple1'") } @@ -159,3 +170,140 @@ func TestAddRegistryRefFailure(t *testing.T) { t.Error("Expected registry to fail to add registry with duplicate name and different uri") } } + +func TestGetEnvironmentSpecSuccess(t *testing.T) { + const ( + env = "dev" + namespace = "default" + server = "http://example.com" + k8sVersion = "1.8.0" + ) + + example1 := Spec{ + Environments: EnvironmentSpecs{ + env: &EnvironmentSpec{ + Destinations: EnvironmentDestinationSpecs{ + &EnvironmentDestinationSpec{ + Namespace: namespace, + Server: server, + }, + }, + KubernetesVersion: k8sVersion, + }, + }, + } + + r1, ok := example1.GetEnvironmentSpec(env) + if r1 == nil || !ok { + t.Errorf("Expected environments to contain '%s'", env) + } + + if len(r1.Destinations) != 1 || r1.Destinations[0].Namespace != namespace || + r1.Destinations[0].Server != server || r1.KubernetesVersion != k8sVersion { + t.Errorf("Environment did not add correct values:\n%s", r1) + } +} + +func TestGetEnvironmentSpecFailure(t *testing.T) { + example1 := Spec{ + Environments: EnvironmentSpecs{ + "dev": &EnvironmentSpec{ + Destinations: EnvironmentDestinationSpecs{ + &EnvironmentDestinationSpec{ + Namespace: "default", + Server: "http://example.com", + }, + }, + KubernetesVersion: "1.8.0", + }, + }, + } + + r1, ok := example1.GetEnvironmentSpec("prod") + if r1 != nil || ok { + t.Error("Expected environemnts to not contain 'prod'") + } +} + +func TestAddEnvironmentSpecSuccess(t *testing.T) { + const ( + env = "dev" + namespace = "default" + server = "http://example.com" + k8sVersion = "1.8.0" + ) + + example1 := Spec{ + Environments: EnvironmentSpecs{}, + } + + err := example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(env, namespace, server, k8sVersion)) + if err != nil { + t.Errorf("Expected environment add to succeed:\n%s", err) + } + + r1, ok1 := example1.GetEnvironmentSpec(env) + if !ok1 || len(r1.Destinations) != 1 || r1.Destinations[0].Namespace != namespace || + r1.Destinations[0].Server != server || r1.KubernetesVersion != k8sVersion { + t.Errorf("Environment did not add correct values:\n%s", r1) + } +} + +func TestAddEnvironmentSpecFailure(t *testing.T) { + const ( + envName1 = "dev" + envName2 = "" + namespace = "default" + server = "http://example.com" + k8sVersion = "1.8.0" + ) + + example1 := Spec{ + Environments: EnvironmentSpecs{ + envName1: &EnvironmentSpec{ + Destinations: EnvironmentDestinationSpecs{ + &EnvironmentDestinationSpec{ + Namespace: namespace, + Server: server, + }, + }, + KubernetesVersion: k8sVersion, + }, + }, + } + + err := example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(envName2, namespace, server, k8sVersion)) + if err != ErrEnvironmentNameInvalid { + t.Error("Expected failure while adding environment with an invalid name") + } + + err = example1.AddEnvironmentSpec(makeSimpleEnvironmentSpec(envName1, namespace, server, k8sVersion)) + if err != ErrEnvironmentExists { + t.Error("Expected failure while adding environment with duplicate name") + } +} + +func TestDeleteEnvironmentSpec(t *testing.T) { + example1 := Spec{ + Environments: EnvironmentSpecs{ + "dev": &EnvironmentSpec{ + Destinations: EnvironmentDestinationSpecs{ + &EnvironmentDestinationSpec{ + Namespace: "default", + Server: "http://example.com", + }, + }, + KubernetesVersion: "1.8.0", + }, + }, + } + + err := example1.DeleteEnvironmentSpec("dev") + if err != nil { + t.Error("Expected to successfully delete an environment in spec") + } + + if _, ok := example1.GetEnvironmentSpec("dev"); ok { + t.Error("Expected environment 'dev' to be deleted from spec, but still exists") + } +} diff --git a/metadata/component.go b/metadata/component.go index 7cbe06ebf768608ca6afe3775195770b9c47b94a..709ee716b5d3331a0db025594a5e34a5e61b0644 100644 --- a/metadata/component.go +++ b/metadata/component.go @@ -171,6 +171,38 @@ func (m *manager) DeleteComponent(name string) error { return nil } +func (m *manager) GetComponentParams(component string) (param.Params, error) { + text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + if err != nil { + return nil, err + } + + return param.GetComponentParams(component, string(text)) +} + +func (m *manager) GetAllComponentParams() (map[string]param.Params, error) { + text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + if err != nil { + return nil, err + } + + return param.GetAllComponentParams(string(text)) +} + +func (m *manager) SetComponentParams(component string, params param.Params) error { + text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + if err != nil { + return err + } + + jsonnet, err := param.SetComponentParams(component, string(text), params) + if err != nil { + return err + } + + return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(jsonnet), defaultFilePermissions) +} + func (m *manager) findComponentPath(name string) (string, error) { componentPaths, err := m.ComponentPaths() if err != nil { @@ -198,3 +230,31 @@ func (m *manager) findComponentPath(name string) (string, error) { return componentPath, nil } + +func (m *manager) writeComponentParams(componentName string, params param.Params) error { + text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) + if err != nil { + return err + } + + appended, err := param.AppendComponent(componentName, string(text), params) + if err != nil { + return err + } + + return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions) +} + +func genComponentParamsContent() []byte { + return []byte(`{ + global: { + // User-defined global parameters; accessible to all component and environments, Ex: + // replicas: 4, + }, + components: { + // Component-level parameters, defined initially from 'ks prototype use ...' + // Each object below should correspond to a component in the components/ directory + }, +} +`) +} diff --git a/metadata/component_test.go b/metadata/component_test.go index 00a5ddb299fdaa46b4f68f31111714216f3e8dd7..d5021bcd819ca377f810fcdab8d4df6396ca05ca 100644 --- a/metadata/component_test.go +++ b/metadata/component_test.go @@ -138,3 +138,21 @@ func TestFindComponentPath(t *testing.T) { t.Fatalf("m.findComponentPath failed; expected '%s', got '%s'", expected, path) } } + +func TestGenComponentParamsContent(t *testing.T) { + expected := `{ + global: { + // User-defined global parameters; accessible to all component and environments, Ex: + // replicas: 4, + }, + components: { + // Component-level parameters, defined initially from 'ks prototype use ...' + // Each object below should correspond to a component in the components/ directory + }, +} +` + content := string(genComponentParamsContent()) + if content != expected { + t.Fatalf("Expected to generate:\n%s\n, got:\n%s", expected, content) + } +} diff --git a/metadata/manager.go b/metadata/manager.go index 19128b3cadfbbeba9427045aa4967865dc315fb0..8423f16a4254a8e2f1c4cf3353a28ecdeb9bcc65 100644 --- a/metadata/manager.go +++ b/metadata/manager.go @@ -23,7 +23,6 @@ import ( "github.com/ksonnet/ksonnet/generator" "github.com/ksonnet/ksonnet/metadata/app" - param "github.com/ksonnet/ksonnet/metadata/params" "github.com/ksonnet/ksonnet/metadata/registry" log "github.com/sirupsen/logrus" "github.com/spf13/afero" @@ -223,38 +222,6 @@ func (m *manager) EnvPaths(env string) (metadataPath, mainPath, paramsPath, spec return } -func (m *manager) GetComponentParams(component string) (param.Params, error) { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) - if err != nil { - return nil, err - } - - return param.GetComponentParams(component, string(text)) -} - -func (m *manager) GetAllComponentParams() (map[string]param.Params, error) { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) - if err != nil { - return nil, err - } - - return param.GetAllComponentParams(string(text)) -} - -func (m *manager) SetComponentParams(component string, params param.Params) error { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) - if err != nil { - return err - } - - jsonnet, err := param.SetComponentParams(component, string(text), params) - if err != nil { - return err - } - - return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(jsonnet), defaultFilePermissions) -} - // AppSpec will return the specification for a ksonnet application // (typically stored in `app.yaml`) func (m *manager) AppSpec() (*app.Spec, error) { @@ -356,34 +323,6 @@ func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte, return nil } -func (m *manager) writeComponentParams(componentName string, params param.Params) error { - text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath)) - if err != nil { - return err - } - - appended, err := param.AppendComponent(componentName, string(text), params) - if err != nil { - return err - } - - return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions) -} - -func genComponentParamsContent() []byte { - return []byte(`{ - global: { - // User-defined global parameters; accessible to all component and environments, Ex: - // replicas: 4, - }, - components: { - // Component-level parameters, defined initially from 'ks prototype use ...' - // Each object below should correspond to a component in the components/ directory - }, -} -`) -} - func generateRegistryYAMLData(incubatorReg registry.Manager) ([]byte, error) { regSpec, err := incubatorReg.FetchRegistrySpec() if err != nil { @@ -395,11 +334,12 @@ func generateRegistryYAMLData(incubatorReg registry.Manager) ([]byte, error) { func generateAppYAMLData(name string, refs ...*app.RegistryRefSpec) ([]byte, error) { content := app.Spec{ - APIVersion: app.DefaultAPIVersion, - Kind: app.Kind, - Name: name, - Version: app.DefaultVersion, - Registries: app.RegistryRefSpecs{}, + APIVersion: app.DefaultAPIVersion, + Kind: app.Kind, + Name: name, + Version: app.DefaultVersion, + Registries: app.RegistryRefSpecs{}, + Environments: app.EnvironmentSpecs{}, } for _, ref := range refs { diff --git a/metadata/manager_test.go b/metadata/manager_test.go index 9c4cf463005d0c697b6c00f40be286ebd0b21fef..88175f9556b02b9af3328f66db88e92d5bb5f86a 100644 --- a/metadata/manager_test.go +++ b/metadata/manager_test.go @@ -277,21 +277,3 @@ func TestDoubleNewFailure(t *testing.T) { t.Fatalf("Expected to fail to create app with message '%s', got '%s'", targetErr, err.Error()) } } - -func TestGenComponentParamsContent(t *testing.T) { - expected := `{ - global: { - // User-defined global parameters; accessible to all component and environments, Ex: - // replicas: 4, - }, - components: { - // Component-level parameters, defined initially from 'ks prototype use ...' - // Each object below should correspond to a component in the components/ directory - }, -} -` - content := string(genComponentParamsContent()) - if content != expected { - t.Fatalf("Expected to generate:\n%s\n, got:\n%s", expected, content) - } -}