diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 8d4ef464f..357441014 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -162,13 +162,13 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct timing.SetTime(timingInfo, timing.ComponentsCreated) // TODO#185 : Temporarily do devfile flattening in main reconcile loop; this should be moved to a subcontroller. flattenHelpers := flatten.ResolverTools{ - InstanceNamespace: workspace.Namespace, - Context: ctx, - K8sClient: r.Client, - InternalRegistry: ®istry.InternalRegistryImpl{}, - HttpClient: http.DefaultClient, + DefaultNamespace: workspace.Namespace, + Context: ctx, + K8sClient: r.Client, + InternalRegistry: ®istry.InternalRegistryImpl{}, + HttpClient: http.DefaultClient, } - flattenedWorkspace, err := flatten.ResolveDevWorkspace(workspace.Spec.Template, flattenHelpers) + flattenedWorkspace, err := flatten.ResolveDevWorkspace(&workspace.Spec.Template, flattenHelpers) if err != nil { reqLogger.Info("DevWorkspace start failed") reconcileStatus.Phase = devworkspace.WorkspaceStatusFailed @@ -186,7 +186,7 @@ func (r *DevWorkspaceReconciler) Reconcile(req ctrl.Request) (reconcileResult ct } } - devfilePodAdditions, err := containerlib.GetKubeContainersFromDevfile(workspace.Spec.Template) + devfilePodAdditions, err := containerlib.GetKubeContainersFromDevfile(&workspace.Spec.Template) if err != nil { reqLogger.Info("DevWorkspace start failed") reconcileStatus.Phase = devworkspace.WorkspaceStatusFailed diff --git a/pkg/library/annotate/plugins.go b/pkg/library/annotate/plugins.go index 3577f23a7..565c0c440 100644 --- a/pkg/library/annotate/plugins.go +++ b/pkg/library/annotate/plugins.go @@ -17,31 +17,31 @@ import ( "github.com/devfile/api/v2/pkg/attributes" ) -// AddSourceAttributesForPlugin adds an attribute 'controller.devfile.io/imported-by=sourceID' to all elements of +// AddSourceAttributesForTemplate adds an attribute 'controller.devfile.io/imported-by=sourceID' to all elements of // a plugin that support attributes. -func AddSourceAttributesForPlugin(sourceID string, plugin *dw.DevWorkspaceTemplateSpec) { - for idx, component := range plugin.Components { +func AddSourceAttributesForTemplate(sourceID string, template *dw.DevWorkspaceTemplateSpec) { + for idx, component := range template.Components { if component.Attributes == nil { - plugin.Components[idx].Attributes = attributes.Attributes{} + template.Components[idx].Attributes = attributes.Attributes{} } - plugin.Components[idx].Attributes.PutString(PluginSourceAttribute, sourceID) + template.Components[idx].Attributes.PutString(PluginSourceAttribute, sourceID) } - for idx, command := range plugin.Commands { + for idx, command := range template.Commands { if command.Attributes == nil { - plugin.Commands[idx].Attributes = attributes.Attributes{} + template.Commands[idx].Attributes = attributes.Attributes{} } - plugin.Commands[idx].Attributes.PutString(PluginSourceAttribute, sourceID) + template.Commands[idx].Attributes.PutString(PluginSourceAttribute, sourceID) } - for idx, project := range plugin.Projects { + for idx, project := range template.Projects { if project.Attributes == nil { - plugin.Projects[idx].Attributes = attributes.Attributes{} + template.Projects[idx].Attributes = attributes.Attributes{} } - plugin.Projects[idx].Attributes.PutString(PluginSourceAttribute, sourceID) + template.Projects[idx].Attributes.PutString(PluginSourceAttribute, sourceID) } - for idx, project := range plugin.StarterProjects { + for idx, project := range template.StarterProjects { if project.Attributes == nil { - plugin.Projects[idx].Attributes = attributes.Attributes{} + template.StarterProjects[idx].Attributes = attributes.Attributes{} } - plugin.Projects[idx].Attributes.PutString(PluginSourceAttribute, sourceID) + template.StarterProjects[idx].Attributes.PutString(PluginSourceAttribute, sourceID) } } diff --git a/pkg/library/container/container.go b/pkg/library/container/container.go index 74f79c6cf..14051a7f4 100644 --- a/pkg/library/container/container.go +++ b/pkg/library/container/container.go @@ -40,7 +40,7 @@ import ( // rewritten as Volumes are added to PodAdditions, in order to support e.g. using one PVC to hold all volumes // // Note: Requires DevWorkspace to be flattened (i.e. the DevWorkspace contains no Parent or Components of type Plugin) -func GetKubeContainersFromDevfile(workspace devworkspace.DevWorkspaceTemplateSpec) (*v1alpha1.PodAdditions, error) { +func GetKubeContainersFromDevfile(workspace *devworkspace.DevWorkspaceTemplateSpec) (*v1alpha1.PodAdditions, error) { if !flatten.DevWorkspaceIsFlattened(workspace) { return nil, fmt.Errorf("devfile is not flattened") } diff --git a/pkg/library/container/container_test.go b/pkg/library/container/container_test.go index fcefdabab..52e693596 100644 --- a/pkg/library/container/container_test.go +++ b/pkg/library/container/container_test.go @@ -29,9 +29,9 @@ import ( ) type testCase struct { - Name string `json:"name,omitempty"` - Input devworkspace.DevWorkspaceTemplateSpec `json:"input,omitempty"` - Output testOutput `json:"output,omitempty"` + Name string `json:"name,omitempty"` + Input *devworkspace.DevWorkspaceTemplateSpec `json:"input,omitempty"` + Output testOutput `json:"output,omitempty"` } type testOutput struct { diff --git a/pkg/library/flatten/common.go b/pkg/library/flatten/common.go index f86e04206..7c014700d 100644 --- a/pkg/library/flatten/common.go +++ b/pkg/library/flatten/common.go @@ -14,7 +14,7 @@ package flatten import devworkspace "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" -func DevWorkspaceIsFlattened(devworkspace devworkspace.DevWorkspaceTemplateSpec) bool { +func DevWorkspaceIsFlattened(devworkspace *devworkspace.DevWorkspaceTemplateSpec) bool { if devworkspace.Parent != nil { return false } diff --git a/pkg/library/flatten/flatten.go b/pkg/library/flatten/flatten.go index 091b05f27..83ad155f0 100644 --- a/pkg/library/flatten/flatten.go +++ b/pkg/library/flatten/flatten.go @@ -30,21 +30,18 @@ import ( ) type ResolverTools struct { - InstanceNamespace string - Context context.Context - K8sClient client.Client - InternalRegistry registry.InternalRegistry - HttpClient network.HTTPGetter + DefaultNamespace string + Context context.Context + K8sClient client.Client + InternalRegistry registry.InternalRegistry + HttpClient network.HTTPGetter } // ResolveDevWorkspace takes a devworkspace and returns a "resolved" version of it -- i.e. one where all plugins and parents // are inlined as components. -// TODO: -// - Implement flattening for DevWorkspace parents -// - Implement plugin references by ID and URI -func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { +func ResolveDevWorkspace(workspace *devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools) (*devworkspace.DevWorkspaceTemplateSpec, error) { // Web terminals get default container components if they do not specify one - if err := web_terminal.AddDefaultContainerIfNeeded(&workspace); err != nil { + if err := web_terminal.AddDefaultContainerIfNeeded(workspace); err != nil { return nil, err } @@ -56,15 +53,24 @@ func ResolveDevWorkspace(workspace devworkspace.DevWorkspaceTemplateSpec, toolin return resolvedDW, nil } -func recursiveResolve(workspace devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools, resolveCtx *resolutionContextTree) (*devworkspace.DevWorkspaceTemplateSpec, error) { +func recursiveResolve(workspace *devworkspace.DevWorkspaceTemplateSpec, tooling ResolverTools, resolveCtx *resolutionContextTree) (*devworkspace.DevWorkspaceTemplateSpec, error) { if DevWorkspaceIsFlattened(workspace) { return workspace.DeepCopy(), nil } + + resolvedParent := &devworkspace.DevWorkspaceTemplateSpecContent{} if workspace.Parent != nil { - // TODO: Add support for flattening DevWorkspace parents - return nil, fmt.Errorf("DevWorkspace parent is unsupported") + resolvedParentSpec, err := resolveParentComponent(workspace.Parent, tooling) + if err != nil { + return nil, err + } + if !DevWorkspaceIsFlattened(resolvedParentSpec) { + // TODO: implemenent this + return nil, fmt.Errorf("parents containing plugins or parents are not supported") + } + annotate.AddSourceAttributesForTemplate("parent", resolvedParentSpec) + resolvedParent = &resolvedParentSpec.DevWorkspaceTemplateSpecContent } - resolvedContent := &devworkspace.DevWorkspaceTemplateSpecContent{} resolvedContent.Projects = workspace.Projects resolvedContent.StarterProjects = workspace.StarterProjects @@ -86,17 +92,17 @@ func recursiveResolve(workspace devworkspace.DevWorkspaceTemplateSpec, tooling R return nil, err } - resolvedPlugin, err := recursiveResolve(*pluginComponent, tooling, newCtx) + resolvedPlugin, err := recursiveResolve(pluginComponent, tooling, newCtx) if err != nil { return nil, err } - annotate.AddSourceAttributesForPlugin(component.Name, resolvedPlugin) + annotate.AddSourceAttributesForTemplate(component.Name, resolvedPlugin) pluginSpecContents = append(pluginSpecContents, &resolvedPlugin.DevWorkspaceTemplateSpecContent) } } - resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, nil, pluginSpecContents...) + resolvedContent, err := overriding.MergeDevWorkspaceTemplateSpec(resolvedContent, resolvedParent, pluginSpecContents...) if err != nil { return nil, fmt.Errorf("failed to merge DevWorkspace parents/plugins: %w", err) } @@ -106,22 +112,49 @@ func recursiveResolve(workspace devworkspace.DevWorkspaceTemplateSpec, tooling R }, nil } +// resolveParentComponent resolves the parent DevWorkspaceTemplateSpec that a parent reference refers to. +func resolveParentComponent(parent *devworkspace.Parent, tools ResolverTools) (resolvedParent *devworkspace.DevWorkspaceTemplateSpec, err error) { + switch { + case parent.Kubernetes != nil: + // Search in default namespace if namespace ref is unset + if parent.Kubernetes.Namespace == "" { + parent.Kubernetes.Namespace = tools.DefaultNamespace + } + resolvedParent, err = resolveElementByKubernetesImport("parent", parent.Kubernetes, tools) + case parent.Uri != "": + resolvedParent, err = resolveElementByURI("parent", parent.Uri, tools) + case parent.Id != "": + resolvedParent, err = resolveElementById("parent", parent.Id, parent.RegistryUrl, tools) + default: + err = fmt.Errorf("devfile parent does not define any resources") + } + if err != nil { + return nil, err + } + if parent.Components != nil || parent.Commands != nil || parent.Projects != nil || parent.StarterProjects != nil { + overrideSpec, err := overriding.OverrideDevWorkspaceTemplateSpec(&resolvedParent.DevWorkspaceTemplateSpecContent, parent.ParentOverrides) + + if err != nil { + return nil, err + } + resolvedParent.DevWorkspaceTemplateSpecContent = *overrideSpec + } + return resolvedParent, nil +} + +// resolvePluginComponent resolves the DevWorkspaceTemplateSpec that a plugin component refers to. The name parameter is +// used to construct meaningful error messages (e.g. issue resolving plugin 'name') func resolvePluginComponent( name string, plugin *devworkspace.PluginComponent, - tooling ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { + tools ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { switch { - // TODO: Add support for plugin ID and URI case plugin.Kubernetes != nil: - // Search in devworkspace's namespace if namespace ref is unset - if plugin.Kubernetes.Namespace == "" { - plugin.Kubernetes.Namespace = tooling.InstanceNamespace - } - resolvedPlugin, err = resolvePluginComponentByKubernetesReference(name, plugin, tooling) + resolvedPlugin, err = resolveElementByKubernetesImport(name, plugin.Kubernetes, tools) case plugin.Uri != "": - resolvedPlugin, err = resolvePluginComponentByURI(name, plugin, tooling) + resolvedPlugin, err = resolveElementByURI(name, plugin.Uri, tools) case plugin.Id != "": - resolvedPlugin, err = resolvePluginComponentById(name, plugin, tooling) + resolvedPlugin, err = resolveElementById(name, plugin.Id, plugin.RegistryUrl, tools) default: err = fmt.Errorf("plugin %s does not define any resources", name) } @@ -143,17 +176,32 @@ func resolvePluginComponent( return resolvedPlugin, nil } -func resolvePluginComponentByKubernetesReference( +// resolveElementByKubernetesImport resolves a plugin specified by a Kubernetes reference. +// The name parameter is used to construct meaningful error messages (e.g. issue resolving plugin 'name') +func resolveElementByKubernetesImport( name string, - plugin *devworkspace.PluginComponent, - tooling ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { + kubeReference *devworkspace.KubernetesCustomResourceImportReference, + tools ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { + + if tools.K8sClient == nil { + return nil, fmt.Errorf("cannot resolve resources by kubernetes reference: no kubernetes client provided") + } + + // Search in default namespace if namespace ref is unset + namespace := kubeReference.Namespace + if namespace == "" { + if tools.DefaultNamespace == "" { + return nil, fmt.Errorf("'%s' specifies a kubernetes reference without namespace and a default is not provided", name) + } + namespace = tools.DefaultNamespace + } var dwTemplate devworkspace.DevWorkspaceTemplate namespacedName := types.NamespacedName{ - Name: plugin.Kubernetes.Name, - Namespace: plugin.Kubernetes.Namespace, + Name: kubeReference.Name, + Namespace: namespace, } - err = tooling.K8sClient.Get(tooling.Context, namespacedName, &dwTemplate) + err = tools.K8sClient.Get(tools.Context, namespacedName, &dwTemplate) if err != nil { if errors.IsNotFound(err) { return nil, fmt.Errorf("plugin for component %s not found", name) @@ -163,47 +211,62 @@ func resolvePluginComponentByKubernetesReference( return &dwTemplate.Spec, nil } -func resolvePluginComponentById( +// resolveElementById resolves a component specified by ID and registry URL. The name parameter is used to +// construct meaningful error messages (e.g. issue resolving plugin 'name'). When registry URL is empty, +// the DefaultRegistryURL from tools is used. +func resolveElementById( name string, - plugin *devworkspace.PluginComponent, + id string, + registryUrl string, tools ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { // Check internal registry for plugins that do not specify a registry - if plugin.RegistryUrl == "" { + if registryUrl == "" { if tools.InternalRegistry == nil { return nil, fmt.Errorf("plugin %s does not specify a registryUrl and no internal registry is configured", name) } - if !tools.InternalRegistry.IsInInternalRegistry(plugin.Id) { + if !tools.InternalRegistry.IsInInternalRegistry(id) { return nil, fmt.Errorf("plugin for component %s does not specify a registry and is not present in the internal registry", name) } - pluginDWT, err := tools.InternalRegistry.ReadPluginFromInternalRegistry(plugin.Id) + pluginDWT, err := tools.InternalRegistry.ReadPluginFromInternalRegistry(id) if err != nil { return nil, fmt.Errorf("failed to read plugin for component %s from internal registry: %w", name, err) } return &pluginDWT.Spec, nil + } - pluginURL, err := url.Parse(plugin.RegistryUrl) + if tools.HttpClient == nil { + return nil, fmt.Errorf("cannot resolve resources by id: no HTTP client provided") + } + + pluginURL, err := url.Parse(registryUrl) if err != nil { - return nil, fmt.Errorf("failed to parse registry URL for plugin %s: %w", name, err) + return nil, fmt.Errorf("failed to parse registry URL for component %s: %w", name, err) } - pluginURL.Path = path.Join(pluginURL.Path, "plugins", plugin.Id) + pluginURL.Path = path.Join(pluginURL.Path, id) dwt, err := network.FetchDevWorkspaceTemplate(pluginURL.String(), tools.HttpClient) if err != nil { - return nil, fmt.Errorf("failed to resolve plugin %s from registry %s: %w", name, plugin.RegistryUrl, err) + return nil, fmt.Errorf("failed to resolve component %s from registry %s: %w", name, registryUrl, err) } return dwt, nil } -func resolvePluginComponentByURI( +// resolveElementByURI resolves a plugin defined by URI. The name parameter is used to construct meaningful +// error messages (e.g. issue resolving plugin 'name') +func resolveElementByURI( name string, - plugin *devworkspace.PluginComponent, + uri string, tools ResolverTools) (resolvedPlugin *devworkspace.DevWorkspaceTemplateSpec, err error) { - dwt, err := network.FetchDevWorkspaceTemplate(plugin.Uri, tools.HttpClient) + if tools.HttpClient == nil { + return nil, fmt.Errorf("cannot resolve resources by id: no HTTP client provided") + } + + dwt, err := network.FetchDevWorkspaceTemplate(uri, tools.HttpClient) if err != nil { - return nil, fmt.Errorf("failed to resolve plugin %s by URI: %w", name, err) + return nil, fmt.Errorf("failed to resolve component %s by URI: %w", name, err) } return dwt, nil } diff --git a/pkg/library/flatten/flatten_test.go b/pkg/library/flatten/flatten_test.go index 64ad8b371..86c7ce55a 100644 --- a/pkg/library/flatten/flatten_test.go +++ b/pkg/library/flatten/flatten_test.go @@ -29,12 +29,13 @@ func TestResolveDevWorkspaceKubernetesReference(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testClient := &testutil.FakeK8sClient{ - Plugins: tt.Input.Plugins, - Errors: tt.Input.Errors, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ - Context: context.Background(), - K8sClient: testClient, + Context: context.Background(), + DefaultNamespace: "test-ignored", + K8sClient: testClient, } outputWorkspace, err := ResolveDevWorkspace(tt.Input.Workspace, testResolverTools) if tt.Output.ErrRegexp != nil && assert.Error(t, err) { @@ -59,7 +60,7 @@ func TestResolveDevWorkspaceInternalRegistry(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testRegistry := &testutil.FakeInternalRegistry{ - Plugins: tt.Input.Plugins, + Plugins: tt.Input.DevWorkspaceResources, Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ @@ -89,8 +90,9 @@ func TestResolveDevWorkspacePluginRegistry(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - Plugins: tt.Input.DevfilePlugins, - Errors: tt.Input.Errors, + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -119,8 +121,9 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { // sanity check: input defines components assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") testHttpGetter := &testutil.FakeHTTPGetter{ - Plugins: tt.Input.DevfilePlugins, - Errors: tt.Input.Errors, + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, } testResolverTools := ResolverTools{ Context: context.Background(), @@ -140,3 +143,113 @@ func TestResolveDevWorkspacePluginURI(t *testing.T) { }) } } + +func TestResolveDevWorkspaceParents(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/parent") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + DefaultNamespace: "test-ignored", + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + } + outputWorkspace, err := ResolveDevWorkspace(tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspaceMissingDefaults(t *testing.T) { + tests := []testutil.TestCase{ + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-registry-url.yaml"), + testutil.LoadTestCaseOrPanic(t, "testdata/general/fail-nicely-when-no-namespace.yaml"), + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + } + outputWorkspace, err := ResolveDevWorkspace(tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} + +func TestResolveDevWorkspaceAnnotations(t *testing.T) { + tests := testutil.LoadAllTestsOrPanic(t, "testdata/annotate") + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + // sanity check: input defines components + assert.True(t, len(tt.Input.Workspace.Components) > 0, "Test case defines workspace with no components") + testHttpGetter := &testutil.FakeHTTPGetter{ + DevfileResources: tt.Input.DevfileResources, + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testK8sClient := &testutil.FakeK8sClient{ + DevWorkspaceResources: tt.Input.DevWorkspaceResources, + Errors: tt.Input.Errors, + } + testResolverTools := ResolverTools{ + Context: context.Background(), + K8sClient: testK8sClient, + HttpClient: testHttpGetter, + DefaultNamespace: "default-namespace", + } + outputWorkspace, err := ResolveDevWorkspace(tt.Input.Workspace, testResolverTools) + if tt.Output.ErrRegexp != nil && assert.Error(t, err) { + assert.Regexp(t, *tt.Output.ErrRegexp, err.Error(), "Error message should match") + } else { + if !assert.NoError(t, err, "Should not return error") { + return + } + assert.Truef(t, cmp.Equal(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts), + "Workspace should match expected output:\n%s", + cmp.Diff(tt.Output.Workspace, outputWorkspace, testutil.WorkspaceTemplateDiffOpts)) + } + }) + } +} diff --git a/pkg/library/flatten/internal/testutil/common.go b/pkg/library/flatten/internal/testutil/common.go index 19ad7084f..2ea1837fc 100644 --- a/pkg/library/flatten/internal/testutil/common.go +++ b/pkg/library/flatten/internal/testutil/common.go @@ -19,12 +19,12 @@ import ( "testing" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/devworkspace-operator/pkg/config" - "github.com/devfile/devworkspace-operator/pkg/library/flatten/network" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" + + "github.com/devfile/devworkspace-operator/pkg/config" ) var WorkspaceTemplateDiffOpts = cmp.Options{ @@ -60,11 +60,11 @@ type TestCase struct { } type TestInput struct { - Workspace dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` - // Plugins is a map of plugin "name" to devworkspace template; namespace is ignored. - Plugins map[string]dw.DevWorkspaceTemplate `json:"plugins,omitempty"` - // DevfilePlugins is a map of plugin "name" to devfile - DevfilePlugins map[string]network.Devfile `json:"devfilePlugins,omitempty"` + Workspace *dw.DevWorkspaceTemplateSpec `json:"workspace,omitempty"` + // DevWorkspaceResources is a map of string keys to devworkspace templates + DevWorkspaceResources map[string]dw.DevWorkspaceTemplate `json:"devworkspaceResources,omitempty"` + // DevfileResources is a map of string keys to devfile resources + DevfileResources map[string]dw.Devfile `json:"devfileResources,omitempty"` // Errors is a map of plugin name to the error that should be returned when attempting to retrieve it. Errors map[string]TestPluginError `json:"errors,omitempty"` } diff --git a/pkg/library/flatten/internal/testutil/http.go b/pkg/library/flatten/internal/testutil/http.go index e0b2dfefb..0a5666def 100644 --- a/pkg/library/flatten/internal/testutil/http.go +++ b/pkg/library/flatten/internal/testutil/http.go @@ -19,14 +19,16 @@ import ( "io" "net/http" + dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "sigs.k8s.io/yaml" "github.com/devfile/devworkspace-operator/pkg/library/flatten/network" ) type FakeHTTPGetter struct { - Plugins map[string]network.Devfile - Errors map[string]TestPluginError + DevfileResources map[string]dw.Devfile + DevWorkspaceResources map[string]dw.DevWorkspaceTemplate + Errors map[string]TestPluginError } var _ network.HTTPGetter = (*FakeHTTPGetter)(nil) @@ -38,7 +40,7 @@ type fakeRespBody struct { func (_ *fakeRespBody) Close() error { return nil } func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { - if plugin, ok := reg.Plugins[location]; ok { + if plugin, ok := reg.DevfileResources[location]; ok { yamlBytes, err := yaml.Marshal(plugin) if err != nil { return nil, fmt.Errorf("error marshalling plugin in test: %w", err) @@ -49,6 +51,18 @@ func (reg *FakeHTTPGetter) Get(location string) (*http.Response, error) { } return resp, nil } + if plugin, ok := reg.DevWorkspaceResources[location]; ok { + yamlBytes, err := yaml.Marshal(plugin) + if err != nil { + return nil, fmt.Errorf("error marshalling plugin in test: %w", err) + } + resp := &http.Response{ + StatusCode: http.StatusOK, + Body: &fakeRespBody{bytes.NewBuffer(yamlBytes)}, + } + return resp, nil + } + if err, ok := reg.Errors[location]; ok { if err.StatusCode != 0 { return &http.Response{ diff --git a/pkg/library/flatten/internal/testutil/k8sClient.go b/pkg/library/flatten/internal/testutil/k8sClient.go index 1694c7551..1acab810f 100644 --- a/pkg/library/flatten/internal/testutil/k8sClient.go +++ b/pkg/library/flatten/internal/testutil/k8sClient.go @@ -25,9 +25,9 @@ import ( ) type FakeK8sClient struct { - client.Client // To satisfy interface; override all used methods - Plugins map[string]v1alpha2.DevWorkspaceTemplate - Errors map[string]TestPluginError + client.Client // To satisfy interface; override all used methods + DevWorkspaceResources map[string]v1alpha2.DevWorkspaceTemplate + Errors map[string]TestPluginError } func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.ObjectKey, obj runtime.Object) error { @@ -35,7 +35,7 @@ func (client *FakeK8sClient) Get(_ context.Context, namespacedName client.Object if !ok { return fmt.Errorf("called Get() in fake client with non-DevWorkspaceTemplate") } - if plugin, ok := client.Plugins[namespacedName.Name]; ok { + if plugin, ok := client.DevWorkspaceResources[namespacedName.Name]; ok { *template = plugin return nil } diff --git a/pkg/library/flatten/network/devfile.go b/pkg/library/flatten/network/devfile.go index 044eb5051..c866fb56b 100644 --- a/pkg/library/flatten/network/devfile.go +++ b/pkg/library/flatten/network/devfile.go @@ -17,17 +17,11 @@ import ( "regexp" dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - devfilev2 "github.com/devfile/api/v2/pkg/devfile" ) var SupportedSchemaVersionRegexp = regexp.MustCompile(`^2\..+`) -type Devfile struct { - devfilev2.DevfileHeader - dw.DevWorkspaceTemplateSpec -} - -func ConvertDevfileToDevWorkspaceTemplate(devfile *Devfile) (*dw.DevWorkspaceTemplate, error) { +func ConvertDevfileToDevWorkspaceTemplate(devfile *dw.Devfile) (*dw.DevWorkspaceTemplate, error) { if !SupportedSchemaVersionRegexp.MatchString(devfile.SchemaVersion) { return nil, fmt.Errorf("could not process devfile: unsupported schemaVersion '%s'", devfile.SchemaVersion) } diff --git a/pkg/library/flatten/network/fetch.go b/pkg/library/flatten/network/fetch.go index f07d1344b..ae21a9a6b 100644 --- a/pkg/library/flatten/network/fetch.go +++ b/pkg/library/flatten/network/fetch.go @@ -39,17 +39,35 @@ func FetchDevWorkspaceTemplate(location string, httpClient HTTPGetter) (*dw.DevW return nil, fmt.Errorf("could not read data from %s: %w", location, err) } - // Assume we're getting a devfile, not a DevWorkspaceTemplate (TODO: Detect type and handle both?) - devfile := &Devfile{} - err = yaml.Unmarshal(bytes, devfile) - if err != nil { + devfile := &dw.Devfile{} + if err := yaml.Unmarshal(bytes, devfile); err != nil { return nil, fmt.Errorf("could not unmarshal devfile from response: %w", err) } + if devfile.SchemaVersion != "" { + dwt, err := ConvertDevfileToDevWorkspaceTemplate(devfile) + if err != nil { + return nil, fmt.Errorf("failed to convert devfile to DevWorkspaceTemplate: %s", err) + } + return &dwt.Spec, nil + } - dwt, err := ConvertDevfileToDevWorkspaceTemplate(devfile) - if err != nil { - return nil, fmt.Errorf("failed to convert devfile to DevWorkspaceTemplate: %s", err) + // Assume we didn't get a devfile, check if content is DevWorkspace + devworkspace := &dw.DevWorkspace{} + if err := yaml.Unmarshal(bytes, devworkspace); err != nil { + return nil, fmt.Errorf("could not unmarshal devworkspace from response: %w", err) + } + if devworkspace.Kind == "DevWorkspace" { + return &devworkspace.Spec.Template, nil + } + + // Check if content is DevWorkspaceTemplate + dwt := &dw.DevWorkspaceTemplate{} + if err := yaml.Unmarshal(bytes, dwt); err != nil { + return nil, fmt.Errorf("could not unmarshal devworkspacetemplate from response: %w", err) + } + if dwt.Kind == "DevWorkspaceTemplate" { + return &dwt.Spec, nil } - return &dwt.Spec, nil + return nil, fmt.Errorf("could not find devfile or devworkspace object at '%s'", location) } diff --git a/pkg/library/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml b/pkg/library/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml new file mode 100644 index 000000000..5a98fd431 --- /dev/null +++ b/pkg/library/flatten/testdata/annotate/annotate-devfile-with-importing-source.yaml @@ -0,0 +1,67 @@ +name: "Annotates resources merged into devfile with source attribute" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: test-plugin + plugin: + uri: https://test-plugin.io/test-plugin + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + projects: + - name: parent-project + starterProjects: + - name: parent-starter-project + components: + - name: parent-component + container: + image: test-img + commands: + - id: parent-command + devfileResources: + "https://test-plugin.io/test-plugin": + schemaVersion: 2.1.0 + metadata: + name: test-plugin + components: + - name: plugin-component + container: + image: plugin-img + commands: + - id: plugin-command +output: + workspace: + projects: + - name: parent-project + attributes: + controller.devfile.io/imported-by: parent + starterProjects: + - name: parent-starter-project + attributes: + controller.devfile.io/imported-by: parent + components: + - name: parent-component + attributes: + controller.devfile.io/imported-by: parent + container: + image: test-img + - name: plugin-component + attributes: + controller.devfile.io/imported-by: test-plugin + container: + image: plugin-img + commands: + - id: parent-command + attributes: + controller.devfile.io/imported-by: parent + - id: plugin-command + attributes: + controller.devfile.io/imported-by: test-plugin diff --git a/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml b/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml new file mode 100644 index 000000000..cc88ffc17 --- /dev/null +++ b/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_id.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no HTTP client provided and id is used" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: test/plugin + +output: + errRegexp: "cannot resolve resources by id: no HTTP client provided" diff --git a/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml b/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml new file mode 100644 index 000000000..ebb37d7b7 --- /dev/null +++ b/pkg/library/flatten/testdata/general/fail-nicely-when-no-http-client-provided_uri.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no HTTP client provided and uri is used" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: https://test-repo.io/my-plugino + +output: + errRegexp: "cannot resolve resources by URI: no HTTP client provided" diff --git a/pkg/library/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml b/pkg/library/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml new file mode 100644 index 000000000..561728974 --- /dev/null +++ b/pkg/library/flatten/testdata/general/fail-nicely-when-no-k8s-client-provided.yaml @@ -0,0 +1,13 @@ +name: "Fails nicely when no Kubernetes client provided" + +input: + workspace: + components: + - name: test-plugin + plugin: + kubernetes: + name: test-plugin-a + namespace: test-ns + +output: + errRegexp: "cannot resolve resources by kubernetes reference: no kubernetes client provided" diff --git a/pkg/library/flatten/testdata/general/fail-nicely-when-no-namespace.yaml b/pkg/library/flatten/testdata/general/fail-nicely-when-no-namespace.yaml new file mode 100644 index 000000000..d0867c002 --- /dev/null +++ b/pkg/library/flatten/testdata/general/fail-nicely-when-no-namespace.yaml @@ -0,0 +1,12 @@ +name: "Fails nicely when no Kubernetes namespace is provided and there's no default" + +input: + workspace: + components: + - name: test-plugin + plugin: + kubernetes: + name: test-plugin-a + +output: + errRegexp: "specifies a kubernetes reference without namespace and a default is not provided" diff --git a/pkg/library/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml b/pkg/library/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml new file mode 100644 index 000000000..e4485febb --- /dev/null +++ b/pkg/library/flatten/testdata/general/fail-nicely-when-no-registry-url.yaml @@ -0,0 +1,11 @@ +name: "Fails nicely when no registry URL is provided and there's no default" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: test/plugin + +output: + errRegexp: "plugin test-plugin does not specify a registryUrl and no internal registry is configured" diff --git a/pkg/library/flatten/testdata/internal-registry/defaulting-web-terminal-component.yaml b/pkg/library/flatten/testdata/internal-registry/defaulting-web-terminal-component.yaml index e3589d647..e50740e07 100644 --- a/pkg/library/flatten/testdata/internal-registry/defaulting-web-terminal-component.yaml +++ b/pkg/library/flatten/testdata/internal-registry/defaulting-web-terminal-component.yaml @@ -7,7 +7,7 @@ input: plugin: id: redhat-developer/web-terminal/4.5.0 - plugins: + devworkspaceResources: redhat-developer/web-terminal/4.5.0: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/internal-registry/plugin-in-internal-registry.yaml b/pkg/library/flatten/testdata/internal-registry/plugin-in-internal-registry.yaml index a4dfd50ff..e56982be2 100644 --- a/pkg/library/flatten/testdata/internal-registry/plugin-in-internal-registry.yaml +++ b/pkg/library/flatten/testdata/internal-registry/plugin-in-internal-registry.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: id: my/test/plugin - plugins: + devworkspaceResources: my/test/plugin: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml b/pkg/library/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml index 97df12ef4..f5fdb20c9 100644 --- a/pkg/library/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml +++ b/pkg/library/flatten/testdata/k8s-ref/error_bad-plugin-merge.yaml @@ -11,7 +11,7 @@ input: - name: non-existent container: memoryLimit: 512Mi - plugins: + devworkspaceResources: override: spec: components: diff --git a/pkg/library/flatten/testdata/k8s-ref/error_conflicting-merge.yaml b/pkg/library/flatten/testdata/k8s-ref/error_conflicting-merge.yaml index a41d0ea09..0c096bca6 100644 --- a/pkg/library/flatten/testdata/k8s-ref/error_conflicting-merge.yaml +++ b/pkg/library/flatten/testdata/k8s-ref/error_conflicting-merge.yaml @@ -10,7 +10,7 @@ input: - name: my-component container: image: test-image - plugins: + devworkspaceResources: test-plugin: spec: components: diff --git a/pkg/library/flatten/testdata/k8s-ref/error_has-parent.yaml b/pkg/library/flatten/testdata/k8s-ref/error_has-parent.yaml deleted file mode 100644 index 36031cee0..000000000 --- a/pkg/library/flatten/testdata/k8s-ref/error_has-parent.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: "Workspace has parent" - -input: - workspace: - parent: - kubernetes: - name: my-parent - components: - - name: my-component - container: - image: test-image - - -output: - errRegexp: "DevWorkspace parent is unsupported" diff --git a/pkg/library/flatten/testdata/k8s-ref/error_plugin-references-self.yml b/pkg/library/flatten/testdata/k8s-ref/error_plugin-references-self.yml index c551e576e..61084f171 100644 --- a/pkg/library/flatten/testdata/k8s-ref/error_plugin-references-self.yml +++ b/pkg/library/flatten/testdata/k8s-ref/error_plugin-references-self.yml @@ -7,7 +7,7 @@ input: plugin: kubernetes: name: plugin-a - plugins: + devworkspaceResources: plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml b/pkg/library/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml index 9a8f8ee08..f6fcfff14 100644 --- a/pkg/library/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml +++ b/pkg/library/flatten/testdata/k8s-ref/error_plugins-have-cycle.yml @@ -7,7 +7,7 @@ input: plugin: kubernetes: name: plugin-a - plugins: + devworkspaceResources: plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml b/pkg/library/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml index de7dde79f..c61dc9c50 100644 --- a/pkg/library/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml +++ b/pkg/library/flatten/testdata/k8s-ref/nested-plugins-annotation.yaml @@ -8,7 +8,7 @@ input: kubernetes: name: test-plugin-a namespace: test-ns - plugins: + devworkspaceResources: test-plugin-a: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/k8s-ref/nodejs-workspace.yaml b/pkg/library/flatten/testdata/k8s-ref/nodejs-workspace.yaml index 44a413f27..2351c7c9c 100644 --- a/pkg/library/flatten/testdata/k8s-ref/nodejs-workspace.yaml +++ b/pkg/library/flatten/testdata/k8s-ref/nodejs-workspace.yaml @@ -76,7 +76,7 @@ input: } ] } - plugins: + devworkspaceResources: theia-next: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml b/pkg/library/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml index c598788db..8f32c78ba 100644 --- a/pkg/library/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml +++ b/pkg/library/flatten/testdata/k8s-ref/web-terminal-with-plugin.yaml @@ -16,7 +16,7 @@ input: kubernetes: name: web-terminal namespace: devworkspace-plugins - plugins: + devworkspaceResources: web-terminal: kind: DevWorkspaceTemplate apiVersion: workspace.devfile.io/v1alpha2 diff --git a/pkg/library/flatten/testdata/parent/error_parent-has-parent.yaml b/pkg/library/flatten/testdata/parent/error_parent-has-parent.yaml new file mode 100644 index 000000000..4de009da0 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/error_parent-has-parent.yaml @@ -0,0 +1,31 @@ +name: "Fails when parent is not flattened (has parent)" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + parent: + id: another-parent + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + errRegexp: "parents containing plugins or parents are not supported" diff --git a/pkg/library/flatten/testdata/parent/error_parent-has-plugins.yaml b/pkg/library/flatten/testdata/parent/error_parent-has-plugins.yaml new file mode 100644 index 000000000..b88051064 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/error_parent-has-plugins.yaml @@ -0,0 +1,26 @@ +name: "Fails when parent is not flattened (has plugin)" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + plugin: + id: parent-plugin + +output: + errRegexp: "parents containing plugins or parents are not supported" diff --git a/pkg/library/flatten/testdata/parent/resolve-parent-and-plugins.yaml b/pkg/library/flatten/testdata/parent/resolve-parent-and-plugins.yaml new file mode 100644 index 000000000..11dd4dd17 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/resolve-parent-and-plugins.yaml @@ -0,0 +1,70 @@ +name: "Resolve parent and plugins" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + - name: test-plugin + plugin: + uri: https://test-plugin.io/test-plugin + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + devfileResources: + "https://test-plugin.io/test-plugin": + schemaVersion: 2.1.0 + metadata: + name: test-plugin + components: + - name: plugin-component + container: + image: plugin-img + env: + - name: plugin-env + value: original-value +output: + workspace: + components: + - name: parent-component + attributes: + controller.devfile.io/imported-by: parent + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container + - name: plugin-component + attributes: + controller.devfile.io/imported-by: test-plugin + container: + image: plugin-img + env: + - name: plugin-env + value: original-value diff --git a/pkg/library/flatten/testdata/parent/resolve-parent-by-id.yaml b/pkg/library/flatten/testdata/parent/resolve-parent-by-id.yaml new file mode 100644 index 000000000..ba86550f0 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/resolve-parent-by-id.yaml @@ -0,0 +1,46 @@ +name: "Resolve parent by ID" + +input: + workspace: + parent: + id: test/parent/id + registryUrl: https://test-registry.io/ + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devfileResources: + "https://test-registry.io/test/parent/id": + schemaVersion: 2.1.0 + metadata: + name: parent-devfile + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + attributes: + controller.devfile.io/imported-by: parent + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/library/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml b/pkg/library/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml new file mode 100644 index 000000000..68e697cd2 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/resolve-parent-by-k8s-reference.yaml @@ -0,0 +1,48 @@ +name: "Resolve parent by Kubernetes reference" + +input: + workspace: + parent: + kubernetes: + name: test-parent-k8s + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devworkspaceResources: + test-parent-k8s: + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: parent-devworkspacetemplate + spec: + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + attributes: + controller.devfile.io/imported-by: parent + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/library/flatten/testdata/parent/resolve-parent-by-uri.yaml b/pkg/library/flatten/testdata/parent/resolve-parent-by-uri.yaml new file mode 100644 index 000000000..63a9a65e4 --- /dev/null +++ b/pkg/library/flatten/testdata/parent/resolve-parent-by-uri.yaml @@ -0,0 +1,45 @@ +name: "Resolve parent by URI" + +input: + workspace: + parent: + uri: https://test.io/path/to/parent + components: + - name: parent-component + container: + env: + - name: test-env + value: test-value + components: + - name: regular-component + container: + image: regular-test-image + name: regular-container + devfileResources: + "https://test.io/path/to/parent": + schemaVersion: 2.1.0 + metadata: + name: parent-devfile + components: + - name: parent-component + container: + image: test-img + env: + - name: test-env + value: original-value + +output: + workspace: + components: + - name: parent-component + attributes: + controller.devfile.io/imported-by: parent + container: + image: test-img + env: + - name: test-env + value: test-value + - name: regular-component + container: + image: regular-test-image + name: regular-container diff --git a/pkg/library/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml b/pkg/library/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml new file mode 100644 index 000000000..c2b0ed51b --- /dev/null +++ b/pkg/library/flatten/testdata/plugin-id/error_fetch-unparseable-file.yaml @@ -0,0 +1,23 @@ +name: "DevWorkspace registry contains non-devfile type content" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + devworkspaceResources: + "https://test-registry.io/subpath/my/test/plugin": + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + errRegexp: "could not find devfile or devworkspace object at 'https://test-registry.io/subpath/my/test/plugin'" diff --git a/pkg/library/flatten/testdata/plugin-id/error_invalid-schema-version.yaml b/pkg/library/flatten/testdata/plugin-id/error_invalid-schema-version.yaml index 9b2839acc..ceb400f11 100644 --- a/pkg/library/flatten/testdata/plugin-id/error_invalid-schema-version.yaml +++ b/pkg/library/flatten/testdata/plugin-id/error_invalid-schema-version.yaml @@ -7,8 +7,8 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + devfileResources: + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 1.0.0 metadata: name: "plugin-a" diff --git a/pkg/library/flatten/testdata/plugin-id/error_on-fetch.yaml b/pkg/library/flatten/testdata/plugin-id/error_on-fetch.yaml index 92a44a691..6267d3501 100644 --- a/pkg/library/flatten/testdata/plugin-id/error_on-fetch.yaml +++ b/pkg/library/flatten/testdata/plugin-id/error_on-fetch.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" errors: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": message: "testing error" output: diff --git a/pkg/library/flatten/testdata/plugin-id/error_plugin-not-found.yaml b/pkg/library/flatten/testdata/plugin-id/error_plugin-not-found.yaml index 36da9f474..eccc35839 100644 --- a/pkg/library/flatten/testdata/plugin-id/error_plugin-not-found.yaml +++ b/pkg/library/flatten/testdata/plugin-id/error_plugin-not-found.yaml @@ -8,7 +8,7 @@ input: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" errors: - "https://test-registry.io/subpath/plugins/my/test/plugin": + "https://test-registry.io/subpath/my/test/plugin": statusCode: 404 output: diff --git a/pkg/library/flatten/testdata/plugin-id/error_unparseable-url.yaml b/pkg/library/flatten/testdata/plugin-id/error_unparseable-url.yaml index 62fd534ac..4669ce023 100644 --- a/pkg/library/flatten/testdata/plugin-id/error_unparseable-url.yaml +++ b/pkg/library/flatten/testdata/plugin-id/error_unparseable-url.yaml @@ -9,4 +9,4 @@ input: registryUrl: ":/test-registry.io/subpath" output: - errRegexp: "failed to parse registry URL for plugin test-plugin" + errRegexp: "failed to parse registry URL for component test-plugin" diff --git a/pkg/library/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml b/pkg/library/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml new file mode 100644 index 000000000..8cb47ed55 --- /dev/null +++ b/pkg/library/flatten/testdata/plugin-id/resolve-devworkspace-instead-of-devfile.yaml @@ -0,0 +1,32 @@ +name: "DevWorkspace references DevWorkspaceTemplate plugin from registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + id: my/test/plugin + registryUrl: "https://test-registry.io/subpath" + devworkspaceResources: + "https://test-registry.io/subpath/my/test/plugin": + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + workspace: + components: + - name: plugin-a + attributes: + controller.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image diff --git a/pkg/library/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml b/pkg/library/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml index 3c2ed8075..aa93e9bb0 100644 --- a/pkg/library/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml +++ b/pkg/library/flatten/testdata/plugin-id/resolve-plugin-by-id.yaml @@ -7,8 +7,8 @@ input: plugin: id: my/test/plugin registryUrl: "https://test-registry.io/subpath" - devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + devfileResources: + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: name: "plugin-a" diff --git a/pkg/library/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml b/pkg/library/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml index 30201375d..d0fa411a8 100644 --- a/pkg/library/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml +++ b/pkg/library/flatten/testdata/plugin-id/resolve-plugin-multiple-registries.yaml @@ -11,8 +11,8 @@ input: plugin: id: my/test/plugin-2 registryUrl: "https://test-registry-2.io/subpath" - devfilePlugins: - "https://test-registry.io/subpath/plugins/my/test/plugin": + devfileResources: + "https://test-registry.io/subpath/my/test/plugin": schemaVersion: 2.0.0 metadata: name: "plugin-a" @@ -21,7 +21,7 @@ input: container: name: test-container image: test-image - "https://test-registry-2.io/subpath/plugins/my/test/plugin-2": + "https://test-registry-2.io/subpath/my/test/plugin-2": schemaVersion: 2.0.0 metadata: name: "plugin-b" diff --git a/pkg/library/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml b/pkg/library/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml new file mode 100644 index 000000000..c4aa2e2ec --- /dev/null +++ b/pkg/library/flatten/testdata/plugin-uri/error_fetch-unparseable-file.yaml @@ -0,0 +1,22 @@ +name: "DevWorkspace reference URI containing non-devfile type content" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + devworkspaceResources: + "https://my-plugin.io/test": + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + errRegexp: "could not find devfile or devworkspace object at 'https://my-plugin.io/test'" diff --git a/pkg/library/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml b/pkg/library/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml index ea5f777a8..9a92e17d4 100644 --- a/pkg/library/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml +++ b/pkg/library/flatten/testdata/plugin-uri/error_invalid-schema-version.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: https://test-registry.io/old-devfiles - devfilePlugins: + devfileResources: "https://test-registry.io/old-devfiles": schemaVersion: 1.0.0 metadata: diff --git a/pkg/library/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml b/pkg/library/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml new file mode 100644 index 000000000..5f97a7285 --- /dev/null +++ b/pkg/library/flatten/testdata/plugin-uri/resolve-devworkspace-instead-of-devfile.yaml @@ -0,0 +1,31 @@ +name: "DevWorkspace references DevWorkspaceTemplate plugin from registry" + +input: + workspace: + components: + - name: test-plugin + plugin: + uri: "https://my-plugin.io/test" + devworkspaceResources: + "https://my-plugin.io/test": + kind: DevWorkspaceTemplate + apiVersion: workspace.devfile.io/v1alpha2 + metadata: + name: test-plugin + spec: + components: + - name: plugin-a + container: + name: test-container + image: test-image + + +output: + workspace: + components: + - name: plugin-a + attributes: + controller.devfile.io/imported-by: "test-plugin" + container: + name: test-container + image: test-image diff --git a/pkg/library/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml b/pkg/library/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml index 2be037828..410e10695 100644 --- a/pkg/library/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml +++ b/pkg/library/flatten/testdata/plugin-uri/resolve-multiple-plugins-by-uri.yaml @@ -9,7 +9,7 @@ input: - name: test-plugin-2 plugin: uri: "https://my-plugin-alt.io/test" - devfilePlugins: + devfileResources: "https://my-plugin.io/test": schemaVersion: 2.0.0 metadata: diff --git a/pkg/library/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml b/pkg/library/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml index fd928c4d2..977638635 100644 --- a/pkg/library/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml +++ b/pkg/library/flatten/testdata/plugin-uri/resolve-plugin-by-uri.yaml @@ -6,7 +6,7 @@ input: - name: test-plugin plugin: uri: "https://my-plugin.io/test" - devfilePlugins: + devfileResources: "https://my-plugin.io/test": schemaVersion: 2.0.0 metadata: