From 26632652df2d61662f69daaff01220306433b42e Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Thu, 18 Feb 2021 17:14:30 -0500 Subject: [PATCH 01/10] Add Global Attribute and Substitution Signed-off-by: Maysun J Faisal --- ...pace.devfile.io_devworkspaces.v1beta1.yaml | 26 ++ crds/workspace.devfile.io_devworkspaces.yaml | 26 ++ ...file.io_devworkspacetemplates.v1beta1.yaml | 25 ++ ...pace.devfile.io_devworkspacetemplates.yaml | 25 ++ .../v1alpha2/devworkspacetemplate_spec.go | 10 + .../v1alpha2/zz_generated.deepcopy.go | 28 ++ .../v1alpha2/zz_generated.parent_overrides.go | 12 + .../v1alpha2/zz_generated.plugin_overrides.go | 6 + pkg/utils/overriding/keys.go | 23 ++ pkg/utils/overriding/merging.go | 16 + pkg/utils/overriding/merging_test.go | 358 ++++++++++++++++++ pkg/utils/overriding/overriding_test.go | 144 +------ .../merges/duplicate-with-parent/main.yaml | 3 + .../merges/duplicate-with-parent/parent.yaml | 3 + .../duplicate-with-parent/result-error.txt | 3 +- .../merges/no-duplicate/main.yaml | 2 + .../merges/no-duplicate/parent.yaml | 3 + .../merges/no-duplicate/result.yaml | 4 + .../global-attributes-err/original.yaml | 8 + .../patches/global-attributes-err/patch.yaml | 10 + .../global-attributes-err/result-error.txt | 2 + .../patches/global-attributes/original.yaml | 7 + .../patches/global-attributes/patch.yaml | 9 + .../patches/global-attributes/result.yaml | 9 + pkg/validation/attributes/attributes.go | 62 +++ .../attributes/attributes_command.go | 139 +++++++ .../attributes/attributes_command_test.go | 172 +++++++++ .../attributes/attributes_component.go | 194 ++++++++++ .../attributes/attributes_component_test.go | 260 +++++++++++++ .../attributes/attributes_endpoint.go | 23 ++ .../attributes/attributes_endpoint_test.go | 63 +++ pkg/validation/attributes/attributes_event.go | 45 +++ .../attributes/attributes_event_test.go | 69 ++++ .../attributes/attributes_parent.go | 48 +++ .../attributes/attributes_parent_test.go | 95 +++++ .../attributes/attributes_project.go | 115 ++++++ .../attributes/attributes_project_test.go | 215 +++++++++++ pkg/validation/attributes/attributes_test.go | 257 +++++++++++++ .../test-fixtures/all/devfile-bad.yaml | 13 + .../test-fixtures/all/devfile-good.yaml | 64 ++++ .../test-fixtures/commands/apply.yaml | 2 + .../test-fixtures/commands/composite.yaml | 4 + .../test-fixtures/commands/exec.yaml | 7 + .../test-fixtures/components/container.yaml | 20 + .../test-fixtures/components/endpoint.yaml | 5 + .../test-fixtures/components/env.yaml | 2 + .../components/openshift-kubernetes.yaml | 6 + .../test-fixtures/components/volume.yaml | 1 + .../test-fixtures/events/event.yaml | 8 + .../test-fixtures/parent/parent-id.yaml | 2 + .../parent/parent-kubernetes.yaml | 4 + .../test-fixtures/parent/parent-uri.yaml | 2 + .../test-fixtures/projects/git.yaml | 7 + .../test-fixtures/projects/project.yaml | 11 + .../projects/starterproject.yaml | 5 + .../test-fixtures/projects/zip.yaml | 2 + .../latest/dev-workspace-template-spec.json | 20 + schemas/latest/dev-workspace-template.json | 20 + schemas/latest/dev-workspace.json | 20 + schemas/latest/devfile.json | 10 + .../dev-workspace-template-spec.json | 24 ++ .../ide-targeted/dev-workspace-template.json | 24 ++ .../latest/ide-targeted/dev-workspace.json | 24 ++ schemas/latest/ide-targeted/devfile.json | 12 + .../latest/ide-targeted/parent-overrides.json | 12 + .../latest/ide-targeted/plugin-overrides.json | 6 + schemas/latest/parent-overrides.json | 10 + schemas/latest/plugin-overrides.json | 5 + 68 files changed, 2739 insertions(+), 132 deletions(-) create mode 100644 pkg/utils/overriding/merging_test.go create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml create mode 100644 pkg/validation/attributes/attributes.go create mode 100644 pkg/validation/attributes/attributes_command.go create mode 100644 pkg/validation/attributes/attributes_command_test.go create mode 100644 pkg/validation/attributes/attributes_component.go create mode 100644 pkg/validation/attributes/attributes_component_test.go create mode 100644 pkg/validation/attributes/attributes_endpoint.go create mode 100644 pkg/validation/attributes/attributes_endpoint_test.go create mode 100644 pkg/validation/attributes/attributes_event.go create mode 100644 pkg/validation/attributes/attributes_event_test.go create mode 100644 pkg/validation/attributes/attributes_parent.go create mode 100644 pkg/validation/attributes/attributes_parent_test.go create mode 100644 pkg/validation/attributes/attributes_project.go create mode 100644 pkg/validation/attributes/attributes_project_test.go create mode 100644 pkg/validation/attributes/attributes_test.go create mode 100644 pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml create mode 100644 pkg/validation/attributes/test-fixtures/all/devfile-good.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/apply.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/composite.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/exec.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/container.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/endpoint.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/env.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/volume.yaml create mode 100644 pkg/validation/attributes/test-fixtures/events/event.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-id.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/git.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/project.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/starterproject.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/zip.yaml diff --git a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml index bc25b4d9f..fb482c1b5 100644 --- a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml @@ -4140,6 +4140,13 @@ spec: description: Structure of the devworkspace. This is also the specification of a devworkspace template. properties: + attributes: + description: Map of implementation-dependant free-form YAML attributes. + Attribute values can be referenced through out the devfile in + string type fields in the form {{attribute-key}} except for + schemaVersion and metadata + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Predefined, ready-to-use, devworkspace-related commands items: @@ -4823,6 +4830,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in + a parent devfile or a plugin. Overriding is done according + to K8S strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according @@ -5561,6 +5574,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S + strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -6179,6 +6198,13 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated + in a parent devfile or a plugin. Overriding is + done according to K8S strategic merge patch standard + rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is diff --git a/crds/workspace.devfile.io_devworkspaces.yaml b/crds/workspace.devfile.io_devworkspaces.yaml index af4af7b32..e021c75f0 100644 --- a/crds/workspace.devfile.io_devworkspaces.yaml +++ b/crds/workspace.devfile.io_devworkspaces.yaml @@ -4138,6 +4138,13 @@ spec: description: Structure of the devworkspace. This is also the specification of a devworkspace template. properties: + attributes: + description: Map of implementation-dependant free-form YAML attributes. + Attribute values can be referenced through out the devfile in + string type fields in the form {{attribute-key}} except for + schemaVersion and metadata + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Predefined, ready-to-use, devworkspace-related commands items: @@ -4828,6 +4835,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in + a parent devfile or a plugin. Overriding is done according + to K8S strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according @@ -5566,6 +5579,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S + strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -6184,6 +6203,13 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated + in a parent devfile or a plugin. Overriding is + done according to K8S strategic merge patch standard + rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is diff --git a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml index 6acf50752..500bafaca 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml @@ -3914,6 +3914,13 @@ spec: description: Structure of the devworkspace. This is also the specification of a devworkspace template. properties: + attributes: + description: Map of implementation-dependant free-form YAML attributes. + Attribute values can be referenced through out the devfile in string + type fields in the form {{attribute-key}} except for schemaVersion + and metadata + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Predefined, ready-to-use, devworkspace-related commands items: @@ -4572,6 +4579,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S + strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -5284,6 +5297,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S strategic + merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge @@ -5881,6 +5900,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in + a parent devfile or a plugin. Overriding is done according + to K8S strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according diff --git a/crds/workspace.devfile.io_devworkspacetemplates.yaml b/crds/workspace.devfile.io_devworkspacetemplates.yaml index 18cca9c1f..736f835a6 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.yaml @@ -3912,6 +3912,13 @@ spec: description: Structure of the devworkspace. This is also the specification of a devworkspace template. properties: + attributes: + description: Map of implementation-dependant free-form YAML attributes. + Attribute values can be referenced through out the devfile in string + type fields in the form {{attribute-key}} except for schemaVersion + and metadata + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Predefined, ready-to-use, devworkspace-related commands items: @@ -4577,6 +4584,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S + strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -5289,6 +5302,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in a parent + devfile or a plugin. Overriding is done according to K8S strategic + merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge @@ -5886,6 +5905,12 @@ spec: - required: - kubernetes properties: + attributes: + description: Overrides of attributes encapsulated in + a parent devfile or a plugin. Overriding is done according + to K8S strategic merge patch standard rules. + type: object + x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according diff --git a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go index 6511bcfa0..5801bd7cb 100644 --- a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go +++ b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go @@ -1,5 +1,7 @@ package v1alpha2 +import attributes "github.com/devfile/api/v2/pkg/attributes" + // Structure of the devworkspace. This is also the specification of a devworkspace template. // +devfile:jsonschema:generate type DevWorkspaceTemplateSpec struct { @@ -12,6 +14,14 @@ type DevWorkspaceTemplateSpec struct { // +devfile:overrides:generate type DevWorkspaceTemplateSpecContent struct { + // Map of implementation-dependant free-form YAML attributes. + // Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} + // except for schemaVersion and metadata + // +optional + // +patchStrategy=merge + // +devfile:overrides:include:description=Overrides of attributes encapsulated in a parent devfile or a plugin. + Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` + // List of the devworkspace components, such as editor and plugins, // user-provided containers, or other types of components // +optional diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go index 0569781f4..978c61087 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go @@ -1416,6 +1416,13 @@ func (in *DevWorkspaceTemplateSpec) DeepCopy() *DevWorkspaceTemplateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DevWorkspaceTemplateSpecContent) DeepCopyInto(out *DevWorkspaceTemplateSpecContent) { *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(attributes.Attributes, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]Component, len(*in)) @@ -2355,6 +2362,13 @@ func (in *Parent) DeepCopy() *Parent { func (in *ParentOverrides) DeepCopyInto(out *ParentOverrides) { *out = *in out.OverridesBase = in.OverridesBase + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(attributes.Attributes, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ComponentParentOverride, len(*in)) @@ -2435,6 +2449,13 @@ func (in *PluginComponentParentOverride) DeepCopy() *PluginComponentParentOverri func (in *PluginOverrides) DeepCopyInto(out *PluginOverrides) { *out = *in out.OverridesBase = in.OverridesBase + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(attributes.Attributes, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ComponentPluginOverride, len(*in)) @@ -2465,6 +2486,13 @@ func (in *PluginOverrides) DeepCopy() *PluginOverrides { func (in *PluginOverridesParentOverride) DeepCopyInto(out *PluginOverridesParentOverride) { *out = *in out.OverridesBaseParentOverride = in.OverridesBaseParentOverride + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(attributes.Attributes, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ComponentPluginOverrideParentOverride, len(*in)) diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go index bcc457cc0..646e43e6c 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go @@ -8,6 +8,12 @@ import ( type ParentOverrides struct { OverridesBase `json:",inline"` + // Overrides of attributes encapsulated in a parent devfile or a plugin. + // Overriding is done according to K8S strategic merge patch standard rules. + // +optional + // +patchStrategy=merge + Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` + // Overrides of components encapsulated in a parent devfile or a plugin. // Overriding is done according to K8S strategic merge patch standard rules. // +optional @@ -502,6 +508,12 @@ type ImportReferenceParentOverride struct { type PluginOverridesParentOverride struct { OverridesBaseParentOverride `json:",inline"` + // Overrides of attributes encapsulated in a parent devfile or a plugin. + // Overriding is done according to K8S strategic merge patch standard rules. + // +optional + // +patchStrategy=merge + Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` + // Overrides of components encapsulated in a parent devfile or a plugin. // Overriding is done according to K8S strategic merge patch standard rules. // +optional diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go b/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go index d94b3e685..24435d493 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go @@ -8,6 +8,12 @@ import ( type PluginOverrides struct { OverridesBase `json:",inline"` + // Overrides of attributes encapsulated in a parent devfile or a plugin. + // Overriding is done according to K8S strategic merge patch standard rules. + // +optional + // +patchStrategy=merge + Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` + // Overrides of components encapsulated in a parent devfile or a plugin. // Overriding is done according to K8S strategic merge patch standard rules. // +optional diff --git a/pkg/utils/overriding/keys.go b/pkg/utils/overriding/keys.go index aa25dc472..b3ee24c1f 100644 --- a/pkg/utils/overriding/keys.go +++ b/pkg/utils/overriding/keys.go @@ -1,7 +1,10 @@ package overriding import ( + "reflect" + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + attributesPkg "github.com/devfile/api/v2/pkg/attributes" "github.com/hashicorp/go-multierror" "k8s.io/apimachinery/pkg/util/sets" ) @@ -27,7 +30,27 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain for listType, listElem := range topLevelList { listTypeToKeys[listType] = append(listTypeToKeys[listType], sets.NewString(listElem.GetKeys()...)) } + + value := reflect.ValueOf(topLevelListContainer) + + var attributeValue reflect.Value + // toplevelListContainers can contain either a pointer or a struct and needs to be safeguarded when using reflect + if value.Kind() == reflect.Ptr { + attributeValue = value.Elem().FieldByName("Attributes") + } else { + attributeValue = value.FieldByName("Attributes") + } + + if attributeValue.CanInterface() { + attributes := attributeValue.Interface().(attributesPkg.Attributes) + var attributeKeys []string + for k := range attributes { + attributeKeys = append(attributeKeys, k) + } + listTypeToKeys["Attributes"] = append(listTypeToKeys["Attributes"], sets.NewString(attributeKeys...)) + } } + for listType, keySets := range listTypeToKeys { errors = multierror.Append(errors, doCheck(listType, keySets)...) } diff --git a/pkg/utils/overriding/merging.go b/pkg/utils/overriding/merging.go index 72a1e5093..f31dfdd57 100644 --- a/pkg/utils/overriding/merging.go +++ b/pkg/utils/overriding/merging.go @@ -6,6 +6,7 @@ import ( "strings" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/api/v2/pkg/attributes" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/yaml" @@ -105,6 +106,21 @@ func MergeDevWorkspaceTemplateSpec( preStopCommands = preStopCommands.Union(sets.NewString(content.Events.PreStop...)) postStopCommands = postStopCommands.Union(sets.NewString(content.Events.PostStop...)) } + + var err error + if len(content.Attributes) > 0 { + if len(result.Attributes) == 0 { + result.Attributes = attributes.Attributes{} + } + for k, v := range content.Attributes { + result.Attributes.FromMap(map[string]interface{}{ + k: v, + }, &err) + if err != nil { + return nil, err + } + } + } } if result.Events != nil { diff --git a/pkg/utils/overriding/merging_test.go b/pkg/utils/overriding/merging_test.go new file mode 100644 index 000000000..3e087d925 --- /dev/null +++ b/pkg/utils/overriding/merging_test.go @@ -0,0 +1,358 @@ +package overriding + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + workspaces "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + attributesPkg "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/json" + yamlMachinery "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/yaml" +) + +func TestBasicMerging(t *testing.T) { + + tests := []struct { + name string + mainContent *workspaces.DevWorkspaceTemplateSpecContent + parentFlattenedContent *workspaces.DevWorkspaceTemplateSpecContent + pluginFlattenedContents []*workspaces.DevWorkspaceTemplateSpecContent + expected *workspaces.DevWorkspaceTemplateSpecContent + wantErr *string + }{ + { + name: "Basic Merging", + mainContent: &workspaces.DevWorkspaceTemplateSpecContent{ + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "main": true, + }, nil), + Commands: []workspaces.Command{ + { + Id: "mainCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + }, + Components: []workspaces.Component{ + { + Name: "mainComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + { + Name: "mainPluginComponent", + ComponentUnion: workspaces.ComponentUnion{ + Plugin: &workspaces.PluginComponent{ + ImportReference: workspaces.ImportReference{ + ImportReferenceUnion: workspaces.ImportReferenceUnion{ + Uri: "uri", + }, + }, + }, + }, + }, + }, + Events: &workspaces.Events{ + WorkspaceEvents: workspaces.WorkspaceEvents{ + PostStop: []string{"post-stop-main"}, + }, + }, + }, + pluginFlattenedContents: []*workspaces.DevWorkspaceTemplateSpecContent{ + { + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version2": "plugin", + }, nil), + Commands: []workspaces.Command{ + { + Id: "pluginCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + }, + Components: []workspaces.Component{ + { + Name: "pluginComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + }, + Events: &workspaces.Events{ + WorkspaceEvents: workspaces.WorkspaceEvents{ + PostStop: []string{"post-stop-plugin"}, + }, + }, + }, + }, + parentFlattenedContent: &workspaces.DevWorkspaceTemplateSpecContent{ + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": "parent", + }, nil), + Commands: []workspaces.Command{ + { + Id: "parentCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + }, + Components: []workspaces.Component{ + { + Name: "parentComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + }, + Events: &workspaces.Events{ + WorkspaceEvents: workspaces.WorkspaceEvents{ + PostStop: []string{"post-stop-parent"}, + PostStart: []string{"post-start-parent"}, + }, + }, + }, + expected: &workspaces.DevWorkspaceTemplateSpecContent{ + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": "parent", + "version2": "plugin", + "main": true, + }, nil), + Commands: []workspaces.Command{ + { + Id: "parentCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + { + Id: "pluginCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + { + Id: "mainCommand", + CommandUnion: workspaces.CommandUnion{ + Exec: &workspaces.ExecCommand{ + WorkingDir: "dir", + }, + }, + }, + }, + Components: []workspaces.Component{ + { + Name: "parentComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + { + Name: "pluginComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + { + Name: "mainComponent", + ComponentUnion: workspaces.ComponentUnion{ + Container: &workspaces.ContainerComponent{ + Container: workspaces.Container{ + Image: "image", + }, + }, + }, + }, + }, + Events: &workspaces.Events{ + WorkspaceEvents: workspaces.WorkspaceEvents{ + PreStart: []string{}, + PostStart: []string{"post-start-parent"}, + PreStop: []string{}, + PostStop: []string{"post-stop-main", "post-stop-parent", "post-stop-plugin"}, + }, + }, + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mergedContent, err := MergeDevWorkspaceTemplateSpec(tt.mainContent, tt.parentFlattenedContent, tt.pluginFlattenedContents...) + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, tt.expected, mergedContent, "The two values should be the same.") + }) + } +} + +func mergingPatchTest(main, parent, expected []byte, expectedError string, plugins ...[]byte) func(t *testing.T) { + return func(t *testing.T) { + result, err := MergeDevWorkspaceTemplateSpecBytes(main, parent, plugins...) + if err != nil { + compareErrorMessages(t, expectedError, err.Error(), "wrong error") + return + } + if expectedError != "" { + t.Error("Expected error but did not get one") + return + } + + resultJson, err := json.Marshal(result) + if err != nil { + t.Error(err) + } + resultYaml, err := yaml.JSONToYAML(resultJson) + if err != nil { + t.Error(err) + } + + expectedJson, err := yamlMachinery.ToJSON(expected) + if err != nil { + t.Error(err) + } + expectedYaml, err := yaml.JSONToYAML(expectedJson) + if err != nil { + t.Error(err) + } + + assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.") + } +} + +func TestMerging(t *testing.T) { + filepath.Walk("test-fixtures/merges", func(path string, info os.FileInfo, err error) error { + if !info.IsDir() && info.Name() == "main.yaml" { + if err != nil { + t.Error(err) + return nil + } + main, err := ioutil.ReadFile(path) + if err != nil { + t.Error(err) + return nil + } + dirPath := filepath.Dir(path) + parent := []byte{} + parentFile := filepath.Join(dirPath, "parent.yaml") + if _, err = os.Stat(parentFile); err == nil { + parent, err = ioutil.ReadFile(parentFile) + if err != nil { + t.Error(err) + return nil + } + } + + plugins := [][]byte{} + pluginFile := filepath.Join(dirPath, "plugin.yaml") + if _, err = os.Stat(pluginFile); err == nil { + plugin, err := ioutil.ReadFile(filepath.Join(dirPath, "plugin.yaml")) + if err != nil { + t.Error(err) + return nil + } + plugins = append(plugins, plugin) + } + result := []byte{} + resultError := "" + errorFile := filepath.Join(dirPath, "result-error.txt") + if _, err = os.Stat(errorFile); err == nil { + resultErrorBytes, err := ioutil.ReadFile(errorFile) + if err != nil { + t.Error(err) + return nil + } + resultError = string(resultErrorBytes) + } else { + result, err = ioutil.ReadFile(filepath.Join(dirPath, "result.yaml")) + if err != nil { + t.Error(err) + return nil + } + } + testName := filepath.Base(dirPath) + + t.Run(testName, mergingPatchTest(main, parent, result, resultError, plugins...)) + } + return nil + }) +} + +func TestMergingOnlyPlugins(t *testing.T) { + baseFile := "test-fixtures/merges/no-parent/main.yaml" + pluginFile := "test-fixtures/merges/no-parent/plugin.yaml" + resultFile := "test-fixtures/merges/no-parent/result.yaml" + + baseDWT := workspaces.DevWorkspaceTemplateSpecContent{} + pluginDWT := workspaces.DevWorkspaceTemplateSpecContent{} + expectedDWT := workspaces.DevWorkspaceTemplateSpecContent{} + + readFileToStruct(t, baseFile, &baseDWT) + readFileToStruct(t, pluginFile, &pluginDWT) + readFileToStruct(t, resultFile, &expectedDWT) + + gotDWT, err := MergeDevWorkspaceTemplateSpec(&baseDWT, nil, &pluginDWT) + if assert.NoError(t, err) { + assert.Equal(t, &expectedDWT, gotDWT) + } +} + +func TestMergingOnlyParent(t *testing.T) { + // Reuse only plugin case since it's compatible + baseFile := "test-fixtures/merges/no-parent/main.yaml" + parentFile := "test-fixtures/merges/no-parent/plugin.yaml" + resultFile := "test-fixtures/merges/no-parent/result.yaml" + + baseDWT := workspaces.DevWorkspaceTemplateSpecContent{} + parentDWT := workspaces.DevWorkspaceTemplateSpecContent{} + expectedDWT := workspaces.DevWorkspaceTemplateSpecContent{} + + readFileToStruct(t, baseFile, &baseDWT) + readFileToStruct(t, parentFile, &parentDWT) + readFileToStruct(t, resultFile, &expectedDWT) + + gotDWT, err := MergeDevWorkspaceTemplateSpec(&baseDWT, &parentDWT) + if assert.NoError(t, err) { + assert.Equal(t, &expectedDWT, gotDWT) + } +} diff --git a/pkg/utils/overriding/overriding_test.go b/pkg/utils/overriding/overriding_test.go index 5b6af2dd5..ebbcb52b3 100644 --- a/pkg/utils/overriding/overriding_test.go +++ b/pkg/utils/overriding/overriding_test.go @@ -8,6 +8,7 @@ import ( "testing" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + attributesPkg "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/util/json" yamlMachinery "k8s.io/apimachinery/pkg/util/yaml" @@ -51,6 +52,10 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": "main", + "xyz": "xyz", + }, nil), } patch := dw.ParentOverrides{ @@ -81,6 +86,9 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": "patch", + }, nil), } expected := &dw.DevWorkspaceTemplateSpecContent{ @@ -125,11 +133,16 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": "patch", + "xyz": "xyz", + }, nil), } result, err := OverrideDevWorkspaceTemplateSpec(&original, &patch) if err != nil { t.Error(err) + return } assert.Equal(t, expected, result, "The two values should be the same.") @@ -212,98 +225,6 @@ func TestOverridingPatches(t *testing.T) { }) } -func mergingPatchTest(main, parent, expected []byte, expectedError string, plugins ...[]byte) func(t *testing.T) { - return func(t *testing.T) { - result, err := MergeDevWorkspaceTemplateSpecBytes(main, parent, plugins...) - if err != nil { - compareErrorMessages(t, expectedError, err.Error(), "wrong error") - return - } - if expectedError != "" { - t.Error("Expected error but did not get one") - return - } - - resultJson, err := json.Marshal(result) - if err != nil { - t.Error(err) - } - resultYaml, err := yaml.JSONToYAML(resultJson) - if err != nil { - t.Error(err) - } - - expectedJson, err := yamlMachinery.ToJSON(expected) - if err != nil { - t.Error(err) - } - expectedYaml, err := yaml.JSONToYAML(expectedJson) - if err != nil { - t.Error(err) - } - - assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.") - } -} - -func TestMerging(t *testing.T) { - filepath.Walk("test-fixtures/merges", func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && info.Name() == "main.yaml" { - if err != nil { - t.Error(err) - return nil - } - main, err := ioutil.ReadFile(path) - if err != nil { - t.Error(err) - return nil - } - dirPath := filepath.Dir(path) - parent := []byte{} - parentFile := filepath.Join(dirPath, "parent.yaml") - if _, err = os.Stat(parentFile); err == nil { - parent, err = ioutil.ReadFile(parentFile) - if err != nil { - t.Error(err) - return nil - } - } - - plugins := [][]byte{} - pluginFile := filepath.Join(dirPath, "plugin.yaml") - if _, err = os.Stat(pluginFile); err == nil { - plugin, err := ioutil.ReadFile(filepath.Join(dirPath, "plugin.yaml")) - if err != nil { - t.Error(err) - return nil - } - plugins = append(plugins, plugin) - } - result := []byte{} - resultError := "" - errorFile := filepath.Join(dirPath, "result-error.txt") - if _, err = os.Stat(errorFile); err == nil { - resultErrorBytes, err := ioutil.ReadFile(errorFile) - if err != nil { - t.Error(err) - return nil - } - resultError = string(resultErrorBytes) - } else { - result, err = ioutil.ReadFile(filepath.Join(dirPath, "result.yaml")) - if err != nil { - t.Error(err) - return nil - } - } - testName := filepath.Base(dirPath) - - t.Run(testName, mergingPatchTest(main, parent, result, resultError, plugins...)) - } - return nil - }) -} - func TestPluginOverrides(t *testing.T) { originalFile := "test-fixtures/patches/override-just-plugin/original.yaml" patchFile := "test-fixtures/patches/override-just-plugin/patch.yaml" @@ -323,45 +244,6 @@ func TestPluginOverrides(t *testing.T) { } } -func TestMergingOnlyPlugins(t *testing.T) { - baseFile := "test-fixtures/merges/no-parent/main.yaml" - pluginFile := "test-fixtures/merges/no-parent/plugin.yaml" - resultFile := "test-fixtures/merges/no-parent/result.yaml" - - baseDWT := dw.DevWorkspaceTemplateSpecContent{} - pluginDWT := dw.DevWorkspaceTemplateSpecContent{} - expectedDWT := dw.DevWorkspaceTemplateSpecContent{} - - readFileToStruct(t, baseFile, &baseDWT) - readFileToStruct(t, pluginFile, &pluginDWT) - readFileToStruct(t, resultFile, &expectedDWT) - - gotDWT, err := MergeDevWorkspaceTemplateSpec(&baseDWT, nil, &pluginDWT) - if assert.NoError(t, err) { - assert.Equal(t, &expectedDWT, gotDWT) - } -} - -func TestMergingOnlyParent(t *testing.T) { - // Reuse only plugin case since it's compatible - baseFile := "test-fixtures/merges/no-parent/main.yaml" - parentFile := "test-fixtures/merges/no-parent/plugin.yaml" - resultFile := "test-fixtures/merges/no-parent/result.yaml" - - baseDWT := dw.DevWorkspaceTemplateSpecContent{} - parentDWT := dw.DevWorkspaceTemplateSpecContent{} - expectedDWT := dw.DevWorkspaceTemplateSpecContent{} - - readFileToStruct(t, baseFile, &baseDWT) - readFileToStruct(t, parentFile, &parentDWT) - readFileToStruct(t, resultFile, &expectedDWT) - - gotDWT, err := MergeDevWorkspaceTemplateSpec(&baseDWT, &parentDWT) - if assert.NoError(t, err) { - assert.Equal(t, &expectedDWT, gotDWT) - } -} - func readFileToStruct(t *testing.T, path string, into interface{}) { bytes, err := ioutil.ReadFile(path) if err != nil { diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml index 8a832c6e3..bf3b6154a 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml @@ -1,5 +1,8 @@ parent: uri: "anyParent" +attributes: + objectAttribute: + attributeField: 9.9 components: - container: image: "aDifferentValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml index 989a491c6..d5fa00155 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml @@ -1,3 +1,6 @@ +attributes: + objectAttribute: + attributeField: 10.10 components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt index be0507ac0..1fe9c1e87 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt @@ -1,2 +1,3 @@ -1 error occurred: +2 errors occurred: * Some Components are already defined in parent: existing-in-parent. If you want to override them, you should do it in the parent scope. + * Some Attributes are already defined in parent: objectAttribute. If you want to override them, you should do it in the parent scope. diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml index bf23b1dd0..75d84820a 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml @@ -1,5 +1,7 @@ parent: uri: "anyParent" +attributes: + mainAttribute: true components: - plugin: uri: "aCustomLocation" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml index 4bf4a854c..80e9f98aa 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml @@ -1,3 +1,6 @@ +attributes: + objectAttribute: + attributeField: 9.9 components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml index fc5c89742..3baeda67d 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml @@ -1,3 +1,7 @@ +attributes: + mainAttribute: true + objectAttribute: + attributeField: 9.9 components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml new file mode 100644 index 000000000..49da51e72 --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml @@ -0,0 +1,8 @@ +attributes: + boolAttributeToChange: true + stringAttributeToKeep: stringValue + + objectAttributeToChange: + attributeField: 9.9 + objectAttributeField: + objectAttributeSubField: 10 diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml new file mode 100644 index 000000000..35bf38b43 --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml @@ -0,0 +1,10 @@ +attributes: + boolAttributeToChange: false + + newAttributeValid: false + objectAttributeToChange: + + newObjectAttributeField: + objectAttributeSubField: 11 + newObjectAttributeSubField: newObjectAttributeFieldValue + newField: true diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt new file mode 100644 index 000000000..cb08e8d98 --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt @@ -0,0 +1,2 @@ +1 error occurred: + * Some Attributes do not override any existing element: newAttributeValid. They should be defined in the main body, as new elements, not in the overriding section diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml b/pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml new file mode 100644 index 000000000..b13770dfc --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml @@ -0,0 +1,7 @@ +attributes: + boolAttributeToChange: true + stringAttributeToKeep: stringValue + objectAttributeToChange: + attributeField: 9.9 + objectAttributeField: + objectAttributeSubField: 10 diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml b/pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml new file mode 100644 index 000000000..688a439a1 --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml @@ -0,0 +1,9 @@ +attributes: + boolAttributeToChange: false + + objectAttributeToChange: + + newObjectAttributeField: + objectAttributeSubField: 11 + newObjectAttributeSubField: newObjectAttributeFieldValue + newField: true diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml b/pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml new file mode 100644 index 000000000..f93675973 --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml @@ -0,0 +1,9 @@ +attributes: + boolAttributeToChange: false + stringAttributeToKeep: stringValue + objectAttributeToChange: + + newObjectAttributeField: + objectAttributeSubField: 11 + newObjectAttributeSubField: newObjectAttributeFieldValue + newField: true \ No newline at end of file diff --git a/pkg/validation/attributes/attributes.go b/pkg/validation/attributes/attributes.go new file mode 100644 index 000000000..027050a90 --- /dev/null +++ b/pkg/validation/attributes/attributes.go @@ -0,0 +1,62 @@ +package attributes + +import ( + "regexp" + "strings" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateGlobalAttribute validates the workspace template spec data for global attribute references +func ValidateGlobalAttribute(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { + + var err error + + if workspaceTemplateSpec != nil { + // Validate the components + if err = ValidateComponents(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Components); err != nil { + return err + } + + // Validate the commands + if err = ValidateCommands(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Commands); err != nil { + return err + } + + // Validate the events + if err = ValidateEvents(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Events); err != nil { + return err + } + + // Validate the projects + if err = ValidateProjects(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Projects); err != nil { + return err + } + + // Validate the starter projects + if err = ValidateStarterProjects(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.StarterProjects); err != nil { + return err + } + } + + return nil +} + +var globalAttributeRegex = regexp.MustCompile(`\{{2}(.*?)\}{2}`) + +// validateAndReplaceDataWithAttribute validates the string for a global attribute and replaces it. An error +// is returned if the string references an invalid global attribute key +func validateAndReplaceDataWithAttribute(val string, attributes apiAttributes.Attributes) (string, error) { + matches := globalAttributeRegex.FindAllStringSubmatch(val, -1) + for _, match := range matches { + var err error + attrValue := attributes.GetString(match[1], &err) + if err != nil { + return "", err + } + val = strings.Replace(val, match[0], attrValue, -1) + } + + return val, nil +} diff --git a/pkg/validation/attributes/attributes_command.go b/pkg/validation/attributes/attributes_command.go new file mode 100644 index 000000000..8155ebd98 --- /dev/null +++ b/pkg/validation/attributes/attributes_command.go @@ -0,0 +1,139 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateCommands validates the commands data for a global attribute +func ValidateCommands(attributes apiAttributes.Attributes, commands *[]v1alpha2.Command) error { + + if commands != nil { + for i := range *commands { + var err error + + // Validate various command types + switch { + case (*commands)[i].Exec != nil: + if err = validateExecCommand(attributes, (*commands)[i].Exec); err != nil { + return err + } + case (*commands)[i].Composite != nil: + if err = validateCompositeCommand(attributes, (*commands)[i].Composite); err != nil { + return err + } + case (*commands)[i].Apply != nil: + if err = validateApplyCommand(attributes, (*commands)[i].Apply); err != nil { + return err + } + case (*commands)[i].VscodeLaunch != nil || (*commands)[i].VscodeTask != nil: + var vscodeCommand *v1alpha2.VscodeConfigurationCommand + if (*commands)[i].VscodeLaunch != nil { + vscodeCommand = (*commands)[i].VscodeLaunch + } else { + vscodeCommand = (*commands)[i].VscodeTask + } + if err = validateVsCodeCommand(attributes, vscodeCommand); err != nil { + return err + } + } + } + } + + return nil +} + +// validateExecCommand validates the exec command data for a global attribute +func validateExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.ExecCommand) error { + var err error + + if exec != nil { + // Validate exec command line + if exec.CommandLine, err = validateAndReplaceDataWithAttribute(exec.CommandLine, attributes); err != nil { + return err + } + + // Validate exec component + if exec.Component, err = validateAndReplaceDataWithAttribute(exec.Component, attributes); err != nil { + return err + } + + // Validate exec working dir + if exec.WorkingDir, err = validateAndReplaceDataWithAttribute(exec.WorkingDir, attributes); err != nil { + return err + } + + // Validate exec label + if exec.Label, err = validateAndReplaceDataWithAttribute(exec.Label, attributes); err != nil { + return err + } + + // Validate exec env + if len(exec.Env) > 0 { + if err = validateEnv(attributes, &exec.Env); err != nil { + return err + } + } + } + + return nil +} + +// validateExecCommand validates the composite command data for a global attribute +func validateCompositeCommand(attributes apiAttributes.Attributes, composite *v1alpha2.CompositeCommand) error { + var err error + + if composite != nil { + // Validate composite label + if composite.Label, err = validateAndReplaceDataWithAttribute(composite.Label, attributes); err != nil { + return err + } + + // Validate composite commands + for i := range composite.Commands { + if composite.Commands[i], err = validateAndReplaceDataWithAttribute(composite.Commands[i], attributes); err != nil { + return err + } + } + } + + return nil +} + +// validateApplyCommand validates the apply command data for a global attribute +func validateApplyCommand(attributes apiAttributes.Attributes, apply *v1alpha2.ApplyCommand) error { + var err error + + if apply != nil { + // Validate composite label + if apply.Label, err = validateAndReplaceDataWithAttribute(apply.Label, attributes); err != nil { + return err + } + + // Validate apply component + if apply.Component, err = validateAndReplaceDataWithAttribute(apply.Component, attributes); err != nil { + return err + } + } + + return nil +} + +// validateVsCodeCommand validates the vs code command data for a global attribute +func validateVsCodeCommand(attributes apiAttributes.Attributes, vscodeCommand *v1alpha2.VscodeConfigurationCommand) error { + var err error + + if vscodeCommand != nil { + // Validate vscode command uri + if vscodeCommand.Uri, err = validateAndReplaceDataWithAttribute(vscodeCommand.Uri, attributes); err != nil { + return err + } + + // Validate vscode command inlined + if vscodeCommand.Inlined, err = validateAndReplaceDataWithAttribute(vscodeCommand.Inlined, attributes); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_command_test.go b/pkg/validation/attributes/attributes_command_test.go new file mode 100644 index 000000000..1b7aa3aa5 --- /dev/null +++ b/pkg/validation/attributes/attributes_command_test.go @@ -0,0 +1,172 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateExecCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.ExecCommand + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/exec.yaml", + expected: v1alpha2.ExecCommand{ + CommandLine: "tail -f /dev/null", + WorkingDir: "FOO", + Component: "BAR", + LabeledCommand: v1alpha2.LabeledCommand{ + Label: "1", + }, + Env: []v1alpha2.EnvVar{ + { + Name: "FOO", + Value: "BAR", + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1", + "devnull": "/dev/null", + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/exec.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testExecCommand := v1alpha2.ExecCommand{} + + readFileToStruct(t, tt.testFile, &testExecCommand) + + err := validateExecCommand(tt.attributes, &testExecCommand) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testExecCommand, "The two values should be the same.") + } + }) + } +} + +func TestValidateCompositeCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.CompositeCommand + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/composite.yaml", + expected: v1alpha2.CompositeCommand{ + LabeledCommand: v1alpha2.LabeledCommand{ + Label: "1", + }, + Commands: []string{ + "FOO", + "BAR", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/composite.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCompositeCommand := v1alpha2.CompositeCommand{} + + readFileToStruct(t, tt.testFile, &testCompositeCommand) + + err := validateCompositeCommand(tt.attributes, &testCompositeCommand) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testCompositeCommand, "The two values should be the same.") + } + }) + } +} + +func TestValidateApplyCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.ApplyCommand + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/apply.yaml", + expected: v1alpha2.ApplyCommand{ + LabeledCommand: v1alpha2.LabeledCommand{ + Label: "1", + }, + Component: "FOO", + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/apply.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApplyCommand := v1alpha2.ApplyCommand{} + + readFileToStruct(t, tt.testFile, &testApplyCommand) + + err := validateApplyCommand(tt.attributes, &testApplyCommand) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testApplyCommand, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_component.go b/pkg/validation/attributes/attributes_component.go new file mode 100644 index 000000000..a603a5496 --- /dev/null +++ b/pkg/validation/attributes/attributes_component.go @@ -0,0 +1,194 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateComponents validates the components data for a global attribute +func ValidateComponents(attributes apiAttributes.Attributes, components *[]v1alpha2.Component) error { + + if components != nil { + for i := range *components { + var err error + + // Validate various component types + switch { + case (*components)[i].Container != nil: + if err = validateContainerComponent(attributes, (*components)[i].Container); err != nil { + return err + } + case (*components)[i].Kubernetes != nil: + if err = validateKubernetesComponent(attributes, (*components)[i].Kubernetes); err != nil { + return err + } + case (*components)[i].Openshift != nil: + if err = validateOpenShiftComponent(attributes, (*components)[i].Openshift); err != nil { + return err + } + case (*components)[i].Volume != nil: + if err = validateVolumeComponent(attributes, (*components)[i].Volume); err != nil { + return err + } + } + } + } + + return nil +} + +// validateContainerComponent validates the container component data for a global attribute +func validateContainerComponent(attributes apiAttributes.Attributes, container *v1alpha2.ContainerComponent) error { + var err error + + if container != nil { + // Validate container image + if container.Image, err = validateAndReplaceDataWithAttribute(container.Image, attributes); err != nil { + return err + } + + // Validate container commands + for i := range container.Command { + if container.Command[i], err = validateAndReplaceDataWithAttribute(container.Command[i], attributes); err != nil { + return err + } + } + + // Validate container args + for i := range container.Args { + if container.Args[i], err = validateAndReplaceDataWithAttribute(container.Args[i], attributes); err != nil { + return err + } + } + + // Validate memory limit + if container.MemoryLimit, err = validateAndReplaceDataWithAttribute(container.MemoryLimit, attributes); err != nil { + return err + } + + // Validate memory limit + if container.MemoryRequest, err = validateAndReplaceDataWithAttribute(container.MemoryRequest, attributes); err != nil { + return err + } + + // Validate source mapping + if container.SourceMapping, err = validateAndReplaceDataWithAttribute(container.SourceMapping, attributes); err != nil { + return err + } + + // Validate container env + if len(container.Env) > 0 { + if err = validateEnv(attributes, &container.Env); err != nil { + return err + } + } + + // Validate container volume mounts + for i := range container.VolumeMounts { + if container.VolumeMounts[i].Name, err = validateAndReplaceDataWithAttribute(container.VolumeMounts[i].Name, attributes); err != nil { + return err + } + if container.VolumeMounts[i].Path, err = validateAndReplaceDataWithAttribute(container.VolumeMounts[i].Path, attributes); err != nil { + return err + } + } + + // Validate container endpoints + if len(container.Endpoints) > 0 { + if err = validateEndpoint(attributes, &container.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateEnv validates the env data for a global attribute +func validateEnv(attributes apiAttributes.Attributes, env *[]v1alpha2.EnvVar) error { + + if env != nil { + for i := range *env { + var err error + + // Validate env name + if (*env)[i].Name, err = validateAndReplaceDataWithAttribute((*env)[i].Name, attributes); err != nil { + return err + } + + // Validate env value + if (*env)[i].Value, err = validateAndReplaceDataWithAttribute((*env)[i].Value, attributes); err != nil { + return err + } + + } + } + + return nil +} + +// validateKubernetesComponent validates the kubernetes component data for a global attribute +func validateKubernetesComponent(attributes apiAttributes.Attributes, kubernetes *v1alpha2.KubernetesComponent) error { + var err error + + if kubernetes != nil { + // Validate kubernetes uri + if kubernetes.Uri, err = validateAndReplaceDataWithAttribute(kubernetes.Uri, attributes); err != nil { + return err + } + + // Validate kubernetes inlined + if kubernetes.Inlined, err = validateAndReplaceDataWithAttribute(kubernetes.Inlined, attributes); err != nil { + return err + } + + // Validate kubernetes endpoints + if len(kubernetes.Endpoints) > 0 { + if err = validateEndpoint(attributes, &kubernetes.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateOpenShiftComponent validates the openshift component data for a global attribute +func validateOpenShiftComponent(attributes apiAttributes.Attributes, openshift *v1alpha2.OpenshiftComponent) error { + var err error + + if openshift != nil { + // Validate openshift uri + if openshift.Uri, err = validateAndReplaceDataWithAttribute(openshift.Uri, attributes); err != nil { + return err + } + + // Validate openshift inlined + if openshift.Inlined, err = validateAndReplaceDataWithAttribute(openshift.Inlined, attributes); err != nil { + return err + } + + // Validate openshift endpoints + if len(openshift.Endpoints) > 0 { + if err = validateEndpoint(attributes, &openshift.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateVolumeComponent validates the volume component data for a global attribute +func validateVolumeComponent(attributes apiAttributes.Attributes, volume *v1alpha2.VolumeComponent) error { + var err error + + if volume != nil { + // Validate volume size + if volume.Size, err = validateAndReplaceDataWithAttribute(volume.Size, attributes); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_component_test.go b/pkg/validation/attributes/attributes_component_test.go new file mode 100644 index 000000000..7d1a1247e --- /dev/null +++ b/pkg/validation/attributes/attributes_component_test.go @@ -0,0 +1,260 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateContainerComponent(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.ContainerComponent + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/components/container.yaml", + expected: v1alpha2.ContainerComponent{ + Container: v1alpha2.Container{ + Image: "image-1", + Command: []string{"tail", "-f", "/dev/null"}, + Args: []string{"/dev/null"}, + Env: []v1alpha2.EnvVar{ + { + Name: "FOO", + Value: "BAR", + }, + }, + VolumeMounts: []v1alpha2.VolumeMount{ + { + Name: "vol1", + Path: "/FOO", + }, + }, + MemoryLimit: "FOO", + MemoryRequest: "FOO", + SourceMapping: "FOO", + }, + Endpoints: []v1alpha2.Endpoint{ + { + Name: "endpoint1", + Exposure: "public", + Path: "FOO", + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1", + "devnull": "/dev/null", + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/components/container.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testContainerComponent := v1alpha2.ContainerComponent{} + + readFileToStruct(t, tt.testFile, &testContainerComponent) + + err := validateContainerComponent(tt.attributes, &testContainerComponent) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testContainerComponent, "The two values should be the same.") + } + }) + } +} + +func TestValidateOpenShiftAndKubernetesComponent(t *testing.T) { + + expectedKubeLikeComp := v1alpha2.K8sLikeComponent{ + K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ + Uri: "uri", + Inlined: "inlined", + }, + Endpoints: []v1alpha2.Endpoint{ + { + Name: "endpoint1", + Exposure: "public", + Path: "FOO", + }, + }, + } + + tests := []struct { + name string + testFile string + expectedOpenShift v1alpha2.OpenshiftComponent + expectedKubernetes v1alpha2.KubernetesComponent + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + expectedOpenShift: v1alpha2.OpenshiftComponent{ + K8sLikeComponent: expectedKubeLikeComp, + }, + expectedKubernetes: v1alpha2.KubernetesComponent{ + K8sLikeComponent: expectedKubeLikeComp, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "uri": "uri", + "inlined": "inlined", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testOpenshiftComponent := v1alpha2.OpenshiftComponent{} + testKubernetesComponent := v1alpha2.KubernetesComponent{} + + readFileToStruct(t, tt.testFile, &testOpenshiftComponent) + readFileToStruct(t, tt.testFile, &testKubernetesComponent) + + err := validateOpenShiftComponent(tt.attributes, &testOpenshiftComponent) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expectedOpenShift, testOpenshiftComponent, "The two values should be the same.") + } + + err = validateKubernetesComponent(tt.attributes, &testKubernetesComponent) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expectedKubernetes, testKubernetesComponent, "The two values should be the same.") + } + }) + } +} + +func TestValidateVolumeComponent(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.VolumeComponent + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/components/volume.yaml", + expected: v1alpha2.VolumeComponent{ + Volume: v1alpha2.Volume{ + Size: "1Gi", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "size": "1Gi", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/components/volume.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testVolumeComponent := v1alpha2.VolumeComponent{} + + readFileToStruct(t, tt.testFile, &testVolumeComponent) + + err := validateVolumeComponent(tt.attributes, &testVolumeComponent) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testVolumeComponent, "The two values should be the same.") + } + }) + } +} + +func TestValidateEnv(t *testing.T) { + + tests := []struct { + name string + testFile string + expected []v1alpha2.EnvVar + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/components/env.yaml", + expected: []v1alpha2.EnvVar{ + { + Name: "FOO", + Value: "BAR", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + "bar": "BAR", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/components/env.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testEnv := v1alpha2.EnvVar{} + + readFileToStruct(t, tt.testFile, &testEnv) + + testEnvArr := []v1alpha2.EnvVar{testEnv} + + err := validateEnv(tt.attributes, &testEnvArr) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testEnvArr, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_endpoint.go b/pkg/validation/attributes/attributes_endpoint.go new file mode 100644 index 000000000..944498a09 --- /dev/null +++ b/pkg/validation/attributes/attributes_endpoint.go @@ -0,0 +1,23 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// validateEndpoint validates the endpoint data for a global attribute +func validateEndpoint(attributes apiAttributes.Attributes, endpoints *[]v1alpha2.Endpoint) error { + + if endpoints != nil { + for i := range *endpoints { + var err error + + // Validate endpoint path + if (*endpoints)[i].Path, err = validateAndReplaceDataWithAttribute((*endpoints)[i].Path, attributes); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_endpoint_test.go b/pkg/validation/attributes/attributes_endpoint_test.go new file mode 100644 index 000000000..225d2738d --- /dev/null +++ b/pkg/validation/attributes/attributes_endpoint_test.go @@ -0,0 +1,63 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateEndpoint(t *testing.T) { + + tests := []struct { + name string + testFile string + expected []v1alpha2.Endpoint + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/components/endpoint.yaml", + expected: []v1alpha2.Endpoint{ + { + Name: "endpoint1", + TargetPort: 9999, + Exposure: "public", + Protocol: "https", + Path: "/FOO", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/components/endpoint.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "notfoo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testEndpoint := v1alpha2.Endpoint{} + + readFileToStruct(t, tt.testFile, &testEndpoint) + + testEndpointArr := []v1alpha2.Endpoint{testEndpoint} + + err := validateEndpoint(tt.attributes, &testEndpointArr) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testEndpointArr, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_event.go b/pkg/validation/attributes/attributes_event.go new file mode 100644 index 000000000..90a435558 --- /dev/null +++ b/pkg/validation/attributes/attributes_event.go @@ -0,0 +1,45 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateEvents validates the events data for a global attribute +func ValidateEvents(attributes apiAttributes.Attributes, events *v1alpha2.Events) error { + var err error + + if events != nil { + switch { + case len(events.PreStart) > 0: + for i := range events.PreStart { + if events.PreStart[i], err = validateAndReplaceDataWithAttribute(events.PreStart[i], attributes); err != nil { + return err + } + } + fallthrough + case len(events.PostStart) > 0: + for i := range events.PostStart { + if events.PostStart[i], err = validateAndReplaceDataWithAttribute(events.PostStart[i], attributes); err != nil { + return err + } + } + fallthrough + case len(events.PreStop) > 0: + for i := range events.PreStop { + if events.PreStop[i], err = validateAndReplaceDataWithAttribute(events.PreStop[i], attributes); err != nil { + return err + } + } + fallthrough + case len(events.PostStop) > 0: + for i := range events.PostStop { + if events.PostStop[i], err = validateAndReplaceDataWithAttribute(events.PostStop[i], attributes); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_event_test.go b/pkg/validation/attributes/attributes_event_test.go new file mode 100644 index 000000000..4d38918ac --- /dev/null +++ b/pkg/validation/attributes/attributes_event_test.go @@ -0,0 +1,69 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateEvents(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.Events + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/events/event.yaml", + expected: v1alpha2.Events{ + WorkspaceEvents: v1alpha2.WorkspaceEvents{ + PreStart: []string{ + "FOO", + }, + PostStart: []string{ + "BAR", + }, + PreStop: []string{ + "FOOBAR", + }, + PostStop: []string{ + "BARFOO", + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/events/event.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testEvents := v1alpha2.Events{} + + readFileToStruct(t, tt.testFile, &testEvents) + + err := ValidateEvents(tt.attributes, &testEvents) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testEvents, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_parent.go b/pkg/validation/attributes/attributes_parent.go new file mode 100644 index 000000000..3572625f9 --- /dev/null +++ b/pkg/validation/attributes/attributes_parent.go @@ -0,0 +1,48 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateParent validates the parent data for a global attribute except parent overrides +func ValidateParent(attributes apiAttributes.Attributes, parent *v1alpha2.Parent) error { + var err error + + if parent != nil { + switch { + case parent.Id != "": + // Validate parent id + if parent.Id, err = validateAndReplaceDataWithAttribute(parent.Id, attributes); err != nil { + return err + } + case parent.Uri != "": + // Validate parent uri + if parent.Uri, err = validateAndReplaceDataWithAttribute(parent.Uri, attributes); err != nil { + return err + } + case parent.Kubernetes != nil: + // Validate parent kubernetes name + if parent.Kubernetes.Name, err = validateAndReplaceDataWithAttribute(parent.Kubernetes.Name, attributes); err != nil { + return err + } + + // Validate parent kubernetes namespace + if parent.Kubernetes.Namespace, err = validateAndReplaceDataWithAttribute(parent.Kubernetes.Namespace, attributes); err != nil { + return err + } + } + + // Validate parent registry url + if parent.RegistryUrl, err = validateAndReplaceDataWithAttribute(parent.RegistryUrl, attributes); err != nil { + return err + } + + // Note: No need to substitute parent overrides at this point. Call global attribute validation/substitution + // after merging the flattened parent devfile to the main devfile. Parent's global attribute key can + // be overridden in parent overrides or the alternative is to mention the attribute as a main devfile + // global attribute if parent devfile does not have a global attribute + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_parent_test.go b/pkg/validation/attributes/attributes_parent_test.go new file mode 100644 index 000000000..8d02cb683 --- /dev/null +++ b/pkg/validation/attributes/attributes_parent_test.go @@ -0,0 +1,95 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateParent(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.Parent + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Uri Substitution", + testFile: "test-fixtures/parent/parent-uri.yaml", + expected: v1alpha2.Parent{ + ImportReference: v1alpha2.ImportReference{ + ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ + Uri: "FOO", + }, + RegistryUrl: "FOO", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Good Id Substitution", + testFile: "test-fixtures/parent/parent-id.yaml", + expected: v1alpha2.Parent{ + ImportReference: v1alpha2.ImportReference{ + ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ + Id: "FOO", + }, + RegistryUrl: "FOO", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Good Kube Substitution", + testFile: "test-fixtures/parent/parent-kubernetes.yaml", + expected: v1alpha2.Parent{ + ImportReference: v1alpha2.ImportReference{ + ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ + Kubernetes: &v1alpha2.KubernetesCustomResourceImportReference{ + Name: "FOO", + Namespace: "FOO", + }, + }, + RegistryUrl: "FOO", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/parent/parent-id.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testParent := v1alpha2.Parent{} + + readFileToStruct(t, tt.testFile, &testParent) + + err := ValidateParent(tt.attributes, &testParent) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testParent, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_project.go b/pkg/validation/attributes/attributes_project.go new file mode 100644 index 000000000..834fc68cd --- /dev/null +++ b/pkg/validation/attributes/attributes_project.go @@ -0,0 +1,115 @@ +package attributes + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" +) + +// ValidateProjects validates the projects data for a global attribute +func ValidateProjects(attributes apiAttributes.Attributes, projects *[]v1alpha2.Project) error { + + if projects != nil { + for i := range *projects { + var err error + + // Validate project clonepath + if (*projects)[i].ClonePath, err = validateAndReplaceDataWithAttribute((*projects)[i].ClonePath, attributes); err != nil { + return err + } + + // Validate project sparse checkout dir + for j := range (*projects)[i].SparseCheckoutDirs { + if (*projects)[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithAttribute((*projects)[i].SparseCheckoutDirs[j], attributes); err != nil { + return err + } + } + + // Validate project source + if err = validateProjectSource(attributes, &(*projects)[i].ProjectSource); err != nil { + return err + } + } + } + + return nil +} + +// ValidateStarterProjects validates the starter projects data for a global attribute +func ValidateStarterProjects(attributes apiAttributes.Attributes, starterProjects *[]v1alpha2.StarterProject) error { + + if starterProjects != nil { + for i := range *starterProjects { + var err error + + // Validate starter project description + if (*starterProjects)[i].Description, err = validateAndReplaceDataWithAttribute((*starterProjects)[i].Description, attributes); err != nil { + return err + } + + // Validate starter project sub dir + if (*starterProjects)[i].SubDir, err = validateAndReplaceDataWithAttribute((*starterProjects)[i].SubDir, attributes); err != nil { + return err + } + + // Validate starter project source + if err = validateProjectSource(attributes, &(*starterProjects)[i].ProjectSource); err != nil { + return err + } + } + } + + return nil +} + +// validateProjectSource validates a project source location for a global attribute +func validateProjectSource(attributes apiAttributes.Attributes, projectSource *v1alpha2.ProjectSource) error { + + var err error + + if projectSource != nil { + switch { + case projectSource.Zip != nil: + if projectSource.Zip.Location, err = validateAndReplaceDataWithAttribute(projectSource.Zip.Location, attributes); err != nil { + return err + } + case projectSource.Git != nil || projectSource.Github != nil: + var gitProject *v1alpha2.GitLikeProjectSource + if projectSource.Git != nil { + gitProject = &projectSource.Git.GitLikeProjectSource + } else if projectSource.Github != nil { + gitProject = &projectSource.Github.GitLikeProjectSource + } + + if gitProject.CheckoutFrom != nil { + // validate git checkout revision + if gitProject.CheckoutFrom.Revision, err = validateAndReplaceDataWithAttribute(gitProject.CheckoutFrom.Revision, attributes); err != nil { + return err + } + + // // validate git checkout remote + if gitProject.CheckoutFrom.Remote, err = validateAndReplaceDataWithAttribute(gitProject.CheckoutFrom.Remote, attributes); err != nil { + return err + } + } + + // validate git remotes + for k := range gitProject.Remotes { + // update map value + if gitProject.Remotes[k], err = validateAndReplaceDataWithAttribute(gitProject.Remotes[k], attributes); err != nil { + return err + } + + // update map key + var updatedKey string + if updatedKey, err = validateAndReplaceDataWithAttribute(k, attributes); err != nil { + return err + } else if updatedKey != k { + gitProject.Remotes[updatedKey] = gitProject.Remotes[k] + delete(gitProject.Remotes, k) + } + } + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_project_test.go b/pkg/validation/attributes/attributes_project_test.go new file mode 100644 index 000000000..b52431c12 --- /dev/null +++ b/pkg/validation/attributes/attributes_project_test.go @@ -0,0 +1,215 @@ +package attributes + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" +) + +func TestValidateProjects(t *testing.T) { + + tests := []struct { + name string + testFile string + expected []v1alpha2.Project + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/projects/project.yaml", + expected: []v1alpha2.Project{ + { + Name: "project1", + ClonePath: "/FOO", + SparseCheckoutDirs: []string{ + "/FOO", + "/BAR", + }, + ProjectSource: v1alpha2.ProjectSource{ + Git: &v1alpha2.GitProjectSource{ + GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ + CheckoutFrom: &v1alpha2.CheckoutFrom{ + Revision: "FOO", + }, + Remotes: map[string]string{ + "foo": "BAR", + "FOOBAR": "BARFOO", + }, + }, + }, + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/projects/project.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testProject := v1alpha2.Project{} + + readFileToStruct(t, tt.testFile, &testProject) + + testProjectArr := []v1alpha2.Project{testProject} + + err := ValidateProjects(tt.attributes, &testProjectArr) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testProjectArr, "The two values should be the same.") + } + }) + } +} + +func TestValidateStarterProjects(t *testing.T) { + + tests := []struct { + name string + testFile string + expected []v1alpha2.StarterProject + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/projects/starterproject.yaml", + expected: []v1alpha2.StarterProject{ + { + Name: "starterproject1", + Description: "FOOBAR is not BARFOO", + SubDir: "/FOO", + ProjectSource: v1alpha2.ProjectSource{ + Zip: &v1alpha2.ZipProjectSource{ + Location: "/FOO", + }, + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/projects/starterproject.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testStarterProject := v1alpha2.StarterProject{} + + readFileToStruct(t, tt.testFile, &testStarterProject) + + testStarterProjectArr := []v1alpha2.StarterProject{testStarterProject} + + err := ValidateStarterProjects(tt.attributes, &testStarterProjectArr) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testStarterProjectArr, "The two values should be the same.") + } + }) + } +} + +func TestValidateProjectSrc(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.ProjectSource + attributes apiAttributes.Attributes + wantErr bool + }{ + { + name: "Good Git Substitution", + testFile: "test-fixtures/projects/git.yaml", + expected: v1alpha2.ProjectSource{ + Git: &v1alpha2.GitProjectSource{ + GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ + CheckoutFrom: &v1alpha2.CheckoutFrom{ + Revision: "FOO", + Remote: "BAR", + }, + Remotes: map[string]string{ + "foo": "BAR", + "FOOBAR": "BARFOO", + }, + }, + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Good Zip Substitution", + testFile: "test-fixtures/projects/zip.yaml", + expected: v1alpha2.ProjectSource{ + Zip: &v1alpha2.ZipProjectSource{ + Location: "/FOO", + }, + }, + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: false, + }, + { + name: "Invalid Git Reference", + testFile: "test-fixtures/projects/git.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "foo": "FOO", + }, nil), + wantErr: true, + }, + { + name: "Invalid Zip Reference", + testFile: "test-fixtures/projects/zip.yaml", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "bar": "BAR", + }, nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testProjectSrc := v1alpha2.ProjectSource{} + + readFileToStruct(t, tt.testFile, &testProjectSrc) + + err := validateProjectSource(tt.attributes, &testProjectSrc) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testProjectSrc, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_test.go b/pkg/validation/attributes/attributes_test.go new file mode 100644 index 000000000..8d32bdc46 --- /dev/null +++ b/pkg/validation/attributes/attributes_test.go @@ -0,0 +1,257 @@ +package attributes + +import ( + "io/ioutil" + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + apiAttributes "github.com/devfile/api/v2/pkg/attributes" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/yaml" +) + +func TestValidateGlobalAttributeBasic(t *testing.T) { + + tests := []struct { + name string + testFile string + expected v1alpha2.DevWorkspaceTemplateSpec + wantErr bool + }{ + { + name: "Successful global attribute substitution", + testFile: "test-fixtures/all/devfile-good.yaml", + expected: v1alpha2.DevWorkspaceTemplateSpec{ + DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ + Attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "tag": "xyz", + "version": "1", + "foo": "FOO", + "devnull": "/dev/null", + }, nil), + Components: []v1alpha2.Component{ + { + Name: "component1", + ComponentUnion: v1alpha2.ComponentUnion{ + Container: &v1alpha2.ContainerComponent{ + Container: v1alpha2.Container{ + Image: "image", + Command: []string{"tail", "-f", "/dev/null"}, + Env: []v1alpha2.EnvVar{ + { + Name: "BAR", + Value: "FOO", + }, + { + Name: "FOO", + Value: "BAR", + }, + }, + }, + }, + }, + }, + { + Name: "component2", + ComponentUnion: v1alpha2.ComponentUnion{ + Kubernetes: &v1alpha2.KubernetesComponent{ + K8sLikeComponent: v1alpha2.K8sLikeComponent{ + K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ + Inlined: "FOO", + }, + Endpoints: []v1alpha2.Endpoint{ + { + Name: "endpoint1", + Exposure: "public", + TargetPort: 9999, + }, + }, + }, + }, + }, + }, + }, + Commands: []v1alpha2.Command{ + { + Id: "command1", + CommandUnion: v1alpha2.CommandUnion{ + Exec: &v1alpha2.ExecCommand{ + CommandLine: "test-xyz", + Env: []v1alpha2.EnvVar{ + { + Name: "tag", + Value: "xyz", + }, + { + Name: "FOO", + Value: "BAR", + }, + }, + }, + }, + }, + { + Id: "command2", + CommandUnion: v1alpha2.CommandUnion{ + Composite: &v1alpha2.CompositeCommand{ + Commands: []string{ + "xyz", + "command1", + }, + }, + }, + }, + }, + Events: &v1alpha2.Events{ + WorkspaceEvents: v1alpha2.WorkspaceEvents{ + PreStart: []string{ + "xyz", + "test", + }, + PreStop: []string{ + "1", + }, + }, + }, + Projects: []v1alpha2.Project{ + { + Name: "project1", + ProjectSource: v1alpha2.ProjectSource{ + Git: &v1alpha2.GitProjectSource{ + GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ + CheckoutFrom: &v1alpha2.CheckoutFrom{ + Revision: "xyz", + }, + Remotes: map[string]string{ + "xyz": "/dev/null", + "1": "test", + }, + }, + }, + }, + }, + { + Name: "project2", + ProjectSource: v1alpha2.ProjectSource{ + Zip: &v1alpha2.ZipProjectSource{ + Location: "xyz", + }, + }, + }, + }, + StarterProjects: []v1alpha2.StarterProject{ + { + Name: "starterproject1", + ProjectSource: v1alpha2.ProjectSource{ + Git: &v1alpha2.GitProjectSource{ + GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ + CheckoutFrom: &v1alpha2.CheckoutFrom{ + Revision: "xyz", + }, + Remotes: map[string]string{ + "xyz": "/dev/null", + "1": "test", + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/all/devfile-bad.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testDWT := v1alpha2.DevWorkspaceTemplateSpec{} + + readFileToStruct(t, tt.testFile, &testDWT) + + err := ValidateGlobalAttribute(&testDWT) + if tt.wantErr == (err == nil) { + t.Errorf("error: %v", err) + return + } else if err == nil { + assert.Equal(t, tt.expected, testDWT, "The two values should be the same.") + } + }) + } +} + +func TestValidateAndReplaceDataWithAttribute(t *testing.T) { + + invalidAttributeErr := ".*Attribute with key .* does not exist.*" + wrongAttributeTypeErr := ".*cannot unmarshal object into Go value of type string.*" + + tests := []struct { + name string + testString string + attributes apiAttributes.Attributes + wantValue string + wantErr *string + }{ + { + name: "Valid attribute reference", + testString: "image-{{version}}:{{tag}}-14", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1.x.x", + "tag": "dev", + "import": map[string]interface{}{ + "strategy": "Dockerfile", + }, + }, nil), + wantValue: "image-1.x.x:dev-14", + wantErr: nil, + }, + { + name: "Invalid attribute reference", + testString: "image-{{version}}:{{invalid}}-14", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1.x.x", + "tag": "dev", + }, nil), + wantErr: &invalidAttributeErr, + }, + { + name: "Attribute reference with non-string type value", + testString: "image-{{version}}:{{invalid}}-14", + attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + "version": "1.x.x", + "invalid": map[string]interface{}{ + "key": "value", + }, + }, nil), + wantErr: &wrongAttributeTypeErr, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotValue, err := validateAndReplaceDataWithAttribute(tt.testString, tt.attributes) + if tt.wantErr != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.wantErr, err.Error(), "Error message should match") + } else { + assert.NoError(t, err, "Expected error to be nil") + if gotValue != tt.wantValue { + assert.Equal(t, tt.wantValue, gotValue, "Return value should match") + } + } + }) + } +} + +func readFileToStruct(t *testing.T, path string, into interface{}) { + bytes, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read test file from %s: %s", path, err.Error()) + } + err = yaml.Unmarshal(bytes, into) + if err != nil { + t.Fatalf("Failed to unmarshal file into struct: %s", err.Error()) + } +} diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml b/pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml new file mode 100644 index 000000000..21e2499d4 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml @@ -0,0 +1,13 @@ +attributes: + devnull: /dev/null +components: +- name: component1 + container: + image: image + env: + - name: BAR + value: "{{foo}}" + command: + - tail + - -f + - "{{devnull}}" diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml b/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml new file mode 100644 index 000000000..cd41546c7 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml @@ -0,0 +1,64 @@ +attributes: + tag: xyz + version: "1" + foo: FOO + devnull: /dev/null +projects: +- name: project1 + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{tag}}": "{{devnull}}" + "{{version}}": "test" +- name: project2 + zip: + location: "{{tag}}" +starterProjects: +- name: starterproject1 + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{tag}}": "{{devnull}}" + "{{version}}": "test" +components: +- name: component1 + container: + image: image + env: + - name: BAR + value: "{{foo}}" + - name: FOO + value: BAR + command: + - tail + - -f + - "{{devnull}}" +- name: component2 + kubernetes: + inlined: "{{foo}}" + endpoints: + - name: endpoint1 + exposure: "public" + targetPort: 9999 +commands: +- id: command1 + exec: + commandLine: "test-{{tag}}" + env: + - name: tag + value: "{{tag}}" + - name: FOO + value: BAR +- id: command2 + composite: + commands: + - "{{tag}}" + - command1 +events: + preStart: + - "{{tag}}" + - test + preStop: + - "{{version}}" diff --git a/pkg/validation/attributes/test-fixtures/commands/apply.yaml b/pkg/validation/attributes/test-fixtures/commands/apply.yaml new file mode 100644 index 000000000..5f133087d --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/apply.yaml @@ -0,0 +1,2 @@ +label: "{{version}}" +component: "{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/composite.yaml b/pkg/validation/attributes/test-fixtures/commands/composite.yaml new file mode 100644 index 000000000..6f132f06f --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/composite.yaml @@ -0,0 +1,4 @@ +label: "{{version}}" +commands: + - "{{foo}}" + - BAR \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/exec.yaml b/pkg/validation/attributes/test-fixtures/commands/exec.yaml new file mode 100644 index 000000000..61441c413 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/exec.yaml @@ -0,0 +1,7 @@ +component: "{{bar}}" +commandLine: tail -f {{devnull}} +workingDir: "{{foo}}" +label: "{{version}}" +env: + - name: "{{foo}}" + value: "{{bar}}" diff --git a/pkg/validation/attributes/test-fixtures/components/container.yaml b/pkg/validation/attributes/test-fixtures/components/container.yaml new file mode 100644 index 000000000..1873a3118 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/container.yaml @@ -0,0 +1,20 @@ +image: "image-{{version}}" +env: + - name: "{{foo}}" + value: "{{bar}}" +command: + - tail + - -f + - "{{devnull}}" +args: + - "{{devnull}}" +memoryLimit: "{{foo}}" +memoryRequest: "{{foo}}" +sourceMapping: "{{foo}}" +volumeMounts: + - name: vol1 + path: "/{{foo}}" +endpoints: + - name: endpoint1 + exposure: public + path: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/endpoint.yaml b/pkg/validation/attributes/test-fixtures/components/endpoint.yaml new file mode 100644 index 000000000..84b8c5a3b --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/endpoint.yaml @@ -0,0 +1,5 @@ +name: endpoint1 +exposure: "public" +protocol: "https" +path : "/{{foo}}" +targetPort: 9999 diff --git a/pkg/validation/attributes/test-fixtures/components/env.yaml b/pkg/validation/attributes/test-fixtures/components/env.yaml new file mode 100644 index 000000000..815422f79 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/env.yaml @@ -0,0 +1,2 @@ +name: "{{foo}}" +value: "{{bar}}" diff --git a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml new file mode 100644 index 000000000..a05736bed --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml @@ -0,0 +1,6 @@ +uri: "{{uri}}" +inlined: "{{inlined}}" +endpoints: + - name: endpoint1 + exposure: public + path: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/volume.yaml b/pkg/validation/attributes/test-fixtures/components/volume.yaml new file mode 100644 index 000000000..5a4bf3242 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/volume.yaml @@ -0,0 +1 @@ +size: "{{size}}" diff --git a/pkg/validation/attributes/test-fixtures/events/event.yaml b/pkg/validation/attributes/test-fixtures/events/event.yaml new file mode 100644 index 000000000..d316b275b --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/events/event.yaml @@ -0,0 +1,8 @@ +preStart: + - "{{foo}}" +postStart: + - "{{bar}}" +preStop: + - "{{foo}}{{bar}}" +postStop: + - "{{bar}}{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml new file mode 100644 index 000000000..667952142 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml @@ -0,0 +1,2 @@ +id: "{{foo}}" +registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml new file mode 100644 index 000000000..4aebcd259 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml @@ -0,0 +1,4 @@ +kubernetes: + name: "{{foo}}" + namespace: "{{foo}}" +registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml new file mode 100644 index 000000000..8328a3c92 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml @@ -0,0 +1,2 @@ +uri: "{{foo}}" +registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/git.yaml b/pkg/validation/attributes/test-fixtures/projects/git.yaml new file mode 100644 index 000000000..2bec9bdf5 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/git.yaml @@ -0,0 +1,7 @@ +git: + checkoutFrom: + revision: "{{foo}}" + remote: "{{bar}}" + remotes: + "foo": "{{bar}}" + "{{foo}}{{bar}}": "{{bar}}{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/project.yaml b/pkg/validation/attributes/test-fixtures/projects/project.yaml new file mode 100644 index 000000000..6b6b93866 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/project.yaml @@ -0,0 +1,11 @@ +name: project1 +clonePath: "/{{foo}}" +sparseCheckoutDirs: + - /FOO + - "/{{bar}}" +git: + checkoutFrom: + revision: "{{foo}}" + remotes: + "foo": "{{bar}}" + "{{foo}}{{bar}}": "{{bar}}{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml b/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml new file mode 100644 index 000000000..f45e3209d --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml @@ -0,0 +1,5 @@ +name: starterproject1 +zip: + location: "/{{foo}}" +description: "{{foo}}{{bar}} is not {{bar}}{{foo}}" +subDir: "/{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/zip.yaml b/pkg/validation/attributes/test-fixtures/projects/zip.yaml new file mode 100644 index 000000000..3f2d9a1d1 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/zip.yaml @@ -0,0 +1,2 @@ +zip: + location: "/{{foo}}" \ No newline at end of file diff --git a/schemas/latest/dev-workspace-template-spec.json b/schemas/latest/dev-workspace-template-spec.json index 7052e3732..c325294b6 100644 --- a/schemas/latest/dev-workspace-template-spec.json +++ b/schemas/latest/dev-workspace-template-spec.json @@ -3,6 +3,11 @@ "type": "object", "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -674,6 +679,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1334,6 +1344,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1896,6 +1911,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/dev-workspace-template.json b/schemas/latest/dev-workspace-template.json index 82b985460..6fd593bcb 100644 --- a/schemas/latest/dev-workspace-template.json +++ b/schemas/latest/dev-workspace-template.json @@ -168,6 +168,11 @@ "description": "Structure of the devworkspace. This is also the specification of a devworkspace template.", "type": "object", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -839,6 +844,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1499,6 +1509,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -2061,6 +2076,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/dev-workspace.json b/schemas/latest/dev-workspace.json index aaa101f94..a9279abc6 100644 --- a/schemas/latest/dev-workspace.json +++ b/schemas/latest/dev-workspace.json @@ -181,6 +181,11 @@ "description": "Structure of the devworkspace. This is also the specification of a devworkspace template.", "type": "object", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -852,6 +857,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1512,6 +1522,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -2074,6 +2089,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/devfile.json b/schemas/latest/devfile.json index 126f52945..87facb419 100644 --- a/schemas/latest/devfile.json +++ b/schemas/latest/devfile.json @@ -6,6 +6,11 @@ "schemaVersion" ], "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -704,6 +709,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/dev-workspace-template-spec.json b/schemas/latest/ide-targeted/dev-workspace-template-spec.json index 14c402875..f180ba120 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template-spec.json +++ b/schemas/latest/ide-targeted/dev-workspace-template-spec.json @@ -3,6 +3,12 @@ "type": "object", "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -737,6 +743,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1474,6 +1486,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -2096,6 +2114,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/dev-workspace-template.json b/schemas/latest/ide-targeted/dev-workspace-template.json index 6f9241d52..0d14b965f 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template.json +++ b/schemas/latest/ide-targeted/dev-workspace-template.json @@ -201,6 +201,12 @@ "description": "Structure of the devworkspace. This is also the specification of a devworkspace template.", "type": "object", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -935,6 +941,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1672,6 +1684,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -2294,6 +2312,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/dev-workspace.json b/schemas/latest/ide-targeted/dev-workspace.json index dc98edb75..f3b7f6440 100644 --- a/schemas/latest/ide-targeted/dev-workspace.json +++ b/schemas/latest/ide-targeted/dev-workspace.json @@ -214,6 +214,12 @@ "description": "Structure of the devworkspace. This is also the specification of a devworkspace template.", "type": "object", "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -948,6 +954,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1685,6 +1697,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -2307,6 +2325,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/devfile.json b/schemas/latest/ide-targeted/devfile.json index 77210b65f..912116e21 100644 --- a/schemas/latest/ide-targeted/devfile.json +++ b/schemas/latest/ide-targeted/devfile.json @@ -6,6 +6,12 @@ "schemaVersion" ], "properties": { + "attributes": { + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", "type": "array", @@ -778,6 +784,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/parent-overrides.json b/schemas/latest/ide-targeted/parent-overrides.json index 06ba994bf..62af524b7 100644 --- a/schemas/latest/ide-targeted/parent-overrides.json +++ b/schemas/latest/ide-targeted/parent-overrides.json @@ -3,6 +3,12 @@ "type": "object", "title": "ParentOverrides schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -625,6 +631,12 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/plugin-overrides.json b/schemas/latest/ide-targeted/plugin-overrides.json index 417825c15..711605dd6 100644 --- a/schemas/latest/ide-targeted/plugin-overrides.json +++ b/schemas/latest/ide-targeted/plugin-overrides.json @@ -3,6 +3,12 @@ "type": "object", "title": "PluginOverrides schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true, + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/parent-overrides.json b/schemas/latest/parent-overrides.json index 53f1db705..9b1486c72 100644 --- a/schemas/latest/parent-overrides.json +++ b/schemas/latest/parent-overrides.json @@ -2,6 +2,11 @@ "type": "object", "title": "ParentOverrides schema - Version 2.1.0-alpha", "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -564,6 +569,11 @@ } ], "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/plugin-overrides.json b/schemas/latest/plugin-overrides.json index 09fbba842..5a036b92f 100644 --- a/schemas/latest/plugin-overrides.json +++ b/schemas/latest/plugin-overrides.json @@ -2,6 +2,11 @@ "type": "object", "title": "PluginOverrides schema - Version 2.1.0-alpha", "properties": { + "attributes": { + "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": true + }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", From 06b0dd1a56fe37b0e6b23285d527bd2fe85a814e Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Thu, 18 Feb 2021 17:31:38 -0500 Subject: [PATCH 02/10] Remove global attribute support for vs commands Signed-off-by: Maysun J Faisal --- .../attributes/attributes_command.go | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/pkg/validation/attributes/attributes_command.go b/pkg/validation/attributes/attributes_command.go index 8155ebd98..ae615bd85 100644 --- a/pkg/validation/attributes/attributes_command.go +++ b/pkg/validation/attributes/attributes_command.go @@ -26,16 +26,6 @@ func ValidateCommands(attributes apiAttributes.Attributes, commands *[]v1alpha2. if err = validateApplyCommand(attributes, (*commands)[i].Apply); err != nil { return err } - case (*commands)[i].VscodeLaunch != nil || (*commands)[i].VscodeTask != nil: - var vscodeCommand *v1alpha2.VscodeConfigurationCommand - if (*commands)[i].VscodeLaunch != nil { - vscodeCommand = (*commands)[i].VscodeLaunch - } else { - vscodeCommand = (*commands)[i].VscodeTask - } - if err = validateVsCodeCommand(attributes, vscodeCommand); err != nil { - return err - } } } } @@ -118,22 +108,3 @@ func validateApplyCommand(attributes apiAttributes.Attributes, apply *v1alpha2.A return nil } - -// validateVsCodeCommand validates the vs code command data for a global attribute -func validateVsCodeCommand(attributes apiAttributes.Attributes, vscodeCommand *v1alpha2.VscodeConfigurationCommand) error { - var err error - - if vscodeCommand != nil { - // Validate vscode command uri - if vscodeCommand.Uri, err = validateAndReplaceDataWithAttribute(vscodeCommand.Uri, attributes); err != nil { - return err - } - - // Validate vscode command inlined - if vscodeCommand.Inlined, err = validateAndReplaceDataWithAttribute(vscodeCommand.Inlined, attributes); err != nil { - return err - } - } - - return nil -} From 1460441137b0cecce1af43d3e238fd0ef2a81200 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Fri, 19 Feb 2021 16:58:01 -0500 Subject: [PATCH 03/10] Remove global attributes from plugin overrides Signed-off-by: Maysun J Faisal --- ...pace.devfile.io_devworkspaces.v1beta1.yaml | 24 ++++++------------- crds/workspace.devfile.io_devworkspaces.yaml | 24 ++++++------------- ...file.io_devworkspacetemplates.v1beta1.yaml | 22 +++++------------ ...pace.devfile.io_devworkspacetemplates.yaml | 22 +++++------------ .../v1alpha2/devworkspacetemplate_spec.go | 7 +++--- .../v1alpha2/zz_generated.deepcopy.go | 14 ----------- .../v1alpha2/zz_generated.parent_overrides.go | 8 +------ .../v1alpha2/zz_generated.plugin_overrides.go | 6 ----- pkg/utils/overriding/keys.go | 2 +- .../latest/dev-workspace-template-spec.json | 14 ++--------- schemas/latest/dev-workspace-template.json | 14 ++--------- schemas/latest/dev-workspace.json | 14 ++--------- schemas/latest/devfile.json | 4 ++-- .../dev-workspace-template-spec.json | 20 ++++------------ .../ide-targeted/dev-workspace-template.json | 20 ++++------------ .../latest/ide-targeted/dev-workspace.json | 20 ++++------------ schemas/latest/ide-targeted/devfile.json | 8 +++---- .../latest/ide-targeted/parent-overrides.json | 10 ++------ .../latest/ide-targeted/plugin-overrides.json | 6 ----- schemas/latest/parent-overrides.json | 7 +----- schemas/latest/plugin-overrides.json | 5 ---- 21 files changed, 59 insertions(+), 212 deletions(-) diff --git a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml index fb482c1b5..09e8ad835 100644 --- a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml @@ -4142,9 +4142,12 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced through out the devfile in + Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for - schemaVersion and metadata + schemaVersion and metadata. Exception to the string field include + element's key identifiers(command id, component name, endpoint + name, project name, etc.) and string enums(command group kind, + endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -4830,12 +4833,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in - a parent devfile or a plugin. Overriding is done according - to K8S strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according @@ -5576,8 +5573,8 @@ spec: properties: attributes: description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S - strategic merge patch standard rules. + devfile. Overriding is done according to K8S strategic merge + patch standard rules. type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -6198,13 +6195,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated - in a parent devfile or a plugin. Overriding is - done according to K8S strategic merge patch standard - rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is diff --git a/crds/workspace.devfile.io_devworkspaces.yaml b/crds/workspace.devfile.io_devworkspaces.yaml index e021c75f0..3f69d987a 100644 --- a/crds/workspace.devfile.io_devworkspaces.yaml +++ b/crds/workspace.devfile.io_devworkspaces.yaml @@ -4140,9 +4140,12 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced through out the devfile in + Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for - schemaVersion and metadata + schemaVersion and metadata. Exception to the string field include + element's key identifiers(command id, component name, endpoint + name, project name, etc.) and string enums(command group kind, + endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -4835,12 +4838,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in - a parent devfile or a plugin. Overriding is done according - to K8S strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according @@ -5581,8 +5578,8 @@ spec: properties: attributes: description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S - strategic merge patch standard rules. + devfile. Overriding is done according to K8S strategic merge + patch standard rules. type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -6203,13 +6200,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated - in a parent devfile or a plugin. Overriding is - done according to K8S strategic merge patch standard - rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is diff --git a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml index 500bafaca..c4d20808f 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml @@ -3916,9 +3916,11 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced through out the devfile in string + Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion - and metadata + and metadata. Exception to the string field include element's key + identifiers(command id, component name, endpoint name, project name, + etc.) and string enums(command group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -4579,12 +4581,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S - strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -5299,8 +5295,8 @@ spec: properties: attributes: description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S strategic - merge patch standard rules. + devfile. Overriding is done according to K8S strategic merge + patch standard rules. type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -5900,12 +5896,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in - a parent devfile or a plugin. Overriding is done according - to K8S strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according diff --git a/crds/workspace.devfile.io_devworkspacetemplates.yaml b/crds/workspace.devfile.io_devworkspacetemplates.yaml index 736f835a6..b9ecc51de 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.yaml @@ -3914,9 +3914,11 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced through out the devfile in string + Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion - and metadata + and metadata. Exception to the string field include element's key + identifiers(command id, component name, endpoint name, project name, + etc.) and string enums(command group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -4584,12 +4586,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S - strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S @@ -5304,8 +5300,8 @@ spec: properties: attributes: description: Overrides of attributes encapsulated in a parent - devfile or a plugin. Overriding is done according to K8S strategic - merge patch standard rules. + devfile. Overriding is done according to K8S strategic merge + patch standard rules. type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -5905,12 +5901,6 @@ spec: - required: - kubernetes properties: - attributes: - description: Overrides of attributes encapsulated in - a parent devfile or a plugin. Overriding is done according - to K8S strategic merge patch standard rules. - type: object - x-kubernetes-preserve-unknown-fields: true commands: description: Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according diff --git a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go index 5801bd7cb..ad1f37856 100644 --- a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go +++ b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go @@ -15,11 +15,12 @@ type DevWorkspaceTemplateSpec struct { // +devfile:overrides:generate type DevWorkspaceTemplateSpecContent struct { // Map of implementation-dependant free-form YAML attributes. - // Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} - // except for schemaVersion and metadata + // Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} + // except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, + // component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.) // +optional // +patchStrategy=merge - // +devfile:overrides:include:description=Overrides of attributes encapsulated in a parent devfile or a plugin. + // +devfile:overrides:include:omitInPlugin=true,description=Overrides of attributes encapsulated in a parent devfile. Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` // List of the devworkspace components, such as editor and plugins, diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go index 978c61087..07da9ea5f 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go @@ -2449,13 +2449,6 @@ func (in *PluginComponentParentOverride) DeepCopy() *PluginComponentParentOverri func (in *PluginOverrides) DeepCopyInto(out *PluginOverrides) { *out = *in out.OverridesBase = in.OverridesBase - if in.Attributes != nil { - in, out := &in.Attributes, &out.Attributes - *out = make(attributes.Attributes, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ComponentPluginOverride, len(*in)) @@ -2486,13 +2479,6 @@ func (in *PluginOverrides) DeepCopy() *PluginOverrides { func (in *PluginOverridesParentOverride) DeepCopyInto(out *PluginOverridesParentOverride) { *out = *in out.OverridesBaseParentOverride = in.OverridesBaseParentOverride - if in.Attributes != nil { - in, out := &in.Attributes, &out.Attributes - *out = make(attributes.Attributes, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } if in.Components != nil { in, out := &in.Components, &out.Components *out = make([]ComponentPluginOverrideParentOverride, len(*in)) diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go index 646e43e6c..d55f494cd 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go @@ -8,7 +8,7 @@ import ( type ParentOverrides struct { OverridesBase `json:",inline"` - // Overrides of attributes encapsulated in a parent devfile or a plugin. + // Overrides of attributes encapsulated in a parent devfile. // Overriding is done according to K8S strategic merge patch standard rules. // +optional // +patchStrategy=merge @@ -508,12 +508,6 @@ type ImportReferenceParentOverride struct { type PluginOverridesParentOverride struct { OverridesBaseParentOverride `json:",inline"` - // Overrides of attributes encapsulated in a parent devfile or a plugin. - // Overriding is done according to K8S strategic merge patch standard rules. - // +optional - // +patchStrategy=merge - Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` - // Overrides of components encapsulated in a parent devfile or a plugin. // Overriding is done according to K8S strategic merge patch standard rules. // +optional diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go b/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go index 24435d493..d94b3e685 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.plugin_overrides.go @@ -8,12 +8,6 @@ import ( type PluginOverrides struct { OverridesBase `json:",inline"` - // Overrides of attributes encapsulated in a parent devfile or a plugin. - // Overriding is done according to K8S strategic merge patch standard rules. - // +optional - // +patchStrategy=merge - Attributes attributes.Attributes `json:"attributes,omitempty" patchStrategy:"merge"` - // Overrides of components encapsulated in a parent devfile or a plugin. // Overriding is done according to K8S strategic merge patch standard rules. // +optional diff --git a/pkg/utils/overriding/keys.go b/pkg/utils/overriding/keys.go index b3ee24c1f..c5c54e1ca 100644 --- a/pkg/utils/overriding/keys.go +++ b/pkg/utils/overriding/keys.go @@ -41,7 +41,7 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain attributeValue = value.FieldByName("Attributes") } - if attributeValue.CanInterface() { + if attributeValue.IsValid() && attributeValue.CanInterface() { attributes := attributeValue.Interface().(attributesPkg.Attributes) var attributeKeys []string for k := range attributes { diff --git a/schemas/latest/dev-workspace-template-spec.json b/schemas/latest/dev-workspace-template-spec.json index c325294b6..fed71f253 100644 --- a/schemas/latest/dev-workspace-template-spec.json +++ b/schemas/latest/dev-workspace-template-spec.json @@ -4,7 +4,7 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, @@ -679,11 +679,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1345,7 +1340,7 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true }, @@ -1911,11 +1906,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/dev-workspace-template.json b/schemas/latest/dev-workspace-template.json index 6fd593bcb..668aa59dc 100644 --- a/schemas/latest/dev-workspace-template.json +++ b/schemas/latest/dev-workspace-template.json @@ -169,7 +169,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, @@ -844,11 +844,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1510,7 +1505,7 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true }, @@ -2076,11 +2071,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/dev-workspace.json b/schemas/latest/dev-workspace.json index a9279abc6..36840ff8c 100644 --- a/schemas/latest/dev-workspace.json +++ b/schemas/latest/dev-workspace.json @@ -182,7 +182,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, @@ -857,11 +857,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1523,7 +1518,7 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true }, @@ -2089,11 +2084,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/devfile.json b/schemas/latest/devfile.json index 87facb419..dc220871c 100644 --- a/schemas/latest/devfile.json +++ b/schemas/latest/devfile.json @@ -7,7 +7,7 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, @@ -710,7 +710,7 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true }, diff --git a/schemas/latest/ide-targeted/dev-workspace-template-spec.json b/schemas/latest/ide-targeted/dev-workspace-template-spec.json index f180ba120..ea1d43890 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template-spec.json +++ b/schemas/latest/ide-targeted/dev-workspace-template-spec.json @@ -4,10 +4,10 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -743,12 +743,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1487,10 +1481,10 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", @@ -2114,12 +2108,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/dev-workspace-template.json b/schemas/latest/ide-targeted/dev-workspace-template.json index 0d14b965f..88bcd8d49 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template.json +++ b/schemas/latest/ide-targeted/dev-workspace-template.json @@ -202,10 +202,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -941,12 +941,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1685,10 +1679,10 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", @@ -2312,12 +2306,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/dev-workspace.json b/schemas/latest/ide-targeted/dev-workspace.json index f3b7f6440..a52c385d0 100644 --- a/schemas/latest/ide-targeted/dev-workspace.json +++ b/schemas/latest/ide-targeted/dev-workspace.json @@ -215,10 +215,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -954,12 +954,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", @@ -1698,10 +1692,10 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", @@ -2325,12 +2319,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/devfile.json b/schemas/latest/ide-targeted/devfile.json index 912116e21..ed0ce1f09 100644 --- a/schemas/latest/ide-targeted/devfile.json +++ b/schemas/latest/ide-targeted/devfile.json @@ -7,10 +7,10 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced through out the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -785,10 +785,10 @@ ], "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", diff --git a/schemas/latest/ide-targeted/parent-overrides.json b/schemas/latest/ide-targeted/parent-overrides.json index 62af524b7..04617e33e 100644 --- a/schemas/latest/ide-targeted/parent-overrides.json +++ b/schemas/latest/ide-targeted/parent-overrides.json @@ -4,10 +4,10 @@ "title": "ParentOverrides schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." + "markdownDescription": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", @@ -631,12 +631,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/ide-targeted/plugin-overrides.json b/schemas/latest/ide-targeted/plugin-overrides.json index 711605dd6..417825c15 100644 --- a/schemas/latest/ide-targeted/plugin-overrides.json +++ b/schemas/latest/ide-targeted/plugin-overrides.json @@ -3,12 +3,6 @@ "type": "object", "title": "PluginOverrides schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true, - "markdownDescription": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules." - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/parent-overrides.json b/schemas/latest/parent-overrides.json index 9b1486c72..2ac4323dc 100644 --- a/schemas/latest/parent-overrides.json +++ b/schemas/latest/parent-overrides.json @@ -3,7 +3,7 @@ "title": "ParentOverrides schema - Version 2.1.0-alpha", "properties": { "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", + "description": "Overrides of attributes encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", "type": "object", "additionalProperties": true }, @@ -569,11 +569,6 @@ } ], "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", diff --git a/schemas/latest/plugin-overrides.json b/schemas/latest/plugin-overrides.json index 5a036b92f..09fbba842 100644 --- a/schemas/latest/plugin-overrides.json +++ b/schemas/latest/plugin-overrides.json @@ -2,11 +2,6 @@ "type": "object", "title": "PluginOverrides schema - Version 2.1.0-alpha", "properties": { - "attributes": { - "description": "Overrides of attributes encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", - "type": "object", - "additionalProperties": true - }, "commands": { "description": "Overrides of commands encapsulated in a parent devfile or a plugin. Overriding is done according to K8S strategic merge patch standard rules.", "type": "array", From e38e778bd13c3a7d1108c2eee6836fba8c8c34c8 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Wed, 24 Feb 2021 14:55:28 -0500 Subject: [PATCH 04/10] Address Global Attribute Feedback Signed-off-by: Maysun J Faisal --- ...pace.devfile.io_devworkspaces.v1beta1.yaml | 8 +- crds/workspace.devfile.io_devworkspaces.yaml | 8 +- ...file.io_devworkspacetemplates.v1beta1.yaml | 9 +- ...pace.devfile.io_devworkspacetemplates.yaml | 9 +- .../v1alpha2/devworkspacetemplate_spec.go | 2 +- pkg/utils/overriding/keys.go | 6 +- pkg/validation/attributes/attributes.go | 27 +- .../attributes/attributes_command.go | 54 ++-- .../attributes/attributes_command_test.go | 184 +++++------- .../attributes/attributes_component.go | 97 +++--- .../attributes/attributes_component_test.go | 281 +++++++----------- .../attributes/attributes_endpoint.go | 16 +- .../attributes/attributes_endpoint_test.go | 61 ++-- pkg/validation/attributes/attributes_event.go | 45 --- .../attributes/attributes_event_test.go | 69 ----- .../attributes/attributes_parent.go | 4 +- .../attributes/attributes_parent_test.go | 99 +++--- .../attributes/attributes_project.go | 70 ++--- .../attributes/attributes_project_test.go | 232 ++++++--------- pkg/validation/attributes/attributes_test.go | 167 +---------- .../all/devfile-good-output.yaml | 63 ++++ .../test-fixtures/all/devfile-good.yaml | 5 +- .../attributes/attributes-notreferenced.yaml | 1 + .../attributes/attributes-referenced.yaml | 8 + .../test-fixtures/commands/apply-output.yaml | 2 + .../test-fixtures/commands/apply.yaml | 1 + .../commands/composite-output.yaml | 4 + .../test-fixtures/commands/composite.yaml | 1 + .../test-fixtures/commands/exec-output.yaml | 7 + .../test-fixtures/commands/exec.yaml | 1 + .../components/container-output.yaml | 20 ++ .../test-fixtures/components/container.yaml | 1 + .../components/endpoint-output.yaml | 5 + .../test-fixtures/components/endpoint.yaml | 1 + .../test-fixtures/components/env-output.yaml | 2 + .../test-fixtures/components/env.yaml | 1 + .../openshift-kubernetes-output.yaml | 6 + .../components/openshift-kubernetes.yaml | 1 + .../components/volume-output.yaml | 1 + .../test-fixtures/components/volume.yaml | 1 + .../test-fixtures/events/event.yaml | 8 - .../parent/parent-id-output.yaml | 2 + .../test-fixtures/parent/parent-id.yaml | 1 + .../parent/parent-kubernetes-output.yaml | 4 + .../parent/parent-kubernetes.yaml | 1 + .../parent/parent-uri-output.yaml | 2 + .../test-fixtures/parent/parent-uri.yaml | 1 + .../test-fixtures/projects/git-output.yaml | 7 + .../test-fixtures/projects/git.yaml | 1 + .../projects/project-output.yaml | 11 + .../test-fixtures/projects/project.yaml | 1 + .../projects/starterproject-output.yaml | 5 + .../projects/starterproject.yaml | 1 + .../test-fixtures/projects/zip-output.yaml | 2 + .../test-fixtures/projects/zip.yaml | 1 + .../latest/dev-workspace-template-spec.json | 2 +- schemas/latest/dev-workspace-template.json | 2 +- schemas/latest/dev-workspace.json | 2 +- schemas/latest/devfile.json | 2 +- .../dev-workspace-template-spec.json | 4 +- .../ide-targeted/dev-workspace-template.json | 4 +- .../latest/ide-targeted/dev-workspace.json | 4 +- schemas/latest/ide-targeted/devfile.json | 4 +- 63 files changed, 689 insertions(+), 963 deletions(-) delete mode 100644 pkg/validation/attributes/attributes_event.go delete mode 100644 pkg/validation/attributes/attributes_event_test.go create mode 100644 pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml create mode 100644 pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/apply-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/composite-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/commands/exec-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/container-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/env-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/components/volume-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/events/event.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/git-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/project-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml create mode 100644 pkg/validation/attributes/test-fixtures/projects/zip-output.yaml diff --git a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml index 09e8ad835..45742bdb4 100644 --- a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml @@ -4144,10 +4144,10 @@ spec: description: Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for - schemaVersion and metadata. Exception to the string field include - element's key identifiers(command id, component name, endpoint - name, project name, etc.) and string enums(command group kind, - endpoint exposure, etc.) + schemaVersion, metadata and events. Exception to the string + field include element's key identifiers(command id, component + name, endpoint name, project name, etc.) and string enums(command + group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: diff --git a/crds/workspace.devfile.io_devworkspaces.yaml b/crds/workspace.devfile.io_devworkspaces.yaml index 3f69d987a..be4389f79 100644 --- a/crds/workspace.devfile.io_devworkspaces.yaml +++ b/crds/workspace.devfile.io_devworkspaces.yaml @@ -4142,10 +4142,10 @@ spec: description: Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for - schemaVersion and metadata. Exception to the string field include - element's key identifiers(command id, component name, endpoint - name, project name, etc.) and string enums(command group kind, - endpoint exposure, etc.) + schemaVersion, metadata and events. Exception to the string + field include element's key identifiers(command id, component + name, endpoint name, project name, etc.) and string enums(command + group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: diff --git a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml index c4d20808f..064b1dc7e 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml @@ -3917,10 +3917,11 @@ spec: attributes: description: Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string - type fields in the form {{attribute-key}} except for schemaVersion - and metadata. Exception to the string field include element's key - identifiers(command id, component name, endpoint name, project name, - etc.) and string enums(command group kind, endpoint exposure, etc.) + type fields in the form {{attribute-key}} except for schemaVersion, + metadata and events. Exception to the string field include element's + key identifiers(command id, component name, endpoint name, project + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: diff --git a/crds/workspace.devfile.io_devworkspacetemplates.yaml b/crds/workspace.devfile.io_devworkspacetemplates.yaml index b9ecc51de..439105e92 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.yaml @@ -3915,10 +3915,11 @@ spec: attributes: description: Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string - type fields in the form {{attribute-key}} except for schemaVersion - and metadata. Exception to the string field include element's key - identifiers(command id, component name, endpoint name, project name, - etc.) and string enums(command group kind, endpoint exposure, etc.) + type fields in the form {{attribute-key}} except for schemaVersion, + metadata and events. Exception to the string field include element's + key identifiers(command id, component name, endpoint name, project + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: diff --git a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go index ad1f37856..991cc35e7 100644 --- a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go +++ b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go @@ -16,7 +16,7 @@ type DevWorkspaceTemplateSpec struct { type DevWorkspaceTemplateSpecContent struct { // Map of implementation-dependant free-form YAML attributes. // Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} - // except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, + // except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, // component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.) // +optional // +patchStrategy=merge diff --git a/pkg/utils/overriding/keys.go b/pkg/utils/overriding/keys.go index c5c54e1ca..5e1411efd 100644 --- a/pkg/utils/overriding/keys.go +++ b/pkg/utils/overriding/keys.go @@ -1,6 +1,7 @@ package overriding import ( + "fmt" "reflect" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -42,7 +43,10 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain } if attributeValue.IsValid() && attributeValue.CanInterface() { - attributes := attributeValue.Interface().(attributesPkg.Attributes) + attributes, ok := attributeValue.Interface().(attributesPkg.Attributes) + if !ok { + return fmt.Errorf("unable to fetch Attributes from the devfile data") + } var attributeKeys []string for k := range attributes { attributeKeys = append(attributeKeys, k) diff --git a/pkg/validation/attributes/attributes.go b/pkg/validation/attributes/attributes.go index 027050a90..376017187 100644 --- a/pkg/validation/attributes/attributes.go +++ b/pkg/validation/attributes/attributes.go @@ -8,34 +8,29 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateGlobalAttribute validates the workspace template spec data for global attribute references -func ValidateGlobalAttribute(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { +// ValidateAndReplaceGlobalAttribute validates the workspace template spec data for global attribute references and replaces them with the attribute value +func ValidateAndReplaceGlobalAttribute(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { var err error if workspaceTemplateSpec != nil { - // Validate the components - if err = ValidateComponents(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Components); err != nil { + // Validate the components and replace for global attribute + if err = ValidateAndReplaceForComponents(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Components); err != nil { return err } - // Validate the commands - if err = ValidateCommands(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Commands); err != nil { + // Validate the commands and replace for global attribute + if err = ValidateAndReplaceForCommands(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Commands); err != nil { return err } - // Validate the events - if err = ValidateEvents(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Events); err != nil { + // Validate the projects and replace for global attribute + if err = ValidateAndReplaceForProjects(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Projects); err != nil { return err } - // Validate the projects - if err = ValidateProjects(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.Projects); err != nil { - return err - } - - // Validate the starter projects - if err = ValidateStarterProjects(workspaceTemplateSpec.Attributes, &workspaceTemplateSpec.StarterProjects); err != nil { + // Validate the starter projects and replace for global attribute + if err = ValidateAndReplaceForStarterProjects(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.StarterProjects); err != nil { return err } } @@ -43,7 +38,7 @@ func ValidateGlobalAttribute(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplat return nil } -var globalAttributeRegex = regexp.MustCompile(`\{{2}(.*?)\}{2}`) +var globalAttributeRegex = regexp.MustCompile(`\{\{(.*?)\}\}`) // validateAndReplaceDataWithAttribute validates the string for a global attribute and replaces it. An error // is returned if the string references an invalid global attribute key diff --git a/pkg/validation/attributes/attributes_command.go b/pkg/validation/attributes/attributes_command.go index ae615bd85..2cf0b24b5 100644 --- a/pkg/validation/attributes/attributes_command.go +++ b/pkg/validation/attributes/attributes_command.go @@ -5,27 +5,25 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateCommands validates the commands data for a global attribute -func ValidateCommands(attributes apiAttributes.Attributes, commands *[]v1alpha2.Command) error { - - if commands != nil { - for i := range *commands { - var err error - - // Validate various command types - switch { - case (*commands)[i].Exec != nil: - if err = validateExecCommand(attributes, (*commands)[i].Exec); err != nil { - return err - } - case (*commands)[i].Composite != nil: - if err = validateCompositeCommand(attributes, (*commands)[i].Composite); err != nil { - return err - } - case (*commands)[i].Apply != nil: - if err = validateApplyCommand(attributes, (*commands)[i].Apply); err != nil { - return err - } +// ValidateAndReplaceForCommands validates the commands data for global attribute references and replaces them with the attribute value +func ValidateAndReplaceForCommands(attributes apiAttributes.Attributes, commands []v1alpha2.Command) error { + + for i := range commands { + var err error + + // Validate various command types + switch { + case commands[i].Exec != nil: + if err = validateAndReplaceForExecCommand(attributes, commands[i].Exec); err != nil { + return err + } + case commands[i].Composite != nil: + if err = validateAndReplaceForCompositeCommand(attributes, commands[i].Composite); err != nil { + return err + } + case commands[i].Apply != nil: + if err = validateAndReplaceForApplyCommand(attributes, commands[i].Apply); err != nil { + return err } } } @@ -33,8 +31,8 @@ func ValidateCommands(attributes apiAttributes.Attributes, commands *[]v1alpha2. return nil } -// validateExecCommand validates the exec command data for a global attribute -func validateExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.ExecCommand) error { +// validateAndReplaceForExecCommand validates the exec command data for global attribute references and replaces them with the attribute value +func validateAndReplaceForExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.ExecCommand) error { var err error if exec != nil { @@ -60,7 +58,7 @@ func validateExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.Exe // Validate exec env if len(exec.Env) > 0 { - if err = validateEnv(attributes, &exec.Env); err != nil { + if err = validateAndReplaceForEnv(attributes, exec.Env); err != nil { return err } } @@ -69,8 +67,8 @@ func validateExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.Exe return nil } -// validateExecCommand validates the composite command data for a global attribute -func validateCompositeCommand(attributes apiAttributes.Attributes, composite *v1alpha2.CompositeCommand) error { +// validateAndReplaceForCompositeCommand validates the composite command data for global attribute references and replaces them with the attribute value +func validateAndReplaceForCompositeCommand(attributes apiAttributes.Attributes, composite *v1alpha2.CompositeCommand) error { var err error if composite != nil { @@ -90,8 +88,8 @@ func validateCompositeCommand(attributes apiAttributes.Attributes, composite *v1 return nil } -// validateApplyCommand validates the apply command data for a global attribute -func validateApplyCommand(attributes apiAttributes.Attributes, apply *v1alpha2.ApplyCommand) error { +// validateAndReplaceForApplyCommand validates the apply command data for global attribute references and replaces them with the attribute value +func validateAndReplaceForApplyCommand(attributes apiAttributes.Attributes, apply *v1alpha2.ApplyCommand) error { var err error if apply != nil { diff --git a/pkg/validation/attributes/attributes_command_test.go b/pkg/validation/attributes/attributes_command_test.go index 1b7aa3aa5..671c5c93e 100644 --- a/pkg/validation/attributes/attributes_command_test.go +++ b/pkg/validation/attributes/attributes_command_test.go @@ -8,164 +8,136 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateExecCommand(t *testing.T) { +func TestValidateAndReplaceExecCommand(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.ExecCommand - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/commands/exec.yaml", - expected: v1alpha2.ExecCommand{ - CommandLine: "tail -f /dev/null", - WorkingDir: "FOO", - Component: "BAR", - LabeledCommand: v1alpha2.LabeledCommand{ - Label: "1", - }, - Env: []v1alpha2.EnvVar{ - { - Name: "FOO", - Value: "BAR", - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "version": "1", - "devnull": "/dev/null", - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/commands/exec.yaml", + outputFile: "test-fixtures/commands/exec-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/commands/exec.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/commands/exec.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testExecCommand := v1alpha2.ExecCommand{} - readFileToStruct(t, tt.testFile, &testExecCommand) - err := validateExecCommand(tt.attributes, &testExecCommand) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForExecCommand(testAttribute, &testExecCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testExecCommand, "The two values should be the same.") + expectedExecCommand := v1alpha2.ExecCommand{} + readFileToStruct(t, tt.outputFile, &expectedExecCommand) + assert.Equal(t, expectedExecCommand, testExecCommand, "The two values should be the same.") } }) } } -func TestValidateCompositeCommand(t *testing.T) { +func TestValidateAndReplaceCompositeCommand(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.CompositeCommand - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/commands/composite.yaml", - expected: v1alpha2.CompositeCommand{ - LabeledCommand: v1alpha2.LabeledCommand{ - Label: "1", - }, - Commands: []string{ - "FOO", - "BAR", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "version": "1", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/commands/composite.yaml", + outputFile: "test-fixtures/commands/composite-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/commands/composite.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/commands/composite.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testCompositeCommand := v1alpha2.CompositeCommand{} - readFileToStruct(t, tt.testFile, &testCompositeCommand) - err := validateCompositeCommand(tt.attributes, &testCompositeCommand) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForCompositeCommand(testAttribute, &testCompositeCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testCompositeCommand, "The two values should be the same.") + expectedCompositeCommand := v1alpha2.CompositeCommand{} + readFileToStruct(t, tt.outputFile, &expectedCompositeCommand) + assert.Equal(t, expectedCompositeCommand, testCompositeCommand, "The two values should be the same.") } }) } } -func TestValidateApplyCommand(t *testing.T) { +func TestValidateAndReplaceApplyCommand(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.ApplyCommand - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/commands/apply.yaml", - expected: v1alpha2.ApplyCommand{ - LabeledCommand: v1alpha2.LabeledCommand{ - Label: "1", - }, - Component: "FOO", - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "version": "1", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/commands/apply.yaml", + outputFile: "test-fixtures/commands/apply-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/commands/apply.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/commands/apply.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testApplyCommand := v1alpha2.ApplyCommand{} - readFileToStruct(t, tt.testFile, &testApplyCommand) - err := validateApplyCommand(tt.attributes, &testApplyCommand) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForApplyCommand(testAttribute, &testApplyCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testApplyCommand, "The two values should be the same.") + expectedApplyCommand := v1alpha2.ApplyCommand{} + readFileToStruct(t, tt.outputFile, &expectedApplyCommand) + assert.Equal(t, expectedApplyCommand, testApplyCommand, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/attributes_component.go b/pkg/validation/attributes/attributes_component.go index a603a5496..6a7373b5a 100644 --- a/pkg/validation/attributes/attributes_component.go +++ b/pkg/validation/attributes/attributes_component.go @@ -5,31 +5,29 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateComponents validates the components data for a global attribute -func ValidateComponents(attributes apiAttributes.Attributes, components *[]v1alpha2.Component) error { - - if components != nil { - for i := range *components { - var err error - - // Validate various component types - switch { - case (*components)[i].Container != nil: - if err = validateContainerComponent(attributes, (*components)[i].Container); err != nil { - return err - } - case (*components)[i].Kubernetes != nil: - if err = validateKubernetesComponent(attributes, (*components)[i].Kubernetes); err != nil { - return err - } - case (*components)[i].Openshift != nil: - if err = validateOpenShiftComponent(attributes, (*components)[i].Openshift); err != nil { - return err - } - case (*components)[i].Volume != nil: - if err = validateVolumeComponent(attributes, (*components)[i].Volume); err != nil { - return err - } +// ValidateAndReplaceForComponents validates the components data for global attribute references and replaces them with the attribute value +func ValidateAndReplaceForComponents(attributes apiAttributes.Attributes, components []v1alpha2.Component) error { + + for i := range components { + var err error + + // Validate various component types + switch { + case components[i].Container != nil: + if err = validateAndReplaceForContainerComponent(attributes, components[i].Container); err != nil { + return err + } + case components[i].Kubernetes != nil: + if err = validateAndReplaceForKubernetesComponent(attributes, components[i].Kubernetes); err != nil { + return err + } + case components[i].Openshift != nil: + if err = validateAndReplaceForOpenShiftComponent(attributes, components[i].Openshift); err != nil { + return err + } + case components[i].Volume != nil: + if err = validateAndReplaceForVolumeComponent(attributes, components[i].Volume); err != nil { + return err } } } @@ -37,8 +35,8 @@ func ValidateComponents(attributes apiAttributes.Attributes, components *[]v1alp return nil } -// validateContainerComponent validates the container component data for a global attribute -func validateContainerComponent(attributes apiAttributes.Attributes, container *v1alpha2.ContainerComponent) error { +// validateAndReplaceForContainerComponent validates the container component data for global attribute references and replaces them with the attribute value +func validateAndReplaceForContainerComponent(attributes apiAttributes.Attributes, container *v1alpha2.ContainerComponent) error { var err error if container != nil { @@ -78,7 +76,7 @@ func validateContainerComponent(attributes apiAttributes.Attributes, container * // Validate container env if len(container.Env) > 0 { - if err = validateEnv(attributes, &container.Env); err != nil { + if err = validateAndReplaceForEnv(attributes, container.Env); err != nil { return err } } @@ -95,7 +93,7 @@ func validateContainerComponent(attributes apiAttributes.Attributes, container * // Validate container endpoints if len(container.Endpoints) > 0 { - if err = validateEndpoint(attributes, &container.Endpoints); err != nil { + if err = validateAndReplaceForEndpoint(attributes, container.Endpoints); err != nil { return err } } @@ -104,31 +102,28 @@ func validateContainerComponent(attributes apiAttributes.Attributes, container * return nil } -// validateEnv validates the env data for a global attribute -func validateEnv(attributes apiAttributes.Attributes, env *[]v1alpha2.EnvVar) error { +// validateAndReplaceForEnv validates the env data for global attribute references and replaces them with the attribute value +func validateAndReplaceForEnv(attributes apiAttributes.Attributes, env []v1alpha2.EnvVar) error { - if env != nil { - for i := range *env { - var err error + for i := range env { + var err error - // Validate env name - if (*env)[i].Name, err = validateAndReplaceDataWithAttribute((*env)[i].Name, attributes); err != nil { - return err - } - - // Validate env value - if (*env)[i].Value, err = validateAndReplaceDataWithAttribute((*env)[i].Value, attributes); err != nil { - return err - } + // Validate env name + if env[i].Name, err = validateAndReplaceDataWithAttribute(env[i].Name, attributes); err != nil { + return err + } + // Validate env value + if env[i].Value, err = validateAndReplaceDataWithAttribute(env[i].Value, attributes); err != nil { + return err } } return nil } -// validateKubernetesComponent validates the kubernetes component data for a global attribute -func validateKubernetesComponent(attributes apiAttributes.Attributes, kubernetes *v1alpha2.KubernetesComponent) error { +// validateAndReplaceForKubernetesComponent validates the kubernetes component data for global attribute references and replaces them with the attribute value +func validateAndReplaceForKubernetesComponent(attributes apiAttributes.Attributes, kubernetes *v1alpha2.KubernetesComponent) error { var err error if kubernetes != nil { @@ -144,7 +139,7 @@ func validateKubernetesComponent(attributes apiAttributes.Attributes, kubernetes // Validate kubernetes endpoints if len(kubernetes.Endpoints) > 0 { - if err = validateEndpoint(attributes, &kubernetes.Endpoints); err != nil { + if err = validateAndReplaceForEndpoint(attributes, kubernetes.Endpoints); err != nil { return err } } @@ -153,8 +148,8 @@ func validateKubernetesComponent(attributes apiAttributes.Attributes, kubernetes return nil } -// validateOpenShiftComponent validates the openshift component data for a global attribute -func validateOpenShiftComponent(attributes apiAttributes.Attributes, openshift *v1alpha2.OpenshiftComponent) error { +// validateAndReplaceForOpenShiftComponent validates the openshift component data for global attribute references and replaces them with the attribute value +func validateAndReplaceForOpenShiftComponent(attributes apiAttributes.Attributes, openshift *v1alpha2.OpenshiftComponent) error { var err error if openshift != nil { @@ -170,7 +165,7 @@ func validateOpenShiftComponent(attributes apiAttributes.Attributes, openshift * // Validate openshift endpoints if len(openshift.Endpoints) > 0 { - if err = validateEndpoint(attributes, &openshift.Endpoints); err != nil { + if err = validateAndReplaceForEndpoint(attributes, openshift.Endpoints); err != nil { return err } } @@ -179,8 +174,8 @@ func validateOpenShiftComponent(attributes apiAttributes.Attributes, openshift * return nil } -// validateVolumeComponent validates the volume component data for a global attribute -func validateVolumeComponent(attributes apiAttributes.Attributes, volume *v1alpha2.VolumeComponent) error { +// validateAndReplaceForVolumeComponent validates the volume component data for global attribute references and replaces them with the attribute value +func validateAndReplaceForVolumeComponent(attributes apiAttributes.Attributes, volume *v1alpha2.VolumeComponent) error { var err error if volume != nil { diff --git a/pkg/validation/attributes/attributes_component_test.go b/pkg/validation/attributes/attributes_component_test.go index 7d1a1247e..043bc8efa 100644 --- a/pkg/validation/attributes/attributes_component_test.go +++ b/pkg/validation/attributes/attributes_component_test.go @@ -8,128 +8,72 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateContainerComponent(t *testing.T) { +func TestValidateAndReplaceContainerComponent(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.ContainerComponent - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/container.yaml", - expected: v1alpha2.ContainerComponent{ - Container: v1alpha2.Container{ - Image: "image-1", - Command: []string{"tail", "-f", "/dev/null"}, - Args: []string{"/dev/null"}, - Env: []v1alpha2.EnvVar{ - { - Name: "FOO", - Value: "BAR", - }, - }, - VolumeMounts: []v1alpha2.VolumeMount{ - { - Name: "vol1", - Path: "/FOO", - }, - }, - MemoryLimit: "FOO", - MemoryRequest: "FOO", - SourceMapping: "FOO", - }, - Endpoints: []v1alpha2.Endpoint{ - { - Name: "endpoint1", - Exposure: "public", - Path: "FOO", - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "version": "1", - "devnull": "/dev/null", - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/container.yaml", + outputFile: "test-fixtures/components/container-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/container.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/container.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testContainerComponent := v1alpha2.ContainerComponent{} - readFileToStruct(t, tt.testFile, &testContainerComponent) - err := validateContainerComponent(tt.attributes, &testContainerComponent) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForContainerComponent(testAttribute, &testContainerComponent) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testContainerComponent, "The two values should be the same.") + expectedContainerComponent := v1alpha2.ContainerComponent{} + readFileToStruct(t, tt.outputFile, &expectedContainerComponent) + assert.Equal(t, expectedContainerComponent, testContainerComponent, "The two values should be the same.") } }) } } -func TestValidateOpenShiftAndKubernetesComponent(t *testing.T) { - - expectedKubeLikeComp := v1alpha2.K8sLikeComponent{ - K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ - Uri: "uri", - Inlined: "inlined", - }, - Endpoints: []v1alpha2.Endpoint{ - { - Name: "endpoint1", - Exposure: "public", - Path: "FOO", - }, - }, - } +func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { tests := []struct { - name string - testFile string - expectedOpenShift v1alpha2.OpenshiftComponent - expectedKubernetes v1alpha2.KubernetesComponent - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/openshift-kubernetes.yaml", - expectedOpenShift: v1alpha2.OpenshiftComponent{ - K8sLikeComponent: expectedKubeLikeComp, - }, - expectedKubernetes: v1alpha2.KubernetesComponent{ - K8sLikeComponent: expectedKubeLikeComp, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "uri": "uri", - "inlined": "inlined", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + outputFile: "test-fixtures/components/openshift-kubernetes-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/openshift-kubernetes.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -140,120 +84,121 @@ func TestValidateOpenShiftAndKubernetesComponent(t *testing.T) { readFileToStruct(t, tt.testFile, &testOpenshiftComponent) readFileToStruct(t, tt.testFile, &testKubernetesComponent) - err := validateOpenShiftComponent(tt.attributes, &testOpenshiftComponent) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForOpenShiftComponent(testAttribute, &testOpenshiftComponent) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expectedOpenShift, testOpenshiftComponent, "The two values should be the same.") + expectedOpenshiftComponent := v1alpha2.OpenshiftComponent{} + readFileToStruct(t, tt.outputFile, &expectedOpenshiftComponent) + assert.Equal(t, expectedOpenshiftComponent, testOpenshiftComponent, "The two values should be the same.") } - err = validateKubernetesComponent(tt.attributes, &testKubernetesComponent) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + err = validateAndReplaceForKubernetesComponent(testAttribute, &testKubernetesComponent) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expectedKubernetes, testKubernetesComponent, "The two values should be the same.") + expectedKubernetesComponent := v1alpha2.KubernetesComponent{} + readFileToStruct(t, tt.outputFile, &expectedKubernetesComponent) + assert.Equal(t, expectedKubernetesComponent, testKubernetesComponent, "The two values should be the same.") } }) } } -func TestValidateVolumeComponent(t *testing.T) { +func TestValidateAndReplaceVolumeComponent(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.VolumeComponent - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/volume.yaml", - expected: v1alpha2.VolumeComponent{ - Volume: v1alpha2.Volume{ - Size: "1Gi", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "size": "1Gi", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/volume.yaml", + outputFile: "test-fixtures/components/volume-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/volume.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/volume.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testVolumeComponent := v1alpha2.VolumeComponent{} - readFileToStruct(t, tt.testFile, &testVolumeComponent) - err := validateVolumeComponent(tt.attributes, &testVolumeComponent) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForVolumeComponent(testAttribute, &testVolumeComponent) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testVolumeComponent, "The two values should be the same.") + expectedVolumeComponent := v1alpha2.VolumeComponent{} + readFileToStruct(t, tt.outputFile, &expectedVolumeComponent) + assert.Equal(t, expectedVolumeComponent, testVolumeComponent, "The two values should be the same.") } }) } } -func TestValidateEnv(t *testing.T) { +func TestValidateAndReplaceEnv(t *testing.T) { tests := []struct { - name string - testFile string - expected []v1alpha2.EnvVar - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/env.yaml", - expected: []v1alpha2.EnvVar{ - { - Name: "FOO", - Value: "BAR", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - "bar": "BAR", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/env.yaml", + outputFile: "test-fixtures/components/env-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/env.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/env.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testEnv := v1alpha2.EnvVar{} - readFileToStruct(t, tt.testFile, &testEnv) - testEnvArr := []v1alpha2.EnvVar{testEnv} - err := validateEnv(tt.attributes, &testEnvArr) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForEnv(testAttribute, testEnvArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testEnvArr, "The two values should be the same.") + expectedEnv := v1alpha2.EnvVar{} + readFileToStruct(t, tt.outputFile, &expectedEnv) + expectedEnvArr := []v1alpha2.EnvVar{expectedEnv} + assert.Equal(t, expectedEnvArr, testEnvArr, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/attributes_endpoint.go b/pkg/validation/attributes/attributes_endpoint.go index 944498a09..dad342beb 100644 --- a/pkg/validation/attributes/attributes_endpoint.go +++ b/pkg/validation/attributes/attributes_endpoint.go @@ -5,17 +5,15 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// validateEndpoint validates the endpoint data for a global attribute -func validateEndpoint(attributes apiAttributes.Attributes, endpoints *[]v1alpha2.Endpoint) error { +// validateAndReplaceForEndpoint validates the endpoint data for global attribute references and replaces them with the attribute value +func validateAndReplaceForEndpoint(attributes apiAttributes.Attributes, endpoints []v1alpha2.Endpoint) error { - if endpoints != nil { - for i := range *endpoints { - var err error + for i := range endpoints { + var err error - // Validate endpoint path - if (*endpoints)[i].Path, err = validateAndReplaceDataWithAttribute((*endpoints)[i].Path, attributes); err != nil { - return err - } + // Validate endpoint path + if endpoints[i].Path, err = validateAndReplaceDataWithAttribute(endpoints[i].Path, attributes); err != nil { + return err } } diff --git a/pkg/validation/attributes/attributes_endpoint_test.go b/pkg/validation/attributes/attributes_endpoint_test.go index 225d2738d..eec096827 100644 --- a/pkg/validation/attributes/attributes_endpoint_test.go +++ b/pkg/validation/attributes/attributes_endpoint_test.go @@ -8,55 +8,48 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateEndpoint(t *testing.T) { +func TestValidateAndReplaceEndpoint(t *testing.T) { tests := []struct { - name string - testFile string - expected []v1alpha2.Endpoint - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/endpoint.yaml", - expected: []v1alpha2.Endpoint{ - { - Name: "endpoint1", - TargetPort: 9999, - Exposure: "public", - Protocol: "https", - Path: "/FOO", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/endpoint.yaml", + outputFile: "test-fixtures/components/endpoint-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/endpoint.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "notfoo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/endpoint.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testEndpoint := v1alpha2.Endpoint{} - readFileToStruct(t, tt.testFile, &testEndpoint) - testEndpointArr := []v1alpha2.Endpoint{testEndpoint} - err := validateEndpoint(tt.attributes, &testEndpointArr) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateAndReplaceForEndpoint(testAttribute, testEndpointArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testEndpointArr, "The two values should be the same.") + expectedEndpoint := v1alpha2.Endpoint{} + readFileToStruct(t, tt.outputFile, &expectedEndpoint) + expectedEndpointArr := []v1alpha2.Endpoint{expectedEndpoint} + assert.Equal(t, expectedEndpointArr, testEndpointArr, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/attributes_event.go b/pkg/validation/attributes/attributes_event.go deleted file mode 100644 index 90a435558..000000000 --- a/pkg/validation/attributes/attributes_event.go +++ /dev/null @@ -1,45 +0,0 @@ -package attributes - -import ( - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// ValidateEvents validates the events data for a global attribute -func ValidateEvents(attributes apiAttributes.Attributes, events *v1alpha2.Events) error { - var err error - - if events != nil { - switch { - case len(events.PreStart) > 0: - for i := range events.PreStart { - if events.PreStart[i], err = validateAndReplaceDataWithAttribute(events.PreStart[i], attributes); err != nil { - return err - } - } - fallthrough - case len(events.PostStart) > 0: - for i := range events.PostStart { - if events.PostStart[i], err = validateAndReplaceDataWithAttribute(events.PostStart[i], attributes); err != nil { - return err - } - } - fallthrough - case len(events.PreStop) > 0: - for i := range events.PreStop { - if events.PreStop[i], err = validateAndReplaceDataWithAttribute(events.PreStop[i], attributes); err != nil { - return err - } - } - fallthrough - case len(events.PostStop) > 0: - for i := range events.PostStop { - if events.PostStop[i], err = validateAndReplaceDataWithAttribute(events.PostStop[i], attributes); err != nil { - return err - } - } - } - } - - return nil -} diff --git a/pkg/validation/attributes/attributes_event_test.go b/pkg/validation/attributes/attributes_event_test.go deleted file mode 100644 index 4d38918ac..000000000 --- a/pkg/validation/attributes/attributes_event_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package attributes - -import ( - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" - "github.com/stretchr/testify/assert" -) - -func TestValidateEvents(t *testing.T) { - - tests := []struct { - name string - testFile string - expected v1alpha2.Events - attributes apiAttributes.Attributes - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/events/event.yaml", - expected: v1alpha2.Events{ - WorkspaceEvents: v1alpha2.WorkspaceEvents{ - PreStart: []string{ - "FOO", - }, - PostStart: []string{ - "BAR", - }, - PreStop: []string{ - "FOOBAR", - }, - PostStop: []string{ - "BARFOO", - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/events/event.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testEvents := v1alpha2.Events{} - - readFileToStruct(t, tt.testFile, &testEvents) - - err := ValidateEvents(tt.attributes, &testEvents) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return - } else if err == nil { - assert.Equal(t, tt.expected, testEvents, "The two values should be the same.") - } - }) - } -} diff --git a/pkg/validation/attributes/attributes_parent.go b/pkg/validation/attributes/attributes_parent.go index 3572625f9..d096c814e 100644 --- a/pkg/validation/attributes/attributes_parent.go +++ b/pkg/validation/attributes/attributes_parent.go @@ -5,8 +5,8 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateParent validates the parent data for a global attribute except parent overrides -func ValidateParent(attributes apiAttributes.Attributes, parent *v1alpha2.Parent) error { +// ValidateAndReplaceForParent validates the parent data for global attribute references(except parent overrides) and replaces them with the attribute value +func ValidateAndReplaceForParent(attributes apiAttributes.Attributes, parent *v1alpha2.Parent) error { var err error if parent != nil { diff --git a/pkg/validation/attributes/attributes_parent_test.go b/pkg/validation/attributes/attributes_parent_test.go index 8d02cb683..b472d4b89 100644 --- a/pkg/validation/attributes/attributes_parent_test.go +++ b/pkg/validation/attributes/attributes_parent_test.go @@ -8,87 +8,60 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateParent(t *testing.T) { +func TestValidateAndReplaceParent(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.Parent - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Uri Substitution", - testFile: "test-fixtures/parent/parent-uri.yaml", - expected: v1alpha2.Parent{ - ImportReference: v1alpha2.ImportReference{ - ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ - Uri: "FOO", - }, - RegistryUrl: "FOO", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Uri Substitution", + testFile: "test-fixtures/parent/parent-uri.yaml", + outputFile: "test-fixtures/parent/parent-uri-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Good Id Substitution", - testFile: "test-fixtures/parent/parent-id.yaml", - expected: v1alpha2.Parent{ - ImportReference: v1alpha2.ImportReference{ - ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ - Id: "FOO", - }, - RegistryUrl: "FOO", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Id Substitution", + testFile: "test-fixtures/parent/parent-id.yaml", + outputFile: "test-fixtures/parent/parent-id-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Good Kube Substitution", - testFile: "test-fixtures/parent/parent-kubernetes.yaml", - expected: v1alpha2.Parent{ - ImportReference: v1alpha2.ImportReference{ - ImportReferenceUnion: v1alpha2.ImportReferenceUnion{ - Kubernetes: &v1alpha2.KubernetesCustomResourceImportReference{ - Name: "FOO", - Namespace: "FOO", - }, - }, - RegistryUrl: "FOO", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Kube Substitution", + testFile: "test-fixtures/parent/parent-kubernetes.yaml", + outputFile: "test-fixtures/parent/parent-kubernetes-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/parent/parent-id.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/parent/parent-id.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testParent := v1alpha2.Parent{} - readFileToStruct(t, tt.testFile, &testParent) - err := ValidateParent(tt.attributes, &testParent) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := ValidateAndReplaceForParent(testAttribute, &testParent) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testParent, "The two values should be the same.") + expectedParent := v1alpha2.Parent{} + readFileToStruct(t, tt.outputFile, &expectedParent) + assert.Equal(t, expectedParent, testParent, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/attributes_project.go b/pkg/validation/attributes/attributes_project.go index 834fc68cd..9c3526375 100644 --- a/pkg/validation/attributes/attributes_project.go +++ b/pkg/validation/attributes/attributes_project.go @@ -5,64 +5,60 @@ import ( apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateProjects validates the projects data for a global attribute -func ValidateProjects(attributes apiAttributes.Attributes, projects *[]v1alpha2.Project) error { +// ValidateAndReplaceForProjects validates the projects data for global attribute references and replaces them with the attribute value +func ValidateAndReplaceForProjects(attributes apiAttributes.Attributes, projects []v1alpha2.Project) error { - if projects != nil { - for i := range *projects { - var err error + for i := range projects { + var err error - // Validate project clonepath - if (*projects)[i].ClonePath, err = validateAndReplaceDataWithAttribute((*projects)[i].ClonePath, attributes); err != nil { - return err - } - - // Validate project sparse checkout dir - for j := range (*projects)[i].SparseCheckoutDirs { - if (*projects)[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithAttribute((*projects)[i].SparseCheckoutDirs[j], attributes); err != nil { - return err - } - } + // Validate project clonepath + if projects[i].ClonePath, err = validateAndReplaceDataWithAttribute(projects[i].ClonePath, attributes); err != nil { + return err + } - // Validate project source - if err = validateProjectSource(attributes, &(*projects)[i].ProjectSource); err != nil { + // Validate project sparse checkout dir + for j := range projects[i].SparseCheckoutDirs { + if projects[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithAttribute(projects[i].SparseCheckoutDirs[j], attributes); err != nil { return err } } + + // Validate project source + if err = validateandReplaceForProjectSource(attributes, &projects[i].ProjectSource); err != nil { + return err + } } return nil } -// ValidateStarterProjects validates the starter projects data for a global attribute -func ValidateStarterProjects(attributes apiAttributes.Attributes, starterProjects *[]v1alpha2.StarterProject) error { +// ValidateAndReplaceForStarterProjects validates the starter projects data for global attribute references and replaces them with the attribute value +func ValidateAndReplaceForStarterProjects(attributes apiAttributes.Attributes, starterProjects []v1alpha2.StarterProject) error { - if starterProjects != nil { - for i := range *starterProjects { - var err error + for i := range starterProjects { + var err error - // Validate starter project description - if (*starterProjects)[i].Description, err = validateAndReplaceDataWithAttribute((*starterProjects)[i].Description, attributes); err != nil { - return err - } + // Validate starter project description + if starterProjects[i].Description, err = validateAndReplaceDataWithAttribute(starterProjects[i].Description, attributes); err != nil { + return err + } - // Validate starter project sub dir - if (*starterProjects)[i].SubDir, err = validateAndReplaceDataWithAttribute((*starterProjects)[i].SubDir, attributes); err != nil { - return err - } + // Validate starter project sub dir + if starterProjects[i].SubDir, err = validateAndReplaceDataWithAttribute(starterProjects[i].SubDir, attributes); err != nil { + return err + } - // Validate starter project source - if err = validateProjectSource(attributes, &(*starterProjects)[i].ProjectSource); err != nil { - return err - } + // Validate starter project source + if err = validateandReplaceForProjectSource(attributes, &starterProjects[i].ProjectSource); err != nil { + return err } } return nil } -// validateProjectSource validates a project source location for a global attribute -func validateProjectSource(attributes apiAttributes.Attributes, projectSource *v1alpha2.ProjectSource) error { +// validateandReplaceForProjectSource validates a project source location for global attribute references and replaces them with the attribute value +func validateandReplaceForProjectSource(attributes apiAttributes.Attributes, projectSource *v1alpha2.ProjectSource) error { var err error diff --git a/pkg/validation/attributes/attributes_project_test.go b/pkg/validation/attributes/attributes_project_test.go index b52431c12..9027ceeb5 100644 --- a/pkg/validation/attributes/attributes_project_test.go +++ b/pkg/validation/attributes/attributes_project_test.go @@ -8,207 +8,153 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidateProjects(t *testing.T) { +func TestValidateAndReplaceProjects(t *testing.T) { tests := []struct { - name string - testFile string - expected []v1alpha2.Project - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/projects/project.yaml", - expected: []v1alpha2.Project{ - { - Name: "project1", - ClonePath: "/FOO", - SparseCheckoutDirs: []string{ - "/FOO", - "/BAR", - }, - ProjectSource: v1alpha2.ProjectSource{ - Git: &v1alpha2.GitProjectSource{ - GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ - CheckoutFrom: &v1alpha2.CheckoutFrom{ - Revision: "FOO", - }, - Remotes: map[string]string{ - "foo": "BAR", - "FOOBAR": "BARFOO", - }, - }, - }, - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/projects/project.yaml", + outputFile: "test-fixtures/projects/project-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/projects/project.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/projects/project.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testProject := v1alpha2.Project{} - readFileToStruct(t, tt.testFile, &testProject) - testProjectArr := []v1alpha2.Project{testProject} - err := ValidateProjects(tt.attributes, &testProjectArr) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := ValidateAndReplaceForProjects(testAttribute, testProjectArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testProjectArr, "The two values should be the same.") + expectedProject := v1alpha2.Project{} + readFileToStruct(t, tt.outputFile, &expectedProject) + expectedProjectArr := []v1alpha2.Project{expectedProject} + assert.Equal(t, expectedProjectArr, testProjectArr, "The two values should be the same.") } }) } } -func TestValidateStarterProjects(t *testing.T) { +func TestValidateAndReplaceStarterProjects(t *testing.T) { tests := []struct { - name string - testFile string - expected []v1alpha2.StarterProject - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/projects/starterproject.yaml", - expected: []v1alpha2.StarterProject{ - { - Name: "starterproject1", - Description: "FOOBAR is not BARFOO", - SubDir: "/FOO", - ProjectSource: v1alpha2.ProjectSource{ - Zip: &v1alpha2.ZipProjectSource{ - Location: "/FOO", - }, - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/projects/starterproject.yaml", + outputFile: "test-fixtures/projects/starterproject-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/projects/starterproject.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/projects/starterproject.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testStarterProject := v1alpha2.StarterProject{} - readFileToStruct(t, tt.testFile, &testStarterProject) - testStarterProjectArr := []v1alpha2.StarterProject{testStarterProject} - err := ValidateStarterProjects(tt.attributes, &testStarterProjectArr) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := ValidateAndReplaceForStarterProjects(testAttribute, testStarterProjectArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testStarterProjectArr, "The two values should be the same.") + expectedStarterProject := v1alpha2.StarterProject{} + readFileToStruct(t, tt.outputFile, &expectedStarterProject) + expectedStarterProjectArr := []v1alpha2.StarterProject{expectedStarterProject} + assert.Equal(t, expectedStarterProjectArr, testStarterProjectArr, "The two values should be the same.") } }) } } -func TestValidateProjectSrc(t *testing.T) { +func TestValidateAndReplaceProjectSrc(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.ProjectSource - attributes apiAttributes.Attributes - wantErr bool + name string + testFile string + outputFile string + attributeFile string + wantErr bool }{ { - name: "Good Git Substitution", - testFile: "test-fixtures/projects/git.yaml", - expected: v1alpha2.ProjectSource{ - Git: &v1alpha2.GitProjectSource{ - GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ - CheckoutFrom: &v1alpha2.CheckoutFrom{ - Revision: "FOO", - Remote: "BAR", - }, - Remotes: map[string]string{ - "foo": "BAR", - "FOOBAR": "BARFOO", - }, - }, - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Git Substitution", + testFile: "test-fixtures/projects/git.yaml", + outputFile: "test-fixtures/projects/git-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Good Zip Substitution", - testFile: "test-fixtures/projects/zip.yaml", - expected: v1alpha2.ProjectSource{ - Zip: &v1alpha2.ZipProjectSource{ - Location: "/FOO", - }, - }, - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: false, + name: "Good Zip Substitution", + testFile: "test-fixtures/projects/zip.yaml", + outputFile: "test-fixtures/projects/zip-output.yaml", + attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", + wantErr: false, }, { - name: "Invalid Git Reference", - testFile: "test-fixtures/projects/git.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "foo": "FOO", - }, nil), - wantErr: true, + name: "Invalid Git Reference", + testFile: "test-fixtures/projects/git.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, { - name: "Invalid Zip Reference", - testFile: "test-fixtures/projects/zip.yaml", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "bar": "BAR", - }, nil), - wantErr: true, + name: "Invalid Zip Reference", + testFile: "test-fixtures/projects/zip.yaml", + attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testProjectSrc := v1alpha2.ProjectSource{} - readFileToStruct(t, tt.testFile, &testProjectSrc) - err := validateProjectSource(tt.attributes, &testProjectSrc) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + testAttribute := apiAttributes.Attributes{} + readFileToStruct(t, tt.attributeFile, &testAttribute) + + err := validateandReplaceForProjectSource(testAttribute, &testProjectSrc) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testProjectSrc, "The two values should be the same.") + expectedProjectSrc := v1alpha2.ProjectSource{} + readFileToStruct(t, tt.outputFile, &expectedProjectSrc) + assert.Equal(t, expectedProjectSrc, testProjectSrc, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/attributes_test.go b/pkg/validation/attributes/attributes_test.go index 8d32bdc46..9f85a8309 100644 --- a/pkg/validation/attributes/attributes_test.go +++ b/pkg/validation/attributes/attributes_test.go @@ -13,153 +13,16 @@ import ( func TestValidateGlobalAttributeBasic(t *testing.T) { tests := []struct { - name string - testFile string - expected v1alpha2.DevWorkspaceTemplateSpec - wantErr bool + name string + testFile string + outputFile string + wantErr bool }{ { - name: "Successful global attribute substitution", - testFile: "test-fixtures/all/devfile-good.yaml", - expected: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "tag": "xyz", - "version": "1", - "foo": "FOO", - "devnull": "/dev/null", - }, nil), - Components: []v1alpha2.Component{ - { - Name: "component1", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - Container: v1alpha2.Container{ - Image: "image", - Command: []string{"tail", "-f", "/dev/null"}, - Env: []v1alpha2.EnvVar{ - { - Name: "BAR", - Value: "FOO", - }, - { - Name: "FOO", - Value: "BAR", - }, - }, - }, - }, - }, - }, - { - Name: "component2", - ComponentUnion: v1alpha2.ComponentUnion{ - Kubernetes: &v1alpha2.KubernetesComponent{ - K8sLikeComponent: v1alpha2.K8sLikeComponent{ - K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ - Inlined: "FOO", - }, - Endpoints: []v1alpha2.Endpoint{ - { - Name: "endpoint1", - Exposure: "public", - TargetPort: 9999, - }, - }, - }, - }, - }, - }, - }, - Commands: []v1alpha2.Command{ - { - Id: "command1", - CommandUnion: v1alpha2.CommandUnion{ - Exec: &v1alpha2.ExecCommand{ - CommandLine: "test-xyz", - Env: []v1alpha2.EnvVar{ - { - Name: "tag", - Value: "xyz", - }, - { - Name: "FOO", - Value: "BAR", - }, - }, - }, - }, - }, - { - Id: "command2", - CommandUnion: v1alpha2.CommandUnion{ - Composite: &v1alpha2.CompositeCommand{ - Commands: []string{ - "xyz", - "command1", - }, - }, - }, - }, - }, - Events: &v1alpha2.Events{ - WorkspaceEvents: v1alpha2.WorkspaceEvents{ - PreStart: []string{ - "xyz", - "test", - }, - PreStop: []string{ - "1", - }, - }, - }, - Projects: []v1alpha2.Project{ - { - Name: "project1", - ProjectSource: v1alpha2.ProjectSource{ - Git: &v1alpha2.GitProjectSource{ - GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ - CheckoutFrom: &v1alpha2.CheckoutFrom{ - Revision: "xyz", - }, - Remotes: map[string]string{ - "xyz": "/dev/null", - "1": "test", - }, - }, - }, - }, - }, - { - Name: "project2", - ProjectSource: v1alpha2.ProjectSource{ - Zip: &v1alpha2.ZipProjectSource{ - Location: "xyz", - }, - }, - }, - }, - StarterProjects: []v1alpha2.StarterProject{ - { - Name: "starterproject1", - ProjectSource: v1alpha2.ProjectSource{ - Git: &v1alpha2.GitProjectSource{ - GitLikeProjectSource: v1alpha2.GitLikeProjectSource{ - CheckoutFrom: &v1alpha2.CheckoutFrom{ - Revision: "xyz", - }, - Remotes: map[string]string{ - "xyz": "/dev/null", - "1": "test", - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, + name: "Successful global attribute substitution", + testFile: "test-fixtures/all/devfile-good.yaml", + outputFile: "test-fixtures/all/devfile-good-output.yaml", + wantErr: false, }, { name: "Invalid Reference", @@ -170,15 +33,17 @@ func TestValidateGlobalAttributeBasic(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { testDWT := v1alpha2.DevWorkspaceTemplateSpec{} - readFileToStruct(t, tt.testFile, &testDWT) - err := ValidateGlobalAttribute(&testDWT) - if tt.wantErr == (err == nil) { - t.Errorf("error: %v", err) - return + err := ValidateAndReplaceGlobalAttribute(&testDWT) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) } else if err == nil { - assert.Equal(t, tt.expected, testDWT, "The two values should be the same.") + expectedDWT := v1alpha2.DevWorkspaceTemplateSpec{} + readFileToStruct(t, tt.outputFile, &expectedDWT) + assert.Equal(t, expectedDWT, testDWT, "The two values should be the same.") } }) } diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml b/pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml new file mode 100644 index 000000000..5c68e9e06 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml @@ -0,0 +1,63 @@ +attributes: + tag: xyz + version: "1" + foo: FOO + devnull: /dev/null +projects: +- name: project1 + git: + checkoutFrom: + revision: "xyz" + remotes: + "xyz": "/dev/null" + "1": "test" +- name: project2 + zip: + location: "xyz" +starterProjects: +- name: starterproject1 + git: + checkoutFrom: + revision: "xyz" + remotes: + "xyz": "/dev/null" + "1": "test" +components: +- name: component1 + container: + image: image + env: + - name: BAR + value: "FOO" + - name: FOO + value: BAR + command: + - tail + - -f + - "/dev/null" +- name: component2 + kubernetes: + inlined: "FOO" + endpoints: + - name: endpoint1 + exposure: "public" + targetPort: 9999 +commands: +- id: command1 + exec: + commandLine: "test-xyz" + env: + - name: tag + value: "xyz" + - name: FOO + value: BAR +- id: command2 + composite: + commands: + - "xyz" + - command1 +events: + preStart: + - command1 + preStop: + - "command2" diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml b/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml index cd41546c7..a46b49a07 100644 --- a/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml +++ b/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml @@ -58,7 +58,6 @@ commands: - command1 events: preStart: - - "{{tag}}" - - test + - command1 preStop: - - "{{version}}" + - "command2" diff --git a/pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml b/pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml new file mode 100644 index 000000000..49fd05879 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml @@ -0,0 +1 @@ +abc: xyz diff --git a/pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml b/pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml new file mode 100644 index 000000000..7470c63f5 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml @@ -0,0 +1,8 @@ +tag: xyz +version: "1" +foo: FOO +bar: BAR +devnull: /dev/null +size: "1Gi" +uri: uri +inlined: inlined diff --git a/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml b/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml new file mode 100644 index 000000000..2ea577750 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml @@ -0,0 +1,2 @@ +label: "1" +component: "FOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/apply.yaml b/pkg/validation/attributes/test-fixtures/commands/apply.yaml index 5f133087d..54f5bacad 100644 --- a/pkg/validation/attributes/test-fixtures/commands/apply.yaml +++ b/pkg/validation/attributes/test-fixtures/commands/apply.yaml @@ -1,2 +1,3 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml label: "{{version}}" component: "{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/composite-output.yaml b/pkg/validation/attributes/test-fixtures/commands/composite-output.yaml new file mode 100644 index 000000000..f1a57a117 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/composite-output.yaml @@ -0,0 +1,4 @@ +label: "1" +commands: + - "FOO" + - BAR \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/composite.yaml b/pkg/validation/attributes/test-fixtures/commands/composite.yaml index 6f132f06f..20a060509 100644 --- a/pkg/validation/attributes/test-fixtures/commands/composite.yaml +++ b/pkg/validation/attributes/test-fixtures/commands/composite.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml label: "{{version}}" commands: - "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/commands/exec-output.yaml b/pkg/validation/attributes/test-fixtures/commands/exec-output.yaml new file mode 100644 index 000000000..8f7e852f4 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/commands/exec-output.yaml @@ -0,0 +1,7 @@ +component: "BAR" +commandLine: tail -f /dev/null +workingDir: "FOO" +label: "1" +env: + - name: "FOO" + value: "BAR" diff --git a/pkg/validation/attributes/test-fixtures/commands/exec.yaml b/pkg/validation/attributes/test-fixtures/commands/exec.yaml index 61441c413..dbde64f2d 100644 --- a/pkg/validation/attributes/test-fixtures/commands/exec.yaml +++ b/pkg/validation/attributes/test-fixtures/commands/exec.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml component: "{{bar}}" commandLine: tail -f {{devnull}} workingDir: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/container-output.yaml b/pkg/validation/attributes/test-fixtures/components/container-output.yaml new file mode 100644 index 000000000..15322bd49 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/container-output.yaml @@ -0,0 +1,20 @@ +image: "image-1" +env: + - name: "FOO" + value: "BAR" +command: + - tail + - -f + - "/dev/null" +args: + - "/dev/null" +memoryLimit: "FOO" +memoryRequest: "FOO" +sourceMapping: "FOO" +volumeMounts: + - name: vol1 + path: "/FOO" +endpoints: + - name: endpoint1 + exposure: public + path: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/components/container.yaml b/pkg/validation/attributes/test-fixtures/components/container.yaml index 1873a3118..1a3537b88 100644 --- a/pkg/validation/attributes/test-fixtures/components/container.yaml +++ b/pkg/validation/attributes/test-fixtures/components/container.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml image: "image-{{version}}" env: - name: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml b/pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml new file mode 100644 index 000000000..6e9ad1e3e --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml @@ -0,0 +1,5 @@ +name: endpoint1 +exposure: "public" +protocol: "https" +path : "/FOO" +targetPort: 9999 diff --git a/pkg/validation/attributes/test-fixtures/components/endpoint.yaml b/pkg/validation/attributes/test-fixtures/components/endpoint.yaml index 84b8c5a3b..9802c98eb 100644 --- a/pkg/validation/attributes/test-fixtures/components/endpoint.yaml +++ b/pkg/validation/attributes/test-fixtures/components/endpoint.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml name: endpoint1 exposure: "public" protocol: "https" diff --git a/pkg/validation/attributes/test-fixtures/components/env-output.yaml b/pkg/validation/attributes/test-fixtures/components/env-output.yaml new file mode 100644 index 000000000..efb23067d --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/env-output.yaml @@ -0,0 +1,2 @@ +name: "FOO" +value: "BAR" diff --git a/pkg/validation/attributes/test-fixtures/components/env.yaml b/pkg/validation/attributes/test-fixtures/components/env.yaml index 815422f79..a30ad805f 100644 --- a/pkg/validation/attributes/test-fixtures/components/env.yaml +++ b/pkg/validation/attributes/test-fixtures/components/env.yaml @@ -1,2 +1,3 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml name: "{{foo}}" value: "{{bar}}" diff --git a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml new file mode 100644 index 000000000..931dea4cf --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml @@ -0,0 +1,6 @@ +uri: "uri" +inlined: "inlined" +endpoints: + - name: endpoint1 + exposure: public + path: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml index a05736bed..90343e61c 100644 --- a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml +++ b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml uri: "{{uri}}" inlined: "{{inlined}}" endpoints: diff --git a/pkg/validation/attributes/test-fixtures/components/volume-output.yaml b/pkg/validation/attributes/test-fixtures/components/volume-output.yaml new file mode 100644 index 000000000..2272abcb4 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/components/volume-output.yaml @@ -0,0 +1 @@ +size: "1Gi" diff --git a/pkg/validation/attributes/test-fixtures/components/volume.yaml b/pkg/validation/attributes/test-fixtures/components/volume.yaml index 5a4bf3242..9adee851e 100644 --- a/pkg/validation/attributes/test-fixtures/components/volume.yaml +++ b/pkg/validation/attributes/test-fixtures/components/volume.yaml @@ -1 +1,2 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml size: "{{size}}" diff --git a/pkg/validation/attributes/test-fixtures/events/event.yaml b/pkg/validation/attributes/test-fixtures/events/event.yaml deleted file mode 100644 index d316b275b..000000000 --- a/pkg/validation/attributes/test-fixtures/events/event.yaml +++ /dev/null @@ -1,8 +0,0 @@ -preStart: - - "{{foo}}" -postStart: - - "{{bar}}" -preStop: - - "{{foo}}{{bar}}" -postStop: - - "{{bar}}{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml new file mode 100644 index 000000000..13e325bad --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml @@ -0,0 +1,2 @@ +id: "FOO" +registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml index 667952142..5ea2a1f01 100644 --- a/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml +++ b/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml @@ -1,2 +1,3 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml id: "{{foo}}" registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml new file mode 100644 index 000000000..fe4f7cb95 --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml @@ -0,0 +1,4 @@ +kubernetes: + name: "FOO" + namespace: "FOO" +registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml index 4aebcd259..c06dc5490 100644 --- a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml +++ b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml kubernetes: name: "{{foo}}" namespace: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml new file mode 100644 index 000000000..cb7e9f6fa --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml @@ -0,0 +1,2 @@ +uri: "FOO" +registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml index 8328a3c92..e1a7bf338 100644 --- a/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml +++ b/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml @@ -1,2 +1,3 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml uri: "{{foo}}" registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/git-output.yaml b/pkg/validation/attributes/test-fixtures/projects/git-output.yaml new file mode 100644 index 000000000..8fe872a4e --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/git-output.yaml @@ -0,0 +1,7 @@ +git: + checkoutFrom: + revision: "FOO" + remote: "BAR" + remotes: + "foo": "BAR" + "FOOBAR": "BARFOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/git.yaml b/pkg/validation/attributes/test-fixtures/projects/git.yaml index 2bec9bdf5..fbe541080 100644 --- a/pkg/validation/attributes/test-fixtures/projects/git.yaml +++ b/pkg/validation/attributes/test-fixtures/projects/git.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml git: checkoutFrom: revision: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/project-output.yaml b/pkg/validation/attributes/test-fixtures/projects/project-output.yaml new file mode 100644 index 000000000..74569640c --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/project-output.yaml @@ -0,0 +1,11 @@ +name: project1 +clonePath: "/FOO" +sparseCheckoutDirs: + - /FOO + - "/BAR" +git: + checkoutFrom: + revision: "FOO" + remotes: + "foo": "BAR" + "FOOBAR": "BARFOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/project.yaml b/pkg/validation/attributes/test-fixtures/projects/project.yaml index 6b6b93866..2c1dc6bb7 100644 --- a/pkg/validation/attributes/test-fixtures/projects/project.yaml +++ b/pkg/validation/attributes/test-fixtures/projects/project.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml name: project1 clonePath: "/{{foo}}" sparseCheckoutDirs: diff --git a/pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml b/pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml new file mode 100644 index 000000000..9ab49330c --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml @@ -0,0 +1,5 @@ +name: starterproject1 +zip: + location: "/FOO" +description: "FOOBAR is not BARFOO" +subDir: "/FOO" diff --git a/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml b/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml index f45e3209d..988805c81 100644 --- a/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml +++ b/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml @@ -1,3 +1,4 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml name: starterproject1 zip: location: "/{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml b/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml new file mode 100644 index 000000000..345106b0c --- /dev/null +++ b/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml @@ -0,0 +1,2 @@ +zip: + location: "/FOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/zip.yaml b/pkg/validation/attributes/test-fixtures/projects/zip.yaml index 3f2d9a1d1..30ce769ae 100644 --- a/pkg/validation/attributes/test-fixtures/projects/zip.yaml +++ b/pkg/validation/attributes/test-fixtures/projects/zip.yaml @@ -1,2 +1,3 @@ +# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml zip: location: "/{{foo}}" \ No newline at end of file diff --git a/schemas/latest/dev-workspace-template-spec.json b/schemas/latest/dev-workspace-template-spec.json index fed71f253..158abc9e2 100644 --- a/schemas/latest/dev-workspace-template-spec.json +++ b/schemas/latest/dev-workspace-template-spec.json @@ -4,7 +4,7 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, diff --git a/schemas/latest/dev-workspace-template.json b/schemas/latest/dev-workspace-template.json index 668aa59dc..884a8cb8f 100644 --- a/schemas/latest/dev-workspace-template.json +++ b/schemas/latest/dev-workspace-template.json @@ -169,7 +169,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, diff --git a/schemas/latest/dev-workspace.json b/schemas/latest/dev-workspace.json index 36840ff8c..ad1f73f70 100644 --- a/schemas/latest/dev-workspace.json +++ b/schemas/latest/dev-workspace.json @@ -182,7 +182,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, diff --git a/schemas/latest/devfile.json b/schemas/latest/devfile.json index dc220871c..d27a8099d 100644 --- a/schemas/latest/devfile.json +++ b/schemas/latest/devfile.json @@ -7,7 +7,7 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true }, diff --git a/schemas/latest/ide-targeted/dev-workspace-template-spec.json b/schemas/latest/ide-targeted/dev-workspace-template-spec.json index ea1d43890..619a6f1f8 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template-spec.json +++ b/schemas/latest/ide-targeted/dev-workspace-template-spec.json @@ -4,10 +4,10 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", diff --git a/schemas/latest/ide-targeted/dev-workspace-template.json b/schemas/latest/ide-targeted/dev-workspace-template.json index 88bcd8d49..d242c562b 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template.json +++ b/schemas/latest/ide-targeted/dev-workspace-template.json @@ -202,10 +202,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", diff --git a/schemas/latest/ide-targeted/dev-workspace.json b/schemas/latest/ide-targeted/dev-workspace.json index a52c385d0..d38940985 100644 --- a/schemas/latest/ide-targeted/dev-workspace.json +++ b/schemas/latest/ide-targeted/dev-workspace.json @@ -215,10 +215,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", diff --git a/schemas/latest/ide-targeted/devfile.json b/schemas/latest/ide-targeted/devfile.json index ed0ce1f09..145a19016 100644 --- a/schemas/latest/ide-targeted/devfile.json +++ b/schemas/latest/ide-targeted/devfile.json @@ -7,10 +7,10 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion and metadata. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", From 03ef74bd0414c9965cc6d0a48895acd2e1982272 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Mon, 29 Mar 2021 14:46:48 -0400 Subject: [PATCH 05/10] Update Cabal Feedback - 1 Signed-off-by: Maysun J Faisal --- ...pace.devfile.io_devworkspaces.v1beta1.yaml | 25 ++- crds/workspace.devfile.io_devworkspaces.yaml | 25 ++- ...file.io_devworkspacetemplates.v1beta1.yaml | 25 ++- ...pace.devfile.io_devworkspacetemplates.yaml | 25 ++- .../v1alpha2/devworkspacetemplate_spec.go | 13 +- .../v1alpha2/zz_generated.deepcopy.go | 14 ++ .../v1alpha2/zz_generated.parent_overrides.go | 6 + pkg/devfile/header.go | 2 +- pkg/utils/overriding/keys.go | 21 +- pkg/utils/overriding/merging.go | 9 + pkg/utils/overriding/merging_test.go | 24 ++- pkg/utils/overriding/overriding_test.go | 19 +- .../merges/duplicate-with-parent/main.yaml | 5 +- .../merges/duplicate-with-parent/parent.yaml | 5 +- .../duplicate-with-parent/result-error.txt | 5 +- .../merges/no-duplicate/main.yaml | 2 + .../merges/no-duplicate/parent.yaml | 5 +- .../merges/no-duplicate/result.yaml | 6 +- .../global-attributes-err/result-error.txt | 2 - .../original.yaml | 3 + .../patch.yaml | 5 + .../result-error.txt | 3 + .../original.yaml | 3 + .../patch.yaml | 3 +- .../result.yaml | 5 +- pkg/validation/attributes/attributes.go | 57 ------ .../attributes/attributes_command.go | 108 ---------- .../attributes/attributes_command_test.go | 144 ------------- .../attributes/attributes_component.go | 189 ------------------ .../attributes/attributes_endpoint.go | 21 -- .../attributes/attributes_parent.go | 48 ----- .../attributes/attributes_parent_test.go | 68 ------- .../attributes/attributes_project_test.go | 161 --------------- .../test-fixtures/commands/apply-output.yaml | 2 - .../test-fixtures/commands/apply.yaml | 3 - .../test-fixtures/commands/composite.yaml | 5 - .../test-fixtures/components/env.yaml | 3 - .../openshift-kubernetes-output.yaml | 6 - .../components/openshift-kubernetes.yaml | 7 - .../test-fixtures/components/volume.yaml | 2 - .../parent/parent-id-output.yaml | 2 - .../test-fixtures/parent/parent-id.yaml | 3 - .../parent/parent-kubernetes-output.yaml | 4 - .../parent/parent-kubernetes.yaml | 5 - .../parent/parent-uri-output.yaml | 2 - .../test-fixtures/parent/parent-uri.yaml | 3 - .../test-fixtures/projects/git.yaml | 8 - .../test-fixtures/projects/zip-output.yaml | 2 - .../test-fixtures/projects/zip.yaml | 3 - .../test-fixtures/all/devfile-bad.yaml | 2 +- .../all/devfile-good-output.yaml | 6 +- .../test-fixtures/all/devfile-good.yaml | 6 +- .../test-fixtures/commands/apply-output.yaml | 2 + .../test-fixtures/commands/apply.yaml | 3 + .../commands/composite-output.yaml | 4 +- .../test-fixtures/commands/composite.yaml | 5 + .../test-fixtures/commands/exec-output.yaml | 2 +- .../test-fixtures/commands/exec.yaml | 4 +- .../components/container-output.yaml | 0 .../test-fixtures/components/container.yaml | 2 +- .../components/endpoint-output.yaml | 0 .../test-fixtures/components/endpoint.yaml | 2 +- .../test-fixtures/components/env-output.yaml | 0 .../test-fixtures/components/env.yaml | 3 + .../openshift-kubernetes-output.yaml | 16 ++ .../components/openshift-kubernetes.yaml | 17 ++ .../components/volume-output.yaml | 0 .../test-fixtures/components/volume.yaml | 2 + .../test-fixtures/projects/git-output.yaml | 2 +- .../variables/test-fixtures/projects/git.yaml | 8 + .../projects/project-output.yaml | 2 +- .../test-fixtures/projects/project.yaml | 4 +- .../projects/starterproject-output.yaml | 0 .../projects/starterproject.yaml | 2 +- .../test-fixtures/projects/zip-output.yaml | 2 + .../variables/test-fixtures/projects/zip.yaml | 3 + .../variables/variables-notreferenced.yaml} | 0 .../variables/variables-referenced.yaml} | 0 pkg/validation/variables/variables.go | 56 ++++++ pkg/validation/variables/variables_command.go | 90 +++++++++ .../variables/variables_command_test.go | 143 +++++++++++++ .../variables/variables_component.go | 185 +++++++++++++++++ .../variables_component_test.go} | 141 +++++++------ .../variables/variables_endpoint.go | 20 ++ .../variables_endpoint_test.go} | 37 ++-- .../variables_project.go} | 37 ++-- .../variables/variables_project_test.go | 160 +++++++++++++++ .../variables_test.go} | 46 ++--- .../latest/dev-workspace-template-spec.json | 16 +- schemas/latest/dev-workspace-template.json | 16 +- schemas/latest/dev-workspace.json | 16 +- schemas/latest/devfile.json | 18 +- .../dev-workspace-template-spec.json | 20 +- .../ide-targeted/dev-workspace-template.json | 20 +- .../latest/ide-targeted/dev-workspace.json | 20 +- schemas/latest/ide-targeted/devfile.json | 24 ++- .../latest/ide-targeted/parent-overrides.json | 8 + schemas/latest/parent-overrides.json | 7 + 98 files changed, 1215 insertions(+), 1080 deletions(-) delete mode 100644 pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt rename pkg/utils/overriding/test-fixtures/patches/{global-attributes-err => global-variables-and-attributes-err}/original.yaml (70%) rename pkg/utils/overriding/test-fixtures/patches/{global-attributes => global-variables-and-attributes-err}/patch.yaml (69%) create mode 100644 pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/result-error.txt rename pkg/utils/overriding/test-fixtures/patches/{global-attributes => global-variables-and-attributes}/original.yaml (68%) rename pkg/utils/overriding/test-fixtures/patches/{global-attributes-err => global-variables-and-attributes}/patch.yaml (81%) rename pkg/utils/overriding/test-fixtures/patches/{global-attributes => global-variables-and-attributes}/result.yaml (69%) delete mode 100644 pkg/validation/attributes/attributes.go delete mode 100644 pkg/validation/attributes/attributes_command.go delete mode 100644 pkg/validation/attributes/attributes_command_test.go delete mode 100644 pkg/validation/attributes/attributes_component.go delete mode 100644 pkg/validation/attributes/attributes_endpoint.go delete mode 100644 pkg/validation/attributes/attributes_parent.go delete mode 100644 pkg/validation/attributes/attributes_parent_test.go delete mode 100644 pkg/validation/attributes/attributes_project_test.go delete mode 100644 pkg/validation/attributes/test-fixtures/commands/apply-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/commands/apply.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/commands/composite.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/components/env.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/components/volume.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-id.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/projects/git.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/projects/zip-output.yaml delete mode 100644 pkg/validation/attributes/test-fixtures/projects/zip.yaml rename pkg/validation/{attributes => variables}/test-fixtures/all/devfile-bad.yaml (94%) rename pkg/validation/{attributes => variables}/test-fixtures/all/devfile-good-output.yaml (95%) rename pkg/validation/{attributes => variables}/test-fixtures/all/devfile-good.yaml (95%) create mode 100644 pkg/validation/variables/test-fixtures/commands/apply-output.yaml create mode 100644 pkg/validation/variables/test-fixtures/commands/apply.yaml rename pkg/validation/{attributes => variables}/test-fixtures/commands/composite-output.yaml (55%) create mode 100644 pkg/validation/variables/test-fixtures/commands/composite.yaml rename pkg/validation/{attributes => variables}/test-fixtures/commands/exec-output.yaml (82%) rename pkg/validation/{attributes => variables}/test-fixtures/commands/exec.yaml (54%) rename pkg/validation/{attributes => variables}/test-fixtures/components/container-output.yaml (100%) rename pkg/validation/{attributes => variables}/test-fixtures/components/container.yaml (80%) rename pkg/validation/{attributes => variables}/test-fixtures/components/endpoint-output.yaml (100%) rename pkg/validation/{attributes => variables}/test-fixtures/components/endpoint.yaml (52%) rename pkg/validation/{attributes => variables}/test-fixtures/components/env-output.yaml (100%) create mode 100644 pkg/validation/variables/test-fixtures/components/env.yaml create mode 100644 pkg/validation/variables/test-fixtures/components/openshift-kubernetes-output.yaml create mode 100644 pkg/validation/variables/test-fixtures/components/openshift-kubernetes.yaml rename pkg/validation/{attributes => variables}/test-fixtures/components/volume-output.yaml (100%) create mode 100644 pkg/validation/variables/test-fixtures/components/volume.yaml rename pkg/validation/{attributes => variables}/test-fixtures/projects/git-output.yaml (79%) create mode 100644 pkg/validation/variables/test-fixtures/projects/git.yaml rename pkg/validation/{attributes => variables}/test-fixtures/projects/project-output.yaml (86%) rename pkg/validation/{attributes => variables}/test-fixtures/projects/project.yaml (57%) rename pkg/validation/{attributes => variables}/test-fixtures/projects/starterproject-output.yaml (100%) rename pkg/validation/{attributes => variables}/test-fixtures/projects/starterproject.yaml (60%) create mode 100644 pkg/validation/variables/test-fixtures/projects/zip-output.yaml create mode 100644 pkg/validation/variables/test-fixtures/projects/zip.yaml rename pkg/validation/{attributes/test-fixtures/attributes/attributes-notreferenced.yaml => variables/test-fixtures/variables/variables-notreferenced.yaml} (100%) rename pkg/validation/{attributes/test-fixtures/attributes/attributes-referenced.yaml => variables/test-fixtures/variables/variables-referenced.yaml} (100%) create mode 100644 pkg/validation/variables/variables.go create mode 100644 pkg/validation/variables/variables_command.go create mode 100644 pkg/validation/variables/variables_command_test.go create mode 100644 pkg/validation/variables/variables_component.go rename pkg/validation/{attributes/attributes_component_test.go => variables/variables_component_test.go} (53%) create mode 100644 pkg/validation/variables/variables_endpoint.go rename pkg/validation/{attributes/attributes_endpoint_test.go => variables/variables_endpoint_test.go} (52%) rename pkg/validation/{attributes/attributes_project.go => variables/variables_project.go} (56%) create mode 100644 pkg/validation/variables/variables_project_test.go rename pkg/validation/{attributes/attributes_test.go => variables/variables_test.go} (62%) diff --git a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml index 45742bdb4..ca4dc78bd 100644 --- a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml @@ -4142,12 +4142,6 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced throughout the devfile in - string type fields in the form {{attribute-key}} except for - schemaVersion, metadata and events. Exception to the string - field include element's key identifiers(command id, component - name, endpoint name, project name, etc.) and string enums(command - group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -7164,6 +7158,13 @@ spec: uri: description: Uri of a Devfile yaml file type: string + variables: + additionalProperties: + type: string + description: Overrides of variables encapsulated in a parent + devfile. Overriding is done according to K8S strategic merge + patch standard rules. + type: object type: object projects: description: Projects worked on in the devworkspace, containing @@ -7418,6 +7419,18 @@ spec: - name type: object type: array + variables: + additionalProperties: + type: string + description: Map of string variables. Variable values can be referenced + throughout the devfile in string type fields in the form {{variable-key}} + except for schemaVersion, metadata, parent source. Exception + to the string field also include element's key identifiers (command + id, component name, endpoint name, project name, etc.) and their + references(events, command's component, container's volume mount + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) + type: object type: object required: - started diff --git a/crds/workspace.devfile.io_devworkspaces.yaml b/crds/workspace.devfile.io_devworkspaces.yaml index be4389f79..6e1cfb6de 100644 --- a/crds/workspace.devfile.io_devworkspaces.yaml +++ b/crds/workspace.devfile.io_devworkspaces.yaml @@ -4140,12 +4140,6 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced throughout the devfile in - string type fields in the form {{attribute-key}} except for - schemaVersion, metadata and events. Exception to the string - field include element's key identifiers(command id, component - name, endpoint name, project name, etc.) and string enums(command - group kind, endpoint exposure, etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -7169,6 +7163,13 @@ spec: uri: description: Uri of a Devfile yaml file type: string + variables: + additionalProperties: + type: string + description: Overrides of variables encapsulated in a parent + devfile. Overriding is done according to K8S strategic merge + patch standard rules. + type: object type: object projects: description: Projects worked on in the devworkspace, containing @@ -7423,6 +7424,18 @@ spec: - name type: object type: array + variables: + additionalProperties: + type: string + description: Map of string variables. Variable values can be referenced + throughout the devfile in string type fields in the form {{variable-key}} + except for schemaVersion, metadata, parent source. Exception + to the string field also include element's key identifiers (command + id, component name, endpoint name, project name, etc.) and their + references(events, command's component, container's volume mount + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) + type: object type: object required: - started diff --git a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml index 064b1dc7e..778cfbcca 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml @@ -3916,12 +3916,6 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced throughout the devfile in string - type fields in the form {{attribute-key}} except for schemaVersion, - metadata and events. Exception to the string field include element's - key identifiers(command id, component name, endpoint name, project - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -6829,6 +6823,13 @@ spec: uri: description: Uri of a Devfile yaml file type: string + variables: + additionalProperties: + type: string + description: Overrides of variables encapsulated in a parent devfile. + Overriding is done according to K8S strategic merge patch standard + rules. + type: object type: object projects: description: Projects worked on in the devworkspace, containing names @@ -7069,6 +7070,18 @@ spec: - name type: object type: array + variables: + additionalProperties: + type: string + description: Map of string variables. Variable values can be referenced + throughout the devfile in string type fields in the form {{variable-key}} + except for schemaVersion, metadata, parent source. Exception to + the string field also include element's key identifiers (command + id, component name, endpoint name, project name, etc.) and their + references(events, command's component, container's volume mount + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) + type: object type: object type: object served: true diff --git a/crds/workspace.devfile.io_devworkspacetemplates.yaml b/crds/workspace.devfile.io_devworkspacetemplates.yaml index 439105e92..d758d7cd5 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.yaml @@ -3914,12 +3914,6 @@ spec: properties: attributes: description: Map of implementation-dependant free-form YAML attributes. - Attribute values can be referenced throughout the devfile in string - type fields in the form {{attribute-key}} except for schemaVersion, - metadata and events. Exception to the string field include element's - key identifiers(command id, component name, endpoint name, project - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) type: object x-kubernetes-preserve-unknown-fields: true commands: @@ -6834,6 +6828,13 @@ spec: uri: description: Uri of a Devfile yaml file type: string + variables: + additionalProperties: + type: string + description: Overrides of variables encapsulated in a parent devfile. + Overriding is done according to K8S strategic merge patch standard + rules. + type: object type: object projects: description: Projects worked on in the devworkspace, containing names @@ -7074,6 +7075,18 @@ spec: - name type: object type: array + variables: + additionalProperties: + type: string + description: Map of string variables. Variable values can be referenced + throughout the devfile in string type fields in the form {{variable-key}} + except for schemaVersion, metadata, parent source. Exception to + the string field also include element's key identifiers (command + id, component name, endpoint name, project name, etc.) and their + references(events, command's component, container's volume mount + name, etc.) and string enums(command group kind, endpoint exposure, + etc.) + type: object type: object type: object served: true diff --git a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go index 991cc35e7..d60dc262a 100644 --- a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go +++ b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go @@ -14,10 +14,17 @@ type DevWorkspaceTemplateSpec struct { // +devfile:overrides:generate type DevWorkspaceTemplateSpecContent struct { + // Map of string variables. + // Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} + // except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers + // (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's + // volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.) + // +optional + // +patchStrategy=merge + // +devfile:overrides:include:omitInPlugin=true,description=Overrides of variables encapsulated in a parent devfile. + Variables map[string]string `json:"variables,omitempty" patchStrategy:"merge"` + // Map of implementation-dependant free-form YAML attributes. - // Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} - // except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, - // component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.) // +optional // +patchStrategy=merge // +devfile:overrides:include:omitInPlugin=true,description=Overrides of attributes encapsulated in a parent devfile. diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go index 07da9ea5f..612e62bb3 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.deepcopy.go @@ -1416,6 +1416,13 @@ func (in *DevWorkspaceTemplateSpec) DeepCopy() *DevWorkspaceTemplateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DevWorkspaceTemplateSpecContent) DeepCopyInto(out *DevWorkspaceTemplateSpecContent) { *out = *in + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Attributes != nil { in, out := &in.Attributes, &out.Attributes *out = make(attributes.Attributes, len(*in)) @@ -2362,6 +2369,13 @@ func (in *Parent) DeepCopy() *Parent { func (in *ParentOverrides) DeepCopyInto(out *ParentOverrides) { *out = *in out.OverridesBase = in.OverridesBase + if in.Variables != nil { + in, out := &in.Variables, &out.Variables + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.Attributes != nil { in, out := &in.Attributes, &out.Attributes *out = make(attributes.Attributes, len(*in)) diff --git a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go index d55f494cd..7d19fe6a1 100644 --- a/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go +++ b/pkg/apis/workspaces/v1alpha2/zz_generated.parent_overrides.go @@ -8,6 +8,12 @@ import ( type ParentOverrides struct { OverridesBase `json:",inline"` + // Overrides of variables encapsulated in a parent devfile. + // Overriding is done according to K8S strategic merge patch standard rules. + // +optional + // +patchStrategy=merge + Variables map[string]string `json:"variables,omitempty" patchStrategy:"merge"` + // Overrides of attributes encapsulated in a parent devfile. // Overriding is done according to K8S strategic merge patch standard rules. // +optional diff --git a/pkg/devfile/header.go b/pkg/devfile/header.go index 6cd81d2bb..9a63dfe47 100644 --- a/pkg/devfile/header.go +++ b/pkg/devfile/header.go @@ -27,7 +27,7 @@ type DevfileMetadata struct { // +kubebuilder:validation:Pattern=^([0-9]+)\.([0-9]+)\.([0-9]+)(\-[0-9a-z-]+(\.[0-9a-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ Version string `json:"version,omitempty"` - // Map of implementation-dependant free-form YAML attributes. + // Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead. // +optional Attributes attributes.Attributes `json:"attributes,omitempty"` diff --git a/pkg/utils/overriding/keys.go b/pkg/utils/overriding/keys.go index 5e1411efd..cf6061fef 100644 --- a/pkg/utils/overriding/keys.go +++ b/pkg/utils/overriding/keys.go @@ -34,18 +34,37 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain value := reflect.ValueOf(topLevelListContainer) + var variableValue reflect.Value var attributeValue reflect.Value + // toplevelListContainers can contain either a pointer or a struct and needs to be safeguarded when using reflect if value.Kind() == reflect.Ptr { + variableValue = value.Elem().FieldByName("Variables") attributeValue = value.Elem().FieldByName("Attributes") } else { + variableValue = value.FieldByName("Variables") attributeValue = value.FieldByName("Attributes") } + if variableValue.IsValid() && variableValue.Kind() == reflect.Map { + mapIter := variableValue.MapRange() + + var variableKeys []string + for mapIter.Next() { + k := mapIter.Key() + v := mapIter.Value() + if k.Kind() != reflect.String || v.Kind() != reflect.String { + return fmt.Errorf("unable to fetch Global Variables, Global Variables should be map of strings") + } + variableKeys = append(variableKeys, k.String()) + } + listTypeToKeys["Variables"] = append(listTypeToKeys["Variables"], sets.NewString(variableKeys...)) + } + if attributeValue.IsValid() && attributeValue.CanInterface() { attributes, ok := attributeValue.Interface().(attributesPkg.Attributes) if !ok { - return fmt.Errorf("unable to fetch Attributes from the devfile data") + return fmt.Errorf("unable to fetch Global Attributes from the devfile data") } var attributeKeys []string for k := range attributes { diff --git a/pkg/utils/overriding/merging.go b/pkg/utils/overriding/merging.go index f31dfdd57..981f9ff1a 100644 --- a/pkg/utils/overriding/merging.go +++ b/pkg/utils/overriding/merging.go @@ -107,6 +107,15 @@ func MergeDevWorkspaceTemplateSpec( postStopCommands = postStopCommands.Union(sets.NewString(content.Events.PostStop...)) } + if len(content.Variables) > 0 { + if len(result.Variables) == 0 { + result.Variables = make(map[string]string) + } + for k, v := range content.Variables { + result.Variables[k] = v + } + } + var err error if len(content.Attributes) > 0 { if len(result.Attributes) == 0 { diff --git a/pkg/utils/overriding/merging_test.go b/pkg/utils/overriding/merging_test.go index 3e087d925..325be85df 100644 --- a/pkg/utils/overriding/merging_test.go +++ b/pkg/utils/overriding/merging_test.go @@ -27,6 +27,9 @@ func TestBasicMerging(t *testing.T) { { name: "Basic Merging", mainContent: &workspaces.DevWorkspaceTemplateSpecContent{ + Variables: map[string]string{ + "version1": "main", + }, Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ "main": true, }, nil), @@ -72,8 +75,11 @@ func TestBasicMerging(t *testing.T) { }, pluginFlattenedContents: []*workspaces.DevWorkspaceTemplateSpecContent{ { - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + Variables: map[string]string{ "version2": "plugin", + }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "plugin": true, }, nil), Commands: []workspaces.Command{ { @@ -105,8 +111,11 @@ func TestBasicMerging(t *testing.T) { }, }, parentFlattenedContent: &workspaces.DevWorkspaceTemplateSpecContent{ + Variables: map[string]string{ + "version3": "parent", + }, Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "version": "parent", + "parent": true, }, nil), Commands: []workspaces.Command{ { @@ -138,10 +147,15 @@ func TestBasicMerging(t *testing.T) { }, }, expected: &workspaces.DevWorkspaceTemplateSpecContent{ - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "version": "parent", + Variables: map[string]string{ + "version3": "parent", "version2": "plugin", - "main": true, + "version1": "main", + }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "parent": true, + "plugin": true, + "main": true, }, nil), Commands: []workspaces.Command{ { diff --git a/pkg/utils/overriding/overriding_test.go b/pkg/utils/overriding/overriding_test.go index ebbcb52b3..97e045a21 100644 --- a/pkg/utils/overriding/overriding_test.go +++ b/pkg/utils/overriding/overriding_test.go @@ -52,6 +52,10 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, + Variables: map[string]string{ + "version": "main", + "xyz": "xyz", + }, Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ "version": "main", "xyz": "xyz", @@ -86,8 +90,13 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + Variables: map[string]string{ "version": "patch", + }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": map[string]interface{}{ + "patch": true, + }, }, nil), } @@ -133,9 +142,15 @@ func TestBasicToplevelOverriding(t *testing.T) { }, }, }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + Variables: map[string]string{ "version": "patch", "xyz": "xyz", + }, + Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ + "version": map[string]interface{}{ + "patch": true, + }, + "xyz": "xyz", }, nil), } diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml index bf3b6154a..b1ac1a152 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/main.yaml @@ -1,8 +1,9 @@ parent: uri: "anyParent" +variables: + objectVariable: mainValue attributes: - objectAttribute: - attributeField: 9.9 + mainAttribute: true components: - container: image: "aDifferentValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml index d5fa00155..045d2b33a 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/parent.yaml @@ -1,6 +1,7 @@ +variables: + objectVariable: parentValue attributes: - objectAttribute: - attributeField: 10.10 + mainAttribute: false components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt index 1fe9c1e87..358f7a5b9 100644 --- a/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt +++ b/pkg/utils/overriding/test-fixtures/merges/duplicate-with-parent/result-error.txt @@ -1,3 +1,4 @@ -2 errors occurred: +3 errors occurred: * Some Components are already defined in parent: existing-in-parent. If you want to override them, you should do it in the parent scope. - * Some Attributes are already defined in parent: objectAttribute. If you want to override them, you should do it in the parent scope. + * Some Variables are already defined in parent: objectVariable. If you want to override them, you should do it in the parent scope. + * Some Attributes are already defined in parent: mainAttribute. If you want to override them, you should do it in the parent scope. diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml index 75d84820a..4d0509ac6 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml @@ -1,5 +1,7 @@ parent: uri: "anyParent" +variables: + variableValue: main attributes: mainAttribute: true components: diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml index 80e9f98aa..c6f6f8ec6 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml @@ -1,6 +1,7 @@ +variables: + variableParentValue: parent attributes: - objectAttribute: - attributeField: 9.9 + attribute: "parent" components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml index 3baeda67d..6280bcf05 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml @@ -1,7 +1,9 @@ +variables: + variableValue: main + variableParentValue: parent attributes: mainAttribute: true - objectAttribute: - attributeField: 9.9 + attribute: "parent" components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt b/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt deleted file mode 100644 index cb08e8d98..000000000 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/result-error.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 error occurred: - * Some Attributes do not override any existing element: newAttributeValid. They should be defined in the main body, as new elements, not in the overriding section diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/original.yaml similarity index 70% rename from pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml rename to pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/original.yaml index 49da51e72..a7281aec0 100644 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/original.yaml +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/original.yaml @@ -1,3 +1,6 @@ +variables: + stringVariableToChange: original + stringVariableToKeep: stringValue attributes: boolAttributeToChange: true stringAttributeToKeep: stringValue diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/patch.yaml similarity index 69% rename from pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml rename to pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/patch.yaml index 688a439a1..fccfbe8a7 100644 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes/patch.yaml +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/patch.yaml @@ -1,6 +1,11 @@ +variables: + stringVariableToChange: patch + + newVariableValid: nope attributes: boolAttributeToChange: false + newAttributeValid: false objectAttributeToChange: newObjectAttributeField: diff --git a/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/result-error.txt b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/result-error.txt new file mode 100644 index 000000000..5c994d27b --- /dev/null +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes-err/result-error.txt @@ -0,0 +1,3 @@ +2 errors occurred: + * Some Variables do not override any existing element: newVariableValid. They should be defined in the main body, as new elements, not in the overriding section + * Some Attributes do not override any existing element: newAttributeValid. They should be defined in the main body, as new elements, not in the overriding section diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/original.yaml similarity index 68% rename from pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml rename to pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/original.yaml index b13770dfc..8ceb4913e 100644 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes/original.yaml +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/original.yaml @@ -1,3 +1,6 @@ +variables: + stringVariableToChange: originalValue + stringVariableToKeep: stringValue attributes: boolAttributeToChange: true stringAttributeToKeep: stringValue diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/patch.yaml similarity index 81% rename from pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml rename to pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/patch.yaml index 35bf38b43..3a12b69d1 100644 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes-err/patch.yaml +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/patch.yaml @@ -1,7 +1,8 @@ +variables: + stringVariableToChange: patchedValue attributes: boolAttributeToChange: false - newAttributeValid: false objectAttributeToChange: newObjectAttributeField: diff --git a/pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/result.yaml similarity index 69% rename from pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml rename to pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/result.yaml index f93675973..003cced0b 100644 --- a/pkg/utils/overriding/test-fixtures/patches/global-attributes/result.yaml +++ b/pkg/utils/overriding/test-fixtures/patches/global-variables-and-attributes/result.yaml @@ -1,3 +1,6 @@ +variables: + stringVariableToChange: patchedValue + stringVariableToKeep: stringValue attributes: boolAttributeToChange: false stringAttributeToKeep: stringValue @@ -6,4 +9,4 @@ attributes: newObjectAttributeField: objectAttributeSubField: 11 newObjectAttributeSubField: newObjectAttributeFieldValue - newField: true \ No newline at end of file + newField: true diff --git a/pkg/validation/attributes/attributes.go b/pkg/validation/attributes/attributes.go deleted file mode 100644 index 376017187..000000000 --- a/pkg/validation/attributes/attributes.go +++ /dev/null @@ -1,57 +0,0 @@ -package attributes - -import ( - "regexp" - "strings" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// ValidateAndReplaceGlobalAttribute validates the workspace template spec data for global attribute references and replaces them with the attribute value -func ValidateAndReplaceGlobalAttribute(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { - - var err error - - if workspaceTemplateSpec != nil { - // Validate the components and replace for global attribute - if err = ValidateAndReplaceForComponents(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Components); err != nil { - return err - } - - // Validate the commands and replace for global attribute - if err = ValidateAndReplaceForCommands(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Commands); err != nil { - return err - } - - // Validate the projects and replace for global attribute - if err = ValidateAndReplaceForProjects(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.Projects); err != nil { - return err - } - - // Validate the starter projects and replace for global attribute - if err = ValidateAndReplaceForStarterProjects(workspaceTemplateSpec.Attributes, workspaceTemplateSpec.StarterProjects); err != nil { - return err - } - } - - return nil -} - -var globalAttributeRegex = regexp.MustCompile(`\{\{(.*?)\}\}`) - -// validateAndReplaceDataWithAttribute validates the string for a global attribute and replaces it. An error -// is returned if the string references an invalid global attribute key -func validateAndReplaceDataWithAttribute(val string, attributes apiAttributes.Attributes) (string, error) { - matches := globalAttributeRegex.FindAllStringSubmatch(val, -1) - for _, match := range matches { - var err error - attrValue := attributes.GetString(match[1], &err) - if err != nil { - return "", err - } - val = strings.Replace(val, match[0], attrValue, -1) - } - - return val, nil -} diff --git a/pkg/validation/attributes/attributes_command.go b/pkg/validation/attributes/attributes_command.go deleted file mode 100644 index 2cf0b24b5..000000000 --- a/pkg/validation/attributes/attributes_command.go +++ /dev/null @@ -1,108 +0,0 @@ -package attributes - -import ( - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// ValidateAndReplaceForCommands validates the commands data for global attribute references and replaces them with the attribute value -func ValidateAndReplaceForCommands(attributes apiAttributes.Attributes, commands []v1alpha2.Command) error { - - for i := range commands { - var err error - - // Validate various command types - switch { - case commands[i].Exec != nil: - if err = validateAndReplaceForExecCommand(attributes, commands[i].Exec); err != nil { - return err - } - case commands[i].Composite != nil: - if err = validateAndReplaceForCompositeCommand(attributes, commands[i].Composite); err != nil { - return err - } - case commands[i].Apply != nil: - if err = validateAndReplaceForApplyCommand(attributes, commands[i].Apply); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForExecCommand validates the exec command data for global attribute references and replaces them with the attribute value -func validateAndReplaceForExecCommand(attributes apiAttributes.Attributes, exec *v1alpha2.ExecCommand) error { - var err error - - if exec != nil { - // Validate exec command line - if exec.CommandLine, err = validateAndReplaceDataWithAttribute(exec.CommandLine, attributes); err != nil { - return err - } - - // Validate exec component - if exec.Component, err = validateAndReplaceDataWithAttribute(exec.Component, attributes); err != nil { - return err - } - - // Validate exec working dir - if exec.WorkingDir, err = validateAndReplaceDataWithAttribute(exec.WorkingDir, attributes); err != nil { - return err - } - - // Validate exec label - if exec.Label, err = validateAndReplaceDataWithAttribute(exec.Label, attributes); err != nil { - return err - } - - // Validate exec env - if len(exec.Env) > 0 { - if err = validateAndReplaceForEnv(attributes, exec.Env); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForCompositeCommand validates the composite command data for global attribute references and replaces them with the attribute value -func validateAndReplaceForCompositeCommand(attributes apiAttributes.Attributes, composite *v1alpha2.CompositeCommand) error { - var err error - - if composite != nil { - // Validate composite label - if composite.Label, err = validateAndReplaceDataWithAttribute(composite.Label, attributes); err != nil { - return err - } - - // Validate composite commands - for i := range composite.Commands { - if composite.Commands[i], err = validateAndReplaceDataWithAttribute(composite.Commands[i], attributes); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForApplyCommand validates the apply command data for global attribute references and replaces them with the attribute value -func validateAndReplaceForApplyCommand(attributes apiAttributes.Attributes, apply *v1alpha2.ApplyCommand) error { - var err error - - if apply != nil { - // Validate composite label - if apply.Label, err = validateAndReplaceDataWithAttribute(apply.Label, attributes); err != nil { - return err - } - - // Validate apply component - if apply.Component, err = validateAndReplaceDataWithAttribute(apply.Component, attributes); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/validation/attributes/attributes_command_test.go b/pkg/validation/attributes/attributes_command_test.go deleted file mode 100644 index 671c5c93e..000000000 --- a/pkg/validation/attributes/attributes_command_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package attributes - -import ( - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" - "github.com/stretchr/testify/assert" -) - -func TestValidateAndReplaceExecCommand(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/commands/exec.yaml", - outputFile: "test-fixtures/commands/exec-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/commands/exec.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testExecCommand := v1alpha2.ExecCommand{} - readFileToStruct(t, tt.testFile, &testExecCommand) - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := validateAndReplaceForExecCommand(testAttribute, &testExecCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedExecCommand := v1alpha2.ExecCommand{} - readFileToStruct(t, tt.outputFile, &expectedExecCommand) - assert.Equal(t, expectedExecCommand, testExecCommand, "The two values should be the same.") - } - }) - } -} - -func TestValidateAndReplaceCompositeCommand(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/commands/composite.yaml", - outputFile: "test-fixtures/commands/composite-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/commands/composite.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testCompositeCommand := v1alpha2.CompositeCommand{} - readFileToStruct(t, tt.testFile, &testCompositeCommand) - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := validateAndReplaceForCompositeCommand(testAttribute, &testCompositeCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedCompositeCommand := v1alpha2.CompositeCommand{} - readFileToStruct(t, tt.outputFile, &expectedCompositeCommand) - assert.Equal(t, expectedCompositeCommand, testCompositeCommand, "The two values should be the same.") - } - }) - } -} - -func TestValidateAndReplaceApplyCommand(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/commands/apply.yaml", - outputFile: "test-fixtures/commands/apply-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/commands/apply.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testApplyCommand := v1alpha2.ApplyCommand{} - readFileToStruct(t, tt.testFile, &testApplyCommand) - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := validateAndReplaceForApplyCommand(testAttribute, &testApplyCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedApplyCommand := v1alpha2.ApplyCommand{} - readFileToStruct(t, tt.outputFile, &expectedApplyCommand) - assert.Equal(t, expectedApplyCommand, testApplyCommand, "The two values should be the same.") - } - }) - } -} diff --git a/pkg/validation/attributes/attributes_component.go b/pkg/validation/attributes/attributes_component.go deleted file mode 100644 index 6a7373b5a..000000000 --- a/pkg/validation/attributes/attributes_component.go +++ /dev/null @@ -1,189 +0,0 @@ -package attributes - -import ( - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// ValidateAndReplaceForComponents validates the components data for global attribute references and replaces them with the attribute value -func ValidateAndReplaceForComponents(attributes apiAttributes.Attributes, components []v1alpha2.Component) error { - - for i := range components { - var err error - - // Validate various component types - switch { - case components[i].Container != nil: - if err = validateAndReplaceForContainerComponent(attributes, components[i].Container); err != nil { - return err - } - case components[i].Kubernetes != nil: - if err = validateAndReplaceForKubernetesComponent(attributes, components[i].Kubernetes); err != nil { - return err - } - case components[i].Openshift != nil: - if err = validateAndReplaceForOpenShiftComponent(attributes, components[i].Openshift); err != nil { - return err - } - case components[i].Volume != nil: - if err = validateAndReplaceForVolumeComponent(attributes, components[i].Volume); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForContainerComponent validates the container component data for global attribute references and replaces them with the attribute value -func validateAndReplaceForContainerComponent(attributes apiAttributes.Attributes, container *v1alpha2.ContainerComponent) error { - var err error - - if container != nil { - // Validate container image - if container.Image, err = validateAndReplaceDataWithAttribute(container.Image, attributes); err != nil { - return err - } - - // Validate container commands - for i := range container.Command { - if container.Command[i], err = validateAndReplaceDataWithAttribute(container.Command[i], attributes); err != nil { - return err - } - } - - // Validate container args - for i := range container.Args { - if container.Args[i], err = validateAndReplaceDataWithAttribute(container.Args[i], attributes); err != nil { - return err - } - } - - // Validate memory limit - if container.MemoryLimit, err = validateAndReplaceDataWithAttribute(container.MemoryLimit, attributes); err != nil { - return err - } - - // Validate memory limit - if container.MemoryRequest, err = validateAndReplaceDataWithAttribute(container.MemoryRequest, attributes); err != nil { - return err - } - - // Validate source mapping - if container.SourceMapping, err = validateAndReplaceDataWithAttribute(container.SourceMapping, attributes); err != nil { - return err - } - - // Validate container env - if len(container.Env) > 0 { - if err = validateAndReplaceForEnv(attributes, container.Env); err != nil { - return err - } - } - - // Validate container volume mounts - for i := range container.VolumeMounts { - if container.VolumeMounts[i].Name, err = validateAndReplaceDataWithAttribute(container.VolumeMounts[i].Name, attributes); err != nil { - return err - } - if container.VolumeMounts[i].Path, err = validateAndReplaceDataWithAttribute(container.VolumeMounts[i].Path, attributes); err != nil { - return err - } - } - - // Validate container endpoints - if len(container.Endpoints) > 0 { - if err = validateAndReplaceForEndpoint(attributes, container.Endpoints); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForEnv validates the env data for global attribute references and replaces them with the attribute value -func validateAndReplaceForEnv(attributes apiAttributes.Attributes, env []v1alpha2.EnvVar) error { - - for i := range env { - var err error - - // Validate env name - if env[i].Name, err = validateAndReplaceDataWithAttribute(env[i].Name, attributes); err != nil { - return err - } - - // Validate env value - if env[i].Value, err = validateAndReplaceDataWithAttribute(env[i].Value, attributes); err != nil { - return err - } - } - - return nil -} - -// validateAndReplaceForKubernetesComponent validates the kubernetes component data for global attribute references and replaces them with the attribute value -func validateAndReplaceForKubernetesComponent(attributes apiAttributes.Attributes, kubernetes *v1alpha2.KubernetesComponent) error { - var err error - - if kubernetes != nil { - // Validate kubernetes uri - if kubernetes.Uri, err = validateAndReplaceDataWithAttribute(kubernetes.Uri, attributes); err != nil { - return err - } - - // Validate kubernetes inlined - if kubernetes.Inlined, err = validateAndReplaceDataWithAttribute(kubernetes.Inlined, attributes); err != nil { - return err - } - - // Validate kubernetes endpoints - if len(kubernetes.Endpoints) > 0 { - if err = validateAndReplaceForEndpoint(attributes, kubernetes.Endpoints); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForOpenShiftComponent validates the openshift component data for global attribute references and replaces them with the attribute value -func validateAndReplaceForOpenShiftComponent(attributes apiAttributes.Attributes, openshift *v1alpha2.OpenshiftComponent) error { - var err error - - if openshift != nil { - // Validate openshift uri - if openshift.Uri, err = validateAndReplaceDataWithAttribute(openshift.Uri, attributes); err != nil { - return err - } - - // Validate openshift inlined - if openshift.Inlined, err = validateAndReplaceDataWithAttribute(openshift.Inlined, attributes); err != nil { - return err - } - - // Validate openshift endpoints - if len(openshift.Endpoints) > 0 { - if err = validateAndReplaceForEndpoint(attributes, openshift.Endpoints); err != nil { - return err - } - } - } - - return nil -} - -// validateAndReplaceForVolumeComponent validates the volume component data for global attribute references and replaces them with the attribute value -func validateAndReplaceForVolumeComponent(attributes apiAttributes.Attributes, volume *v1alpha2.VolumeComponent) error { - var err error - - if volume != nil { - // Validate volume size - if volume.Size, err = validateAndReplaceDataWithAttribute(volume.Size, attributes); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/validation/attributes/attributes_endpoint.go b/pkg/validation/attributes/attributes_endpoint.go deleted file mode 100644 index dad342beb..000000000 --- a/pkg/validation/attributes/attributes_endpoint.go +++ /dev/null @@ -1,21 +0,0 @@ -package attributes - -import ( - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// validateAndReplaceForEndpoint validates the endpoint data for global attribute references and replaces them with the attribute value -func validateAndReplaceForEndpoint(attributes apiAttributes.Attributes, endpoints []v1alpha2.Endpoint) error { - - for i := range endpoints { - var err error - - // Validate endpoint path - if endpoints[i].Path, err = validateAndReplaceDataWithAttribute(endpoints[i].Path, attributes); err != nil { - return err - } - } - - return nil -} diff --git a/pkg/validation/attributes/attributes_parent.go b/pkg/validation/attributes/attributes_parent.go deleted file mode 100644 index d096c814e..000000000 --- a/pkg/validation/attributes/attributes_parent.go +++ /dev/null @@ -1,48 +0,0 @@ -package attributes - -import ( - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" -) - -// ValidateAndReplaceForParent validates the parent data for global attribute references(except parent overrides) and replaces them with the attribute value -func ValidateAndReplaceForParent(attributes apiAttributes.Attributes, parent *v1alpha2.Parent) error { - var err error - - if parent != nil { - switch { - case parent.Id != "": - // Validate parent id - if parent.Id, err = validateAndReplaceDataWithAttribute(parent.Id, attributes); err != nil { - return err - } - case parent.Uri != "": - // Validate parent uri - if parent.Uri, err = validateAndReplaceDataWithAttribute(parent.Uri, attributes); err != nil { - return err - } - case parent.Kubernetes != nil: - // Validate parent kubernetes name - if parent.Kubernetes.Name, err = validateAndReplaceDataWithAttribute(parent.Kubernetes.Name, attributes); err != nil { - return err - } - - // Validate parent kubernetes namespace - if parent.Kubernetes.Namespace, err = validateAndReplaceDataWithAttribute(parent.Kubernetes.Namespace, attributes); err != nil { - return err - } - } - - // Validate parent registry url - if parent.RegistryUrl, err = validateAndReplaceDataWithAttribute(parent.RegistryUrl, attributes); err != nil { - return err - } - - // Note: No need to substitute parent overrides at this point. Call global attribute validation/substitution - // after merging the flattened parent devfile to the main devfile. Parent's global attribute key can - // be overridden in parent overrides or the alternative is to mention the attribute as a main devfile - // global attribute if parent devfile does not have a global attribute - } - - return nil -} diff --git a/pkg/validation/attributes/attributes_parent_test.go b/pkg/validation/attributes/attributes_parent_test.go deleted file mode 100644 index b472d4b89..000000000 --- a/pkg/validation/attributes/attributes_parent_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package attributes - -import ( - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" - "github.com/stretchr/testify/assert" -) - -func TestValidateAndReplaceParent(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Uri Substitution", - testFile: "test-fixtures/parent/parent-uri.yaml", - outputFile: "test-fixtures/parent/parent-uri-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Good Id Substitution", - testFile: "test-fixtures/parent/parent-id.yaml", - outputFile: "test-fixtures/parent/parent-id-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Good Kube Substitution", - testFile: "test-fixtures/parent/parent-kubernetes.yaml", - outputFile: "test-fixtures/parent/parent-kubernetes-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/parent/parent-id.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testParent := v1alpha2.Parent{} - readFileToStruct(t, tt.testFile, &testParent) - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := ValidateAndReplaceForParent(testAttribute, &testParent) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedParent := v1alpha2.Parent{} - readFileToStruct(t, tt.outputFile, &expectedParent) - assert.Equal(t, expectedParent, testParent, "The two values should be the same.") - } - }) - } -} diff --git a/pkg/validation/attributes/attributes_project_test.go b/pkg/validation/attributes/attributes_project_test.go deleted file mode 100644 index 9027ceeb5..000000000 --- a/pkg/validation/attributes/attributes_project_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package attributes - -import ( - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" - "github.com/stretchr/testify/assert" -) - -func TestValidateAndReplaceProjects(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/projects/project.yaml", - outputFile: "test-fixtures/projects/project-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/projects/project.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testProject := v1alpha2.Project{} - readFileToStruct(t, tt.testFile, &testProject) - testProjectArr := []v1alpha2.Project{testProject} - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := ValidateAndReplaceForProjects(testAttribute, testProjectArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedProject := v1alpha2.Project{} - readFileToStruct(t, tt.outputFile, &expectedProject) - expectedProjectArr := []v1alpha2.Project{expectedProject} - assert.Equal(t, expectedProjectArr, testProjectArr, "The two values should be the same.") - } - }) - } -} - -func TestValidateAndReplaceStarterProjects(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Substitution", - testFile: "test-fixtures/projects/starterproject.yaml", - outputFile: "test-fixtures/projects/starterproject-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Reference", - testFile: "test-fixtures/projects/starterproject.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testStarterProject := v1alpha2.StarterProject{} - readFileToStruct(t, tt.testFile, &testStarterProject) - testStarterProjectArr := []v1alpha2.StarterProject{testStarterProject} - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := ValidateAndReplaceForStarterProjects(testAttribute, testStarterProjectArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedStarterProject := v1alpha2.StarterProject{} - readFileToStruct(t, tt.outputFile, &expectedStarterProject) - expectedStarterProjectArr := []v1alpha2.StarterProject{expectedStarterProject} - assert.Equal(t, expectedStarterProjectArr, testStarterProjectArr, "The two values should be the same.") - } - }) - } -} - -func TestValidateAndReplaceProjectSrc(t *testing.T) { - - tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool - }{ - { - name: "Good Git Substitution", - testFile: "test-fixtures/projects/git.yaml", - outputFile: "test-fixtures/projects/git-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Good Zip Substitution", - testFile: "test-fixtures/projects/zip.yaml", - outputFile: "test-fixtures/projects/zip-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, - }, - { - name: "Invalid Git Reference", - testFile: "test-fixtures/projects/git.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - { - name: "Invalid Zip Reference", - testFile: "test-fixtures/projects/zip.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - testProjectSrc := v1alpha2.ProjectSource{} - readFileToStruct(t, tt.testFile, &testProjectSrc) - - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) - - err := validateandReplaceForProjectSource(testAttribute, &testProjectSrc) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedProjectSrc := v1alpha2.ProjectSource{} - readFileToStruct(t, tt.outputFile, &expectedProjectSrc) - assert.Equal(t, expectedProjectSrc, testProjectSrc, "The two values should be the same.") - } - }) - } -} diff --git a/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml b/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml deleted file mode 100644 index 2ea577750..000000000 --- a/pkg/validation/attributes/test-fixtures/commands/apply-output.yaml +++ /dev/null @@ -1,2 +0,0 @@ -label: "1" -component: "FOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/apply.yaml b/pkg/validation/attributes/test-fixtures/commands/apply.yaml deleted file mode 100644 index 54f5bacad..000000000 --- a/pkg/validation/attributes/test-fixtures/commands/apply.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -label: "{{version}}" -component: "{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/commands/composite.yaml b/pkg/validation/attributes/test-fixtures/commands/composite.yaml deleted file mode 100644 index 20a060509..000000000 --- a/pkg/validation/attributes/test-fixtures/commands/composite.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -label: "{{version}}" -commands: - - "{{foo}}" - - BAR \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/components/env.yaml b/pkg/validation/attributes/test-fixtures/components/env.yaml deleted file mode 100644 index a30ad805f..000000000 --- a/pkg/validation/attributes/test-fixtures/components/env.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -name: "{{foo}}" -value: "{{bar}}" diff --git a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml deleted file mode 100644 index 931dea4cf..000000000 --- a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes-output.yaml +++ /dev/null @@ -1,6 +0,0 @@ -uri: "uri" -inlined: "inlined" -endpoints: - - name: endpoint1 - exposure: public - path: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml deleted file mode 100644 index 90343e61c..000000000 --- a/pkg/validation/attributes/test-fixtures/components/openshift-kubernetes.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -uri: "{{uri}}" -inlined: "{{inlined}}" -endpoints: - - name: endpoint1 - exposure: public - path: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/volume.yaml b/pkg/validation/attributes/test-fixtures/components/volume.yaml deleted file mode 100644 index 9adee851e..000000000 --- a/pkg/validation/attributes/test-fixtures/components/volume.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -size: "{{size}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml deleted file mode 100644 index 13e325bad..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-id-output.yaml +++ /dev/null @@ -1,2 +0,0 @@ -id: "FOO" -registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml deleted file mode 100644 index 5ea2a1f01..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-id.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -id: "{{foo}}" -registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml deleted file mode 100644 index fe4f7cb95..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes-output.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kubernetes: - name: "FOO" - namespace: "FOO" -registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml deleted file mode 100644 index c06dc5490..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-kubernetes.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -kubernetes: - name: "{{foo}}" - namespace: "{{foo}}" -registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml deleted file mode 100644 index cb7e9f6fa..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-uri-output.yaml +++ /dev/null @@ -1,2 +0,0 @@ -uri: "FOO" -registryUrl: "FOO" diff --git a/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml b/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml deleted file mode 100644 index e1a7bf338..000000000 --- a/pkg/validation/attributes/test-fixtures/parent/parent-uri.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -uri: "{{foo}}" -registryUrl: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/git.yaml b/pkg/validation/attributes/test-fixtures/projects/git.yaml deleted file mode 100644 index fbe541080..000000000 --- a/pkg/validation/attributes/test-fixtures/projects/git.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -git: - checkoutFrom: - revision: "{{foo}}" - remote: "{{bar}}" - remotes: - "foo": "{{bar}}" - "{{foo}}{{bar}}": "{{bar}}{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml b/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml deleted file mode 100644 index 345106b0c..000000000 --- a/pkg/validation/attributes/test-fixtures/projects/zip-output.yaml +++ /dev/null @@ -1,2 +0,0 @@ -zip: - location: "/FOO" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/projects/zip.yaml b/pkg/validation/attributes/test-fixtures/projects/zip.yaml deleted file mode 100644 index 30ce769ae..000000000 --- a/pkg/validation/attributes/test-fixtures/projects/zip.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -zip: - location: "/{{foo}}" \ No newline at end of file diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml b/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml similarity index 94% rename from pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml rename to pkg/validation/variables/test-fixtures/all/devfile-bad.yaml index 21e2499d4..0f8809349 100644 --- a/pkg/validation/attributes/test-fixtures/all/devfile-bad.yaml +++ b/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml @@ -1,4 +1,4 @@ -attributes: +variables: devnull: /dev/null components: - name: component1 diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml b/pkg/validation/variables/test-fixtures/all/devfile-good-output.yaml similarity index 95% rename from pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml rename to pkg/validation/variables/test-fixtures/all/devfile-good-output.yaml index 5c68e9e06..4cd2a463b 100644 --- a/pkg/validation/attributes/test-fixtures/all/devfile-good-output.yaml +++ b/pkg/validation/variables/test-fixtures/all/devfile-good-output.yaml @@ -1,4 +1,4 @@ -attributes: +variables: tag: xyz version: "1" foo: FOO @@ -54,10 +54,10 @@ commands: - id: command2 composite: commands: - - "xyz" + - xyz - command1 events: preStart: - command1 preStop: - - "command2" + - command2 diff --git a/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml b/pkg/validation/variables/test-fixtures/all/devfile-good.yaml similarity index 95% rename from pkg/validation/attributes/test-fixtures/all/devfile-good.yaml rename to pkg/validation/variables/test-fixtures/all/devfile-good.yaml index a46b49a07..7a33047e6 100644 --- a/pkg/validation/attributes/test-fixtures/all/devfile-good.yaml +++ b/pkg/validation/variables/test-fixtures/all/devfile-good.yaml @@ -1,4 +1,4 @@ -attributes: +variables: tag: xyz version: "1" foo: FOO @@ -54,10 +54,10 @@ commands: - id: command2 composite: commands: - - "{{tag}}" + - xyz - command1 events: preStart: - command1 preStop: - - "command2" + - command2 diff --git a/pkg/validation/variables/test-fixtures/commands/apply-output.yaml b/pkg/validation/variables/test-fixtures/commands/apply-output.yaml new file mode 100644 index 000000000..be0a939fe --- /dev/null +++ b/pkg/validation/variables/test-fixtures/commands/apply-output.yaml @@ -0,0 +1,2 @@ +label: "1" +component: component diff --git a/pkg/validation/variables/test-fixtures/commands/apply.yaml b/pkg/validation/variables/test-fixtures/commands/apply.yaml new file mode 100644 index 000000000..fa8fda518 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/commands/apply.yaml @@ -0,0 +1,3 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +label: "{{version}}" +component: component diff --git a/pkg/validation/attributes/test-fixtures/commands/composite-output.yaml b/pkg/validation/variables/test-fixtures/commands/composite-output.yaml similarity index 55% rename from pkg/validation/attributes/test-fixtures/commands/composite-output.yaml rename to pkg/validation/variables/test-fixtures/commands/composite-output.yaml index f1a57a117..56b773d35 100644 --- a/pkg/validation/attributes/test-fixtures/commands/composite-output.yaml +++ b/pkg/validation/variables/test-fixtures/commands/composite-output.yaml @@ -1,4 +1,4 @@ label: "1" commands: - - "FOO" - - BAR \ No newline at end of file + - FOO + - BAR diff --git a/pkg/validation/variables/test-fixtures/commands/composite.yaml b/pkg/validation/variables/test-fixtures/commands/composite.yaml new file mode 100644 index 000000000..064aa0b69 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/commands/composite.yaml @@ -0,0 +1,5 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +label: "{{version}}" +commands: + - FOO + - BAR diff --git a/pkg/validation/attributes/test-fixtures/commands/exec-output.yaml b/pkg/validation/variables/test-fixtures/commands/exec-output.yaml similarity index 82% rename from pkg/validation/attributes/test-fixtures/commands/exec-output.yaml rename to pkg/validation/variables/test-fixtures/commands/exec-output.yaml index 8f7e852f4..0c2b64896 100644 --- a/pkg/validation/attributes/test-fixtures/commands/exec-output.yaml +++ b/pkg/validation/variables/test-fixtures/commands/exec-output.yaml @@ -1,4 +1,4 @@ -component: "BAR" +component: component commandLine: tail -f /dev/null workingDir: "FOO" label: "1" diff --git a/pkg/validation/attributes/test-fixtures/commands/exec.yaml b/pkg/validation/variables/test-fixtures/commands/exec.yaml similarity index 54% rename from pkg/validation/attributes/test-fixtures/commands/exec.yaml rename to pkg/validation/variables/test-fixtures/commands/exec.yaml index dbde64f2d..16cbf5744 100644 --- a/pkg/validation/attributes/test-fixtures/commands/exec.yaml +++ b/pkg/validation/variables/test-fixtures/commands/exec.yaml @@ -1,5 +1,5 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml -component: "{{bar}}" +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +component: component commandLine: tail -f {{devnull}} workingDir: "{{foo}}" label: "{{version}}" diff --git a/pkg/validation/attributes/test-fixtures/components/container-output.yaml b/pkg/validation/variables/test-fixtures/components/container-output.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/components/container-output.yaml rename to pkg/validation/variables/test-fixtures/components/container-output.yaml diff --git a/pkg/validation/attributes/test-fixtures/components/container.yaml b/pkg/validation/variables/test-fixtures/components/container.yaml similarity index 80% rename from pkg/validation/attributes/test-fixtures/components/container.yaml rename to pkg/validation/variables/test-fixtures/components/container.yaml index 1a3537b88..e7d89047e 100644 --- a/pkg/validation/attributes/test-fixtures/components/container.yaml +++ b/pkg/validation/variables/test-fixtures/components/container.yaml @@ -1,4 +1,4 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml +# Variables are defined in test-fixtures/variables/variables-referenced.yaml image: "image-{{version}}" env: - name: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml b/pkg/validation/variables/test-fixtures/components/endpoint-output.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/components/endpoint-output.yaml rename to pkg/validation/variables/test-fixtures/components/endpoint-output.yaml diff --git a/pkg/validation/attributes/test-fixtures/components/endpoint.yaml b/pkg/validation/variables/test-fixtures/components/endpoint.yaml similarity index 52% rename from pkg/validation/attributes/test-fixtures/components/endpoint.yaml rename to pkg/validation/variables/test-fixtures/components/endpoint.yaml index 9802c98eb..a7be586ab 100644 --- a/pkg/validation/attributes/test-fixtures/components/endpoint.yaml +++ b/pkg/validation/variables/test-fixtures/components/endpoint.yaml @@ -1,4 +1,4 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml +# Variables are defined in test-fixtures/variables/variables-referenced.yaml name: endpoint1 exposure: "public" protocol: "https" diff --git a/pkg/validation/attributes/test-fixtures/components/env-output.yaml b/pkg/validation/variables/test-fixtures/components/env-output.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/components/env-output.yaml rename to pkg/validation/variables/test-fixtures/components/env-output.yaml diff --git a/pkg/validation/variables/test-fixtures/components/env.yaml b/pkg/validation/variables/test-fixtures/components/env.yaml new file mode 100644 index 000000000..b3b2ff490 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/components/env.yaml @@ -0,0 +1,3 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +name: "{{foo}}" +value: "{{bar}}" diff --git a/pkg/validation/variables/test-fixtures/components/openshift-kubernetes-output.yaml b/pkg/validation/variables/test-fixtures/components/openshift-kubernetes-output.yaml new file mode 100644 index 000000000..fb8a4a8f7 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/components/openshift-kubernetes-output.yaml @@ -0,0 +1,16 @@ +uri: "http://link/uri" +inlined: | + apiVersion: batch/v1 + kind: Job + metadata: + name: pi + spec: + template: + spec: + containers: + - name: job + image: inlined +endpoints: + - name: endpoint1 + exposure: public + path: "FOO" diff --git a/pkg/validation/variables/test-fixtures/components/openshift-kubernetes.yaml b/pkg/validation/variables/test-fixtures/components/openshift-kubernetes.yaml new file mode 100644 index 000000000..5af4e4ca4 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/components/openshift-kubernetes.yaml @@ -0,0 +1,17 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +uri: "http://link/{{uri}}" +inlined: | + apiVersion: batch/v1 + kind: Job + metadata: + name: pi + spec: + template: + spec: + containers: + - name: job + image: {{inlined}} +endpoints: + - name: endpoint1 + exposure: public + path: "{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/components/volume-output.yaml b/pkg/validation/variables/test-fixtures/components/volume-output.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/components/volume-output.yaml rename to pkg/validation/variables/test-fixtures/components/volume-output.yaml diff --git a/pkg/validation/variables/test-fixtures/components/volume.yaml b/pkg/validation/variables/test-fixtures/components/volume.yaml new file mode 100644 index 000000000..f8339ebf1 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/components/volume.yaml @@ -0,0 +1,2 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +size: "{{size}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/git-output.yaml b/pkg/validation/variables/test-fixtures/projects/git-output.yaml similarity index 79% rename from pkg/validation/attributes/test-fixtures/projects/git-output.yaml rename to pkg/validation/variables/test-fixtures/projects/git-output.yaml index 8fe872a4e..6e8c843d7 100644 --- a/pkg/validation/attributes/test-fixtures/projects/git-output.yaml +++ b/pkg/validation/variables/test-fixtures/projects/git-output.yaml @@ -4,4 +4,4 @@ git: remote: "BAR" remotes: "foo": "BAR" - "FOOBAR": "BARFOO" \ No newline at end of file + "FOOBAR": "BARFOO" diff --git a/pkg/validation/variables/test-fixtures/projects/git.yaml b/pkg/validation/variables/test-fixtures/projects/git.yaml new file mode 100644 index 000000000..987cf3434 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/projects/git.yaml @@ -0,0 +1,8 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +git: + checkoutFrom: + revision: "{{foo}}" + remote: "{{bar}}" + remotes: + "foo": "{{bar}}" + "{{foo}}{{bar}}": "{{bar}}{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/project-output.yaml b/pkg/validation/variables/test-fixtures/projects/project-output.yaml similarity index 86% rename from pkg/validation/attributes/test-fixtures/projects/project-output.yaml rename to pkg/validation/variables/test-fixtures/projects/project-output.yaml index 74569640c..47920c2fc 100644 --- a/pkg/validation/attributes/test-fixtures/projects/project-output.yaml +++ b/pkg/validation/variables/test-fixtures/projects/project-output.yaml @@ -8,4 +8,4 @@ git: revision: "FOO" remotes: "foo": "BAR" - "FOOBAR": "BARFOO" \ No newline at end of file + "FOOBAR": "BARFOO" diff --git a/pkg/validation/attributes/test-fixtures/projects/project.yaml b/pkg/validation/variables/test-fixtures/projects/project.yaml similarity index 57% rename from pkg/validation/attributes/test-fixtures/projects/project.yaml rename to pkg/validation/variables/test-fixtures/projects/project.yaml index 2c1dc6bb7..399a803ef 100644 --- a/pkg/validation/attributes/test-fixtures/projects/project.yaml +++ b/pkg/validation/variables/test-fixtures/projects/project.yaml @@ -1,4 +1,4 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml +# Variables are defined in test-fixtures/variables/variables-referenced.yaml name: project1 clonePath: "/{{foo}}" sparseCheckoutDirs: @@ -9,4 +9,4 @@ git: revision: "{{foo}}" remotes: "foo": "{{bar}}" - "{{foo}}{{bar}}": "{{bar}}{{foo}}" \ No newline at end of file + "{{foo}}{{bar}}": "{{bar}}{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml b/pkg/validation/variables/test-fixtures/projects/starterproject-output.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/projects/starterproject-output.yaml rename to pkg/validation/variables/test-fixtures/projects/starterproject-output.yaml diff --git a/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml b/pkg/validation/variables/test-fixtures/projects/starterproject.yaml similarity index 60% rename from pkg/validation/attributes/test-fixtures/projects/starterproject.yaml rename to pkg/validation/variables/test-fixtures/projects/starterproject.yaml index 988805c81..0db6f1c2e 100644 --- a/pkg/validation/attributes/test-fixtures/projects/starterproject.yaml +++ b/pkg/validation/variables/test-fixtures/projects/starterproject.yaml @@ -1,4 +1,4 @@ -# Attributes are defined in test-fixtures/attributes/attributes-referenced.yaml +# Variables are defined in test-fixtures/variables/variables-referenced.yaml name: starterproject1 zip: location: "/{{foo}}" diff --git a/pkg/validation/variables/test-fixtures/projects/zip-output.yaml b/pkg/validation/variables/test-fixtures/projects/zip-output.yaml new file mode 100644 index 000000000..e7e11076b --- /dev/null +++ b/pkg/validation/variables/test-fixtures/projects/zip-output.yaml @@ -0,0 +1,2 @@ +zip: + location: "/FOO" diff --git a/pkg/validation/variables/test-fixtures/projects/zip.yaml b/pkg/validation/variables/test-fixtures/projects/zip.yaml new file mode 100644 index 000000000..5e4efd4c0 --- /dev/null +++ b/pkg/validation/variables/test-fixtures/projects/zip.yaml @@ -0,0 +1,3 @@ +# Variables are defined in test-fixtures/variables/variables-referenced.yaml +zip: + location: "/{{foo}}" diff --git a/pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml b/pkg/validation/variables/test-fixtures/variables/variables-notreferenced.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/attributes/attributes-notreferenced.yaml rename to pkg/validation/variables/test-fixtures/variables/variables-notreferenced.yaml diff --git a/pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml b/pkg/validation/variables/test-fixtures/variables/variables-referenced.yaml similarity index 100% rename from pkg/validation/attributes/test-fixtures/attributes/attributes-referenced.yaml rename to pkg/validation/variables/test-fixtures/variables/variables-referenced.yaml diff --git a/pkg/validation/variables/variables.go b/pkg/validation/variables/variables.go new file mode 100644 index 000000000..c01f70c91 --- /dev/null +++ b/pkg/validation/variables/variables.go @@ -0,0 +1,56 @@ +package variables + +import ( + "fmt" + "regexp" + "strings" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// ValidateAndReplaceGlobalVariable validates the workspace template spec data for global variable references and replaces them with the variable value +func ValidateAndReplaceGlobalVariable(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { + + var err error + + if workspaceTemplateSpec != nil { + // Validate the components and replace for global variable + if err = ValidateAndReplaceForComponents(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Components); err != nil { + return err + } + + // Validate the commands and replace for global variable + if err = ValidateAndReplaceForCommands(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Commands); err != nil { + return err + } + + // Validate the projects and replace for global variable + if err = ValidateAndReplaceForProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Projects); err != nil { + return err + } + + // Validate the starter projects and replace for global variable + if err = ValidateAndReplaceForStarterProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.StarterProjects); err != nil { + return err + } + } + + return nil +} + +var globalVariableRegex = regexp.MustCompile(`\{\{(.*?)\}\}`) + +// validateAndReplaceDataWithVariable validates the string for a global variable and replaces it. An error +// is returned if the string references an invalid global variable key +func validateAndReplaceDataWithVariable(val string, variables map[string]string) (string, error) { + matches := globalVariableRegex.FindAllStringSubmatch(val, -1) + for _, match := range matches { + varValue, ok := variables[match[1]] + if !ok { + return "", fmt.Errorf("Variable with key %q does not exist", match[1]) + } + val = strings.Replace(val, match[0], varValue, -1) + } + + return val, nil +} diff --git a/pkg/validation/variables/variables_command.go b/pkg/validation/variables/variables_command.go new file mode 100644 index 000000000..7b4ce350a --- /dev/null +++ b/pkg/validation/variables/variables_command.go @@ -0,0 +1,90 @@ +package variables + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// ValidateAndReplaceForCommands validates the commands data for global variable references and replaces them with the variable value +func ValidateAndReplaceForCommands(variables map[string]string, commands []v1alpha2.Command) error { + + for i := range commands { + var err error + + // Validate various command types + switch { + case commands[i].Exec != nil: + if err = validateAndReplaceForExecCommand(variables, commands[i].Exec); err != nil { + return err + } + case commands[i].Composite != nil: + if err = validateAndReplaceForCompositeCommand(variables, commands[i].Composite); err != nil { + return err + } + case commands[i].Apply != nil: + if err = validateAndReplaceForApplyCommand(variables, commands[i].Apply); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForExecCommand validates the exec command data for global variable references and replaces them with the variable value +func validateAndReplaceForExecCommand(variables map[string]string, exec *v1alpha2.ExecCommand) error { + var err error + + if exec != nil { + // Validate exec command line + if exec.CommandLine, err = validateAndReplaceDataWithVariable(exec.CommandLine, variables); err != nil { + return err + } + + // Validate exec working dir + if exec.WorkingDir, err = validateAndReplaceDataWithVariable(exec.WorkingDir, variables); err != nil { + return err + } + + // Validate exec label + if exec.Label, err = validateAndReplaceDataWithVariable(exec.Label, variables); err != nil { + return err + } + + // Validate exec env + if len(exec.Env) > 0 { + if err = validateAndReplaceForEnv(variables, exec.Env); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForCompositeCommand validates the composite command data for global variable references and replaces them with the variable value +func validateAndReplaceForCompositeCommand(variables map[string]string, composite *v1alpha2.CompositeCommand) error { + var err error + + if composite != nil { + // Validate composite label + if composite.Label, err = validateAndReplaceDataWithVariable(composite.Label, variables); err != nil { + return err + } + } + + return nil +} + +// validateAndReplaceForApplyCommand validates the apply command data for global variable references and replaces them with the variable value +func validateAndReplaceForApplyCommand(variables map[string]string, apply *v1alpha2.ApplyCommand) error { + var err error + + if apply != nil { + // Validate apply label + if apply.Label, err = validateAndReplaceDataWithVariable(apply.Label, variables); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/validation/variables/variables_command_test.go b/pkg/validation/variables/variables_command_test.go new file mode 100644 index 000000000..fd7662f1a --- /dev/null +++ b/pkg/validation/variables/variables_command_test.go @@ -0,0 +1,143 @@ +package variables + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/stretchr/testify/assert" +) + +func TestValidateAndReplaceExecCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/exec.yaml", + outputFile: "test-fixtures/commands/exec-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/exec.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testExecCommand := v1alpha2.ExecCommand{} + readFileToStruct(t, tt.testFile, &testExecCommand) + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := validateAndReplaceForExecCommand(testVariable, &testExecCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedExecCommand := v1alpha2.ExecCommand{} + readFileToStruct(t, tt.outputFile, &expectedExecCommand) + assert.Equal(t, expectedExecCommand, testExecCommand, "The two values should be the same.") + } + }) + } +} + +func TestValidateAndReplaceCompositeCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/composite.yaml", + outputFile: "test-fixtures/commands/composite-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/composite.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCompositeCommand := v1alpha2.CompositeCommand{} + readFileToStruct(t, tt.testFile, &testCompositeCommand) + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := validateAndReplaceForCompositeCommand(testVariable, &testCompositeCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedCompositeCommand := v1alpha2.CompositeCommand{} + readFileToStruct(t, tt.outputFile, &expectedCompositeCommand) + assert.Equal(t, expectedCompositeCommand, testCompositeCommand, "The two values should be the same.") + } + }) + } +} + +func TestValidateAndReplaceApplyCommand(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/commands/apply.yaml", + outputFile: "test-fixtures/commands/apply-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/commands/apply.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testApplyCommand := v1alpha2.ApplyCommand{} + readFileToStruct(t, tt.testFile, &testApplyCommand) + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := validateAndReplaceForApplyCommand(testVariable, &testApplyCommand) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedApplyCommand := v1alpha2.ApplyCommand{} + readFileToStruct(t, tt.outputFile, &expectedApplyCommand) + assert.Equal(t, expectedApplyCommand, testApplyCommand, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/variables/variables_component.go b/pkg/validation/variables/variables_component.go new file mode 100644 index 000000000..0a7e24ca1 --- /dev/null +++ b/pkg/validation/variables/variables_component.go @@ -0,0 +1,185 @@ +package variables + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// ValidateAndReplaceForComponents validates the components data for global variable references and replaces them with the variable value +func ValidateAndReplaceForComponents(variables map[string]string, components []v1alpha2.Component) error { + + for i := range components { + var err error + + // Validate various component types + switch { + case components[i].Container != nil: + if err = validateAndReplaceForContainerComponent(variables, components[i].Container); err != nil { + return err + } + case components[i].Kubernetes != nil: + if err = validateAndReplaceForKubernetesComponent(variables, components[i].Kubernetes); err != nil { + return err + } + case components[i].Openshift != nil: + if err = validateAndReplaceForOpenShiftComponent(variables, components[i].Openshift); err != nil { + return err + } + case components[i].Volume != nil: + if err = validateAndReplaceForVolumeComponent(variables, components[i].Volume); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForContainerComponent validates the container component data for global variable references and replaces them with the variable value +func validateAndReplaceForContainerComponent(variables map[string]string, container *v1alpha2.ContainerComponent) error { + var err error + + if container != nil { + // Validate container image + if container.Image, err = validateAndReplaceDataWithVariable(container.Image, variables); err != nil { + return err + } + + // Validate container commands + for i := range container.Command { + if container.Command[i], err = validateAndReplaceDataWithVariable(container.Command[i], variables); err != nil { + return err + } + } + + // Validate container args + for i := range container.Args { + if container.Args[i], err = validateAndReplaceDataWithVariable(container.Args[i], variables); err != nil { + return err + } + } + + // Validate memory limit + if container.MemoryLimit, err = validateAndReplaceDataWithVariable(container.MemoryLimit, variables); err != nil { + return err + } + + // Validate memory request + if container.MemoryRequest, err = validateAndReplaceDataWithVariable(container.MemoryRequest, variables); err != nil { + return err + } + + // Validate source mapping + if container.SourceMapping, err = validateAndReplaceDataWithVariable(container.SourceMapping, variables); err != nil { + return err + } + + // Validate container env + if len(container.Env) > 0 { + if err = validateAndReplaceForEnv(variables, container.Env); err != nil { + return err + } + } + + // Validate container volume mounts + for i := range container.VolumeMounts { + if container.VolumeMounts[i].Path, err = validateAndReplaceDataWithVariable(container.VolumeMounts[i].Path, variables); err != nil { + return err + } + } + + // Validate container endpoints + if len(container.Endpoints) > 0 { + if err = validateAndReplaceForEndpoint(variables, container.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForEnv validates the env data for global variable references and replaces them with the variable value +func validateAndReplaceForEnv(variables map[string]string, env []v1alpha2.EnvVar) error { + + for i := range env { + var err error + + // Validate env name + if env[i].Name, err = validateAndReplaceDataWithVariable(env[i].Name, variables); err != nil { + return err + } + + // Validate env value + if env[i].Value, err = validateAndReplaceDataWithVariable(env[i].Value, variables); err != nil { + return err + } + } + + return nil +} + +// validateAndReplaceForKubernetesComponent validates the kubernetes component data for global variable references and replaces them with the variable value +func validateAndReplaceForKubernetesComponent(variables map[string]string, kubernetes *v1alpha2.KubernetesComponent) error { + var err error + + if kubernetes != nil { + // Validate kubernetes uri + if kubernetes.Uri, err = validateAndReplaceDataWithVariable(kubernetes.Uri, variables); err != nil { + return err + } + + // Validate kubernetes inlined + if kubernetes.Inlined, err = validateAndReplaceDataWithVariable(kubernetes.Inlined, variables); err != nil { + return err + } + + // Validate kubernetes endpoints + if len(kubernetes.Endpoints) > 0 { + if err = validateAndReplaceForEndpoint(variables, kubernetes.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForOpenShiftComponent validates the openshift component data for global variable references and replaces them with the variable value +func validateAndReplaceForOpenShiftComponent(variables map[string]string, openshift *v1alpha2.OpenshiftComponent) error { + var err error + + if openshift != nil { + // Validate openshift uri + if openshift.Uri, err = validateAndReplaceDataWithVariable(openshift.Uri, variables); err != nil { + return err + } + + // Validate openshift inlined + if openshift.Inlined, err = validateAndReplaceDataWithVariable(openshift.Inlined, variables); err != nil { + return err + } + + // Validate openshift endpoints + if len(openshift.Endpoints) > 0 { + if err = validateAndReplaceForEndpoint(variables, openshift.Endpoints); err != nil { + return err + } + } + } + + return nil +} + +// validateAndReplaceForVolumeComponent validates the volume component data for global variable references and replaces them with the variable value +func validateAndReplaceForVolumeComponent(variables map[string]string, volume *v1alpha2.VolumeComponent) error { + var err error + + if volume != nil { + // Validate volume size + if volume.Size, err = validateAndReplaceDataWithVariable(volume.Size, variables); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_component_test.go b/pkg/validation/variables/variables_component_test.go similarity index 53% rename from pkg/validation/attributes/attributes_component_test.go rename to pkg/validation/variables/variables_component_test.go index 043bc8efa..090416767 100644 --- a/pkg/validation/attributes/attributes_component_test.go +++ b/pkg/validation/variables/variables_component_test.go @@ -1,34 +1,33 @@ -package attributes +package variables import ( "testing" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" ) func TestValidateAndReplaceContainerComponent(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool + name string + testFile string + outputFile string + variableFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/container.yaml", - outputFile: "test-fixtures/components/container-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/container.yaml", + outputFile: "test-fixtures/components/container-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/container.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/container.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -36,10 +35,10 @@ func TestValidateAndReplaceContainerComponent(t *testing.T) { testContainerComponent := v1alpha2.ContainerComponent{} readFileToStruct(t, tt.testFile, &testContainerComponent) - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) - err := validateAndReplaceForContainerComponent(testAttribute, &testContainerComponent) + err := validateAndReplaceForContainerComponent(testVariable, &testContainerComponent) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { @@ -56,24 +55,24 @@ func TestValidateAndReplaceContainerComponent(t *testing.T) { func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool + name string + testFile string + outputFile string + variableFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/openshift-kubernetes.yaml", - outputFile: "test-fixtures/components/openshift-kubernetes-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + outputFile: "test-fixtures/components/openshift-kubernetes-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/openshift-kubernetes.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/openshift-kubernetes.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -84,10 +83,10 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { readFileToStruct(t, tt.testFile, &testOpenshiftComponent) readFileToStruct(t, tt.testFile, &testKubernetesComponent) - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) - err := validateAndReplaceForOpenShiftComponent(testAttribute, &testOpenshiftComponent) + err := validateAndReplaceForOpenShiftComponent(testVariable, &testOpenshiftComponent) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { @@ -98,7 +97,7 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { assert.Equal(t, expectedOpenshiftComponent, testOpenshiftComponent, "The two values should be the same.") } - err = validateAndReplaceForKubernetesComponent(testAttribute, &testKubernetesComponent) + err = validateAndReplaceForKubernetesComponent(testVariable, &testKubernetesComponent) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { @@ -115,24 +114,24 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { func TestValidateAndReplaceVolumeComponent(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool + name string + testFile string + outputFile string + variableFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/volume.yaml", - outputFile: "test-fixtures/components/volume-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/volume.yaml", + outputFile: "test-fixtures/components/volume-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/volume.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/volume.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -140,10 +139,10 @@ func TestValidateAndReplaceVolumeComponent(t *testing.T) { testVolumeComponent := v1alpha2.VolumeComponent{} readFileToStruct(t, tt.testFile, &testVolumeComponent) - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) - err := validateAndReplaceForVolumeComponent(testAttribute, &testVolumeComponent) + err := validateAndReplaceForVolumeComponent(testVariable, &testVolumeComponent) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { @@ -160,24 +159,24 @@ func TestValidateAndReplaceVolumeComponent(t *testing.T) { func TestValidateAndReplaceEnv(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool + name string + testFile string + outputFile string + variableFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/env.yaml", - outputFile: "test-fixtures/components/env-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/env.yaml", + outputFile: "test-fixtures/components/env-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/env.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/env.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -186,10 +185,10 @@ func TestValidateAndReplaceEnv(t *testing.T) { readFileToStruct(t, tt.testFile, &testEnv) testEnvArr := []v1alpha2.EnvVar{testEnv} - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) - err := validateAndReplaceForEnv(testAttribute, testEnvArr) + err := validateAndReplaceForEnv(testVariable, testEnvArr) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { diff --git a/pkg/validation/variables/variables_endpoint.go b/pkg/validation/variables/variables_endpoint.go new file mode 100644 index 000000000..032be8004 --- /dev/null +++ b/pkg/validation/variables/variables_endpoint.go @@ -0,0 +1,20 @@ +package variables + +import ( + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" +) + +// validateAndReplaceForEndpoint validates the endpoint data for global variable references and replaces them with the variable value +func validateAndReplaceForEndpoint(variables map[string]string, endpoints []v1alpha2.Endpoint) error { + + for i := range endpoints { + var err error + + // Validate endpoint path + if endpoints[i].Path, err = validateAndReplaceDataWithVariable(endpoints[i].Path, variables); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/validation/attributes/attributes_endpoint_test.go b/pkg/validation/variables/variables_endpoint_test.go similarity index 52% rename from pkg/validation/attributes/attributes_endpoint_test.go rename to pkg/validation/variables/variables_endpoint_test.go index eec096827..55073a400 100644 --- a/pkg/validation/attributes/attributes_endpoint_test.go +++ b/pkg/validation/variables/variables_endpoint_test.go @@ -1,34 +1,33 @@ -package attributes +package variables import ( "testing" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" ) func TestValidateAndReplaceEndpoint(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - attributeFile string - wantErr bool + name string + testFile string + outputFile string + variableFile string + wantErr bool }{ { - name: "Good Substitution", - testFile: "test-fixtures/components/endpoint.yaml", - outputFile: "test-fixtures/components/endpoint-output.yaml", - attributeFile: "test-fixtures/attributes/attributes-referenced.yaml", - wantErr: false, + name: "Good Substitution", + testFile: "test-fixtures/components/endpoint.yaml", + outputFile: "test-fixtures/components/endpoint-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, }, { - name: "Invalid Reference", - testFile: "test-fixtures/components/endpoint.yaml", - attributeFile: "test-fixtures/attributes/attributes-notreferenced.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/components/endpoint.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, }, } for _, tt := range tests { @@ -37,10 +36,10 @@ func TestValidateAndReplaceEndpoint(t *testing.T) { readFileToStruct(t, tt.testFile, &testEndpoint) testEndpointArr := []v1alpha2.Endpoint{testEndpoint} - testAttribute := apiAttributes.Attributes{} - readFileToStruct(t, tt.attributeFile, &testAttribute) + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) - err := validateAndReplaceForEndpoint(testAttribute, testEndpointArr) + err := validateAndReplaceForEndpoint(testVariable, testEndpointArr) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { diff --git a/pkg/validation/attributes/attributes_project.go b/pkg/validation/variables/variables_project.go similarity index 56% rename from pkg/validation/attributes/attributes_project.go rename to pkg/validation/variables/variables_project.go index 9c3526375..e9d72a502 100644 --- a/pkg/validation/attributes/attributes_project.go +++ b/pkg/validation/variables/variables_project.go @@ -1,30 +1,29 @@ -package attributes +package variables import ( "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" ) -// ValidateAndReplaceForProjects validates the projects data for global attribute references and replaces them with the attribute value -func ValidateAndReplaceForProjects(attributes apiAttributes.Attributes, projects []v1alpha2.Project) error { +// ValidateAndReplaceForProjects validates the projects data for global variable references and replaces them with the variable value +func ValidateAndReplaceForProjects(variables map[string]string, projects []v1alpha2.Project) error { for i := range projects { var err error // Validate project clonepath - if projects[i].ClonePath, err = validateAndReplaceDataWithAttribute(projects[i].ClonePath, attributes); err != nil { + if projects[i].ClonePath, err = validateAndReplaceDataWithVariable(projects[i].ClonePath, variables); err != nil { return err } // Validate project sparse checkout dir for j := range projects[i].SparseCheckoutDirs { - if projects[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithAttribute(projects[i].SparseCheckoutDirs[j], attributes); err != nil { + if projects[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithVariable(projects[i].SparseCheckoutDirs[j], variables); err != nil { return err } } // Validate project source - if err = validateandReplaceForProjectSource(attributes, &projects[i].ProjectSource); err != nil { + if err = validateandReplaceForProjectSource(variables, &projects[i].ProjectSource); err != nil { return err } } @@ -32,24 +31,24 @@ func ValidateAndReplaceForProjects(attributes apiAttributes.Attributes, projects return nil } -// ValidateAndReplaceForStarterProjects validates the starter projects data for global attribute references and replaces them with the attribute value -func ValidateAndReplaceForStarterProjects(attributes apiAttributes.Attributes, starterProjects []v1alpha2.StarterProject) error { +// ValidateAndReplaceForStarterProjects validates the starter projects data for global variable references and replaces them with the variable value +func ValidateAndReplaceForStarterProjects(variables map[string]string, starterProjects []v1alpha2.StarterProject) error { for i := range starterProjects { var err error // Validate starter project description - if starterProjects[i].Description, err = validateAndReplaceDataWithAttribute(starterProjects[i].Description, attributes); err != nil { + if starterProjects[i].Description, err = validateAndReplaceDataWithVariable(starterProjects[i].Description, variables); err != nil { return err } // Validate starter project sub dir - if starterProjects[i].SubDir, err = validateAndReplaceDataWithAttribute(starterProjects[i].SubDir, attributes); err != nil { + if starterProjects[i].SubDir, err = validateAndReplaceDataWithVariable(starterProjects[i].SubDir, variables); err != nil { return err } // Validate starter project source - if err = validateandReplaceForProjectSource(attributes, &starterProjects[i].ProjectSource); err != nil { + if err = validateandReplaceForProjectSource(variables, &starterProjects[i].ProjectSource); err != nil { return err } } @@ -57,15 +56,15 @@ func ValidateAndReplaceForStarterProjects(attributes apiAttributes.Attributes, s return nil } -// validateandReplaceForProjectSource validates a project source location for global attribute references and replaces them with the attribute value -func validateandReplaceForProjectSource(attributes apiAttributes.Attributes, projectSource *v1alpha2.ProjectSource) error { +// validateandReplaceForProjectSource validates a project source location for global variable references and replaces them with the variable value +func validateandReplaceForProjectSource(variables map[string]string, projectSource *v1alpha2.ProjectSource) error { var err error if projectSource != nil { switch { case projectSource.Zip != nil: - if projectSource.Zip.Location, err = validateAndReplaceDataWithAttribute(projectSource.Zip.Location, attributes); err != nil { + if projectSource.Zip.Location, err = validateAndReplaceDataWithVariable(projectSource.Zip.Location, variables); err != nil { return err } case projectSource.Git != nil || projectSource.Github != nil: @@ -78,12 +77,12 @@ func validateandReplaceForProjectSource(attributes apiAttributes.Attributes, pro if gitProject.CheckoutFrom != nil { // validate git checkout revision - if gitProject.CheckoutFrom.Revision, err = validateAndReplaceDataWithAttribute(gitProject.CheckoutFrom.Revision, attributes); err != nil { + if gitProject.CheckoutFrom.Revision, err = validateAndReplaceDataWithVariable(gitProject.CheckoutFrom.Revision, variables); err != nil { return err } // // validate git checkout remote - if gitProject.CheckoutFrom.Remote, err = validateAndReplaceDataWithAttribute(gitProject.CheckoutFrom.Remote, attributes); err != nil { + if gitProject.CheckoutFrom.Remote, err = validateAndReplaceDataWithVariable(gitProject.CheckoutFrom.Remote, variables); err != nil { return err } } @@ -91,13 +90,13 @@ func validateandReplaceForProjectSource(attributes apiAttributes.Attributes, pro // validate git remotes for k := range gitProject.Remotes { // update map value - if gitProject.Remotes[k], err = validateAndReplaceDataWithAttribute(gitProject.Remotes[k], attributes); err != nil { + if gitProject.Remotes[k], err = validateAndReplaceDataWithVariable(gitProject.Remotes[k], variables); err != nil { return err } // update map key var updatedKey string - if updatedKey, err = validateAndReplaceDataWithAttribute(k, attributes); err != nil { + if updatedKey, err = validateAndReplaceDataWithVariable(k, variables); err != nil { return err } else if updatedKey != k { gitProject.Remotes[updatedKey] = gitProject.Remotes[k] diff --git a/pkg/validation/variables/variables_project_test.go b/pkg/validation/variables/variables_project_test.go new file mode 100644 index 000000000..44933f775 --- /dev/null +++ b/pkg/validation/variables/variables_project_test.go @@ -0,0 +1,160 @@ +package variables + +import ( + "testing" + + "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/stretchr/testify/assert" +) + +func TestValidateAndReplaceProjects(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/projects/project.yaml", + outputFile: "test-fixtures/projects/project-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/projects/project.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testProject := v1alpha2.Project{} + readFileToStruct(t, tt.testFile, &testProject) + testProjectArr := []v1alpha2.Project{testProject} + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := ValidateAndReplaceForProjects(testVariable, testProjectArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedProject := v1alpha2.Project{} + readFileToStruct(t, tt.outputFile, &expectedProject) + expectedProjectArr := []v1alpha2.Project{expectedProject} + assert.Equal(t, expectedProjectArr, testProjectArr, "The two values should be the same.") + } + }) + } +} + +func TestValidateAndReplaceStarterProjects(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Substitution", + testFile: "test-fixtures/projects/starterproject.yaml", + outputFile: "test-fixtures/projects/starterproject-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Reference", + testFile: "test-fixtures/projects/starterproject.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testStarterProject := v1alpha2.StarterProject{} + readFileToStruct(t, tt.testFile, &testStarterProject) + testStarterProjectArr := []v1alpha2.StarterProject{testStarterProject} + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := ValidateAndReplaceForStarterProjects(testVariable, testStarterProjectArr) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedStarterProject := v1alpha2.StarterProject{} + readFileToStruct(t, tt.outputFile, &expectedStarterProject) + expectedStarterProjectArr := []v1alpha2.StarterProject{expectedStarterProject} + assert.Equal(t, expectedStarterProjectArr, testStarterProjectArr, "The two values should be the same.") + } + }) + } +} + +func TestValidateAndReplaceProjectSrc(t *testing.T) { + + tests := []struct { + name string + testFile string + outputFile string + variableFile string + wantErr bool + }{ + { + name: "Good Git Substitution", + testFile: "test-fixtures/projects/git.yaml", + outputFile: "test-fixtures/projects/git-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Good Zip Substitution", + testFile: "test-fixtures/projects/zip.yaml", + outputFile: "test-fixtures/projects/zip-output.yaml", + variableFile: "test-fixtures/variables/variables-referenced.yaml", + wantErr: false, + }, + { + name: "Invalid Git Reference", + testFile: "test-fixtures/projects/git.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + { + name: "Invalid Zip Reference", + testFile: "test-fixtures/projects/zip.yaml", + variableFile: "test-fixtures/variables/variables-notreferenced.yaml", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testProjectSrc := v1alpha2.ProjectSource{} + readFileToStruct(t, tt.testFile, &testProjectSrc) + + testVariable := make(map[string]string) + readFileToStruct(t, tt.variableFile, &testVariable) + + err := validateandReplaceForProjectSource(testVariable, &testProjectSrc) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } else if err == nil { + expectedProjectSrc := v1alpha2.ProjectSource{} + readFileToStruct(t, tt.outputFile, &expectedProjectSrc) + assert.Equal(t, expectedProjectSrc, testProjectSrc, "The two values should be the same.") + } + }) + } +} diff --git a/pkg/validation/attributes/attributes_test.go b/pkg/validation/variables/variables_test.go similarity index 62% rename from pkg/validation/attributes/attributes_test.go rename to pkg/validation/variables/variables_test.go index 9f85a8309..9db29c1c6 100644 --- a/pkg/validation/attributes/attributes_test.go +++ b/pkg/validation/variables/variables_test.go @@ -1,16 +1,15 @@ -package attributes +package variables import ( "io/ioutil" "testing" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - apiAttributes "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" "sigs.k8s.io/yaml" ) -func TestValidateGlobalAttributeBasic(t *testing.T) { +func TestValidateGlobalVariableBasic(t *testing.T) { tests := []struct { name string @@ -19,7 +18,7 @@ func TestValidateGlobalAttributeBasic(t *testing.T) { wantErr bool }{ { - name: "Successful global attribute substitution", + name: "Successful global variable substitution", testFile: "test-fixtures/all/devfile-good.yaml", outputFile: "test-fixtures/all/devfile-good-output.yaml", wantErr: false, @@ -35,7 +34,7 @@ func TestValidateGlobalAttributeBasic(t *testing.T) { testDWT := v1alpha2.DevWorkspaceTemplateSpec{} readFileToStruct(t, tt.testFile, &testDWT) - err := ValidateAndReplaceGlobalAttribute(&testDWT) + err := ValidateAndReplaceGlobalVariable(&testDWT) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { @@ -49,55 +48,40 @@ func TestValidateGlobalAttributeBasic(t *testing.T) { } } -func TestValidateAndReplaceDataWithAttribute(t *testing.T) { +func TestValidateAndReplaceDataWithVariable(t *testing.T) { - invalidAttributeErr := ".*Attribute with key .* does not exist.*" - wrongAttributeTypeErr := ".*cannot unmarshal object into Go value of type string.*" + invalidVariableErr := ".*Variable with key .* does not exist.*" tests := []struct { name string testString string - attributes apiAttributes.Attributes + variables map[string]string wantValue string wantErr *string }{ { - name: "Valid attribute reference", + name: "Valid variable reference", testString: "image-{{version}}:{{tag}}-14", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + variables: map[string]string{ "version": "1.x.x", "tag": "dev", - "import": map[string]interface{}{ - "strategy": "Dockerfile", - }, - }, nil), + }, wantValue: "image-1.x.x:dev-14", wantErr: nil, }, { - name: "Invalid attribute reference", + name: "Invalid variable reference", testString: "image-{{version}}:{{invalid}}-14", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ + variables: map[string]string{ "version": "1.x.x", "tag": "dev", - }, nil), - wantErr: &invalidAttributeErr, - }, - { - name: "Attribute reference with non-string type value", - testString: "image-{{version}}:{{invalid}}-14", - attributes: apiAttributes.Attributes{}.FromMap(map[string]interface{}{ - "version": "1.x.x", - "invalid": map[string]interface{}{ - "key": "value", - }, - }, nil), - wantErr: &wrongAttributeTypeErr, + }, + wantErr: &invalidVariableErr, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotValue, err := validateAndReplaceDataWithAttribute(tt.testString, tt.attributes) + gotValue, err := validateAndReplaceDataWithVariable(tt.testString, tt.variables) if tt.wantErr != nil && assert.Error(t, err) { assert.Regexp(t, *tt.wantErr, err.Error(), "Error message should match") } else { diff --git a/schemas/latest/dev-workspace-template-spec.json b/schemas/latest/dev-workspace-template-spec.json index 158abc9e2..bc98c48ad 100644 --- a/schemas/latest/dev-workspace-template-spec.json +++ b/schemas/latest/dev-workspace-template-spec.json @@ -4,7 +4,7 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true }, @@ -2772,6 +2772,13 @@ "uri": { "description": "Uri of a Devfile yaml file", "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false @@ -3072,6 +3079,13 @@ }, "additionalProperties": false } + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false diff --git a/schemas/latest/dev-workspace-template.json b/schemas/latest/dev-workspace-template.json index 884a8cb8f..a57cd2f60 100644 --- a/schemas/latest/dev-workspace-template.json +++ b/schemas/latest/dev-workspace-template.json @@ -169,7 +169,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true }, @@ -2937,6 +2937,13 @@ "uri": { "description": "Uri of a Devfile yaml file", "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false @@ -3237,6 +3244,13 @@ }, "additionalProperties": false } + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false diff --git a/schemas/latest/dev-workspace.json b/schemas/latest/dev-workspace.json index ad1f73f70..5de1cfb1e 100644 --- a/schemas/latest/dev-workspace.json +++ b/schemas/latest/dev-workspace.json @@ -182,7 +182,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true }, @@ -2950,6 +2950,13 @@ "uri": { "description": "Uri of a Devfile yaml file", "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false @@ -3250,6 +3257,13 @@ }, "additionalProperties": false } + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false diff --git a/schemas/latest/devfile.json b/schemas/latest/devfile.json index d27a8099d..7c702f671 100644 --- a/schemas/latest/devfile.json +++ b/schemas/latest/devfile.json @@ -7,7 +7,7 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true }, @@ -637,7 +637,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes.", + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead.", "type": "object", "additionalProperties": true }, @@ -1534,6 +1534,13 @@ "uri": { "description": "Uri of a Devfile yaml file", "type": "string" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false @@ -1793,6 +1800,13 @@ }, "additionalProperties": false } + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false diff --git a/schemas/latest/ide-targeted/dev-workspace-template-spec.json b/schemas/latest/ide-targeted/dev-workspace-template-spec.json index 619a6f1f8..cccb47bcb 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template-spec.json +++ b/schemas/latest/ide-targeted/dev-workspace-template-spec.json @@ -4,10 +4,10 @@ "title": "DevWorkspaceTemplateSpec schema - Version 2.1.0-alpha - IDE-targeted variant", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -3083,6 +3083,14 @@ "description": "Uri of a Devfile yaml file", "type": "string", "markdownDescription": "Uri of a Devfile yaml file" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." } }, "additionalProperties": false, @@ -3420,6 +3428,14 @@ "additionalProperties": false }, "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/dev-workspace-template.json b/schemas/latest/ide-targeted/dev-workspace-template.json index d242c562b..d8d065dc4 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template.json +++ b/schemas/latest/ide-targeted/dev-workspace-template.json @@ -202,10 +202,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -3281,6 +3281,14 @@ "description": "Uri of a Devfile yaml file", "type": "string", "markdownDescription": "Uri of a Devfile yaml file" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." } }, "additionalProperties": false, @@ -3618,6 +3626,14 @@ "additionalProperties": false }, "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/dev-workspace.json b/schemas/latest/ide-targeted/dev-workspace.json index d38940985..f2454187f 100644 --- a/schemas/latest/ide-targeted/dev-workspace.json +++ b/schemas/latest/ide-targeted/dev-workspace.json @@ -215,10 +215,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -3294,6 +3294,14 @@ "description": "Uri of a Devfile yaml file", "type": "string", "markdownDescription": "Uri of a Devfile yaml file" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." } }, "additionalProperties": false, @@ -3631,6 +3639,14 @@ "additionalProperties": false }, "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/devfile.json b/schemas/latest/ide-targeted/devfile.json index 145a19016..9f34183f7 100644 --- a/schemas/latest/ide-targeted/devfile.json +++ b/schemas/latest/ide-targeted/devfile.json @@ -7,10 +7,10 @@ ], "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of implementation-dependant free-form YAML attributes.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Attribute values can be referenced throughout the devfile in string type fields in the form {{attribute-key}} except for schemaVersion, metadata and events. Exception to the string field include element's key identifiers(command id, component name, endpoint name, project name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of implementation-dependant free-form YAML attributes." }, "commands": { "description": "Predefined, ready-to-use, devworkspace-related commands", @@ -700,10 +700,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes.", + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes." + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead." }, "description": { "description": "Optional devfile description", @@ -1711,6 +1711,14 @@ "description": "Uri of a Devfile yaml file", "type": "string", "markdownDescription": "Uri of a Devfile yaml file" + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." } }, "additionalProperties": false, @@ -2006,6 +2014,14 @@ "additionalProperties": false }, "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" + }, + "variables": { + "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/parent-overrides.json b/schemas/latest/ide-targeted/parent-overrides.json index 04617e33e..0d9ab20d8 100644 --- a/schemas/latest/ide-targeted/parent-overrides.json +++ b/schemas/latest/ide-targeted/parent-overrides.json @@ -1576,6 +1576,14 @@ "additionalProperties": false }, "markdownDescription": "Overrides of starterProjects encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "markdownDescription": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules." } }, "additionalProperties": false, diff --git a/schemas/latest/parent-overrides.json b/schemas/latest/parent-overrides.json index 2ac4323dc..45e078129 100644 --- a/schemas/latest/parent-overrides.json +++ b/schemas/latest/parent-overrides.json @@ -1408,6 +1408,13 @@ }, "additionalProperties": false } + }, + "variables": { + "description": "Overrides of variables encapsulated in a parent devfile. Overriding is done according to K8S strategic merge patch standard rules.", + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "additionalProperties": false From e391e80e8d6cc0db18662e6c28ca66aa6521e1fc Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Wed, 7 Apr 2021 16:55:44 -0400 Subject: [PATCH 06/10] Devfile Cabal Feedback 2 - Var Warning Signed-off-by: Maysun J Faisal --- ...pace.devfile.io_devworkspaces.v1beta1.yaml | 16 +-- crds/workspace.devfile.io_devworkspaces.yaml | 16 +-- ...file.io_devworkspacetemplates.v1beta1.yaml | 16 +-- ...pace.devfile.io_devworkspacetemplates.yaml | 16 +-- .../v1alpha2/devworkspacetemplate_spec.go | 12 +- pkg/devfile/header.go | 2 +- pkg/utils/overriding/keys.go | 4 +- pkg/utils/overriding/merging.go | 4 +- pkg/validation/variables/errors.go | 15 +++ .../test-fixtures/all/devfile-bad-output.yaml | 80 ++++++++++++ .../test-fixtures/all/devfile-bad.yaml | 73 ++++++++++- .../test-fixtures/projects/zip-output.yaml | 2 +- .../variables/test-fixtures/projects/zip.yaml | 2 +- pkg/validation/variables/utils.go | 24 ++++ pkg/validation/variables/utils_test.go | 100 +++++++++++++++ pkg/validation/variables/variables.go | 52 +++++--- pkg/validation/variables/variables_command.go | 45 ++++--- .../variables/variables_command_test.go | 24 ++-- .../variables/variables_component.go | 79 +++++++----- .../variables/variables_component_test.go | 32 +++-- .../variables/variables_endpoint.go | 6 +- .../variables/variables_endpoint_test.go | 8 +- pkg/validation/variables/variables_project.go | 62 ++++++--- .../variables/variables_project_test.go | 46 +++---- pkg/validation/variables/variables_test.go | 120 ++++++++++++++---- .../latest/dev-workspace-template-spec.json | 2 +- schemas/latest/dev-workspace-template.json | 2 +- schemas/latest/dev-workspace.json | 2 +- schemas/latest/devfile.json | 4 +- .../dev-workspace-template-spec.json | 4 +- .../ide-targeted/dev-workspace-template.json | 4 +- .../latest/ide-targeted/dev-workspace.json | 4 +- schemas/latest/ide-targeted/devfile.json | 8 +- 33 files changed, 658 insertions(+), 228 deletions(-) create mode 100644 pkg/validation/variables/errors.go create mode 100644 pkg/validation/variables/test-fixtures/all/devfile-bad-output.yaml create mode 100644 pkg/validation/variables/utils.go create mode 100644 pkg/validation/variables/utils_test.go diff --git a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml index ca4dc78bd..5eafae52c 100644 --- a/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspaces.v1beta1.yaml @@ -7422,14 +7422,14 @@ spec: variables: additionalProperties: type: string - description: Map of string variables. Variable values can be referenced - throughout the devfile in string type fields in the form {{variable-key}} - except for schemaVersion, metadata, parent source. Exception - to the string field also include element's key identifiers (command - id, component name, endpoint name, project name, etc.) and their - references(events, command's component, container's volume mount - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) + description: "Map of key-value variables used for string replacement + in the devfile. Values can can be referenced via {{variable-key}} + to replace the corresponding value in string fields in the devfile. + Replacement cannot be used for \n - schemaVersion, metadata, + parent source - element identifiers, e.g. command id, component + name, endpoint name, project name - references to identifiers, + e.g. in events, a command's component, container's volume mount + name - string enums, e.g. command group kind, endpoint exposure" type: object type: object required: diff --git a/crds/workspace.devfile.io_devworkspaces.yaml b/crds/workspace.devfile.io_devworkspaces.yaml index 6e1cfb6de..439456298 100644 --- a/crds/workspace.devfile.io_devworkspaces.yaml +++ b/crds/workspace.devfile.io_devworkspaces.yaml @@ -7427,14 +7427,14 @@ spec: variables: additionalProperties: type: string - description: Map of string variables. Variable values can be referenced - throughout the devfile in string type fields in the form {{variable-key}} - except for schemaVersion, metadata, parent source. Exception - to the string field also include element's key identifiers (command - id, component name, endpoint name, project name, etc.) and their - references(events, command's component, container's volume mount - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) + description: "Map of key-value variables used for string replacement + in the devfile. Values can can be referenced via {{variable-key}} + to replace the corresponding value in string fields in the devfile. + Replacement cannot be used for \n - schemaVersion, metadata, + parent source - element identifiers, e.g. command id, component + name, endpoint name, project name - references to identifiers, + e.g. in events, a command's component, container's volume mount + name - string enums, e.g. command group kind, endpoint exposure" type: object type: object required: diff --git a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml index 778cfbcca..fda3dc322 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.v1beta1.yaml @@ -7073,14 +7073,14 @@ spec: variables: additionalProperties: type: string - description: Map of string variables. Variable values can be referenced - throughout the devfile in string type fields in the form {{variable-key}} - except for schemaVersion, metadata, parent source. Exception to - the string field also include element's key identifiers (command - id, component name, endpoint name, project name, etc.) and their - references(events, command's component, container's volume mount - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) + description: "Map of key-value variables used for string replacement + in the devfile. Values can can be referenced via {{variable-key}} + to replace the corresponding value in string fields in the devfile. + Replacement cannot be used for \n - schemaVersion, metadata, parent + source - element identifiers, e.g. command id, component name, + endpoint name, project name - references to identifiers, e.g. in + events, a command's component, container's volume mount name - + string enums, e.g. command group kind, endpoint exposure" type: object type: object type: object diff --git a/crds/workspace.devfile.io_devworkspacetemplates.yaml b/crds/workspace.devfile.io_devworkspacetemplates.yaml index d758d7cd5..1645b72cc 100644 --- a/crds/workspace.devfile.io_devworkspacetemplates.yaml +++ b/crds/workspace.devfile.io_devworkspacetemplates.yaml @@ -7078,14 +7078,14 @@ spec: variables: additionalProperties: type: string - description: Map of string variables. Variable values can be referenced - throughout the devfile in string type fields in the form {{variable-key}} - except for schemaVersion, metadata, parent source. Exception to - the string field also include element's key identifiers (command - id, component name, endpoint name, project name, etc.) and their - references(events, command's component, container's volume mount - name, etc.) and string enums(command group kind, endpoint exposure, - etc.) + description: "Map of key-value variables used for string replacement + in the devfile. Values can can be referenced via {{variable-key}} + to replace the corresponding value in string fields in the devfile. + Replacement cannot be used for \n - schemaVersion, metadata, parent + source - element identifiers, e.g. command id, component name, + endpoint name, project name - references to identifiers, e.g. in + events, a command's component, container's volume mount name - + string enums, e.g. command group kind, endpoint exposure" type: object type: object type: object diff --git a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go index d60dc262a..f6afd03b2 100644 --- a/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go +++ b/pkg/apis/workspaces/v1alpha2/devworkspacetemplate_spec.go @@ -14,11 +14,13 @@ type DevWorkspaceTemplateSpec struct { // +devfile:overrides:generate type DevWorkspaceTemplateSpecContent struct { - // Map of string variables. - // Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} - // except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers - // (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's - // volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.) + // Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} + // to replace the corresponding value in string fields in the devfile. Replacement cannot be used for + // + // - schemaVersion, metadata, parent source + // - element identifiers, e.g. command id, component name, endpoint name, project name + // - references to identifiers, e.g. in events, a command's component, container's volume mount name + // - string enums, e.g. command group kind, endpoint exposure // +optional // +patchStrategy=merge // +devfile:overrides:include:omitInPlugin=true,description=Overrides of variables encapsulated in a parent devfile. diff --git a/pkg/devfile/header.go b/pkg/devfile/header.go index 9a63dfe47..5b6aa75e6 100644 --- a/pkg/devfile/header.go +++ b/pkg/devfile/header.go @@ -27,7 +27,7 @@ type DevfileMetadata struct { // +kubebuilder:validation:Pattern=^([0-9]+)\.([0-9]+)\.([0-9]+)(\-[0-9a-z-]+(\.[0-9a-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ Version string `json:"version,omitempty"` - // Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead. + // Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead. // +optional Attributes attributes.Attributes `json:"attributes,omitempty"` diff --git a/pkg/utils/overriding/keys.go b/pkg/utils/overriding/keys.go index cf6061fef..b525e8a7b 100644 --- a/pkg/utils/overriding/keys.go +++ b/pkg/utils/overriding/keys.go @@ -54,7 +54,7 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain k := mapIter.Key() v := mapIter.Value() if k.Kind() != reflect.String || v.Kind() != reflect.String { - return fmt.Errorf("unable to fetch Global Variables, Global Variables should be map of strings") + return fmt.Errorf("unable to fetch top-level Variables, top-level Variables should be map of strings") } variableKeys = append(variableKeys, k.String()) } @@ -64,7 +64,7 @@ func checkKeys(doCheck checkFn, toplevelListContainers ...dw.TopLevelListContain if attributeValue.IsValid() && attributeValue.CanInterface() { attributes, ok := attributeValue.Interface().(attributesPkg.Attributes) if !ok { - return fmt.Errorf("unable to fetch Global Attributes from the devfile data") + return fmt.Errorf("unable to fetch top-level Attributes from the devfile data") } var attributeKeys []string for k := range attributes { diff --git a/pkg/utils/overriding/merging.go b/pkg/utils/overriding/merging.go index 981f9ff1a..279e54de9 100644 --- a/pkg/utils/overriding/merging.go +++ b/pkg/utils/overriding/merging.go @@ -122,9 +122,7 @@ func MergeDevWorkspaceTemplateSpec( result.Attributes = attributes.Attributes{} } for k, v := range content.Attributes { - result.Attributes.FromMap(map[string]interface{}{ - k: v, - }, &err) + result.Attributes.Put(k, v, &err) if err != nil { return nil, err } diff --git a/pkg/validation/variables/errors.go b/pkg/validation/variables/errors.go new file mode 100644 index 000000000..80f8c4618 --- /dev/null +++ b/pkg/validation/variables/errors.go @@ -0,0 +1,15 @@ +package variables + +import ( + "fmt" + "strings" +) + +// InvalidKeysError returns an error for the invalid keys +type InvalidKeysError struct { + Keys []string +} + +func (e *InvalidKeysError) Error() string { + return fmt.Sprintf("invalid variable references - %s", strings.Join(e.Keys, ",")) +} diff --git a/pkg/validation/variables/test-fixtures/all/devfile-bad-output.yaml b/pkg/validation/variables/test-fixtures/all/devfile-bad-output.yaml new file mode 100644 index 000000000..373fd73bb --- /dev/null +++ b/pkg/validation/variables/test-fixtures/all/devfile-bad-output.yaml @@ -0,0 +1,80 @@ +variables: + devnull: /dev/null +projects: +- name: project1 + clonePath: "{{path}}" + sparseCheckoutDirs: + - xyz + - "{{dir}}" + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{dir}}": "{{version1}}/dev/null-/dev/null" + "{{version}}": "test" +- name: project2 + zip: + location: "{{tag}}" +starterProjects: +- name: starterproject1 + description: "{{desc}}" + subDir: "{{dir}}" + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{tag}}": "/dev/null" + "{{dir}}": "test" +- name: starterproject2 + zip: + location: "{{tag}}" +components: +- name: component1 + container: + image: "{{a}}" + env: + - name: BAR + value: "{{b}}" + - name: "{{c}}" + value: "{{bar}}" + command: + - tail + - -f + - "{{b}}" + - "{{c}}" +- name: component2 + kubernetes: + inlined: "{{foo}}" + endpoints: + - name: endpoint1 + exposure: "public" + protocol: "https" + path : "/{{x}}}" + targetPort: 9998 + - name: endpoint2 + path : "{{bar}}" + targetPort: 9999 +- name: component3 + volume: + size: "{{xyz}}" +- name: component4 + openshift: + uri: "{{foo}}" +commands: +- id: command1 + exec: + commandLine: "test-{{tag}}" + env: + - name: tag + value: "{{tag}}" + - name: FOO + value: "{{BAR}}" +- id: command2 + composite: + commands: + - xyz + - command1 + label: "{{abc}}" +- id: command3 + apply: + label: "{{abc}}" diff --git a/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml b/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml index 0f8809349..7e46bc4c6 100644 --- a/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml +++ b/pkg/validation/variables/test-fixtures/all/devfile-bad.yaml @@ -1,13 +1,80 @@ variables: devnull: /dev/null +projects: +- name: project1 + clonePath: "{{path}}" + sparseCheckoutDirs: + - xyz + - "{{dir}}" + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{dir}}": "{{version1}}{{devnull}}-{{devnull}}" + "{{version}}": "test" +- name: project2 + zip: + location: "{{tag}}" +starterProjects: +- name: starterproject1 + description: "{{desc}}" + subDir: "{{dir}}" + git: + checkoutFrom: + revision: "{{tag}}" + remotes: + "{{tag}}": "{{devnull}}" + "{{dir}}": "test" +- name: starterproject2 + zip: + location: "{{tag}}" components: - name: component1 container: - image: image + image: "{{a}}" env: - name: BAR - value: "{{foo}}" + value: "{{b}}" + - name: "{{c}}" + value: "{{bar}}" command: - tail - -f - - "{{devnull}}" + - "{{b}}" + - "{{c}}" +- name: component2 + kubernetes: + inlined: "{{foo}}" + endpoints: + - name: endpoint1 + exposure: "public" + protocol: "https" + path : "/{{x}}}" + targetPort: 9998 + - name: endpoint2 + path : "{{bar}}" + targetPort: 9999 +- name: component3 + volume: + size: "{{xyz}}" +- name: component4 + openshift: + uri: "{{foo}}" +commands: +- id: command1 + exec: + commandLine: "test-{{tag}}" + env: + - name: tag + value: "{{tag}}" + - name: FOO + value: "{{BAR}}" +- id: command2 + composite: + commands: + - xyz + - command1 + label: "{{abc}}" +- id: command3 + apply: + label: "{{abc}}" diff --git a/pkg/validation/variables/test-fixtures/projects/zip-output.yaml b/pkg/validation/variables/test-fixtures/projects/zip-output.yaml index e7e11076b..bfc61e30a 100644 --- a/pkg/validation/variables/test-fixtures/projects/zip-output.yaml +++ b/pkg/validation/variables/test-fixtures/projects/zip-output.yaml @@ -1,2 +1,2 @@ zip: - location: "/FOO" + location: "/FOOBAR" diff --git a/pkg/validation/variables/test-fixtures/projects/zip.yaml b/pkg/validation/variables/test-fixtures/projects/zip.yaml index 5e4efd4c0..390c96ead 100644 --- a/pkg/validation/variables/test-fixtures/projects/zip.yaml +++ b/pkg/validation/variables/test-fixtures/projects/zip.yaml @@ -1,3 +1,3 @@ # Variables are defined in test-fixtures/variables/variables-referenced.yaml zip: - location: "/{{foo}}" + location: "/{{foo}}{{bar}}" diff --git a/pkg/validation/variables/utils.go b/pkg/validation/variables/utils.go new file mode 100644 index 000000000..be6621f96 --- /dev/null +++ b/pkg/validation/variables/utils.go @@ -0,0 +1,24 @@ +package variables + +// checkForInvalidError checks for InvalidKeysError and stores the key in the map +func checkForInvalidError(invalidKeys map[string]bool, err error) { + if verr, ok := err.(*InvalidKeysError); ok { + for _, key := range verr.Keys { + invalidKeys[key] = true + } + } +} + +// processInvalidKeys processes the invalid keys and return InvalidKeysError if present +func processInvalidKeys(invalidKeys map[string]bool) error { + var invalidKeysArr []string + for key := range invalidKeys { + invalidKeysArr = append(invalidKeysArr, key) + } + + if len(invalidKeysArr) > 0 { + return &InvalidKeysError{Keys: invalidKeysArr} + } + + return nil +} diff --git a/pkg/validation/variables/utils_test.go b/pkg/validation/variables/utils_test.go new file mode 100644 index 000000000..db44bee68 --- /dev/null +++ b/pkg/validation/variables/utils_test.go @@ -0,0 +1,100 @@ +package variables + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckForInvalidError(t *testing.T) { + + tests := []struct { + name string + wantInvalidKeys map[string]bool + err error + }{ + { + name: "No error", + wantInvalidKeys: make(map[string]bool), + err: nil, + }, + { + name: "Different error", + wantInvalidKeys: make(map[string]bool), + err: fmt.Errorf("an error"), + }, + { + name: "InvalidKeysError error", + wantInvalidKeys: map[string]bool{ + "key1": true, + "key2": true, + }, + err: &InvalidKeysError{Keys: []string{"key1", "key2"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testMap := make(map[string]bool) + checkForInvalidError(testMap, tt.err) + assert.Equal(t, tt.wantInvalidKeys, testMap, "the key map should be the same") + }) + } +} + +func TestProcessInvalidKeys(t *testing.T) { + + tests := []struct { + name string + invalidKeys map[string]bool + wantErr bool + }{ + { + name: "No invalid keys", + invalidKeys: make(map[string]bool), + wantErr: false, + }, + { + name: "InvalidKeysError error", + invalidKeys: map[string]bool{ + "key1": true, + "key2": true, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := processInvalidKeys(tt.invalidKeys) + if tt.wantErr && err == nil { + t.Errorf("Expected error from test but got nil") + } else if !tt.wantErr && err != nil { + t.Errorf("Got unexpected error: %s", err) + } + }) + } +} + +// testStringArrElements checks if string slices have the same elements +func testStringArrElements(aa, bb []string) bool { + + if len(aa) != len(bb) { + return false + } + + for _, a := range aa { + matched := false + for _, b := range bb { + if a == b { + matched = true + break + } + } + + if !matched { + return false + } + } + + return true +} diff --git a/pkg/validation/variables/variables.go b/pkg/validation/variables/variables.go index c01f70c91..0de2f0d25 100644 --- a/pkg/validation/variables/variables.go +++ b/pkg/validation/variables/variables.go @@ -1,55 +1,67 @@ package variables import ( - "fmt" "regexp" "strings" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ) +var globalVariableRegex = regexp.MustCompile(`\{\{(.*?)\}\}`) + +// VariableWarning stores the invalid variable references for each devfile object +type VariableWarning struct { + // Commands stores a map of command ids to the invalid variable references + Commands map[string][]string + + // Components stores a map of component names to the invalid variable references + Components map[string][]string + + // Projects stores a map of project names to the invalid variable references + Projects map[string][]string + + // StarterProjects stores a map of starter project names to the invalid variable references + StarterProjects map[string][]string +} + // ValidateAndReplaceGlobalVariable validates the workspace template spec data for global variable references and replaces them with the variable value -func ValidateAndReplaceGlobalVariable(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) error { +func ValidateAndReplaceGlobalVariable(workspaceTemplateSpec *v1alpha2.DevWorkspaceTemplateSpec) VariableWarning { - var err error + var variableWarning VariableWarning if workspaceTemplateSpec != nil { // Validate the components and replace for global variable - if err = ValidateAndReplaceForComponents(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Components); err != nil { - return err - } + variableWarning.Components = ValidateAndReplaceForComponents(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Components) // Validate the commands and replace for global variable - if err = ValidateAndReplaceForCommands(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Commands); err != nil { - return err - } + variableWarning.Commands = ValidateAndReplaceForCommands(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Commands) // Validate the projects and replace for global variable - if err = ValidateAndReplaceForProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Projects); err != nil { - return err - } + variableWarning.Projects = ValidateAndReplaceForProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.Projects) // Validate the starter projects and replace for global variable - if err = ValidateAndReplaceForStarterProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.StarterProjects); err != nil { - return err - } + variableWarning.StarterProjects = ValidateAndReplaceForStarterProjects(workspaceTemplateSpec.Variables, workspaceTemplateSpec.StarterProjects) } - return nil + return variableWarning } -var globalVariableRegex = regexp.MustCompile(`\{\{(.*?)\}\}`) - // validateAndReplaceDataWithVariable validates the string for a global variable and replaces it. An error // is returned if the string references an invalid global variable key func validateAndReplaceDataWithVariable(val string, variables map[string]string) (string, error) { matches := globalVariableRegex.FindAllStringSubmatch(val, -1) + var invalidKeys []string for _, match := range matches { varValue, ok := variables[match[1]] if !ok { - return "", fmt.Errorf("Variable with key %q does not exist", match[1]) + invalidKeys = append(invalidKeys, match[1]) + } else { + val = strings.Replace(val, match[0], varValue, -1) } - val = strings.Replace(val, match[0], varValue, -1) + } + + if len(invalidKeys) > 0 { + return val, &InvalidKeysError{Keys: invalidKeys} } return val, nil diff --git a/pkg/validation/variables/variables_command.go b/pkg/validation/variables/variables_command.go index 7b4ce350a..335ff54bd 100644 --- a/pkg/validation/variables/variables_command.go +++ b/pkg/validation/variables/variables_command.go @@ -4,8 +4,11 @@ import ( "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ) -// ValidateAndReplaceForCommands validates the commands data for global variable references and replaces them with the variable value -func ValidateAndReplaceForCommands(variables map[string]string, commands []v1alpha2.Command) error { +// ValidateAndReplaceForCommands validates the commands data for global variable references and replaces them with the variable value. +// Returns a map of command ids and invalid variable references if present. +func ValidateAndReplaceForCommands(variables map[string]string, commands []v1alpha2.Command) map[string][]string { + + commandsWarningMap := make(map[string][]string) for i := range commands { var err error @@ -14,77 +17,89 @@ func ValidateAndReplaceForCommands(variables map[string]string, commands []v1alp switch { case commands[i].Exec != nil: if err = validateAndReplaceForExecCommand(variables, commands[i].Exec); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + commandsWarningMap[commands[i].Id] = verr.Keys + } } case commands[i].Composite != nil: if err = validateAndReplaceForCompositeCommand(variables, commands[i].Composite); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + commandsWarningMap[commands[i].Id] = verr.Keys + } } case commands[i].Apply != nil: if err = validateAndReplaceForApplyCommand(variables, commands[i].Apply); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + commandsWarningMap[commands[i].Id] = verr.Keys + } } } } - return nil + return commandsWarningMap } // validateAndReplaceForExecCommand validates the exec command data for global variable references and replaces them with the variable value func validateAndReplaceForExecCommand(variables map[string]string, exec *v1alpha2.ExecCommand) error { var err error + invalidKeys := make(map[string]bool) + if exec != nil { // Validate exec command line if exec.CommandLine, err = validateAndReplaceDataWithVariable(exec.CommandLine, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate exec working dir if exec.WorkingDir, err = validateAndReplaceDataWithVariable(exec.WorkingDir, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate exec label if exec.Label, err = validateAndReplaceDataWithVariable(exec.Label, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate exec env if len(exec.Env) > 0 { if err = validateAndReplaceForEnv(variables, exec.Env); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForCompositeCommand validates the composite command data for global variable references and replaces them with the variable value func validateAndReplaceForCompositeCommand(variables map[string]string, composite *v1alpha2.CompositeCommand) error { var err error + invalidKeys := make(map[string]bool) + if composite != nil { // Validate composite label if composite.Label, err = validateAndReplaceDataWithVariable(composite.Label, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForApplyCommand validates the apply command data for global variable references and replaces them with the variable value func validateAndReplaceForApplyCommand(variables map[string]string, apply *v1alpha2.ApplyCommand) error { var err error + invalidKeys := make(map[string]bool) + if apply != nil { // Validate apply label if apply.Label, err = validateAndReplaceDataWithVariable(apply.Label, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } - return nil + return processInvalidKeys(invalidKeys) } diff --git a/pkg/validation/variables/variables_command_test.go b/pkg/validation/variables/variables_command_test.go index fd7662f1a..81dc3a38a 100644 --- a/pkg/validation/variables/variables_command_test.go +++ b/pkg/validation/variables/variables_command_test.go @@ -26,6 +26,7 @@ func TestValidateAndReplaceExecCommand(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/commands/exec.yaml", + outputFile: "test-fixtures/commands/exec.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -39,11 +40,12 @@ func TestValidateAndReplaceExecCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForExecCommand(testVariable, &testExecCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedExecCommand := v1alpha2.ExecCommand{} readFileToStruct(t, tt.outputFile, &expectedExecCommand) assert.Equal(t, expectedExecCommand, testExecCommand, "The two values should be the same.") @@ -71,6 +73,7 @@ func TestValidateAndReplaceCompositeCommand(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/commands/composite.yaml", + outputFile: "test-fixtures/commands/composite.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -84,11 +87,12 @@ func TestValidateAndReplaceCompositeCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForCompositeCommand(testVariable, &testCompositeCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedCompositeCommand := v1alpha2.CompositeCommand{} readFileToStruct(t, tt.outputFile, &expectedCompositeCommand) assert.Equal(t, expectedCompositeCommand, testCompositeCommand, "The two values should be the same.") @@ -116,6 +120,7 @@ func TestValidateAndReplaceApplyCommand(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/commands/apply.yaml", + outputFile: "test-fixtures/commands/apply.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -129,11 +134,12 @@ func TestValidateAndReplaceApplyCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForApplyCommand(testVariable, &testApplyCommand) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedApplyCommand := v1alpha2.ApplyCommand{} readFileToStruct(t, tt.outputFile, &expectedApplyCommand) assert.Equal(t, expectedApplyCommand, testApplyCommand, "The two values should be the same.") diff --git a/pkg/validation/variables/variables_component.go b/pkg/validation/variables/variables_component.go index 0a7e24ca1..63aa424e1 100644 --- a/pkg/validation/variables/variables_component.go +++ b/pkg/validation/variables/variables_component.go @@ -5,7 +5,10 @@ import ( ) // ValidateAndReplaceForComponents validates the components data for global variable references and replaces them with the variable value -func ValidateAndReplaceForComponents(variables map[string]string, components []v1alpha2.Component) error { +// Returns a map of component names and invalid variable references if present. +func ValidateAndReplaceForComponents(variables map[string]string, components []v1alpha2.Component) map[string][]string { + + componentsWarningMap := make(map[string][]string) for i := range components { var err error @@ -14,172 +17,190 @@ func ValidateAndReplaceForComponents(variables map[string]string, components []v switch { case components[i].Container != nil: if err = validateAndReplaceForContainerComponent(variables, components[i].Container); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + componentsWarningMap[components[i].Name] = verr.Keys + } } case components[i].Kubernetes != nil: if err = validateAndReplaceForKubernetesComponent(variables, components[i].Kubernetes); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + componentsWarningMap[components[i].Name] = verr.Keys + } } case components[i].Openshift != nil: if err = validateAndReplaceForOpenShiftComponent(variables, components[i].Openshift); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + componentsWarningMap[components[i].Name] = verr.Keys + } } case components[i].Volume != nil: if err = validateAndReplaceForVolumeComponent(variables, components[i].Volume); err != nil { - return err + if verr, ok := err.(*InvalidKeysError); ok { + componentsWarningMap[components[i].Name] = verr.Keys + } } } } - return nil + return componentsWarningMap } // validateAndReplaceForContainerComponent validates the container component data for global variable references and replaces them with the variable value func validateAndReplaceForContainerComponent(variables map[string]string, container *v1alpha2.ContainerComponent) error { var err error + invalidKeys := make(map[string]bool) + if container != nil { // Validate container image if container.Image, err = validateAndReplaceDataWithVariable(container.Image, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate container commands for i := range container.Command { if container.Command[i], err = validateAndReplaceDataWithVariable(container.Command[i], variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // Validate container args for i := range container.Args { if container.Args[i], err = validateAndReplaceDataWithVariable(container.Args[i], variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // Validate memory limit if container.MemoryLimit, err = validateAndReplaceDataWithVariable(container.MemoryLimit, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate memory request if container.MemoryRequest, err = validateAndReplaceDataWithVariable(container.MemoryRequest, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate source mapping if container.SourceMapping, err = validateAndReplaceDataWithVariable(container.SourceMapping, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate container env if len(container.Env) > 0 { if err = validateAndReplaceForEnv(variables, container.Env); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // Validate container volume mounts for i := range container.VolumeMounts { if container.VolumeMounts[i].Path, err = validateAndReplaceDataWithVariable(container.VolumeMounts[i].Path, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // Validate container endpoints if len(container.Endpoints) > 0 { if err = validateAndReplaceForEndpoint(variables, container.Endpoints); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForEnv validates the env data for global variable references and replaces them with the variable value func validateAndReplaceForEnv(variables map[string]string, env []v1alpha2.EnvVar) error { + invalidKeys := make(map[string]bool) + for i := range env { var err error // Validate env name if env[i].Name, err = validateAndReplaceDataWithVariable(env[i].Name, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate env value if env[i].Value, err = validateAndReplaceDataWithVariable(env[i].Value, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForKubernetesComponent validates the kubernetes component data for global variable references and replaces them with the variable value func validateAndReplaceForKubernetesComponent(variables map[string]string, kubernetes *v1alpha2.KubernetesComponent) error { var err error + invalidKeys := make(map[string]bool) + if kubernetes != nil { // Validate kubernetes uri if kubernetes.Uri, err = validateAndReplaceDataWithVariable(kubernetes.Uri, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate kubernetes inlined if kubernetes.Inlined, err = validateAndReplaceDataWithVariable(kubernetes.Inlined, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate kubernetes endpoints if len(kubernetes.Endpoints) > 0 { if err = validateAndReplaceForEndpoint(variables, kubernetes.Endpoints); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForOpenShiftComponent validates the openshift component data for global variable references and replaces them with the variable value func validateAndReplaceForOpenShiftComponent(variables map[string]string, openshift *v1alpha2.OpenshiftComponent) error { var err error + invalidKeys := make(map[string]bool) + if openshift != nil { // Validate openshift uri if openshift.Uri, err = validateAndReplaceDataWithVariable(openshift.Uri, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate openshift inlined if openshift.Inlined, err = validateAndReplaceDataWithVariable(openshift.Inlined, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate openshift endpoints if len(openshift.Endpoints) > 0 { if err = validateAndReplaceForEndpoint(variables, openshift.Endpoints); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } } - return nil + return processInvalidKeys(invalidKeys) } // validateAndReplaceForVolumeComponent validates the volume component data for global variable references and replaces them with the variable value func validateAndReplaceForVolumeComponent(variables map[string]string, volume *v1alpha2.VolumeComponent) error { var err error + invalidKeys := make(map[string]bool) + if volume != nil { // Validate volume size if volume.Size, err = validateAndReplaceDataWithVariable(volume.Size, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } - return nil + return processInvalidKeys(invalidKeys) } diff --git a/pkg/validation/variables/variables_component_test.go b/pkg/validation/variables/variables_component_test.go index 090416767..dd9c0f7a6 100644 --- a/pkg/validation/variables/variables_component_test.go +++ b/pkg/validation/variables/variables_component_test.go @@ -26,6 +26,7 @@ func TestValidateAndReplaceContainerComponent(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/components/container.yaml", + outputFile: "test-fixtures/components/container.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -39,11 +40,12 @@ func TestValidateAndReplaceContainerComponent(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForContainerComponent(testVariable, &testContainerComponent) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedContainerComponent := v1alpha2.ContainerComponent{} readFileToStruct(t, tt.outputFile, &expectedContainerComponent) assert.Equal(t, expectedContainerComponent, testContainerComponent, "The two values should be the same.") @@ -71,6 +73,7 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/components/openshift-kubernetes.yaml", + outputFile: "test-fixtures/components/openshift-kubernetes.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -98,11 +101,12 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { } err = validateAndReplaceForKubernetesComponent(testVariable, &testKubernetesComponent) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedKubernetesComponent := v1alpha2.KubernetesComponent{} readFileToStruct(t, tt.outputFile, &expectedKubernetesComponent) assert.Equal(t, expectedKubernetesComponent, testKubernetesComponent, "The two values should be the same.") @@ -130,6 +134,7 @@ func TestValidateAndReplaceVolumeComponent(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/components/volume.yaml", + outputFile: "test-fixtures/components/volume.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -143,11 +148,12 @@ func TestValidateAndReplaceVolumeComponent(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForVolumeComponent(testVariable, &testVolumeComponent) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedVolumeComponent := v1alpha2.VolumeComponent{} readFileToStruct(t, tt.outputFile, &expectedVolumeComponent) assert.Equal(t, expectedVolumeComponent, testVolumeComponent, "The two values should be the same.") @@ -175,6 +181,7 @@ func TestValidateAndReplaceEnv(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/components/env.yaml", + outputFile: "test-fixtures/components/env.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -189,11 +196,12 @@ func TestValidateAndReplaceEnv(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForEnv(testVariable, testEnvArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedEnv := v1alpha2.EnvVar{} readFileToStruct(t, tt.outputFile, &expectedEnv) expectedEnvArr := []v1alpha2.EnvVar{expectedEnv} diff --git a/pkg/validation/variables/variables_endpoint.go b/pkg/validation/variables/variables_endpoint.go index 032be8004..eaa6252c0 100644 --- a/pkg/validation/variables/variables_endpoint.go +++ b/pkg/validation/variables/variables_endpoint.go @@ -7,14 +7,16 @@ import ( // validateAndReplaceForEndpoint validates the endpoint data for global variable references and replaces them with the variable value func validateAndReplaceForEndpoint(variables map[string]string, endpoints []v1alpha2.Endpoint) error { + invalidKeys := make(map[string]bool) + for i := range endpoints { var err error // Validate endpoint path if endpoints[i].Path, err = validateAndReplaceDataWithVariable(endpoints[i].Path, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } - return nil + return processInvalidKeys(invalidKeys) } diff --git a/pkg/validation/variables/variables_endpoint_test.go b/pkg/validation/variables/variables_endpoint_test.go index 55073a400..e3bec1805 100644 --- a/pkg/validation/variables/variables_endpoint_test.go +++ b/pkg/validation/variables/variables_endpoint_test.go @@ -26,6 +26,7 @@ func TestValidateAndReplaceEndpoint(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/components/endpoint.yaml", + outputFile: "test-fixtures/components/endpoint.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -40,11 +41,12 @@ func TestValidateAndReplaceEndpoint(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForEndpoint(testVariable, testEndpointArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedEndpoint := v1alpha2.Endpoint{} readFileToStruct(t, tt.outputFile, &expectedEndpoint) expectedEndpointArr := []v1alpha2.Endpoint{expectedEndpoint} diff --git a/pkg/validation/variables/variables_project.go b/pkg/validation/variables/variables_project.go index e9d72a502..309715848 100644 --- a/pkg/validation/variables/variables_project.go +++ b/pkg/validation/variables/variables_project.go @@ -4,56 +4,76 @@ import ( "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" ) -// ValidateAndReplaceForProjects validates the projects data for global variable references and replaces them with the variable value -func ValidateAndReplaceForProjects(variables map[string]string, projects []v1alpha2.Project) error { +// ValidateAndReplaceForProjects validates the projects data for global variable references and replaces them with the variable value. +// Returns a map of project names and invalid variable references if present. +func ValidateAndReplaceForProjects(variables map[string]string, projects []v1alpha2.Project) map[string][]string { + + projectsWarningMap := make(map[string][]string) for i := range projects { var err error + invalidKeys := make(map[string]bool) + // Validate project clonepath if projects[i].ClonePath, err = validateAndReplaceDataWithVariable(projects[i].ClonePath, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate project sparse checkout dir for j := range projects[i].SparseCheckoutDirs { if projects[i].SparseCheckoutDirs[j], err = validateAndReplaceDataWithVariable(projects[i].SparseCheckoutDirs[j], variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // Validate project source if err = validateandReplaceForProjectSource(variables, &projects[i].ProjectSource); err != nil { - return err + checkForInvalidError(invalidKeys, err) + } + + err = processInvalidKeys(invalidKeys) + if verr, ok := err.(*InvalidKeysError); ok { + projectsWarningMap[projects[i].Name] = verr.Keys } } - return nil + return projectsWarningMap } -// ValidateAndReplaceForStarterProjects validates the starter projects data for global variable references and replaces them with the variable value -func ValidateAndReplaceForStarterProjects(variables map[string]string, starterProjects []v1alpha2.StarterProject) error { +// ValidateAndReplaceForStarterProjects validates the starter projects data for global variable references and replaces them with the variable value. +// Returns a map of starter project names and invalid variable references if present. +func ValidateAndReplaceForStarterProjects(variables map[string]string, starterProjects []v1alpha2.StarterProject) map[string][]string { + + starterProjectsWarningMap := make(map[string][]string) for i := range starterProjects { var err error + invalidKeys := make(map[string]bool) + // Validate starter project description if starterProjects[i].Description, err = validateAndReplaceDataWithVariable(starterProjects[i].Description, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate starter project sub dir if starterProjects[i].SubDir, err = validateAndReplaceDataWithVariable(starterProjects[i].SubDir, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // Validate starter project source if err = validateandReplaceForProjectSource(variables, &starterProjects[i].ProjectSource); err != nil { - return err + checkForInvalidError(invalidKeys, err) + } + + err = processInvalidKeys(invalidKeys) + if verr, ok := err.(*InvalidKeysError); ok { + starterProjectsWarningMap[starterProjects[i].Name] = verr.Keys } } - return nil + return starterProjectsWarningMap } // validateandReplaceForProjectSource validates a project source location for global variable references and replaces them with the variable value @@ -61,11 +81,13 @@ func validateandReplaceForProjectSource(variables map[string]string, projectSour var err error + invalidKeys := make(map[string]bool) + if projectSource != nil { switch { case projectSource.Zip != nil: if projectSource.Zip.Location, err = validateAndReplaceDataWithVariable(projectSource.Zip.Location, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } case projectSource.Git != nil || projectSource.Github != nil: var gitProject *v1alpha2.GitLikeProjectSource @@ -78,26 +100,26 @@ func validateandReplaceForProjectSource(variables map[string]string, projectSour if gitProject.CheckoutFrom != nil { // validate git checkout revision if gitProject.CheckoutFrom.Revision, err = validateAndReplaceDataWithVariable(gitProject.CheckoutFrom.Revision, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } // // validate git checkout remote if gitProject.CheckoutFrom.Remote, err = validateAndReplaceDataWithVariable(gitProject.CheckoutFrom.Remote, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } } // validate git remotes for k := range gitProject.Remotes { - // update map value + // validate remote map value if gitProject.Remotes[k], err = validateAndReplaceDataWithVariable(gitProject.Remotes[k], variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } - // update map key + // validate remote map key var updatedKey string if updatedKey, err = validateAndReplaceDataWithVariable(k, variables); err != nil { - return err + checkForInvalidError(invalidKeys, err) } else if updatedKey != k { gitProject.Remotes[updatedKey] = gitProject.Remotes[k] delete(gitProject.Remotes, k) @@ -106,5 +128,5 @@ func validateandReplaceForProjectSource(variables map[string]string, projectSour } } - return nil + return processInvalidKeys(invalidKeys) } diff --git a/pkg/validation/variables/variables_project_test.go b/pkg/validation/variables/variables_project_test.go index 44933f775..fc18cb6b5 100644 --- a/pkg/validation/variables/variables_project_test.go +++ b/pkg/validation/variables/variables_project_test.go @@ -26,6 +26,7 @@ func TestValidateAndReplaceProjects(t *testing.T) { { name: "Invalid Reference", testFile: "test-fixtures/projects/project.yaml", + outputFile: "test-fixtures/projects/project.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -39,17 +40,11 @@ func TestValidateAndReplaceProjects(t *testing.T) { testVariable := make(map[string]string) readFileToStruct(t, tt.variableFile, &testVariable) - err := ValidateAndReplaceForProjects(testVariable, testProjectArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedProject := v1alpha2.Project{} - readFileToStruct(t, tt.outputFile, &expectedProject) - expectedProjectArr := []v1alpha2.Project{expectedProject} - assert.Equal(t, expectedProjectArr, testProjectArr, "The two values should be the same.") - } + ValidateAndReplaceForProjects(testVariable, testProjectArr) + expectedProject := v1alpha2.Project{} + readFileToStruct(t, tt.outputFile, &expectedProject) + expectedProjectArr := []v1alpha2.Project{expectedProject} + assert.Equal(t, expectedProjectArr, testProjectArr, "The two values should be the same.") }) } } @@ -61,20 +56,18 @@ func TestValidateAndReplaceStarterProjects(t *testing.T) { testFile string outputFile string variableFile string - wantErr bool }{ { name: "Good Substitution", testFile: "test-fixtures/projects/starterproject.yaml", outputFile: "test-fixtures/projects/starterproject-output.yaml", variableFile: "test-fixtures/variables/variables-referenced.yaml", - wantErr: false, }, { name: "Invalid Reference", testFile: "test-fixtures/projects/starterproject.yaml", + outputFile: "test-fixtures/projects/starterproject.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", - wantErr: true, }, } for _, tt := range tests { @@ -86,17 +79,11 @@ func TestValidateAndReplaceStarterProjects(t *testing.T) { testVariable := make(map[string]string) readFileToStruct(t, tt.variableFile, &testVariable) - err := ValidateAndReplaceForStarterProjects(testVariable, testStarterProjectArr) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedStarterProject := v1alpha2.StarterProject{} - readFileToStruct(t, tt.outputFile, &expectedStarterProject) - expectedStarterProjectArr := []v1alpha2.StarterProject{expectedStarterProject} - assert.Equal(t, expectedStarterProjectArr, testStarterProjectArr, "The two values should be the same.") - } + ValidateAndReplaceForStarterProjects(testVariable, testStarterProjectArr) + expectedStarterProject := v1alpha2.StarterProject{} + readFileToStruct(t, tt.outputFile, &expectedStarterProject) + expectedStarterProjectArr := []v1alpha2.StarterProject{expectedStarterProject} + assert.Equal(t, expectedStarterProjectArr, testStarterProjectArr, "The two values should be the same.") }) } } @@ -127,12 +114,14 @@ func TestValidateAndReplaceProjectSrc(t *testing.T) { { name: "Invalid Git Reference", testFile: "test-fixtures/projects/git.yaml", + outputFile: "test-fixtures/projects/git.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, { name: "Invalid Zip Reference", testFile: "test-fixtures/projects/zip.yaml", + outputFile: "test-fixtures/projects/zip.yaml", variableFile: "test-fixtures/variables/variables-notreferenced.yaml", wantErr: true, }, @@ -146,11 +135,12 @@ func TestValidateAndReplaceProjectSrc(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateandReplaceForProjectSource(testVariable, &testProjectSrc) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") + verr, ok := err.(*InvalidKeysError) + if tt.wantErr && !ok { + t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) - } else if err == nil { + } else { expectedProjectSrc := v1alpha2.ProjectSource{} readFileToStruct(t, tt.outputFile, &expectedProjectSrc) assert.Equal(t, expectedProjectSrc, testProjectSrc, "The two values should be the same.") diff --git a/pkg/validation/variables/variables_test.go b/pkg/validation/variables/variables_test.go index 9db29c1c6..6f7b2984f 100644 --- a/pkg/validation/variables/variables_test.go +++ b/pkg/validation/variables/variables_test.go @@ -2,6 +2,7 @@ package variables import ( "io/ioutil" + "reflect" "testing" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -12,21 +13,42 @@ import ( func TestValidateGlobalVariableBasic(t *testing.T) { tests := []struct { - name string - testFile string - outputFile string - wantErr bool + name string + testFile string + outputFile string + wantWarning VariableWarning }{ { - name: "Successful global variable substitution", - testFile: "test-fixtures/all/devfile-good.yaml", - outputFile: "test-fixtures/all/devfile-good-output.yaml", - wantErr: false, + name: "Successful global variable substitution", + testFile: "test-fixtures/all/devfile-good.yaml", + outputFile: "test-fixtures/all/devfile-good-output.yaml", + wantWarning: VariableWarning{}, }, { - name: "Invalid Reference", - testFile: "test-fixtures/all/devfile-bad.yaml", - wantErr: true, + name: "Invalid Reference", + testFile: "test-fixtures/all/devfile-bad.yaml", + outputFile: "test-fixtures/all/devfile-bad-output.yaml", + wantWarning: VariableWarning{ + Commands: map[string][]string{ + "command1": {"tag", "BAR"}, + "command2": {"abc"}, + "command3": {"abc"}, + }, + Components: map[string][]string{ + "component1": {"a", "b", "c", "bar"}, + "component2": {"foo", "x", "bar"}, + "component3": {"xyz"}, + "component4": {"foo"}, + }, + Projects: map[string][]string{ + "project1": {"tag", "version1", "path", "dir", "version"}, + "project2": {"tag"}, + }, + StarterProjects: map[string][]string{ + "starterproject1": {"tag", "desc", "dir"}, + "starterproject2": {"tag"}, + }, + }, }, } for _, tt := range tests { @@ -34,15 +56,57 @@ func TestValidateGlobalVariableBasic(t *testing.T) { testDWT := v1alpha2.DevWorkspaceTemplateSpec{} readFileToStruct(t, tt.testFile, &testDWT) - err := ValidateAndReplaceGlobalVariable(&testDWT) - if tt.wantErr && err == nil { - t.Errorf("Expected error from test but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("Got unexpected error: %s", err) - } else if err == nil { - expectedDWT := v1alpha2.DevWorkspaceTemplateSpec{} - readFileToStruct(t, tt.outputFile, &expectedDWT) - assert.Equal(t, expectedDWT, testDWT, "The two values should be the same.") + warning := ValidateAndReplaceGlobalVariable(&testDWT) + + expectedDWT := v1alpha2.DevWorkspaceTemplateSpec{} + readFileToStruct(t, tt.outputFile, &expectedDWT) + assert.Equal(t, expectedDWT, testDWT, "The two values should be the same.") + + // match the warning + if !reflect.DeepEqual(tt.wantWarning, VariableWarning{}) { + // commands + for gotCommand, gotInvalidVars := range warning.Commands { + if wantInvalidVars, ok := tt.wantWarning.Commands[gotCommand]; !ok { + t.Errorf("unexpected command %s found in the warning", gotCommand) + } else { + if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { + t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) + } + } + } + + // components + for gotComponent, gotInvalidVars := range warning.Components { + if wantInvalidVars, ok := tt.wantWarning.Components[gotComponent]; !ok { + t.Errorf("unexpected component %s found in the warning", gotComponent) + } else { + if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { + t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) + } + } + } + + // projects + for gotProject, gotInvalidVars := range warning.Projects { + if wantInvalidVars, ok := tt.wantWarning.Projects[gotProject]; !ok { + t.Errorf("unexpected project %s found in the warning", gotProject) + } else { + if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { + t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) + } + } + } + + // starter projects + for gotStarterProject, gotInvalidVars := range warning.StarterProjects { + if wantInvalidVars, ok := tt.wantWarning.StarterProjects[gotStarterProject]; !ok { + t.Errorf("unexpected starter project %s found in the warning", gotStarterProject) + } else { + if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { + t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) + } + } + } } }) } @@ -50,7 +114,7 @@ func TestValidateGlobalVariableBasic(t *testing.T) { func TestValidateAndReplaceDataWithVariable(t *testing.T) { - invalidVariableErr := ".*Variable with key .* does not exist.*" + invalidVariableErr := ".*invalid variable references.*" tests := []struct { name string @@ -61,22 +125,24 @@ func TestValidateAndReplaceDataWithVariable(t *testing.T) { }{ { name: "Valid variable reference", - testString: "image-{{version}}:{{tag}}-14", + testString: "image-{{version}}:{{tag}}{{development}}-14", variables: map[string]string{ - "version": "1.x.x", - "tag": "dev", + "version": "1.x.x", + "tag": "dev", + "development": "sandbox", }, - wantValue: "image-1.x.x:dev-14", + wantValue: "image-1.x.x:devsandbox-14", wantErr: nil, }, { name: "Invalid variable reference", - testString: "image-{{version}}:{{invalid}}-14", + testString: "image-{{version}}:{{tag}}{{invalid}}-14{{invalid}}", variables: map[string]string{ "version": "1.x.x", "tag": "dev", }, - wantErr: &invalidVariableErr, + wantValue: "image-1.x.x:dev{{invalid}}-14{{invald}}", + wantErr: &invalidVariableErr, }, } for _, tt := range tests { diff --git a/schemas/latest/dev-workspace-template-spec.json b/schemas/latest/dev-workspace-template-spec.json index bc98c48ad..43bf992cc 100644 --- a/schemas/latest/dev-workspace-template-spec.json +++ b/schemas/latest/dev-workspace-template-spec.json @@ -3081,7 +3081,7 @@ } }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" diff --git a/schemas/latest/dev-workspace-template.json b/schemas/latest/dev-workspace-template.json index a57cd2f60..3366199a8 100644 --- a/schemas/latest/dev-workspace-template.json +++ b/schemas/latest/dev-workspace-template.json @@ -3246,7 +3246,7 @@ } }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" diff --git a/schemas/latest/dev-workspace.json b/schemas/latest/dev-workspace.json index 5de1cfb1e..10c00aff0 100644 --- a/schemas/latest/dev-workspace.json +++ b/schemas/latest/dev-workspace.json @@ -3259,7 +3259,7 @@ } }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" diff --git a/schemas/latest/devfile.json b/schemas/latest/devfile.json index 7c702f671..7cb0eb71e 100644 --- a/schemas/latest/devfile.json +++ b/schemas/latest/devfile.json @@ -637,7 +637,7 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead.", + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", "type": "object", "additionalProperties": true }, @@ -1802,7 +1802,7 @@ } }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" diff --git a/schemas/latest/ide-targeted/dev-workspace-template-spec.json b/schemas/latest/ide-targeted/dev-workspace-template-spec.json index cccb47bcb..32e4561f4 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template-spec.json +++ b/schemas/latest/ide-targeted/dev-workspace-template-spec.json @@ -3430,12 +3430,12 @@ "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" }, - "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/dev-workspace-template.json b/schemas/latest/ide-targeted/dev-workspace-template.json index d8d065dc4..7c9f3be2d 100644 --- a/schemas/latest/ide-targeted/dev-workspace-template.json +++ b/schemas/latest/ide-targeted/dev-workspace-template.json @@ -3628,12 +3628,12 @@ "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" }, - "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/dev-workspace.json b/schemas/latest/ide-targeted/dev-workspace.json index f2454187f..91b552514 100644 --- a/schemas/latest/ide-targeted/dev-workspace.json +++ b/schemas/latest/ide-targeted/dev-workspace.json @@ -3641,12 +3641,12 @@ "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" }, - "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure" } }, "additionalProperties": false, diff --git a/schemas/latest/ide-targeted/devfile.json b/schemas/latest/ide-targeted/devfile.json index 9f34183f7..04039a3cf 100644 --- a/schemas/latest/ide-targeted/devfile.json +++ b/schemas/latest/ide-targeted/devfile.json @@ -700,10 +700,10 @@ "type": "object", "properties": { "attributes": { - "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead.", + "description": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead.", "type": "object", "additionalProperties": true, - "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the global attributes instead." + "markdownDescription": "Map of implementation-dependant free-form YAML attributes. Deprecated, use the top-level attributes field instead." }, "description": { "description": "Optional devfile description", @@ -2016,12 +2016,12 @@ "markdownDescription": "StarterProjects is a project that can be used as a starting point when bootstrapping new projects" }, "variables": { - "description": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)", + "description": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure", "type": "object", "additionalProperties": { "type": "string" }, - "markdownDescription": "Map of string variables. Variable values can be referenced throughout the devfile in string type fields in the form {{variable-key}} except for schemaVersion, metadata, parent source. Exception to the string field also include element's key identifiers (command id, component name, endpoint name, project name, etc.) and their references(events, command's component, container's volume mount name, etc.) and string enums(command group kind, endpoint exposure, etc.)" + "markdownDescription": "Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} to replace the corresponding value in string fields in the devfile. Replacement cannot be used for\n\n - schemaVersion, metadata, parent source - element identifiers, e.g. command id, component name, endpoint name, project name - references to identifiers, e.g. in events, a command's component, container's volume mount name - string enums, e.g. command group kind, endpoint exposure" } }, "additionalProperties": false, From 491bad9a761ed22569f3700de3322c26b309b9e8 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Wed, 7 Apr 2021 17:09:08 -0400 Subject: [PATCH 07/10] Update dw merging tests for main branch dw rename Signed-off-by: Maysun J Faisal --- pkg/utils/overriding/merging_test.go | 130 +++++++++++++-------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/pkg/utils/overriding/merging_test.go b/pkg/utils/overriding/merging_test.go index 325be85df..df77b8629 100644 --- a/pkg/utils/overriding/merging_test.go +++ b/pkg/utils/overriding/merging_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - workspaces "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" attributesPkg "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/util/json" @@ -18,37 +18,37 @@ func TestBasicMerging(t *testing.T) { tests := []struct { name string - mainContent *workspaces.DevWorkspaceTemplateSpecContent - parentFlattenedContent *workspaces.DevWorkspaceTemplateSpecContent - pluginFlattenedContents []*workspaces.DevWorkspaceTemplateSpecContent - expected *workspaces.DevWorkspaceTemplateSpecContent + mainContent *dw.DevWorkspaceTemplateSpecContent + parentFlattenedContent *dw.DevWorkspaceTemplateSpecContent + pluginFlattenedContents []*dw.DevWorkspaceTemplateSpecContent + expected *dw.DevWorkspaceTemplateSpecContent wantErr *string }{ { name: "Basic Merging", - mainContent: &workspaces.DevWorkspaceTemplateSpecContent{ + mainContent: &dw.DevWorkspaceTemplateSpecContent{ Variables: map[string]string{ "version1": "main", }, Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ "main": true, }, nil), - Commands: []workspaces.Command{ + Commands: []dw.Command{ { Id: "mainCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, }, - Components: []workspaces.Component{ + Components: []dw.Component{ { Name: "mainComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, @@ -56,10 +56,10 @@ func TestBasicMerging(t *testing.T) { }, { Name: "mainPluginComponent", - ComponentUnion: workspaces.ComponentUnion{ - Plugin: &workspaces.PluginComponent{ - ImportReference: workspaces.ImportReference{ - ImportReferenceUnion: workspaces.ImportReferenceUnion{ + ComponentUnion: dw.ComponentUnion{ + Plugin: &dw.PluginComponent{ + ImportReference: dw.ImportReference{ + ImportReferenceUnion: dw.ImportReferenceUnion{ Uri: "uri", }, }, @@ -67,13 +67,13 @@ func TestBasicMerging(t *testing.T) { }, }, }, - Events: &workspaces.Events{ - WorkspaceEvents: workspaces.WorkspaceEvents{ + Events: &dw.Events{ + DevWorkspaceEvents: dw.DevWorkspaceEvents{ PostStop: []string{"post-stop-main"}, }, }, }, - pluginFlattenedContents: []*workspaces.DevWorkspaceTemplateSpecContent{ + pluginFlattenedContents: []*dw.DevWorkspaceTemplateSpecContent{ { Variables: map[string]string{ "version2": "plugin", @@ -81,72 +81,72 @@ func TestBasicMerging(t *testing.T) { Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ "plugin": true, }, nil), - Commands: []workspaces.Command{ + Commands: []dw.Command{ { Id: "pluginCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, }, - Components: []workspaces.Component{ + Components: []dw.Component{ { Name: "pluginComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, }, }, }, - Events: &workspaces.Events{ - WorkspaceEvents: workspaces.WorkspaceEvents{ + Events: &dw.Events{ + DevWorkspaceEvents: dw.DevWorkspaceEvents{ PostStop: []string{"post-stop-plugin"}, }, }, }, }, - parentFlattenedContent: &workspaces.DevWorkspaceTemplateSpecContent{ + parentFlattenedContent: &dw.DevWorkspaceTemplateSpecContent{ Variables: map[string]string{ "version3": "parent", }, Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ "parent": true, }, nil), - Commands: []workspaces.Command{ + Commands: []dw.Command{ { Id: "parentCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, }, - Components: []workspaces.Component{ + Components: []dw.Component{ { Name: "parentComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, }, }, }, - Events: &workspaces.Events{ - WorkspaceEvents: workspaces.WorkspaceEvents{ + Events: &dw.Events{ + DevWorkspaceEvents: dw.DevWorkspaceEvents{ PostStop: []string{"post-stop-parent"}, PostStart: []string{"post-start-parent"}, }, }, }, - expected: &workspaces.DevWorkspaceTemplateSpecContent{ + expected: &dw.DevWorkspaceTemplateSpecContent{ Variables: map[string]string{ "version3": "parent", "version2": "plugin", @@ -157,38 +157,38 @@ func TestBasicMerging(t *testing.T) { "plugin": true, "main": true, }, nil), - Commands: []workspaces.Command{ + Commands: []dw.Command{ { Id: "parentCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, { Id: "pluginCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, { Id: "mainCommand", - CommandUnion: workspaces.CommandUnion{ - Exec: &workspaces.ExecCommand{ + CommandUnion: dw.CommandUnion{ + Exec: &dw.ExecCommand{ WorkingDir: "dir", }, }, }, }, - Components: []workspaces.Component{ + Components: []dw.Component{ { Name: "parentComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, @@ -196,9 +196,9 @@ func TestBasicMerging(t *testing.T) { }, { Name: "pluginComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, @@ -206,17 +206,17 @@ func TestBasicMerging(t *testing.T) { }, { Name: "mainComponent", - ComponentUnion: workspaces.ComponentUnion{ - Container: &workspaces.ContainerComponent{ - Container: workspaces.Container{ + ComponentUnion: dw.ComponentUnion{ + Container: &dw.ContainerComponent{ + Container: dw.Container{ Image: "image", }, }, }, }, }, - Events: &workspaces.Events{ - WorkspaceEvents: workspaces.WorkspaceEvents{ + Events: &dw.Events{ + DevWorkspaceEvents: dw.DevWorkspaceEvents{ PreStart: []string{}, PostStart: []string{"post-start-parent"}, PreStop: []string{}, @@ -337,9 +337,9 @@ func TestMergingOnlyPlugins(t *testing.T) { pluginFile := "test-fixtures/merges/no-parent/plugin.yaml" resultFile := "test-fixtures/merges/no-parent/result.yaml" - baseDWT := workspaces.DevWorkspaceTemplateSpecContent{} - pluginDWT := workspaces.DevWorkspaceTemplateSpecContent{} - expectedDWT := workspaces.DevWorkspaceTemplateSpecContent{} + baseDWT := dw.DevWorkspaceTemplateSpecContent{} + pluginDWT := dw.DevWorkspaceTemplateSpecContent{} + expectedDWT := dw.DevWorkspaceTemplateSpecContent{} readFileToStruct(t, baseFile, &baseDWT) readFileToStruct(t, pluginFile, &pluginDWT) @@ -357,9 +357,9 @@ func TestMergingOnlyParent(t *testing.T) { parentFile := "test-fixtures/merges/no-parent/plugin.yaml" resultFile := "test-fixtures/merges/no-parent/result.yaml" - baseDWT := workspaces.DevWorkspaceTemplateSpecContent{} - parentDWT := workspaces.DevWorkspaceTemplateSpecContent{} - expectedDWT := workspaces.DevWorkspaceTemplateSpecContent{} + baseDWT := dw.DevWorkspaceTemplateSpecContent{} + parentDWT := dw.DevWorkspaceTemplateSpecContent{} + expectedDWT := dw.DevWorkspaceTemplateSpecContent{} readFileToStruct(t, baseFile, &baseDWT) readFileToStruct(t, parentFile, &parentDWT) From d817f8f2a55702e4fe7634de727bd59cc3216aa2 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Fri, 9 Apr 2021 15:56:13 -0400 Subject: [PATCH 08/10] PR feedback tests - 1 Signed-off-by: Maysun J Faisal --- pkg/utils/overriding/merging_test.go | 234 +----------------- .../merges/no-duplicate/main.yaml | 4 +- .../merges/no-duplicate/parent.yaml | 4 +- .../merges/no-duplicate/plugin.yaml | 4 + .../merges/no-duplicate/result.yaml | 10 +- pkg/validation/variables/errors.go | 16 ++ pkg/validation/variables/utils.go | 14 -- pkg/validation/variables/utils_test.go | 24 -- .../variables/variables_command_test.go | 12 +- .../variables/variables_component_test.go | 16 +- .../variables/variables_endpoint_test.go | 4 +- .../variables/variables_project_test.go | 4 +- pkg/validation/variables/variables_test.go | 26 +- 13 files changed, 58 insertions(+), 314 deletions(-) diff --git a/pkg/utils/overriding/merging_test.go b/pkg/utils/overriding/merging_test.go index df77b8629..20e8f7188 100644 --- a/pkg/utils/overriding/merging_test.go +++ b/pkg/utils/overriding/merging_test.go @@ -7,239 +7,11 @@ import ( "testing" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - attributesPkg "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/json" yamlMachinery "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/yaml" ) -func TestBasicMerging(t *testing.T) { - - tests := []struct { - name string - mainContent *dw.DevWorkspaceTemplateSpecContent - parentFlattenedContent *dw.DevWorkspaceTemplateSpecContent - pluginFlattenedContents []*dw.DevWorkspaceTemplateSpecContent - expected *dw.DevWorkspaceTemplateSpecContent - wantErr *string - }{ - { - name: "Basic Merging", - mainContent: &dw.DevWorkspaceTemplateSpecContent{ - Variables: map[string]string{ - "version1": "main", - }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "main": true, - }, nil), - Commands: []dw.Command{ - { - Id: "mainCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - }, - Components: []dw.Component{ - { - Name: "mainComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - { - Name: "mainPluginComponent", - ComponentUnion: dw.ComponentUnion{ - Plugin: &dw.PluginComponent{ - ImportReference: dw.ImportReference{ - ImportReferenceUnion: dw.ImportReferenceUnion{ - Uri: "uri", - }, - }, - }, - }, - }, - }, - Events: &dw.Events{ - DevWorkspaceEvents: dw.DevWorkspaceEvents{ - PostStop: []string{"post-stop-main"}, - }, - }, - }, - pluginFlattenedContents: []*dw.DevWorkspaceTemplateSpecContent{ - { - Variables: map[string]string{ - "version2": "plugin", - }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "plugin": true, - }, nil), - Commands: []dw.Command{ - { - Id: "pluginCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - }, - Components: []dw.Component{ - { - Name: "pluginComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - }, - Events: &dw.Events{ - DevWorkspaceEvents: dw.DevWorkspaceEvents{ - PostStop: []string{"post-stop-plugin"}, - }, - }, - }, - }, - parentFlattenedContent: &dw.DevWorkspaceTemplateSpecContent{ - Variables: map[string]string{ - "version3": "parent", - }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "parent": true, - }, nil), - Commands: []dw.Command{ - { - Id: "parentCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - }, - Components: []dw.Component{ - { - Name: "parentComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - }, - Events: &dw.Events{ - DevWorkspaceEvents: dw.DevWorkspaceEvents{ - PostStop: []string{"post-stop-parent"}, - PostStart: []string{"post-start-parent"}, - }, - }, - }, - expected: &dw.DevWorkspaceTemplateSpecContent{ - Variables: map[string]string{ - "version3": "parent", - "version2": "plugin", - "version1": "main", - }, - Attributes: attributesPkg.Attributes{}.FromMap(map[string]interface{}{ - "parent": true, - "plugin": true, - "main": true, - }, nil), - Commands: []dw.Command{ - { - Id: "parentCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - { - Id: "pluginCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - { - Id: "mainCommand", - CommandUnion: dw.CommandUnion{ - Exec: &dw.ExecCommand{ - WorkingDir: "dir", - }, - }, - }, - }, - Components: []dw.Component{ - { - Name: "parentComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - { - Name: "pluginComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - { - Name: "mainComponent", - ComponentUnion: dw.ComponentUnion{ - Container: &dw.ContainerComponent{ - Container: dw.Container{ - Image: "image", - }, - }, - }, - }, - }, - Events: &dw.Events{ - DevWorkspaceEvents: dw.DevWorkspaceEvents{ - PreStart: []string{}, - PostStart: []string{"post-start-parent"}, - PreStop: []string{}, - PostStop: []string{"post-stop-main", "post-stop-parent", "post-stop-plugin"}, - }, - }, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mergedContent, err := MergeDevWorkspaceTemplateSpec(tt.mainContent, tt.parentFlattenedContent, tt.pluginFlattenedContents...) - if err != nil { - t.Error(err) - return - } - - assert.Equal(t, tt.expected, mergedContent, "The two values should be the same.") - }) - } -} - func mergingPatchTest(main, parent, expected []byte, expectedError string, plugins ...[]byte) func(t *testing.T) { return func(t *testing.T) { result, err := MergeDevWorkspaceTemplateSpecBytes(main, parent, plugins...) @@ -252,11 +24,7 @@ func mergingPatchTest(main, parent, expected []byte, expectedError string, plugi return } - resultJson, err := json.Marshal(result) - if err != nil { - t.Error(err) - } - resultYaml, err := yaml.JSONToYAML(resultJson) + resultYaml, err := yaml.Marshal(result) if err != nil { t.Error(err) } diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml index 4d0509ac6..b1e2fb0d9 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/main.yaml @@ -1,9 +1,9 @@ parent: uri: "anyParent" variables: - variableValue: main + version1: main attributes: - mainAttribute: true + main: true components: - plugin: uri: "aCustomLocation" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml index c6f6f8ec6..5e5637f72 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/parent.yaml @@ -1,7 +1,7 @@ variables: - variableParentValue: parent + version2: parent attributes: - attribute: "parent" + parent: true components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/plugin.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/plugin.yaml index 43c089c57..1ad3f11c7 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/plugin.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/plugin.yaml @@ -1,3 +1,7 @@ +variables: + version3: plugin +attributes: + plugin: true components: - container: image: "aValue" diff --git a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml index 6280bcf05..fe10426e2 100644 --- a/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml +++ b/pkg/utils/overriding/test-fixtures/merges/no-duplicate/result.yaml @@ -1,9 +1,11 @@ variables: - variableValue: main - variableParentValue: parent + version1: main + version2: parent + version3: plugin attributes: - mainAttribute: true - attribute: "parent" + main: true + parent: true + plugin: true components: - container: image: "aValue" diff --git a/pkg/validation/variables/errors.go b/pkg/validation/variables/errors.go index 80f8c4618..f94ff026f 100644 --- a/pkg/validation/variables/errors.go +++ b/pkg/validation/variables/errors.go @@ -2,6 +2,7 @@ package variables import ( "fmt" + "sort" "strings" ) @@ -13,3 +14,18 @@ type InvalidKeysError struct { func (e *InvalidKeysError) Error() string { return fmt.Sprintf("invalid variable references - %s", strings.Join(e.Keys, ",")) } + +// processInvalidKeys processes the invalid keys and return InvalidKeysError if present +func processInvalidKeys(invalidKeys map[string]bool) error { + var invalidKeysArr []string + for key := range invalidKeys { + invalidKeysArr = append(invalidKeysArr, key) + } + + if len(invalidKeysArr) > 0 { + sort.Strings(invalidKeysArr) + return &InvalidKeysError{Keys: invalidKeysArr} + } + + return nil +} diff --git a/pkg/validation/variables/utils.go b/pkg/validation/variables/utils.go index be6621f96..be047ee25 100644 --- a/pkg/validation/variables/utils.go +++ b/pkg/validation/variables/utils.go @@ -8,17 +8,3 @@ func checkForInvalidError(invalidKeys map[string]bool, err error) { } } } - -// processInvalidKeys processes the invalid keys and return InvalidKeysError if present -func processInvalidKeys(invalidKeys map[string]bool) error { - var invalidKeysArr []string - for key := range invalidKeys { - invalidKeysArr = append(invalidKeysArr, key) - } - - if len(invalidKeysArr) > 0 { - return &InvalidKeysError{Keys: invalidKeysArr} - } - - return nil -} diff --git a/pkg/validation/variables/utils_test.go b/pkg/validation/variables/utils_test.go index db44bee68..5f7edb573 100644 --- a/pkg/validation/variables/utils_test.go +++ b/pkg/validation/variables/utils_test.go @@ -74,27 +74,3 @@ func TestProcessInvalidKeys(t *testing.T) { }) } } - -// testStringArrElements checks if string slices have the same elements -func testStringArrElements(aa, bb []string) bool { - - if len(aa) != len(bb) { - return false - } - - for _, a := range aa { - matched := false - for _, b := range bb { - if a == b { - matched = true - break - } - } - - if !matched { - return false - } - } - - return true -} diff --git a/pkg/validation/variables/variables_command_test.go b/pkg/validation/variables/variables_command_test.go index 81dc3a38a..d5f34ae18 100644 --- a/pkg/validation/variables/variables_command_test.go +++ b/pkg/validation/variables/variables_command_test.go @@ -40,9 +40,9 @@ func TestValidateAndReplaceExecCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForExecCommand(testVariable, &testExecCommand) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { @@ -87,9 +87,9 @@ func TestValidateAndReplaceCompositeCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForCompositeCommand(testVariable, &testCompositeCommand) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { @@ -134,9 +134,9 @@ func TestValidateAndReplaceApplyCommand(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForApplyCommand(testVariable, &testApplyCommand) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/pkg/validation/variables/variables_component_test.go b/pkg/validation/variables/variables_component_test.go index dd9c0f7a6..c972c8888 100644 --- a/pkg/validation/variables/variables_component_test.go +++ b/pkg/validation/variables/variables_component_test.go @@ -40,9 +40,9 @@ func TestValidateAndReplaceContainerComponent(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForContainerComponent(testVariable, &testContainerComponent) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { @@ -101,9 +101,9 @@ func TestValidateAndReplaceOpenShiftKubernetesComponent(t *testing.T) { } err = validateAndReplaceForKubernetesComponent(testVariable, &testKubernetesComponent) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { @@ -148,9 +148,9 @@ func TestValidateAndReplaceVolumeComponent(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForVolumeComponent(testVariable, &testVolumeComponent) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { @@ -196,9 +196,9 @@ func TestValidateAndReplaceEnv(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForEnv(testVariable, testEnvArr) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/pkg/validation/variables/variables_endpoint_test.go b/pkg/validation/variables/variables_endpoint_test.go index e3bec1805..42e157eb7 100644 --- a/pkg/validation/variables/variables_endpoint_test.go +++ b/pkg/validation/variables/variables_endpoint_test.go @@ -41,9 +41,9 @@ func TestValidateAndReplaceEndpoint(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateAndReplaceForEndpoint(testVariable, testEndpointArr) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/pkg/validation/variables/variables_project_test.go b/pkg/validation/variables/variables_project_test.go index fc18cb6b5..ca932fa57 100644 --- a/pkg/validation/variables/variables_project_test.go +++ b/pkg/validation/variables/variables_project_test.go @@ -135,9 +135,9 @@ func TestValidateAndReplaceProjectSrc(t *testing.T) { readFileToStruct(t, tt.variableFile, &testVariable) err := validateandReplaceForProjectSource(testVariable, &testProjectSrc) - verr, ok := err.(*InvalidKeysError) + _, ok := err.(*InvalidKeysError) if tt.wantErr && !ok { - t.Errorf("Expected InvalidKeysError error from test but got %+v", verr) + t.Errorf("Expected InvalidKeysError error from test but got %+v", err) } else if !tt.wantErr && err != nil { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/pkg/validation/variables/variables_test.go b/pkg/validation/variables/variables_test.go index 6f7b2984f..6aa1936dd 100644 --- a/pkg/validation/variables/variables_test.go +++ b/pkg/validation/variables/variables_test.go @@ -30,22 +30,22 @@ func TestValidateGlobalVariableBasic(t *testing.T) { outputFile: "test-fixtures/all/devfile-bad-output.yaml", wantWarning: VariableWarning{ Commands: map[string][]string{ - "command1": {"tag", "BAR"}, + "command1": {"BAR", "tag"}, "command2": {"abc"}, "command3": {"abc"}, }, Components: map[string][]string{ - "component1": {"a", "b", "c", "bar"}, - "component2": {"foo", "x", "bar"}, + "component1": {"a", "b", "bar", "c"}, + "component2": {"bar", "foo", "x"}, "component3": {"xyz"}, "component4": {"foo"}, }, Projects: map[string][]string{ - "project1": {"tag", "version1", "path", "dir", "version"}, + "project1": {"dir", "path", "tag", "version", "version1"}, "project2": {"tag"}, }, StarterProjects: map[string][]string{ - "starterproject1": {"tag", "desc", "dir"}, + "starterproject1": {"desc", "dir", "tag"}, "starterproject2": {"tag"}, }, }, @@ -69,9 +69,7 @@ func TestValidateGlobalVariableBasic(t *testing.T) { if wantInvalidVars, ok := tt.wantWarning.Commands[gotCommand]; !ok { t.Errorf("unexpected command %s found in the warning", gotCommand) } else { - if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { - t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) - } + assert.Equal(t, wantInvalidVars, gotInvalidVars, "the invalid keys should be the same") } } @@ -80,9 +78,7 @@ func TestValidateGlobalVariableBasic(t *testing.T) { if wantInvalidVars, ok := tt.wantWarning.Components[gotComponent]; !ok { t.Errorf("unexpected component %s found in the warning", gotComponent) } else { - if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { - t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) - } + assert.Equal(t, wantInvalidVars, gotInvalidVars, "the invalid keys should be the same") } } @@ -91,9 +87,7 @@ func TestValidateGlobalVariableBasic(t *testing.T) { if wantInvalidVars, ok := tt.wantWarning.Projects[gotProject]; !ok { t.Errorf("unexpected project %s found in the warning", gotProject) } else { - if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { - t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) - } + assert.Equal(t, wantInvalidVars, gotInvalidVars, "the invalid keys should be the same") } } @@ -102,9 +96,7 @@ func TestValidateGlobalVariableBasic(t *testing.T) { if wantInvalidVars, ok := tt.wantWarning.StarterProjects[gotStarterProject]; !ok { t.Errorf("unexpected starter project %s found in the warning", gotStarterProject) } else { - if isEqual := testStringArrElements(wantInvalidVars, gotInvalidVars); !isEqual { - t.Errorf("wantInvalidVars %+v not equal as gotInvalidVars %+v", wantInvalidVars, gotInvalidVars) - } + assert.Equal(t, wantInvalidVars, gotInvalidVars, "the invalid keys should be the same") } } } From 4c6cc6e690a8f6b3ddf102c0aa9ef169453ee415 Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Fri, 9 Apr 2021 16:27:58 -0400 Subject: [PATCH 09/10] PR feedback test - 1.1 Signed-off-by: Maysun J Faisal --- pkg/validation/variables/errors.go | 6 +++--- pkg/validation/variables/utils_test.go | 4 ++-- pkg/validation/variables/variables_command.go | 6 +++--- pkg/validation/variables/variables_component.go | 10 +++++----- pkg/validation/variables/variables_endpoint.go | 2 +- pkg/validation/variables/variables_project.go | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/validation/variables/errors.go b/pkg/validation/variables/errors.go index f94ff026f..572eefaac 100644 --- a/pkg/validation/variables/errors.go +++ b/pkg/validation/variables/errors.go @@ -15,10 +15,10 @@ func (e *InvalidKeysError) Error() string { return fmt.Sprintf("invalid variable references - %s", strings.Join(e.Keys, ",")) } -// processInvalidKeys processes the invalid keys and return InvalidKeysError if present -func processInvalidKeys(invalidKeys map[string]bool) error { +// newInvalidKeysError processes the invalid key set and returns an InvalidKeysError if present +func newInvalidKeysError(keySet map[string]bool) error { var invalidKeysArr []string - for key := range invalidKeys { + for key := range keySet { invalidKeysArr = append(invalidKeysArr, key) } diff --git a/pkg/validation/variables/utils_test.go b/pkg/validation/variables/utils_test.go index 5f7edb573..a77d25f3d 100644 --- a/pkg/validation/variables/utils_test.go +++ b/pkg/validation/variables/utils_test.go @@ -42,7 +42,7 @@ func TestCheckForInvalidError(t *testing.T) { } } -func TestProcessInvalidKeys(t *testing.T) { +func TestNewInvalidKeysError(t *testing.T) { tests := []struct { name string @@ -65,7 +65,7 @@ func TestProcessInvalidKeys(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := processInvalidKeys(tt.invalidKeys) + err := newInvalidKeysError(tt.invalidKeys) if tt.wantErr && err == nil { t.Errorf("Expected error from test but got nil") } else if !tt.wantErr && err != nil { diff --git a/pkg/validation/variables/variables_command.go b/pkg/validation/variables/variables_command.go index 335ff54bd..8e9c08679 100644 --- a/pkg/validation/variables/variables_command.go +++ b/pkg/validation/variables/variables_command.go @@ -69,7 +69,7 @@ func validateAndReplaceForExecCommand(variables map[string]string, exec *v1alpha } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForCompositeCommand validates the composite command data for global variable references and replaces them with the variable value @@ -85,7 +85,7 @@ func validateAndReplaceForCompositeCommand(variables map[string]string, composit } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForApplyCommand validates the apply command data for global variable references and replaces them with the variable value @@ -101,5 +101,5 @@ func validateAndReplaceForApplyCommand(variables map[string]string, apply *v1alp } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } diff --git a/pkg/validation/variables/variables_component.go b/pkg/validation/variables/variables_component.go index 63aa424e1..ce5a3f1bc 100644 --- a/pkg/validation/variables/variables_component.go +++ b/pkg/validation/variables/variables_component.go @@ -108,7 +108,7 @@ func validateAndReplaceForContainerComponent(variables map[string]string, contai } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForEnv validates the env data for global variable references and replaces them with the variable value @@ -130,7 +130,7 @@ func validateAndReplaceForEnv(variables map[string]string, env []v1alpha2.EnvVar } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForKubernetesComponent validates the kubernetes component data for global variable references and replaces them with the variable value @@ -158,7 +158,7 @@ func validateAndReplaceForKubernetesComponent(variables map[string]string, kuber } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForOpenShiftComponent validates the openshift component data for global variable references and replaces them with the variable value @@ -186,7 +186,7 @@ func validateAndReplaceForOpenShiftComponent(variables map[string]string, opensh } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } // validateAndReplaceForVolumeComponent validates the volume component data for global variable references and replaces them with the variable value @@ -202,5 +202,5 @@ func validateAndReplaceForVolumeComponent(variables map[string]string, volume *v } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } diff --git a/pkg/validation/variables/variables_endpoint.go b/pkg/validation/variables/variables_endpoint.go index eaa6252c0..17a1e2ed0 100644 --- a/pkg/validation/variables/variables_endpoint.go +++ b/pkg/validation/variables/variables_endpoint.go @@ -18,5 +18,5 @@ func validateAndReplaceForEndpoint(variables map[string]string, endpoints []v1al } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } diff --git a/pkg/validation/variables/variables_project.go b/pkg/validation/variables/variables_project.go index 309715848..136e7ec1a 100644 --- a/pkg/validation/variables/variables_project.go +++ b/pkg/validation/variables/variables_project.go @@ -32,7 +32,7 @@ func ValidateAndReplaceForProjects(variables map[string]string, projects []v1alp checkForInvalidError(invalidKeys, err) } - err = processInvalidKeys(invalidKeys) + err = newInvalidKeysError(invalidKeys) if verr, ok := err.(*InvalidKeysError); ok { projectsWarningMap[projects[i].Name] = verr.Keys } @@ -67,7 +67,7 @@ func ValidateAndReplaceForStarterProjects(variables map[string]string, starterPr checkForInvalidError(invalidKeys, err) } - err = processInvalidKeys(invalidKeys) + err = newInvalidKeysError(invalidKeys) if verr, ok := err.(*InvalidKeysError); ok { starterProjectsWarningMap[starterProjects[i].Name] = verr.Keys } @@ -128,5 +128,5 @@ func validateandReplaceForProjectSource(variables map[string]string, projectSour } } - return processInvalidKeys(invalidKeys) + return newInvalidKeysError(invalidKeys) } From 74d27dd339af529da0e0c10afdf07161c393af6c Mon Sep 17 00:00:00 2001 From: Maysun J Faisal Date: Mon, 19 Apr 2021 13:11:09 -0400 Subject: [PATCH 10/10] Update merging and overriding tests Signed-off-by: Maysun J Faisal --- pkg/utils/overriding/merging_test.go | 32 +++++----------------- pkg/utils/overriding/overriding_test.go | 36 +++++-------------------- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/pkg/utils/overriding/merging_test.go b/pkg/utils/overriding/merging_test.go index 20e8f7188..59e1b4bb6 100644 --- a/pkg/utils/overriding/merging_test.go +++ b/pkg/utils/overriding/merging_test.go @@ -8,13 +8,11 @@ import ( dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/stretchr/testify/assert" - yamlMachinery "k8s.io/apimachinery/pkg/util/yaml" - "sigs.k8s.io/yaml" ) -func mergingPatchTest(main, parent, expected []byte, expectedError string, plugins ...[]byte) func(t *testing.T) { +func mergingPatchTest(main, parent []byte, expected dw.DevWorkspaceTemplateSpecContent, expectedError string, plugins ...[]byte) func(t *testing.T) { return func(t *testing.T) { - result, err := MergeDevWorkspaceTemplateSpecBytes(main, parent, plugins...) + actual, err := MergeDevWorkspaceTemplateSpecBytes(main, parent, plugins...) if err != nil { compareErrorMessages(t, expectedError, err.Error(), "wrong error") return @@ -24,21 +22,7 @@ func mergingPatchTest(main, parent, expected []byte, expectedError string, plugi return } - resultYaml, err := yaml.Marshal(result) - if err != nil { - t.Error(err) - } - - expectedJson, err := yamlMachinery.ToJSON(expected) - if err != nil { - t.Error(err) - } - expectedYaml, err := yaml.JSONToYAML(expectedJson) - if err != nil { - t.Error(err) - } - - assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.") + assert.Equal(t, &expected, actual, "The two values should be the same") } } @@ -75,7 +59,7 @@ func TestMerging(t *testing.T) { } plugins = append(plugins, plugin) } - result := []byte{} + var resultTemplate dw.DevWorkspaceTemplateSpecContent resultError := "" errorFile := filepath.Join(dirPath, "result-error.txt") if _, err = os.Stat(errorFile); err == nil { @@ -86,15 +70,11 @@ func TestMerging(t *testing.T) { } resultError = string(resultErrorBytes) } else { - result, err = ioutil.ReadFile(filepath.Join(dirPath, "result.yaml")) - if err != nil { - t.Error(err) - return nil - } + readFileToStruct(t, filepath.Join(dirPath, "result.yaml"), &resultTemplate) } testName := filepath.Base(dirPath) - t.Run(testName, mergingPatchTest(main, parent, result, resultError, plugins...)) + t.Run(testName, mergingPatchTest(main, parent, resultTemplate, resultError, plugins...)) } return nil }) diff --git a/pkg/utils/overriding/overriding_test.go b/pkg/utils/overriding/overriding_test.go index 97e045a21..41c9499af 100644 --- a/pkg/utils/overriding/overriding_test.go +++ b/pkg/utils/overriding/overriding_test.go @@ -10,8 +10,6 @@ import ( dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" attributesPkg "github.com/devfile/api/v2/pkg/attributes" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/json" - yamlMachinery "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/yaml" ) @@ -163,9 +161,9 @@ func TestBasicToplevelOverriding(t *testing.T) { assert.Equal(t, expected, result, "The two values should be the same.") } -func overridingPatchTest(original, patch, expected []byte, expectedError string) func(t *testing.T) { +func overridingPatchTest(original, patch []byte, expected dw.DevWorkspaceTemplateSpecContent, expectedError string) func(t *testing.T) { return func(t *testing.T) { - result, err := OverrideDevWorkspaceTemplateSpecBytes(original, patch) + actual, err := OverrideDevWorkspaceTemplateSpecBytes(original, patch) if err != nil { compareErrorMessages(t, expectedError, err.Error(), "wrong error") return @@ -175,25 +173,7 @@ func overridingPatchTest(original, patch, expected []byte, expectedError string) return } - resultJson, err := json.Marshal(result) - if err != nil { - t.Error(err) - } - resultYaml, err := yaml.JSONToYAML(resultJson) - if err != nil { - t.Error(err) - } - - expectedJson, err := yamlMachinery.ToJSON(expected) - if err != nil { - t.Error(err) - } - expectedYaml, err := yaml.JSONToYAML(expectedJson) - if err != nil { - t.Error(err) - } - - assert.Equal(t, string(expectedYaml), string(resultYaml), "The two values should be the same.") + assert.Equal(t, &expected, actual, "The two values should be the same") } } @@ -215,7 +195,7 @@ func TestOverridingPatches(t *testing.T) { t.Error(err) return nil } - result := []byte{} + var resultTemplate dw.DevWorkspaceTemplateSpecContent resultError := "" errorFile := filepath.Join(dirPath, "result-error.txt") if _, err = os.Stat(errorFile); err == nil { @@ -226,15 +206,11 @@ func TestOverridingPatches(t *testing.T) { } resultError = string(resultErrorBytes) } else { - result, err = ioutil.ReadFile(filepath.Join(dirPath, "result.yaml")) - if err != nil { - t.Error(err) - return nil - } + readFileToStruct(t, filepath.Join(dirPath, "result.yaml"), &resultTemplate) } testName := filepath.Base(dirPath) - t.Run(testName, overridingPatchTest(original, patch, result, resultError)) + t.Run(testName, overridingPatchTest(original, patch, resultTemplate, resultError)) } return nil })