From 4506893f25f9fb7da7ad2382215942bb236c5a0a Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 11:15:16 +0200 Subject: [PATCH 01/11] Get values from context --- pkg/dev/kubedev/kubedev.go | 33 +--- pkg/dev/podmandev/podmandev.go | 12 +- .../adapters/kubernetes/component/adapter.go | 171 ++++++++++-------- .../kubernetes/component/adapter_test.go | 26 +-- .../adapters/kubernetes/component/push.go | 39 ++-- pkg/devfile/adapters/types.go | 1 - pkg/watch/watch.go | 51 +++--- pkg/watch/watch_test.go | 5 + 8 files changed, 184 insertions(+), 154 deletions(-) diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 47972932461..678a653f511 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "path/filepath" "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -15,6 +14,7 @@ import ( "github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/exec" "github.com/redhat-developer/odo/pkg/kclient" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/redhat-developer/odo/pkg/portForward" "github.com/redhat-developer/odo/pkg/preference" "github.com/redhat-developer/odo/pkg/sync" @@ -25,7 +25,6 @@ import ( "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component" "github.com/redhat-developer/odo/pkg/devfile/location" - odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/redhat-developer/odo/pkg/watch" ) @@ -86,10 +85,7 @@ func (o *DevClient) Start( klog.V(4).Infoln("Creating new adapter") var ( - devfileObj = odocontext.GetDevfileObj(ctx) - devfilePath = odocontext.GetDevfilePath(ctx) - path = filepath.Dir(devfilePath) - componentName = odocontext.GetComponentName(ctx) + devfileObj = odocontext.GetDevfileObj(ctx) ) adapter := component.NewKubernetesAdapter( @@ -100,16 +96,11 @@ func (o *DevClient) Start( o.syncClient, o.execClient, o.configAutomountClient, - component.AdapterContext{ - ComponentName: componentName, - Context: path, - AppName: odocontext.GetApplication(ctx), - Devfile: *devfileObj, - FS: o.filesystem, - }) + o.filesystem, + *devfileObj, + ) pushParameters := adapters.PushParameters{ - Path: path, IgnoredFiles: options.IgnorePaths, Debug: options.Debug, DevfileBuildCmd: options.BuildCommand, @@ -130,13 +121,8 @@ func (o *DevClient) Start( klog.V(4).Infoln("Successfully created inner-loop resources") watchParameters := watch.WatchParameters{ - DevfilePath: devfilePath, - Path: path, - ComponentName: componentName, - ApplicationName: odocontext.GetApplication(ctx), DevfileWatchHandler: o.regenerateAdapterAndPush, FileIgnores: options.IgnorePaths, - InitialDevfileObj: *devfileObj, Debug: options.Debug, DevfileBuildCmd: options.BuildCommand, DevfileRunCmd: options.RunCommand, @@ -183,12 +169,7 @@ func (o *DevClient) regenerateComponentAdapterFromWatchParams(parameters watch.W o.syncClient, o.execClient, o.configAutomountClient, - component.AdapterContext{ - ComponentName: parameters.ComponentName, - Context: parameters.Path, - AppName: parameters.ApplicationName, - Devfile: devObj, - FS: o.filesystem, - }, + o.filesystem, + devObj, ), nil } diff --git a/pkg/dev/podmandev/podmandev.go b/pkg/dev/podmandev/podmandev.go index e46a4d579df..3827d9de416 100644 --- a/pkg/dev/podmandev/podmandev.go +++ b/pkg/dev/podmandev/podmandev.go @@ -82,11 +82,8 @@ func (o *DevClient) Start( options dev.StartOptions, ) error { var ( - appName = odocontext.GetApplication(ctx) - componentName = odocontext.GetComponentName(ctx) - devfileObj = odocontext.GetDevfileObj(ctx) - devfilePath = odocontext.GetDevfilePath(ctx) - path = filepath.Dir(devfilePath) + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) componentStatus = watch.ComponentStatus{ ImageComponentsAutoApplied: make(map[string]devfilev1.ImageComponent), @@ -101,11 +98,6 @@ func (o *DevClient) Start( watch.PrintInfoMessage(out, path, options.WatchFiles, promptMessage) watchParameters := watch.WatchParameters{ - DevfilePath: devfilePath, - Path: path, - ComponentName: componentName, - ApplicationName: appName, - InitialDevfileObj: *devfileObj, DevfileWatchHandler: o.watchHandler, FileIgnores: options.IgnorePaths, Debug: options.Debug, diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index 17531a76ed5..e4497603eea 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "path/filepath" "reflect" "strings" "time" @@ -27,6 +28,7 @@ import ( "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/machineoutput" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/redhat-developer/odo/pkg/port" "github.com/redhat-developer/odo/pkg/portForward" "github.com/redhat-developer/odo/pkg/preference" @@ -58,18 +60,10 @@ type Adapter struct { syncClient sync.Client execClient exec.Client configAutomountClient configAutomount.Client + fs filesystem.Filesystem // FS is the object used for building image component if present - AdapterContext - logger machineoutput.MachineEventLoggingClient -} - -// AdapterContext is a construct that is common to all adapters -type AdapterContext struct { - ComponentName string // ComponentName is the odo component name, it is NOT related to any devfile components - Context string // Context is the given directory containing the source code and configs - AppName string // the application name associated to a component - Devfile parser.DevfileObj // Devfile is the object returned by the Devfile parser - FS filesystem.Filesystem // FS is the object used for building image component if present + devfile parser.DevfileObj // Devfile is the object returned by the Devfile parser + logger machineoutput.MachineEventLoggingClient } var _ ComponentAdapter = (*Adapter)(nil) @@ -83,7 +77,8 @@ func NewKubernetesAdapter( syncClient sync.Client, execClient exec.Client, configAutomountClient configAutomount.Client, - context AdapterContext, + fs filesystem.Filesystem, + devfile parser.DevfileObj, ) Adapter { return Adapter{ kubeClient: kubernetesClient, @@ -93,8 +88,10 @@ func NewKubernetesAdapter( syncClient: syncClient, execClient: execClient, configAutomountClient: configAutomountClient, - AdapterContext: context, - logger: machineoutput.NewMachineEventLoggingClient(), + fs: fs, + devfile: devfile, + + logger: machineoutput.NewMachineEventLoggingClient(), } } @@ -103,8 +100,15 @@ func NewKubernetesAdapter( // The componentStatus will be modified to reflect the status of the component when the function returns func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) (err error) { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + // preliminary checks - err = dfutil.ValidateK8sResourceName("component name", a.ComponentName) + err = dfutil.ValidateK8sResourceName("component name", componentName) if err != nil { return err } @@ -120,12 +124,12 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } klog.V(4).Infof("component state: %q\n", componentStatus.State) - err = a.buildPushAutoImageComponents(ctx, a.FS, a.Devfile, componentStatus) + err = a.buildPushAutoImageComponents(ctx, a.fs, a.devfile, componentStatus) if err != nil { return err } - deployment, deploymentExists, err := a.getComponentDeployment() + deployment, deploymentExists, err := a.getComponentDeployment(ctx) if err != nil { return err } @@ -135,11 +139,11 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Set the mode to Dev since we are using "odo dev" here - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.Devfile.Data.GetMetadata()) - labels := odolabels.GetLabels(a.ComponentName, a.AppName, runtime, odolabels.ComponentDevMode, false) + runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) + labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, false) var updated bool - deployment, updated, err = a.createOrUpdateComponent(deploymentExists, libdevfile.DevfileCommands{ + deployment, updated, err = a.createOrUpdateComponent(ctx, deploymentExists, libdevfile.DevfileCommands{ BuildCmd: parameters.DevfileBuildCmd, RunCmd: parameters.DevfileRunCmd, DebugCmd: parameters.DevfileDebugCmd, @@ -150,9 +154,9 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c ownerReference := generator.GetOwnerReference(deployment) // Delete remote resources that are not present in the Devfile - selector := odolabels.GetSelector(a.ComponentName, a.AppName, odolabels.ComponentDevMode, false) + selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, false) - objectsToRemove, serviceBindingSecretsToRemove, err := a.getRemoteResourcesNotPresentInDevfile(selector) + objectsToRemove, serviceBindingSecretsToRemove, err := a.getRemoteResourcesNotPresentInDevfile(ctx, selector) if err != nil { return fmt.Errorf("unable to determine resources to delete: %w", err) } @@ -172,12 +176,12 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Create all the K8s components defined in the devfile - _, err = a.pushDevfileKubernetesComponents(labels, odolabels.ComponentDevMode, ownerReference) + _, err = a.pushDevfileKubernetesComponents(ctx, labels, odolabels.ComponentDevMode, ownerReference) if err != nil { return err } - err = a.updatePVCsOwnerReferences(ownerReference) + err = a.updatePVCsOwnerReferences(ctx, ownerReference) if err != nil { return err } @@ -195,7 +199,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c return nil } - injected, err := a.bindingClient.CheckServiceBindingsInjectionDone(a.ComponentName, a.AppName) + injected, err := a.bindingClient.CheckServiceBindingsInjectionDone(componentName, appName) if err != nil { return err } @@ -206,7 +210,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Check if endpoints changed in Devfile - portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(a.Devfile, parameters.Debug) + portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(a.devfile, parameters.Debug) if err != nil { return err } @@ -218,9 +222,9 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Now the Deployment has a Ready replica, we can get the Pod to work inside it - pod, err := a.kubeClient.GetPodUsingComponentName(a.ComponentName) + pod, err := a.kubeClient.GetPodUsingComponentName(componentName) if err != nil { - return fmt.Errorf("unable to get pod for component %s: %w", a.ComponentName, err) + return fmt.Errorf("unable to get pod for component %s: %w", componentName, err) } // Find at least one pod with the source volume mounted, error out if none can be found @@ -242,7 +246,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Get a sync adapter. Check if project files have changed and sync accordingly compInfo := sync.ComponentInfo{ - ComponentName: a.ComponentName, + ComponentName: componentName, ContainerName: containerName, PodName: pod.GetName(), SyncFolder: syncFolder, @@ -256,7 +260,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } syncParams := sync.SyncParameters{ - Path: parameters.Path, + Path: path, WatchFiles: parameters.WatchFiles, WatchDeletedFiles: parameters.WatchDeletedFiles, IgnoredFiles: parameters.IgnoredFiles, @@ -270,21 +274,21 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c execRequired, err := a.syncClient.SyncFiles(ctx, syncParams) if err != nil { componentStatus.State = watch.StateReady - return fmt.Errorf("failed to sync to component with name %s: %w", a.ComponentName, err) + return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err) } s.End(true) // PostStart events from the devfile will only be executed when the component // didn't previously exist - if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(a.Devfile) { - err = libdevfile.ExecPostStartEvents(ctx, a.Devfile, component.NewExecHandler(a.kubeClient, a.execClient, a.AppName, a.ComponentName, pod.Name, "Executing post-start command in container", parameters.Show)) + if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(a.devfile) { + err = libdevfile.ExecPostStartEvents(ctx, a.devfile, component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) if err != nil { return err } } componentStatus.PostStartEventsDone = true - cmd, err := libdevfile.ValidateAndGetCommand(a.Devfile, cmdName, cmdKind) + cmd, err := libdevfile.ValidateAndGetCommand(a.devfile, cmdName, cmdKind) if err != nil { return err } @@ -296,13 +300,13 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c var running bool var isComposite bool cmdHandler := runHandler{ - fs: a.FS, + fs: a.fs, execClient: a.execClient, kubeClient: a.kubeClient, - appName: a.AppName, - componentName: a.ComponentName, - devfile: a.Devfile, - path: parameters.Path, + appName: appName, + componentName: componentName, + devfile: a.devfile, + path: path, podName: pod.GetName(), ctx: ctx, } @@ -330,9 +334,9 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command, // the handler we pass will be called for each command in that composite command. doExecuteBuildCommand := func() error { - execHandler := component.NewExecHandler(a.kubeClient, a.execClient, a.AppName, a.ComponentName, pod.Name, + execHandler := component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Building your application in container", parameters.Show) - return libdevfile.Build(ctx, a.Devfile, parameters.DevfileBuildCmd, execHandler) + return libdevfile.Build(ctx, a.devfile, parameters.DevfileBuildCmd, execHandler) } if running { if cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) { @@ -345,14 +349,14 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c return err } } - err = libdevfile.ExecuteCommandByNameAndKind(ctx, a.Devfile, cmdName, cmdKind, &cmdHandler, false) + err = libdevfile.ExecuteCommandByNameAndKind(ctx, a.devfile, cmdName, cmdKind, &cmdHandler, false) if err != nil { return err } } if podChanged || portsChanged { - a.portForwardClient.StopPortForwarding(ctx, a.ComponentName) + a.portForwardClient.StopPortForwarding(ctx, componentName) } // Check that the application is actually listening on the ports declared in the Devfile, so we are sure that port-forwarding will work @@ -365,7 +369,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c fmt.Fprintln(log.GetStdout()) } - err = a.portForwardClient.StartPortForwarding(ctx, a.Devfile, a.ComponentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) + err = a.portForwardClient.StartPortForwarding(ctx, a.devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) if err != nil { return adapters.NewErrPortForward(err) } @@ -379,24 +383,30 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // with the expected spec. // Returns the new deployment and if the generation of the deployment has been updated func (a *Adapter) createOrUpdateComponent( + ctx context.Context, componentExists bool, commands libdevfile.DevfileCommands, deployment *appsv1.Deployment, ) (*appsv1.Deployment, bool, error) { - componentName := a.ComponentName + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.Devfile.Data.GetMetadata()) + runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) // Set the labels - labels := odolabels.GetLabels(componentName, a.AppName, runtime, odolabels.ComponentDevMode, true) + labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, true) annotations := make(map[string]string) - odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.AdapterContext.Devfile.Data.GetMetadata())) + odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.devfile.Data.GetMetadata())) odolabels.AddCommonAnnotations(annotations) klog.V(4).Infof("We are deploying these annotations: %s", annotations) - deploymentObjectMeta, err := a.generateDeploymentObjectMeta(deployment, labels, annotations) + deploymentObjectMeta, err := a.generateDeploymentObjectMeta(ctx, deployment, labels, annotations) if err != nil { return nil, false, err } @@ -405,7 +415,7 @@ func (a *Adapter) createOrUpdateComponent( if err != nil { return nil, false, err } - podTemplateSpec, err := generator.GetPodTemplateSpec(a.Devfile, generator.PodTemplateParams{ + podTemplateSpec, err := generator.GetPodTemplateSpec(a.devfile, generator.PodTemplateParams{ ObjectMeta: deploymentObjectMeta, PodSecurityAdmissionPolicy: policy, }) @@ -419,13 +429,13 @@ func (a *Adapter) createOrUpdateComponent( initContainers := podTemplateSpec.Spec.InitContainers - containers, err = utils.UpdateContainersEntrypointsIfNeeded(a.Devfile, containers, commands.BuildCmd, commands.RunCmd, commands.DebugCmd) + containers, err = utils.UpdateContainersEntrypointsIfNeeded(a.devfile, containers, commands.BuildCmd, commands.RunCmd, commands.DebugCmd) if err != nil { return nil, false, err } // Returns the volumes to add to the PodTemplate and adds volumeMounts to the containers and initContainers - volumes, err := a.buildVolumes(containers, initContainers) + volumes, err := a.buildVolumes(ctx, containers, initContainers) if err != nil { return nil, false, err } @@ -449,7 +459,7 @@ func (a *Adapter) createOrUpdateComponent( originalGeneration = deployment.GetGeneration() } - deployment, err = generator.GetDeployment(a.Devfile, deployParams) + deployment, err = generator.GetDeployment(a.devfile, deployParams) if err != nil { return nil, false, err } @@ -457,7 +467,7 @@ func (a *Adapter) createOrUpdateComponent( deployment.Annotations = make(map[string]string) } - if vcsUri := util.GetGitOriginPath(a.Context); vcsUri != "" { + if vcsUri := util.GetGitOriginPath(path); vcsUri != "" { deployment.Annotations["app.openshift.io/vcs-uri"] = vcsUri } @@ -466,7 +476,7 @@ func (a *Adapter) createOrUpdateComponent( serviceAnnotations["service.binding/backend_ip"] = "path={.spec.clusterIP}" serviceAnnotations["service.binding/backend_port"] = "path={.spec.ports},elementType=sliceOfMaps,sourceKey=name,sourceValue=port" - serviceName, err := util.NamespaceKubernetesObjectWithTrim(componentName, a.AppName, 63) + serviceName, err := util.NamespaceKubernetesObjectWithTrim(componentName, appName, 63) if err != nil { return nil, false, err } @@ -475,7 +485,7 @@ func (a *Adapter) createOrUpdateComponent( ObjectMeta: serviceObjectMeta, SelectorLabels: selectorLabels, } - svc, err := generator.GetService(a.Devfile, serviceParams, parsercommon.DevfileOptions{}) + svc, err := generator.GetService(a.devfile, serviceParams, parsercommon.DevfileOptions{}) if err != nil { return nil, false, err @@ -497,7 +507,7 @@ func (a *Adapter) createOrUpdateComponent( } klog.V(2).Infof("Successfully updated component %v", componentName) ownerReference := generator.GetOwnerReference(deployment) - err = a.createOrUpdateServiceForComponent(svc, componentName, ownerReference) + err = a.createOrUpdateServiceForComponent(ctx, svc, ownerReference) if err != nil { return nil, false, err } @@ -539,31 +549,35 @@ func (a *Adapter) createOrUpdateComponent( // - (side effect on input parameters) adds volumeMounts to containers and initContainers for the PVCs and Ephemeral volumes // - (side effect on input parameters) adds volumeMounts for automounted volumes // => Returns the list of Volumes to add to the PodTemplate -func (a *Adapter) buildVolumes(containers, initContainers []corev1.Container) ([]corev1.Volume, error) { +func (a *Adapter) buildVolumes(ctx context.Context, containers, initContainers []corev1.Container) ([]corev1.Volume, error) { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.Devfile.Data.GetMetadata()) + runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) - storageClient := storagepkg.NewClient(a.ComponentName, a.AppName, storagepkg.ClientOptions{ + storageClient := storagepkg.NewClient(componentName, appName, storagepkg.ClientOptions{ Client: a.kubeClient, Runtime: runtime, }) // Create the PVC for the project sources, if not ephemeral - err := storage.HandleOdoSourceStorage(a.kubeClient, storageClient, a.ComponentName, a.prefClient.GetEphemeralSourceVolume()) + err := storage.HandleOdoSourceStorage(a.kubeClient, storageClient, componentName, a.prefClient.GetEphemeralSourceVolume()) if err != nil { return nil, err } // Create PVCs for non-ephemeral Volumes defined in the Devfile // and returns the Ephemeral volumes defined in the Devfile - ephemerals, err := storagepkg.Push(storageClient, a.Devfile) + ephemerals, err := storagepkg.Push(storageClient, a.devfile) if err != nil { return nil, err } // get all the PVCs from the cluster belonging to the component // These PVCs have been created earlier with `storage.HandleOdoSourceStorage` and `storagepkg.Push` - pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", a.ComponentName)) + pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) if err != nil { return nil, err } @@ -586,13 +600,13 @@ func (a *Adapter) buildVolumes(containers, initContainers []corev1.Container) ([ utils.AddOdoMandatoryVolume(containers) // Get PVC volumes and Volume Mounts - pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(a.Devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) + pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(a.devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) if err != nil { return nil, err } allVolumes = append(allVolumes, pvcVolumes...) - ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(a.Devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) + ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(a.devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) if err != nil { return nil, err } @@ -607,8 +621,12 @@ func (a *Adapter) buildVolumes(containers, initContainers []corev1.Container) ([ return allVolumes, nil } -func (a *Adapter) createOrUpdateServiceForComponent(svc *corev1.Service, componentName string, ownerReference metav1.OwnerReference) error { - oldSvc, err := a.kubeClient.GetOneService(a.ComponentName, a.AppName, true) +func (a *Adapter) createOrUpdateServiceForComponent(ctx context.Context, svc *corev1.Service, ownerReference metav1.OwnerReference) error { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) + oldSvc, err := a.kubeClient.GetOneService(componentName, appName, true) originOwnerReferences := svc.OwnerReferences if err != nil { // no old service was found, create a new one @@ -645,11 +663,15 @@ func (a *Adapter) createOrUpdateServiceForComponent(svc *corev1.Service, compone // generateDeploymentObjectMeta generates a ObjectMeta object for the given deployment's name, labels and annotations // if no deployment exists, it creates a new deployment name -func (a Adapter) generateDeploymentObjectMeta(deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) { +func (a Adapter) generateDeploymentObjectMeta(ctx context.Context, deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) if deployment != nil { return generator.GetObjectMeta(deployment.Name, a.kubeClient.GetCurrentNamespace(), labels, annotations), nil } else { - deploymentName, err := util.NamespaceKubernetesObject(a.ComponentName, a.AppName) + deploymentName, err := util.NamespaceKubernetesObject(componentName, appName) if err != nil { return metav1.ObjectMeta{}, err } @@ -660,7 +682,12 @@ func (a Adapter) generateDeploymentObjectMeta(deployment *appsv1.Deployment, lab // getRemoteResourcesNotPresentInDevfile compares the list of Devfile K8s component and remote K8s resources // and returns a list of the remote resources not present in the Devfile and in case the SBO is not installed, a list of service binding secrets that must be deleted; // it ignores the core components (such as deployments, svc, pods; all resources with `component:` label) -func (a Adapter) getRemoteResourcesNotPresentInDevfile(selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { +func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { + var ( + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + currentNamespace := a.kubeClient.GetCurrentNamespace() allRemoteK8sResources, err := a.kubeClient.GetAllResourcesFromSelector(selector, currentNamespace) if err != nil { @@ -683,7 +710,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(selector string) (objects } var devfileK8sResources []devfilev1.Component - devfileK8sResources, err = libdevfile.GetK8sAndOcComponentsToPush(a.Devfile, true) + devfileK8sResources, err = libdevfile.GetK8sAndOcComponentsToPush(a.devfile, true) if err != nil { return nil, nil, fmt.Errorf("unable to obtain resources from the Devfile: %w", err) } @@ -692,7 +719,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(selector string) (objects var devfileK8sResourcesUnstructured []unstructured.Unstructured for _, devfileK := range devfileK8sResources { var devfileKUnstructuredList []unstructured.Unstructured - devfileKUnstructuredList, err = libdevfile.GetK8sComponentAsUnstructuredList(a.Devfile, devfileK.Name, a.Context, devfilefs.DefaultFs{}) + devfileKUnstructuredList, err = libdevfile.GetK8sComponentAsUnstructuredList(a.devfile, devfileK.Name, path, devfilefs.DefaultFs{}) if err != nil { return nil, nil, fmt.Errorf("unable to read the resource: %w", err) } diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/devfile/adapters/kubernetes/component/adapter_test.go index d987508b6f8..2e26c72b96c 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter_test.go @@ -1,6 +1,7 @@ package component import ( + "context" "errors" "testing" @@ -23,6 +24,7 @@ import ( "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil" v1 "k8s.io/api/apps/v1" @@ -106,12 +108,6 @@ func TestCreateOrUpdateComponent(t *testing.T) { }(), } - adapterCtx := AdapterContext{ - ComponentName: testComponentName, - AppName: testAppName, - Devfile: devObj, - } - fkclient, fkclientset := kclient.FakeNew() fkclientset.Kubernetes.PrependReactor("patch", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) { @@ -134,8 +130,12 @@ func TestCreateOrUpdateComponent(t *testing.T) { fakePrefClient.EXPECT().GetEphemeralSourceVolume().AnyTimes() fakeConfigAutomount := configAutomount.NewMockClient(ctrl) fakeConfigAutomount.EXPECT().GetAutomountingVolumes().AnyTimes() - componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, nil, nil, fakeConfigAutomount, adapterCtx) - _, _, err := componentAdapter.createOrUpdateComponent(tt.running, libdevfile.DevfileCommands{}, nil) + componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, nil, nil, fakeConfigAutomount, nil, devObj) + ctx := context.Background() + ctx = odocontext.WithApplication(ctx, "app") + ctx = odocontext.WithComponentName(ctx, "my-component") + ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile") + _, _, err := componentAdapter.createOrUpdateComponent(ctx, tt.running, libdevfile.DevfileCommands{}, nil) // Checks for unexpected error cases if !tt.wantErr == (err != nil) { @@ -242,12 +242,12 @@ func TestAdapter_generateDeploymentObjectMeta(t *testing.T) { a := Adapter{ kubeClient: fakeClient, - AdapterContext: AdapterContext{ - ComponentName: tt.fields.componentName, - AppName: tt.fields.appName, - }, } - got, err := a.generateDeploymentObjectMeta(tt.fields.deployment, tt.args.labels, tt.args.annotations) + ctx := context.Background() + ctx = odocontext.WithApplication(ctx, "app") + ctx = odocontext.WithComponentName(ctx, "nodejs") + ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile") + got, err := a.generateDeploymentObjectMeta(ctx, tt.fields.deployment, tt.args.labels, tt.args.annotations) if (err != nil) != tt.wantErr { t.Errorf("generateDeploymentObjectMeta() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/devfile/adapters/kubernetes/component/push.go b/pkg/devfile/adapters/kubernetes/component/push.go index 4587da2b880..eda541dc1ca 100644 --- a/pkg/devfile/adapters/kubernetes/component/push.go +++ b/pkg/devfile/adapters/kubernetes/component/push.go @@ -3,6 +3,7 @@ package component import ( "context" "fmt" + "path/filepath" "reflect" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -17,6 +18,7 @@ import ( "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" "github.com/redhat-developer/odo/pkg/libdevfile" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/redhat-developer/odo/pkg/service" "github.com/redhat-developer/odo/pkg/testingutil/filesystem" "github.com/redhat-developer/odo/pkg/watch" @@ -24,16 +26,21 @@ import ( // getComponentDeployment returns the deployment associated with the component, if deployed // and indicate if the deployment has been found -func (a *Adapter) getComponentDeployment() (*appsv1.Deployment, bool, error) { +func (a *Adapter) getComponentDeployment(ctx context.Context) (*appsv1.Deployment, bool, error) { + var ( + componentName = odocontext.GetComponentName(ctx) + appName = odocontext.GetApplication(ctx) + ) + // Get the Dev deployment: // Since `odo deploy` can theoretically deploy a deployment as well with the same instance name // we make sure that we are retrieving the deployment with the Dev mode, NOT Deploy. - selectorLabels := odolabels.GetSelector(a.ComponentName, a.AppName, odolabels.ComponentDevMode, true) + selectorLabels := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, true) deployment, err := a.kubeClient.GetOneDeploymentFromSelector(selectorLabels) if err != nil { if _, ok := err.(*kclient.DeploymentNotFoundError); !ok { - return nil, false, fmt.Errorf("unable to determine if component %s exists: %w", a.ComponentName, err) + return nil, false, fmt.Errorf("unable to determine if component %s exists: %w", componentName, err) } } componentExists := deployment != nil @@ -83,29 +90,35 @@ func (a *Adapter) buildPushAutoImageComponents(ctx context.Context, fs filesyste // pushDevfileKubernetesComponents gets the Kubernetes components from the Devfile and push them to the cluster // adding the specified labels and ownerreference to them func (a *Adapter) pushDevfileKubernetesComponents( + ctx context.Context, labels map[string]string, mode string, reference metav1.OwnerReference, ) ([]devfilev1.Component, error) { + var ( + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + // fetch the "kubernetes inlined components" to create them on cluster // from odo standpoint, these components contain yaml manifest of ServiceBinding - k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(a.Devfile, false) + k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(a.devfile, false) if err != nil { return nil, fmt.Errorf("error while trying to fetch service(s) from devfile: %w", err) } // validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster - err = component.ValidateResourcesExist(a.kubeClient, a.Devfile, k8sComponents, a.Context) + err = component.ValidateResourcesExist(a.kubeClient, a.devfile, k8sComponents, path) if err != nil { return nil, err } // Set the annotations for the component type annotations := make(map[string]string) - odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.AdapterContext.Devfile.Data.GetMetadata())) + odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.devfile.Data.GetMetadata())) // create the Kubernetes objects from the manifest and delete the ones not in the devfile - err = service.PushKubernetesResources(a.kubeClient, a.Devfile, k8sComponents, labels, annotations, a.Context, mode, reference) + err = service.PushKubernetesResources(a.kubeClient, a.devfile, k8sComponents, labels, annotations, path, mode, reference) if err != nil { return nil, fmt.Errorf("failed to create Kubernetes resources associated with the component: %w", err) } @@ -113,13 +126,13 @@ func (a *Adapter) pushDevfileKubernetesComponents( } func (a *Adapter) getPushDevfileCommands(parameters adapters.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { - pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(a.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) + pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(a.devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) if err != nil { return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) } if parameters.Debug { - pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(a.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) + pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(a.devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) if e != nil { return nil, fmt.Errorf("debug command is not valid: %w", e) } @@ -129,9 +142,13 @@ func (a *Adapter) getPushDevfileCommands(parameters adapters.PushParameters) (ma return pushDevfileCommands, nil } -func (a *Adapter) updatePVCsOwnerReferences(ownerReference metav1.OwnerReference) error { +func (a *Adapter) updatePVCsOwnerReferences(ctx context.Context, ownerReference metav1.OwnerReference) error { + var ( + componentName = odocontext.GetComponentName(ctx) + ) + // list the latest state of the PVCs - pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", a.ComponentName)) + pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) if err != nil { return err } diff --git a/pkg/devfile/adapters/types.go b/pkg/devfile/adapters/types.go index 570facf8290..51b43c8891e 100644 --- a/pkg/devfile/adapters/types.go +++ b/pkg/devfile/adapters/types.go @@ -7,7 +7,6 @@ import ( // PushParameters is a struct containing the parameters to be used when pushing to a devfile component type PushParameters struct { - Path string // Path refers to the parent folder containing the source code to push up to a component WatchFiles []string // Optional: WatchFiles is the list of changed files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine changed files WatchDeletedFiles []string // Optional: WatchDeletedFiles is the list of deleted files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine deleted files IgnoredFiles []string // IgnoredFiles is the list of files to not push up to a component diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index ef789e43444..f95a0cc1efc 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -4,20 +4,20 @@ import ( "context" "errors" "fmt" - "github.com/redhat-developer/odo/pkg/api" "io" "os" "path/filepath" "reflect" "time" - "github.com/devfile/library/v2/pkg/devfile/parser" + "github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/kclient" "github.com/redhat-developer/odo/pkg/labels" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/log" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/fsnotify/fsnotify" gitignore "github.com/sabhiram/go-gitignore" @@ -59,14 +59,6 @@ func NewWatchClient(kubeClient kclient.ClientInterface) *WatchClient { // WatchParameters is designed to hold the controllables and attributes that the watch function works on type WatchParameters struct { - // Name of component that is to be watched - ComponentName string - // Name of application, the component is part of - ApplicationName string - // DevfilePath is the path of the devfile - DevfilePath string - // The path to the source of component(local or binary) - Path string // List/Slice of files/folders in component source, the updates to which need not be pushed to component deployed pod FileIgnores []string // Custom function that can be used to push detected changes to remote pod. For more info about what each of the parameters to this function, please refer, pkg/component/component.go#PushLocal @@ -81,8 +73,6 @@ type WatchParameters struct { DevfileRunCmd string // DevfileDebugCmd takes the debug command through the command line and overwrites the devfile debug command DevfileDebugCmd string - // InitialDevfileObj is used to compare the devfile between the very first run of odo dev and subsequent ones - InitialDevfileObj parser.DevfileObj // Debug indicates if the debug command should be started after sync, or the run command by default Debug bool // DebugPort indicates which debug port to use for pushing after sync @@ -121,11 +111,19 @@ type evaluateChangesFunc func(events []fsnotify.Event, path string, fileIgnores type processEventsFunc func(ctx context.Context, changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error) func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error { - klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", parameters.Path, parameters.ComponentName, parameters.FileIgnores) + var ( + devfileObj = odocontext.GetDevfileObj(ctx) + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + componentName = odocontext.GetComponentName(ctx) + appName = odocontext.GetApplication(ctx) + ) + + klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", path, componentName, parameters.FileIgnores) var err error if parameters.WatchFiles { - o.sourcesWatcher, err = getFullSourcesWatcher(parameters.Path, parameters.FileIgnores) + o.sourcesWatcher, err = getFullSourcesWatcher(path, parameters.FileIgnores) if err != nil { return err } @@ -138,7 +136,7 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct defer o.sourcesWatcher.Close() if parameters.WatchCluster { - selector := labels.GetSelector(parameters.ComponentName, parameters.ApplicationName, labels.ComponentDevMode, true) + selector := labels.GetSelector(componentName, appName, labels.ComponentDevMode, true) o.deploymentWatcher, err = o.kubeClient.DeploymentWatcher(ctx, selector) if err != nil { return fmt.Errorf("error watching deployment: %v", err) @@ -159,11 +157,11 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct } if parameters.WatchFiles { var devfileFiles []string - devfileFiles, err = libdevfile.GetReferencedLocalFiles(parameters.InitialDevfileObj) + devfileFiles, err = libdevfile.GetReferencedLocalFiles(*devfileObj) if err != nil { return err } - devfileFiles = append(devfileFiles, parameters.DevfilePath) + devfileFiles = append(devfileFiles, devfilePath) for _, f := range devfileFiles { err = o.devfileWatcher.Add(f) if err != nil { @@ -201,6 +199,13 @@ func (o *WatchClient) eventWatcher( componentStatus ComponentStatus, ) error { + var ( + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + componentName = odocontext.GetComponentName(ctx) + appName = odocontext.GetApplication(ctx) + ) + expBackoff := NewExpBackoff() var events []fsnotify.Event @@ -245,7 +250,7 @@ func (o *WatchClient) eventWatcher( var changedFiles, deletedPaths []string if !o.forceSync { // first find the files that have changed (also includes the ones newly created) or deleted - changedFiles, deletedPaths = evaluateChangesHandler(events, parameters.Path, parameters.FileIgnores, o.sourcesWatcher) + changedFiles, deletedPaths = evaluateChangesHandler(events, path, parameters.FileIgnores, o.sourcesWatcher) // process the changes and sync files with remote pod if len(changedFiles) == 0 && len(deletedPaths) == 0 { continue @@ -351,7 +356,7 @@ func (o *WatchClient) eventWatcher( switch kevent := ev.Object.(type) { case *corev1.Event: podName := kevent.InvolvedObject.Name - selector := labels.GetSelector(parameters.ComponentName, parameters.ApplicationName, labels.ComponentDevMode, true) + selector := labels.GetSelector(componentName, appName, labels.ComponentDevMode, true) matching, err := o.kubeClient.IsPodNameMatchingSelector(ctx, podName, selector) if err != nil { return err @@ -444,6 +449,11 @@ func (o *WatchClient) processEvents( componentStatus *ComponentStatus, backoff *ExpBackoff, ) (*time.Duration, error) { + var ( + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + for _, file := range removeDuplicates(append(changedFiles, deletedPaths...)) { fmt.Fprintf(out, "\nFile %s changed\n", file) } @@ -453,7 +463,6 @@ func (o *WatchClient) processEvents( klog.V(4).Infof("Copying files %s to pod", changedFiles) pushParams := adapters.PushParameters{ - Path: parameters.Path, WatchFiles: changedFiles, WatchDeletedFiles: deletedPaths, IgnoredFiles: parameters.FileIgnores, @@ -498,7 +507,7 @@ func (o *WatchClient) processEvents( if oldStatus.State != StateReady && componentStatus.State == StateReady || !reflect.DeepEqual(oldStatus.EndpointsForwarded, componentStatus.EndpointsForwarded) { - PrintInfoMessage(out, parameters.Path, parameters.WatchFiles, parameters.PromptMessage) + PrintInfoMessage(out, path, parameters.WatchFiles, parameters.PromptMessage) } return nil, nil } diff --git a/pkg/watch/watch_test.go b/pkg/watch/watch_test.go index 33247f6f4d2..b6809e10f72 100644 --- a/pkg/watch/watch_test.go +++ b/pkg/watch/watch_test.go @@ -11,6 +11,8 @@ import ( "k8s.io/apimachinery/pkg/watch" "github.com/fsnotify/fsnotify" + + odocontext "github.com/redhat-developer/odo/pkg/odo/context" ) func evaluateChangesHandler(events []fsnotify.Event, path string, fileIgnores []string, watcher *fsnotify.Watcher) ([]string, []string) { @@ -113,6 +115,9 @@ func Test_eventWatcher(t *testing.T) { fileWatcher, _ := fsnotify.NewWatcher() var cancel context.CancelFunc ctx, cancel := context.WithCancel(context.Background()) + ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile") + ctx = odocontext.WithApplication(ctx, "odo") + ctx = odocontext.WithComponentName(ctx, "my-component") out := &bytes.Buffer{} go func() { From 333e1f96a450ec44a9a6a6cfc9c927dd8b654118 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 11:40:56 +0200 Subject: [PATCH 02/11] Move Devfile param to WatchParams and biuld adapter only once --- pkg/dev/kubedev/kubedev.go | 58 +++++++---------- .../adapters/kubernetes/component/adapter.go | 63 +++++++++---------- .../kubernetes/component/adapter_test.go | 9 ++- .../adapters/kubernetes/component/push.go | 13 ++-- pkg/devfile/adapters/types.go | 5 +- 5 files changed, 68 insertions(+), 80 deletions(-) diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 678a653f511..a18e03e7744 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -46,6 +46,8 @@ type DevClient struct { execClient exec.Client deleteClient _delete.Client configAutomountClient configAutomount.Client + + adapter component.Adapter } var _ dev.Client = (*DevClient)(nil) @@ -62,6 +64,17 @@ func NewDevClient( deleteClient _delete.Client, configAutomountClient configAutomount.Client, ) *DevClient { + adapter := component.NewKubernetesAdapter( + kubernetesClient, + prefClient, + portForwardClient, + bindingClient, + syncClient, + execClient, + configAutomountClient, + filesystem, + ) + return &DevClient{ kubernetesClient: kubernetesClient, prefClient: prefClient, @@ -73,6 +86,7 @@ func NewDevClient( execClient: execClient, deleteClient: deleteClient, configAutomountClient: configAutomountClient, + adapter: adapter, } } @@ -88,18 +102,6 @@ func (o *DevClient) Start( devfileObj = odocontext.GetDevfileObj(ctx) ) - adapter := component.NewKubernetesAdapter( - o.kubernetesClient, - o.prefClient, - o.portForwardClient, - o.bindingClient, - o.syncClient, - o.execClient, - o.configAutomountClient, - o.filesystem, - *devfileObj, - ) - pushParameters := adapters.PushParameters{ IgnoredFiles: options.IgnorePaths, Debug: options.Debug, @@ -108,13 +110,14 @@ func (o *DevClient) Start( RandomPorts: options.RandomPorts, CustomForwardedPorts: options.CustomForwardedPorts, ErrOut: errOut, + Devfile: *devfileObj, } klog.V(4).Infoln("Creating inner-loop resources for the component") componentStatus := watch.ComponentStatus{ ImageComponentsAutoApplied: make(map[string]v1alpha2.ImageComponent), } - err := adapter.Push(ctx, pushParameters, &componentStatus) + err := o.adapter.Push(ctx, pushParameters, &componentStatus) if err != nil { return err } @@ -138,38 +141,19 @@ func (o *DevClient) Start( return o.watchClient.WatchAndPush(out, watchParameters, ctx, componentStatus) } -// RegenerateAdapterAndPush regenerates the adapter and pushes the files to remote pod +// RegenerateAdapterAndPush get the new devfile and pushes the files to remote pod func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams adapters.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { - var adapter component.ComponentAdapter - adapter, err := o.regenerateComponentAdapterFromWatchParams(watchParams) + devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), watchParams.Variables) if err != nil { return fmt.Errorf("unable to generate component from watch parameters: %w", err) } - err = adapter.Push(ctx, pushParams, componentStatus) + pushParams.Devfile = devObj + + err = o.adapter.Push(ctx, pushParams, componentStatus) if err != nil { return fmt.Errorf("watch command was unable to push component: %w", err) } - return nil } - -func (o *DevClient) regenerateComponentAdapterFromWatchParams(parameters watch.WatchParameters) (component.ComponentAdapter, error) { - devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), parameters.Variables) - if err != nil { - return nil, err - } - - return component.NewKubernetesAdapter( - o.kubernetesClient, - o.prefClient, - o.portForwardClient, - o.bindingClient, - o.syncClient, - o.execClient, - o.configAutomountClient, - o.filesystem, - devObj, - ), nil -} diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/devfile/adapters/kubernetes/component/adapter.go index e4497603eea..0b43457b8e8 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter.go @@ -41,7 +41,6 @@ import ( devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/v2/pkg/devfile/generator" - "github.com/devfile/library/v2/pkg/devfile/parser" parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" dfutil "github.com/devfile/library/v2/pkg/util" @@ -62,8 +61,7 @@ type Adapter struct { configAutomountClient configAutomount.Client fs filesystem.Filesystem // FS is the object used for building image component if present - devfile parser.DevfileObj // Devfile is the object returned by the Devfile parser - logger machineoutput.MachineEventLoggingClient + logger machineoutput.MachineEventLoggingClient } var _ ComponentAdapter = (*Adapter)(nil) @@ -78,7 +76,6 @@ func NewKubernetesAdapter( execClient exec.Client, configAutomountClient configAutomount.Client, fs filesystem.Filesystem, - devfile parser.DevfileObj, ) Adapter { return Adapter{ kubeClient: kubernetesClient, @@ -89,7 +86,6 @@ func NewKubernetesAdapter( execClient: execClient, configAutomountClient: configAutomountClient, fs: fs, - devfile: devfile, logger: machineoutput.NewMachineEventLoggingClient(), } @@ -124,7 +120,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } klog.V(4).Infof("component state: %q\n", componentStatus.State) - err = a.buildPushAutoImageComponents(ctx, a.fs, a.devfile, componentStatus) + err = a.buildPushAutoImageComponents(ctx, a.fs, parameters.Devfile, componentStatus) if err != nil { return err } @@ -139,11 +135,11 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Set the mode to Dev since we are using "odo dev" here - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) + runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()) labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, false) var updated bool - deployment, updated, err = a.createOrUpdateComponent(ctx, deploymentExists, libdevfile.DevfileCommands{ + deployment, updated, err = a.createOrUpdateComponent(ctx, parameters, deploymentExists, libdevfile.DevfileCommands{ BuildCmd: parameters.DevfileBuildCmd, RunCmd: parameters.DevfileRunCmd, DebugCmd: parameters.DevfileDebugCmd, @@ -156,7 +152,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Delete remote resources that are not present in the Devfile selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, false) - objectsToRemove, serviceBindingSecretsToRemove, err := a.getRemoteResourcesNotPresentInDevfile(ctx, selector) + objectsToRemove, serviceBindingSecretsToRemove, err := a.getRemoteResourcesNotPresentInDevfile(ctx, parameters, selector) if err != nil { return fmt.Errorf("unable to determine resources to delete: %w", err) } @@ -176,7 +172,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Create all the K8s components defined in the devfile - _, err = a.pushDevfileKubernetesComponents(ctx, labels, odolabels.ComponentDevMode, ownerReference) + _, err = a.pushDevfileKubernetesComponents(ctx, parameters, labels, odolabels.ComponentDevMode, ownerReference) if err != nil { return err } @@ -210,7 +206,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Check if endpoints changed in Devfile - portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(a.devfile, parameters.Debug) + portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.Debug) if err != nil { return err } @@ -280,15 +276,15 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // PostStart events from the devfile will only be executed when the component // didn't previously exist - if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(a.devfile) { - err = libdevfile.ExecPostStartEvents(ctx, a.devfile, component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) + if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) { + err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) if err != nil { return err } } componentStatus.PostStartEventsDone = true - cmd, err := libdevfile.ValidateAndGetCommand(a.devfile, cmdName, cmdKind) + cmd, err := libdevfile.ValidateAndGetCommand(parameters.Devfile, cmdName, cmdKind) if err != nil { return err } @@ -305,7 +301,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c kubeClient: a.kubeClient, appName: appName, componentName: componentName, - devfile: a.devfile, + devfile: parameters.Devfile, path: path, podName: pod.GetName(), ctx: ctx, @@ -336,7 +332,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c doExecuteBuildCommand := func() error { execHandler := component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Building your application in container", parameters.Show) - return libdevfile.Build(ctx, a.devfile, parameters.DevfileBuildCmd, execHandler) + return libdevfile.Build(ctx, parameters.Devfile, parameters.DevfileBuildCmd, execHandler) } if running { if cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) { @@ -349,7 +345,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c return err } } - err = libdevfile.ExecuteCommandByNameAndKind(ctx, a.devfile, cmdName, cmdKind, &cmdHandler, false) + err = libdevfile.ExecuteCommandByNameAndKind(ctx, parameters.Devfile, cmdName, cmdKind, &cmdHandler, false) if err != nil { return err } @@ -369,7 +365,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c fmt.Fprintln(log.GetStdout()) } - err = a.portForwardClient.StartPortForwarding(ctx, a.devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) + err = a.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) if err != nil { return adapters.NewErrPortForward(err) } @@ -384,6 +380,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Returns the new deployment and if the generation of the deployment has been updated func (a *Adapter) createOrUpdateComponent( ctx context.Context, + parameters adapters.PushParameters, componentExists bool, commands libdevfile.DevfileCommands, deployment *appsv1.Deployment, @@ -396,13 +393,13 @@ func (a *Adapter) createOrUpdateComponent( path = filepath.Dir(devfilePath) ) - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) + runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()) // Set the labels labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, true) annotations := make(map[string]string) - odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.devfile.Data.GetMetadata())) + odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())) odolabels.AddCommonAnnotations(annotations) klog.V(4).Infof("We are deploying these annotations: %s", annotations) @@ -415,7 +412,7 @@ func (a *Adapter) createOrUpdateComponent( if err != nil { return nil, false, err } - podTemplateSpec, err := generator.GetPodTemplateSpec(a.devfile, generator.PodTemplateParams{ + podTemplateSpec, err := generator.GetPodTemplateSpec(parameters.Devfile, generator.PodTemplateParams{ ObjectMeta: deploymentObjectMeta, PodSecurityAdmissionPolicy: policy, }) @@ -429,13 +426,13 @@ func (a *Adapter) createOrUpdateComponent( initContainers := podTemplateSpec.Spec.InitContainers - containers, err = utils.UpdateContainersEntrypointsIfNeeded(a.devfile, containers, commands.BuildCmd, commands.RunCmd, commands.DebugCmd) + containers, err = utils.UpdateContainersEntrypointsIfNeeded(parameters.Devfile, containers, commands.BuildCmd, commands.RunCmd, commands.DebugCmd) if err != nil { return nil, false, err } // Returns the volumes to add to the PodTemplate and adds volumeMounts to the containers and initContainers - volumes, err := a.buildVolumes(ctx, containers, initContainers) + volumes, err := a.buildVolumes(ctx, parameters, containers, initContainers) if err != nil { return nil, false, err } @@ -459,7 +456,7 @@ func (a *Adapter) createOrUpdateComponent( originalGeneration = deployment.GetGeneration() } - deployment, err = generator.GetDeployment(a.devfile, deployParams) + deployment, err = generator.GetDeployment(parameters.Devfile, deployParams) if err != nil { return nil, false, err } @@ -485,7 +482,7 @@ func (a *Adapter) createOrUpdateComponent( ObjectMeta: serviceObjectMeta, SelectorLabels: selectorLabels, } - svc, err := generator.GetService(a.devfile, serviceParams, parsercommon.DevfileOptions{}) + svc, err := generator.GetService(parameters.Devfile, serviceParams, parsercommon.DevfileOptions{}) if err != nil { return nil, false, err @@ -549,13 +546,13 @@ func (a *Adapter) createOrUpdateComponent( // - (side effect on input parameters) adds volumeMounts to containers and initContainers for the PVCs and Ephemeral volumes // - (side effect on input parameters) adds volumeMounts for automounted volumes // => Returns the list of Volumes to add to the PodTemplate -func (a *Adapter) buildVolumes(ctx context.Context, containers, initContainers []corev1.Container) ([]corev1.Volume, error) { +func (a *Adapter) buildVolumes(ctx context.Context, parameters adapters.PushParameters, containers, initContainers []corev1.Container) ([]corev1.Volume, error) { var ( appName = odocontext.GetApplication(ctx) componentName = odocontext.GetComponentName(ctx) ) - runtime := component.GetComponentRuntimeFromDevfileMetadata(a.devfile.Data.GetMetadata()) + runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()) storageClient := storagepkg.NewClient(componentName, appName, storagepkg.ClientOptions{ Client: a.kubeClient, @@ -570,7 +567,7 @@ func (a *Adapter) buildVolumes(ctx context.Context, containers, initContainers [ // Create PVCs for non-ephemeral Volumes defined in the Devfile // and returns the Ephemeral volumes defined in the Devfile - ephemerals, err := storagepkg.Push(storageClient, a.devfile) + ephemerals, err := storagepkg.Push(storageClient, parameters.Devfile) if err != nil { return nil, err } @@ -600,13 +597,13 @@ func (a *Adapter) buildVolumes(ctx context.Context, containers, initContainers [ utils.AddOdoMandatoryVolume(containers) // Get PVC volumes and Volume Mounts - pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(a.devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) + pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) if err != nil { return nil, err } allVolumes = append(allVolumes, pvcVolumes...) - ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(a.devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) + ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) if err != nil { return nil, err } @@ -682,7 +679,7 @@ func (a Adapter) generateDeploymentObjectMeta(ctx context.Context, deployment *a // getRemoteResourcesNotPresentInDevfile compares the list of Devfile K8s component and remote K8s resources // and returns a list of the remote resources not present in the Devfile and in case the SBO is not installed, a list of service binding secrets that must be deleted; // it ignores the core components (such as deployments, svc, pods; all resources with `component:` label) -func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { +func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, parameters adapters.PushParameters, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { var ( devfilePath = odocontext.GetDevfilePath(ctx) path = filepath.Dir(devfilePath) @@ -710,7 +707,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, sele } var devfileK8sResources []devfilev1.Component - devfileK8sResources, err = libdevfile.GetK8sAndOcComponentsToPush(a.devfile, true) + devfileK8sResources, err = libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, true) if err != nil { return nil, nil, fmt.Errorf("unable to obtain resources from the Devfile: %w", err) } @@ -719,7 +716,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, sele var devfileK8sResourcesUnstructured []unstructured.Unstructured for _, devfileK := range devfileK8sResources { var devfileKUnstructuredList []unstructured.Unstructured - devfileKUnstructuredList, err = libdevfile.GetK8sComponentAsUnstructuredList(a.devfile, devfileK.Name, path, devfilefs.DefaultFs{}) + devfileKUnstructuredList, err = libdevfile.GetK8sComponentAsUnstructuredList(parameters.Devfile, devfileK.Name, path, devfilefs.DefaultFs{}) if err != nil { return nil, nil, fmt.Errorf("unable to read the resource: %w", err) } diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/devfile/adapters/kubernetes/component/adapter_test.go index 2e26c72b96c..c24f45269e1 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/devfile/adapters/kubernetes/component/adapter_test.go @@ -14,13 +14,14 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/redhat-developer/odo/pkg/configAutomount" + "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/preference" + "github.com/redhat-developer/odo/pkg/testingutil" "github.com/redhat-developer/odo/pkg/util" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" devfileParser "github.com/devfile/library/v2/pkg/devfile/parser" - "github.com/devfile/library/v2/pkg/testingutil" "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" @@ -130,12 +131,14 @@ func TestCreateOrUpdateComponent(t *testing.T) { fakePrefClient.EXPECT().GetEphemeralSourceVolume().AnyTimes() fakeConfigAutomount := configAutomount.NewMockClient(ctrl) fakeConfigAutomount.EXPECT().GetAutomountingVolumes().AnyTimes() - componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, nil, nil, fakeConfigAutomount, nil, devObj) + componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, nil, nil, fakeConfigAutomount, nil) ctx := context.Background() ctx = odocontext.WithApplication(ctx, "app") ctx = odocontext.WithComponentName(ctx, "my-component") ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile") - _, _, err := componentAdapter.createOrUpdateComponent(ctx, tt.running, libdevfile.DevfileCommands{}, nil) + _, _, err := componentAdapter.createOrUpdateComponent(ctx, adapters.PushParameters{ + Devfile: devObj, + }, tt.running, libdevfile.DevfileCommands{}, nil) // Checks for unexpected error cases if !tt.wantErr == (err != nil) { diff --git a/pkg/devfile/adapters/kubernetes/component/push.go b/pkg/devfile/adapters/kubernetes/component/push.go index eda541dc1ca..b4ecf1390e5 100644 --- a/pkg/devfile/adapters/kubernetes/component/push.go +++ b/pkg/devfile/adapters/kubernetes/component/push.go @@ -91,6 +91,7 @@ func (a *Adapter) buildPushAutoImageComponents(ctx context.Context, fs filesyste // adding the specified labels and ownerreference to them func (a *Adapter) pushDevfileKubernetesComponents( ctx context.Context, + parameters adapters.PushParameters, labels map[string]string, mode string, reference metav1.OwnerReference, @@ -102,23 +103,23 @@ func (a *Adapter) pushDevfileKubernetesComponents( // fetch the "kubernetes inlined components" to create them on cluster // from odo standpoint, these components contain yaml manifest of ServiceBinding - k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(a.devfile, false) + k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, false) if err != nil { return nil, fmt.Errorf("error while trying to fetch service(s) from devfile: %w", err) } // validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster - err = component.ValidateResourcesExist(a.kubeClient, a.devfile, k8sComponents, path) + err = component.ValidateResourcesExist(a.kubeClient, parameters.Devfile, k8sComponents, path) if err != nil { return nil, err } // Set the annotations for the component type annotations := make(map[string]string) - odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(a.devfile.Data.GetMetadata())) + odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())) // create the Kubernetes objects from the manifest and delete the ones not in the devfile - err = service.PushKubernetesResources(a.kubeClient, a.devfile, k8sComponents, labels, annotations, path, mode, reference) + err = service.PushKubernetesResources(a.kubeClient, parameters.Devfile, k8sComponents, labels, annotations, path, mode, reference) if err != nil { return nil, fmt.Errorf("failed to create Kubernetes resources associated with the component: %w", err) } @@ -126,13 +127,13 @@ func (a *Adapter) pushDevfileKubernetesComponents( } func (a *Adapter) getPushDevfileCommands(parameters adapters.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { - pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(a.devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) + pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) if err != nil { return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) } if parameters.Debug { - pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(a.devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) + pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) if e != nil { return nil, fmt.Errorf("debug command is not valid: %w", e) } diff --git a/pkg/devfile/adapters/types.go b/pkg/devfile/adapters/types.go index 51b43c8891e..9f19edc1882 100644 --- a/pkg/devfile/adapters/types.go +++ b/pkg/devfile/adapters/types.go @@ -1,12 +1,15 @@ package adapters import ( - "github.com/redhat-developer/odo/pkg/api" "io" + + "github.com/devfile/library/v2/pkg/devfile/parser" + "github.com/redhat-developer/odo/pkg/api" ) // PushParameters is a struct containing the parameters to be used when pushing to a devfile component type PushParameters struct { + Devfile parser.DevfileObj WatchFiles []string // Optional: WatchFiles is the list of changed files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine changed files WatchDeletedFiles []string // Optional: WatchDeletedFiles is the list of deleted files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine deleted files IgnoredFiles []string // IgnoredFiles is the list of files to not push up to a component From 9d3faa3d239e646e411c8942e269d716ad6867cc Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 13:57:35 +0200 Subject: [PATCH 03/11] Move pkg/devfile/adapters/kubernetes/* into pkg/dev/kubedev --- pkg/deploy/deploy.go | 2 +- .../adapters => dev/common}/attributes.go | 2 +- .../common}/attributes_test.go | 2 +- .../adapters => dev/common}/errors.go | 2 +- pkg/{devfile/adapters => dev/common}/types.go | 2 +- .../component => dev/kubedev}/handler.go | 2 +- pkg/dev/kubedev/kubedev.go | 25 +- .../adapter.go => dev/kubedev/push.go} | 595 ++++++++++-------- .../kubedev/push_test.go} | 16 +- .../kubedev}/storage/utils.go | 0 .../kubedev}/storage/utils_test.go | 0 .../kubernetes => dev/kubedev}/utils/utils.go | 0 .../kubedev}/utils/utils_test.go | 0 pkg/dev/podmandev/pod.go | 2 +- pkg/dev/podmandev/podmandev.go | 5 +- pkg/dev/podmandev/reconcile.go | 4 +- .../kubernetes/component/interface.go | 13 - .../adapters/kubernetes/component/push.go | 170 ----- .../parent-devfile-commands-only.yaml | 20 + .../parent-devfile-components-only.yaml | 15 + .../testdata/parent-devfile-empty.yaml | 3 + pkg/libdevfile/testdata/parent-devfile.yaml | 32 + pkg/watch/watch.go | 8 +- 23 files changed, 443 insertions(+), 477 deletions(-) rename pkg/{devfile/adapters => dev/common}/attributes.go (97%) rename pkg/{devfile/adapters => dev/common}/attributes_test.go (99%) rename pkg/{devfile/adapters => dev/common}/errors.go (93%) rename pkg/{devfile/adapters => dev/common}/types.go (99%) rename pkg/{devfile/adapters/kubernetes/component => dev/kubedev}/handler.go (99%) rename pkg/{devfile/adapters/kubernetes/component/adapter.go => dev/kubedev/push.go} (69%) rename pkg/{devfile/adapters/kubernetes/component/adapter_test.go => dev/kubedev/push_test.go} (97%) rename pkg/{devfile/adapters/kubernetes => dev/kubedev}/storage/utils.go (100%) rename pkg/{devfile/adapters/kubernetes => dev/kubedev}/storage/utils_test.go (100%) rename pkg/{devfile/adapters/kubernetes => dev/kubedev}/utils/utils.go (100%) rename pkg/{devfile/adapters/kubernetes => dev/kubedev}/utils/utils_test.go (100%) delete mode 100644 pkg/devfile/adapters/kubernetes/component/interface.go delete mode 100644 pkg/devfile/adapters/kubernetes/component/push.go create mode 100644 pkg/libdevfile/testdata/parent-devfile-commands-only.yaml create mode 100644 pkg/libdevfile/testdata/parent-devfile-components-only.yaml create mode 100644 pkg/libdevfile/testdata/parent-devfile-empty.yaml create mode 100644 pkg/libdevfile/testdata/parent-devfile.yaml diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 945238b8861..20c202632ea 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -18,7 +18,7 @@ import ( "github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/configAutomount" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/storage" + "github.com/redhat-developer/odo/pkg/dev/kubedev/storage" "github.com/redhat-developer/odo/pkg/devfile/image" "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" diff --git a/pkg/devfile/adapters/attributes.go b/pkg/dev/common/attributes.go similarity index 97% rename from pkg/devfile/adapters/attributes.go rename to pkg/dev/common/attributes.go index 061a8d3b6e8..49cad270a34 100644 --- a/pkg/devfile/adapters/attributes.go +++ b/pkg/dev/common/attributes.go @@ -1,4 +1,4 @@ -package adapters +package common import ( "path/filepath" diff --git a/pkg/devfile/adapters/attributes_test.go b/pkg/dev/common/attributes_test.go similarity index 99% rename from pkg/devfile/adapters/attributes_test.go rename to pkg/dev/common/attributes_test.go index d4afbefdf82..b926c1d9c04 100644 --- a/pkg/devfile/adapters/attributes_test.go +++ b/pkg/dev/common/attributes_test.go @@ -1,4 +1,4 @@ -package adapters +package common import ( "testing" diff --git a/pkg/devfile/adapters/errors.go b/pkg/dev/common/errors.go similarity index 93% rename from pkg/devfile/adapters/errors.go rename to pkg/dev/common/errors.go index d0bde259ebb..c7bc0851e91 100644 --- a/pkg/devfile/adapters/errors.go +++ b/pkg/dev/common/errors.go @@ -1,4 +1,4 @@ -package adapters +package common import "fmt" diff --git a/pkg/devfile/adapters/types.go b/pkg/dev/common/types.go similarity index 99% rename from pkg/devfile/adapters/types.go rename to pkg/dev/common/types.go index 9f19edc1882..c92aa00f8c1 100644 --- a/pkg/devfile/adapters/types.go +++ b/pkg/dev/common/types.go @@ -1,4 +1,4 @@ -package adapters +package common import ( "io" diff --git a/pkg/devfile/adapters/kubernetes/component/handler.go b/pkg/dev/kubedev/handler.go similarity index 99% rename from pkg/devfile/adapters/kubernetes/component/handler.go rename to pkg/dev/kubedev/handler.go index c4114d7a7cc..0d9ac926e36 100644 --- a/pkg/devfile/adapters/kubernetes/component/handler.go +++ b/pkg/dev/kubedev/handler.go @@ -1,4 +1,4 @@ -package component +package kubedev import ( "context" diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index a18e03e7744..4f0d5234834 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -11,6 +11,7 @@ import ( _delete "github.com/redhat-developer/odo/pkg/component/delete" "github.com/redhat-developer/odo/pkg/configAutomount" "github.com/redhat-developer/odo/pkg/dev" + "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/exec" "github.com/redhat-developer/odo/pkg/kclient" @@ -22,8 +23,6 @@ import ( "k8s.io/klog" - "github.com/redhat-developer/odo/pkg/devfile/adapters" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component" "github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/watch" ) @@ -46,8 +45,6 @@ type DevClient struct { execClient exec.Client deleteClient _delete.Client configAutomountClient configAutomount.Client - - adapter component.Adapter } var _ dev.Client = (*DevClient)(nil) @@ -64,17 +61,6 @@ func NewDevClient( deleteClient _delete.Client, configAutomountClient configAutomount.Client, ) *DevClient { - adapter := component.NewKubernetesAdapter( - kubernetesClient, - prefClient, - portForwardClient, - bindingClient, - syncClient, - execClient, - configAutomountClient, - filesystem, - ) - return &DevClient{ kubernetesClient: kubernetesClient, prefClient: prefClient, @@ -86,7 +72,6 @@ func NewDevClient( execClient: execClient, deleteClient: deleteClient, configAutomountClient: configAutomountClient, - adapter: adapter, } } @@ -102,7 +87,7 @@ func (o *DevClient) Start( devfileObj = odocontext.GetDevfileObj(ctx) ) - pushParameters := adapters.PushParameters{ + pushParameters := common.PushParameters{ IgnoredFiles: options.IgnorePaths, Debug: options.Debug, DevfileBuildCmd: options.BuildCommand, @@ -117,7 +102,7 @@ func (o *DevClient) Start( componentStatus := watch.ComponentStatus{ ImageComponentsAutoApplied: make(map[string]v1alpha2.ImageComponent), } - err := o.adapter.Push(ctx, pushParameters, &componentStatus) + err := o.Push(ctx, pushParameters, &componentStatus) if err != nil { return err } @@ -142,7 +127,7 @@ func (o *DevClient) Start( } // RegenerateAdapterAndPush get the new devfile and pushes the files to remote pod -func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams adapters.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { +func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), watchParams.Variables) if err != nil { @@ -151,7 +136,7 @@ func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams ada pushParams.Devfile = devObj - err = o.adapter.Push(ctx, pushParams, componentStatus) + err = o.Push(ctx, pushParams, componentStatus) if err != nil { return fmt.Errorf("watch command was unable to push component: %w", err) } diff --git a/pkg/devfile/adapters/kubernetes/component/adapter.go b/pkg/dev/kubedev/push.go similarity index 69% rename from pkg/devfile/adapters/kubernetes/component/adapter.go rename to pkg/dev/kubedev/push.go index 0b43457b8e8..d4ac11753a7 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter.go +++ b/pkg/dev/kubedev/push.go @@ -1,4 +1,4 @@ -package component +package kubedev import ( "context" @@ -9,29 +9,25 @@ import ( "strings" "time" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/v2/pkg/devfile/generator" + "github.com/devfile/library/v2/pkg/devfile/parser" + parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem" + dfutil "github.com/devfile/library/v2/pkg/util" "golang.org/x/sync/errgroup" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/utils/pointer" - "github.com/redhat-developer/odo/pkg/binding" "github.com/redhat-developer/odo/pkg/component" - "github.com/redhat-developer/odo/pkg/configAutomount" "github.com/redhat-developer/odo/pkg/dev/common" - "github.com/redhat-developer/odo/pkg/devfile/adapters" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/storage" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/utils" - "github.com/redhat-developer/odo/pkg/exec" + "github.com/redhat-developer/odo/pkg/dev/kubedev/storage" + "github.com/redhat-developer/odo/pkg/dev/kubedev/utils" + "github.com/redhat-developer/odo/pkg/devfile/image" "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/log" - "github.com/redhat-developer/odo/pkg/machineoutput" odocontext "github.com/redhat-developer/odo/pkg/odo/context" "github.com/redhat-developer/odo/pkg/port" - "github.com/redhat-developer/odo/pkg/portForward" - "github.com/redhat-developer/odo/pkg/preference" "github.com/redhat-developer/odo/pkg/service" storagepkg "github.com/redhat-developer/odo/pkg/storage" "github.com/redhat-developer/odo/pkg/sync" @@ -39,62 +35,19 @@ import ( "github.com/redhat-developer/odo/pkg/util" "github.com/redhat-developer/odo/pkg/watch" - devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/library/v2/pkg/devfile/generator" - parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - dfutil "github.com/devfile/library/v2/pkg/util" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog" + "k8s.io/utils/pointer" ) -// Adapter is a component adapter implementation for Kubernetes -type Adapter struct { - kubeClient kclient.ClientInterface - prefClient preference.Client - portForwardClient portForward.Client - bindingClient binding.Client - syncClient sync.Client - execClient exec.Client - configAutomountClient configAutomount.Client - fs filesystem.Filesystem // FS is the object used for building image component if present - - logger machineoutput.MachineEventLoggingClient -} - -var _ ComponentAdapter = (*Adapter)(nil) - -// NewKubernetesAdapter returns a Devfile adapter for the targeted platform -func NewKubernetesAdapter( - kubernetesClient kclient.ClientInterface, - prefClient preference.Client, - portForwardClient portForward.Client, - bindingClient binding.Client, - syncClient sync.Client, - execClient exec.Client, - configAutomountClient configAutomount.Client, - fs filesystem.Filesystem, -) Adapter { - return Adapter{ - kubeClient: kubernetesClient, - prefClient: prefClient, - portForwardClient: portForwardClient, - bindingClient: bindingClient, - syncClient: syncClient, - execClient: execClient, - configAutomountClient: configAutomountClient, - fs: fs, - - logger: machineoutput.NewMachineEventLoggingClient(), - } -} - // Push updates the component if a matching component exists or creates one if it doesn't exist // Once the component has started, it will sync the source code to it. // The componentStatus will be modified to reflect the status of the component when the function returns -func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) (err error) { +func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) (err error) { var ( appName = odocontext.GetApplication(ctx) @@ -109,7 +62,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c return err } - err = dfutil.ValidateK8sResourceName("component namespace", a.kubeClient.GetCurrentNamespace()) + err = dfutil.ValidateK8sResourceName("component namespace", o.kubernetesClient.GetCurrentNamespace()) if err != nil { return err } @@ -120,12 +73,12 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } klog.V(4).Infof("component state: %q\n", componentStatus.State) - err = a.buildPushAutoImageComponents(ctx, a.fs, parameters.Devfile, componentStatus) + err = o.buildPushAutoImageComponents(ctx, o.filesystem, parameters.Devfile, componentStatus) if err != nil { return err } - deployment, deploymentExists, err := a.getComponentDeployment(ctx) + deployment, deploymentExists, err := o.getComponentDeployment(ctx) if err != nil { return err } @@ -139,7 +92,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, false) var updated bool - deployment, updated, err = a.createOrUpdateComponent(ctx, parameters, deploymentExists, libdevfile.DevfileCommands{ + deployment, updated, err = o.createOrUpdateComponent(ctx, parameters, deploymentExists, libdevfile.DevfileCommands{ BuildCmd: parameters.DevfileBuildCmd, RunCmd: parameters.DevfileRunCmd, DebugCmd: parameters.DevfileDebugCmd, @@ -152,12 +105,12 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Delete remote resources that are not present in the Devfile selector := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, false) - objectsToRemove, serviceBindingSecretsToRemove, err := a.getRemoteResourcesNotPresentInDevfile(ctx, parameters, selector) + objectsToRemove, serviceBindingSecretsToRemove, err := o.getRemoteResourcesNotPresentInDevfile(ctx, parameters, selector) if err != nil { return fmt.Errorf("unable to determine resources to delete: %w", err) } - err = a.deleteRemoteResources(objectsToRemove) + err = o.deleteRemoteResources(objectsToRemove) if err != nil { return fmt.Errorf("unable to delete remote resources: %w", err) } @@ -165,19 +118,19 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // this is mainly useful when the Service Binding Operator is not installed; // and the service binding secrets must be deleted manually since they are created by odo if len(serviceBindingSecretsToRemove) != 0 { - err = a.deleteServiceBindingSecrets(serviceBindingSecretsToRemove, deployment) + err = o.deleteServiceBindingSecrets(serviceBindingSecretsToRemove, deployment) if err != nil { return fmt.Errorf("unable to delete service binding secrets: %w", err) } } // Create all the K8s components defined in the devfile - _, err = a.pushDevfileKubernetesComponents(ctx, parameters, labels, odolabels.ComponentDevMode, ownerReference) + _, err = o.pushDevfileKubernetesComponents(ctx, parameters, labels, odolabels.ComponentDevMode, ownerReference) if err != nil { return err } - err = a.updatePVCsOwnerReferences(ctx, ownerReference) + err = o.updatePVCsOwnerReferences(ctx, ownerReference) if err != nil { return err } @@ -195,7 +148,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c return nil } - injected, err := a.bindingClient.CheckServiceBindingsInjectionDone(componentName, appName) + injected, err := o.bindingClient.CheckServiceBindingsInjectionDone(componentName, appName) if err != nil { return err } @@ -210,7 +163,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c if err != nil { return err } - portsChanged := !reflect.DeepEqual(portsToForward, a.portForwardClient.GetForwardedPorts()) + portsChanged := !reflect.DeepEqual(portsToForward, o.portForwardClient.GetForwardedPorts()) if componentStatus.State == watch.StateReady && !portsChanged { // If the deployment is already in Ready State, no need to continue @@ -218,7 +171,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } // Now the Deployment has a Ready replica, we can get the Pod to work inside it - pod, err := a.kubeClient.GetPodUsingComponentName(componentName) + pod, err := o.kubernetesClient.GetPodUsingComponentName(componentName) if err != nil { return fmt.Errorf("unable to get pod for component %s: %w", componentName, err) } @@ -233,7 +186,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c defer s.End(false) // Get commands - pushDevfileCommands, err := a.getPushDevfileCommands(parameters) + pushDevfileCommands, err := o.getPushDevfileCommands(parameters) if err != nil { return fmt.Errorf("failed to validate devfile build and run commands: %w", err) } @@ -264,10 +217,10 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c CompInfo: compInfo, ForcePush: !deploymentExists || podChanged, - Files: adapters.GetSyncFilesFromAttributes(pushDevfileCommands[cmdKind]), + Files: common.GetSyncFilesFromAttributes(pushDevfileCommands[cmdKind]), } - execRequired, err := a.syncClient.SyncFiles(ctx, syncParams) + execRequired, err := o.syncClient.SyncFiles(ctx, syncParams) if err != nil { componentStatus.State = watch.StateReady return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err) @@ -277,7 +230,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // PostStart events from the devfile will only be executed when the component // didn't previously exist if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) { - err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) + err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) if err != nil { return err } @@ -296,9 +249,9 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c var running bool var isComposite bool cmdHandler := runHandler{ - fs: a.fs, - execClient: a.execClient, - kubeClient: a.kubeClient, + fs: o.filesystem, + execClient: o.execClient, + kubeClient: o.kubernetesClient, appName: appName, componentName: componentName, devfile: parameters.Devfile, @@ -330,7 +283,7 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c // Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command, // the handler we pass will be called for each command in that composite command. doExecuteBuildCommand := func() error { - execHandler := component.NewExecHandler(a.kubeClient, a.execClient, appName, componentName, pod.Name, + execHandler := component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, "Building your application in container", parameters.Show) return libdevfile.Build(ctx, parameters.Devfile, parameters.DevfileBuildCmd, execHandler) } @@ -352,12 +305,12 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c } if podChanged || portsChanged { - a.portForwardClient.StopPortForwarding(ctx, componentName) + o.portForwardClient.StopPortForwarding(ctx, componentName) } // Check that the application is actually listening on the ports declared in the Devfile, so we are sure that port-forwarding will work appReadySpinner := log.Spinner("Waiting for the application to be ready") - err = a.checkAppPorts(ctx, pod.Name, portsToForward) + err = o.checkAppPorts(ctx, pod.Name, portsToForward) appReadySpinner.End(err == nil) if err != nil { log.Warningf("Port forwarding might not work correctly: %v", err) @@ -365,22 +318,85 @@ func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, c fmt.Fprintln(log.GetStdout()) } - err = a.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) + err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) if err != nil { - return adapters.NewErrPortForward(err) + return common.NewErrPortForward(err) } - componentStatus.EndpointsForwarded = a.portForwardClient.GetForwardedPorts() + componentStatus.EndpointsForwarded = o.portForwardClient.GetForwardedPorts() componentStatus.State = watch.StateReady return nil } +func (o *DevClient) buildPushAutoImageComponents(ctx context.Context, fs filesystem.Filesystem, devfileObj parser.DevfileObj, compStatus *watch.ComponentStatus) error { + components, err := libdevfile.GetImageComponentsToPushAutomatically(devfileObj) + if err != nil { + return err + } + + for _, c := range components { + if c.Image == nil { + return fmt.Errorf("component %q should be an Image Component", c.Name) + } + alreadyApplied, ok := compStatus.ImageComponentsAutoApplied[c.Name] + if ok && reflect.DeepEqual(*c.Image, alreadyApplied) { + klog.V(1).Infof("Skipping image component %q; already applied and not changed", c.Name) + continue + } + err = image.BuildPushSpecificImage(ctx, fs, c, true) + if err != nil { + return err + } + compStatus.ImageComponentsAutoApplied[c.Name] = *c.Image + } + + // Remove keys that might no longer be valid + devfileHasCompFn := func(n string) bool { + for _, c := range components { + if c.Name == n { + return true + } + } + return false + } + for n := range compStatus.ImageComponentsAutoApplied { + if !devfileHasCompFn(n) { + delete(compStatus.ImageComponentsAutoApplied, n) + } + } + + return nil +} + +// getComponentDeployment returns the deployment associated with the component, if deployed +// and indicate if the deployment has been found +func (o *DevClient) getComponentDeployment(ctx context.Context) (*appsv1.Deployment, bool, error) { + var ( + componentName = odocontext.GetComponentName(ctx) + appName = odocontext.GetApplication(ctx) + ) + + // Get the Dev deployment: + // Since `odo deploy` can theoretically deploy a deployment as well with the same instance name + // we make sure that we are retrieving the deployment with the Dev mode, NOT Deploy. + selectorLabels := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, true) + deployment, err := o.kubernetesClient.GetOneDeploymentFromSelector(selectorLabels) + + if err != nil { + if _, ok := err.(*kclient.DeploymentNotFoundError); !ok { + return nil, false, fmt.Errorf("unable to determine if component %s exists: %w", componentName, err) + } + } + componentExists := deployment != nil + return deployment, componentExists, nil +} + // createOrUpdateComponent creates the deployment or updates it if it already exists // with the expected spec. // Returns the new deployment and if the generation of the deployment has been updated -func (a *Adapter) createOrUpdateComponent( +func (o *DevClient) createOrUpdateComponent( ctx context.Context, - parameters adapters.PushParameters, + parameters common.PushParameters, componentExists bool, commands libdevfile.DevfileCommands, deployment *appsv1.Deployment, @@ -403,12 +419,12 @@ func (a *Adapter) createOrUpdateComponent( odolabels.AddCommonAnnotations(annotations) klog.V(4).Infof("We are deploying these annotations: %s", annotations) - deploymentObjectMeta, err := a.generateDeploymentObjectMeta(ctx, deployment, labels, annotations) + deploymentObjectMeta, err := o.generateDeploymentObjectMeta(ctx, deployment, labels, annotations) if err != nil { return nil, false, err } - policy, err := a.kubeClient.GetCurrentNamespacePolicy() + policy, err := o.kubernetesClient.GetCurrentNamespacePolicy() if err != nil { return nil, false, err } @@ -432,7 +448,7 @@ func (a *Adapter) createOrUpdateComponent( } // Returns the volumes to add to the PodTemplate and adds volumeMounts to the containers and initContainers - volumes, err := a.buildVolumes(ctx, parameters, containers, initContainers) + volumes, err := o.buildVolumes(ctx, parameters, containers, initContainers) if err != nil { return nil, false, err } @@ -477,7 +493,7 @@ func (a *Adapter) createOrUpdateComponent( if err != nil { return nil, false, err } - serviceObjectMeta := generator.GetObjectMeta(serviceName, a.kubeClient.GetCurrentNamespace(), labels, serviceAnnotations) + serviceObjectMeta := generator.GetObjectMeta(serviceName, o.kubernetesClient.GetCurrentNamespace(), labels, serviceAnnotations) serviceParams := generator.ServiceParams{ ObjectMeta: serviceObjectMeta, SelectorLabels: selectorLabels, @@ -492,27 +508,27 @@ func (a *Adapter) createOrUpdateComponent( if componentExists { // If the component already exists, get the resource version of the deploy before updating klog.V(2).Info("The component already exists, attempting to update it") - if a.kubeClient.IsSSASupported() { + if o.kubernetesClient.IsSSASupported() { klog.V(4).Info("Applying deployment") - deployment, err = a.kubeClient.ApplyDeployment(*deployment) + deployment, err = o.kubernetesClient.ApplyDeployment(*deployment) } else { klog.V(4).Info("Updating deployment") - deployment, err = a.kubeClient.UpdateDeployment(*deployment) + deployment, err = o.kubernetesClient.UpdateDeployment(*deployment) } if err != nil { return nil, false, err } klog.V(2).Infof("Successfully updated component %v", componentName) ownerReference := generator.GetOwnerReference(deployment) - err = a.createOrUpdateServiceForComponent(ctx, svc, ownerReference) + err = o.createOrUpdateServiceForComponent(ctx, svc, ownerReference) if err != nil { return nil, false, err } } else { - if a.kubeClient.IsSSASupported() { - deployment, err = a.kubeClient.ApplyDeployment(*deployment) + if o.kubernetesClient.IsSSASupported() { + deployment, err = o.kubernetesClient.ApplyDeployment(*deployment) } else { - deployment, err = a.kubeClient.CreateDeployment(*deployment) + deployment, err = o.kubernetesClient.CreateDeployment(*deployment) } if err != nil { @@ -523,9 +539,9 @@ func (a *Adapter) createOrUpdateComponent( if len(svc.Spec.Ports) > 0 { ownerReference := generator.GetOwnerReference(deployment) originOwnerRefs := svc.OwnerReferences - err = a.kubeClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { + err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { svc.OwnerReferences = append(originOwnerRefs, ownerRef) - _, err = a.kubeClient.CreateService(*svc) + _, err = o.kubernetesClient.CreateService(*svc) return err }) if err != nil { @@ -540,153 +556,17 @@ func (a *Adapter) createOrUpdateComponent( return deployment, newGeneration != originalGeneration, nil } -// buildVolumes: -// - (side effect on cluster) creates the PVC for the project sources if Epehemeral preference is false -// - (side effect on cluster) creates the PVCs for non-ephemeral volumes defined in the Devfile -// - (side effect on input parameters) adds volumeMounts to containers and initContainers for the PVCs and Ephemeral volumes -// - (side effect on input parameters) adds volumeMounts for automounted volumes -// => Returns the list of Volumes to add to the PodTemplate -func (a *Adapter) buildVolumes(ctx context.Context, parameters adapters.PushParameters, containers, initContainers []corev1.Container) ([]corev1.Volume, error) { - var ( - appName = odocontext.GetApplication(ctx) - componentName = odocontext.GetComponentName(ctx) - ) - - runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()) - - storageClient := storagepkg.NewClient(componentName, appName, storagepkg.ClientOptions{ - Client: a.kubeClient, - Runtime: runtime, - }) - - // Create the PVC for the project sources, if not ephemeral - err := storage.HandleOdoSourceStorage(a.kubeClient, storageClient, componentName, a.prefClient.GetEphemeralSourceVolume()) - if err != nil { - return nil, err - } - - // Create PVCs for non-ephemeral Volumes defined in the Devfile - // and returns the Ephemeral volumes defined in the Devfile - ephemerals, err := storagepkg.Push(storageClient, parameters.Devfile) - if err != nil { - return nil, err - } - - // get all the PVCs from the cluster belonging to the component - // These PVCs have been created earlier with `storage.HandleOdoSourceStorage` and `storagepkg.Push` - pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) - if err != nil { - return nil, err - } - - var allVolumes []corev1.Volume - - // Get the name of the PVC for project sources + a map of (storageName => VolumeInfo) - // odoSourcePVCName will be empty when Ephemeral preference is true - odoSourcePVCName, volumeNameToVolInfo, err := storage.GetVolumeInfos(pvcs) - if err != nil { - return nil, err - } - - // Add the volumes for the projects source and the Odo-specific directory - odoMandatoryVolumes := utils.GetOdoContainerVolumes(odoSourcePVCName) - allVolumes = append(allVolumes, odoMandatoryVolumes...) - - // Add the volumeMounts for the project sources volume and the Odo-specific volume into the containers - utils.AddOdoProjectVolume(containers) - utils.AddOdoMandatoryVolume(containers) - - // Get PVC volumes and Volume Mounts - pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) - if err != nil { - return nil, err - } - allVolumes = append(allVolumes, pvcVolumes...) - - ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) - if err != nil { - return nil, err - } - allVolumes = append(allVolumes, ephemeralVolumes...) - - automountVolumes, err := storage.GetAutomountVolumes(a.configAutomountClient, containers, initContainers) - if err != nil { - return nil, err - } - allVolumes = append(allVolumes, automountVolumes...) - - return allVolumes, nil -} - -func (a *Adapter) createOrUpdateServiceForComponent(ctx context.Context, svc *corev1.Service, ownerReference metav1.OwnerReference) error { - var ( - appName = odocontext.GetApplication(ctx) - componentName = odocontext.GetComponentName(ctx) - ) - oldSvc, err := a.kubeClient.GetOneService(componentName, appName, true) - originOwnerReferences := svc.OwnerReferences - if err != nil { - // no old service was found, create a new one - if len(svc.Spec.Ports) > 0 { - err = a.kubeClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { - svc.OwnerReferences = append(originOwnerReferences, ownerReference) - _, err = a.kubeClient.CreateService(*svc) - return err - }) - if err != nil { - return err - } - klog.V(2).Infof("Successfully created Service for component %s", componentName) - } - return nil - } - if len(svc.Spec.Ports) > 0 { - svc.Spec.ClusterIP = oldSvc.Spec.ClusterIP - svc.ResourceVersion = oldSvc.GetResourceVersion() - err = a.kubeClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { - svc.OwnerReferences = append(originOwnerReferences, ownerRef) - _, err = a.kubeClient.UpdateService(*svc) - return err - }) - if err != nil { - return err - } - klog.V(2).Infof("Successfully update Service for component %s", componentName) - return nil - } - // delete the old existing service if the component currently doesn't expose any ports - return a.kubeClient.DeleteService(oldSvc.Name) -} - -// generateDeploymentObjectMeta generates a ObjectMeta object for the given deployment's name, labels and annotations -// if no deployment exists, it creates a new deployment name -func (a Adapter) generateDeploymentObjectMeta(ctx context.Context, deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) { - var ( - appName = odocontext.GetApplication(ctx) - componentName = odocontext.GetComponentName(ctx) - ) - if deployment != nil { - return generator.GetObjectMeta(deployment.Name, a.kubeClient.GetCurrentNamespace(), labels, annotations), nil - } else { - deploymentName, err := util.NamespaceKubernetesObject(componentName, appName) - if err != nil { - return metav1.ObjectMeta{}, err - } - return generator.GetObjectMeta(deploymentName, a.kubeClient.GetCurrentNamespace(), labels, annotations), nil - } -} - // getRemoteResourcesNotPresentInDevfile compares the list of Devfile K8s component and remote K8s resources // and returns a list of the remote resources not present in the Devfile and in case the SBO is not installed, a list of service binding secrets that must be deleted; // it ignores the core components (such as deployments, svc, pods; all resources with `component:` label) -func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, parameters adapters.PushParameters, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { +func (o DevClient) getRemoteResourcesNotPresentInDevfile(ctx context.Context, parameters common.PushParameters, selector string) (objectsToRemove, serviceBindingSecretsToRemove []unstructured.Unstructured, err error) { var ( devfilePath = odocontext.GetDevfilePath(ctx) path = filepath.Dir(devfilePath) ) - currentNamespace := a.kubeClient.GetCurrentNamespace() - allRemoteK8sResources, err := a.kubeClient.GetAllResourcesFromSelector(selector, currentNamespace) + currentNamespace := o.kubernetesClient.GetCurrentNamespace() + allRemoteK8sResources, err := o.kubernetesClient.GetAllResourcesFromSelector(selector, currentNamespace) if err != nil { return nil, nil, fmt.Errorf("unable to fetch remote resources: %w", err) } @@ -723,7 +603,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, para devfileK8sResourcesUnstructured = append(devfileK8sResourcesUnstructured, devfileKUnstructuredList...) } - isSBOSupported, err := a.kubeClient.IsServiceBindingSupported() + isSBOSupported, err := o.kubernetesClient.IsServiceBindingSupported() if err != nil { return nil, nil, fmt.Errorf("error in determining support for the Service Binding Operator: %w", err) } @@ -763,7 +643,7 @@ func (a Adapter) getRemoteResourcesNotPresentInDevfile(ctx context.Context, para } // deleteRemoteResources takes a list of remote resources to be deleted -func (a Adapter) deleteRemoteResources(objectsToRemove []unstructured.Unstructured) error { +func (o DevClient) deleteRemoteResources(objectsToRemove []unstructured.Unstructured) error { if len(objectsToRemove) == 0 { return nil } @@ -781,12 +661,12 @@ func (a Adapter) deleteRemoteResources(objectsToRemove []unstructured.Unstructur // See https://golang.org/doc/faq#closures_and_goroutines for more details. objectToRemove := objectToRemove g.Go(func() error { - gvr, err := a.kubeClient.GetGVRFromGVK(objectToRemove.GroupVersionKind()) + gvr, err := o.kubernetesClient.GetGVRFromGVK(objectToRemove.GroupVersionKind()) if err != nil { return fmt.Errorf("unable to get information about resource: %s/%s: %w", objectToRemove.GetKind(), objectToRemove.GetName(), err) } - err = a.kubeClient.DeleteDynamicResource(objectToRemove.GetName(), gvr, true) + err = o.kubernetesClient.DeleteDynamicResource(objectToRemove.GetName(), gvr, true) if err != nil { if !(kerrors.IsNotFound(err) || kerrors.IsMethodNotSupported(err)) { return fmt.Errorf("unable to delete resource: %s/%s: %w", objectToRemove.GetKind(), objectToRemove.GetName(), err) @@ -807,19 +687,19 @@ func (a Adapter) deleteRemoteResources(objectsToRemove []unstructured.Unstructur // deleteServiceBindingSecrets takes a list of Service Binding secrets(unstructured) that should be deleted; // this is helpful when Service Binding Operator is not installed on the cluster -func (a Adapter) deleteServiceBindingSecrets(serviceBindingSecretsToRemove []unstructured.Unstructured, deployment *appsv1.Deployment) error { +func (o DevClient) deleteServiceBindingSecrets(serviceBindingSecretsToRemove []unstructured.Unstructured, deployment *appsv1.Deployment) error { for _, secretToRemove := range serviceBindingSecretsToRemove { spinner := log.Spinnerf("Deleting Kubernetes resource: %s/%s", secretToRemove.GetKind(), secretToRemove.GetName()) defer spinner.End(false) - err := service.UnbindWithLibrary(a.kubeClient, secretToRemove, deployment) + err := service.UnbindWithLibrary(o.kubernetesClient, secretToRemove, deployment) if err != nil { return fmt.Errorf("failed to unbind secret %q from the application", secretToRemove.GetName()) } // since the library currently doesn't delete the secret after unbinding // delete the secret manually - err = a.kubeClient.DeleteSecret(secretToRemove.GetName(), a.kubeClient.GetCurrentNamespace()) + err = o.kubernetesClient.DeleteSecret(secretToRemove.GetName(), o.kubernetesClient.GetCurrentNamespace()) if err != nil { if !kerrors.IsNotFound(err) { return fmt.Errorf("unable to delete Kubernetes resource: %s/%s: %s", secretToRemove.GetKind(), secretToRemove.GetName(), err.Error()) @@ -831,15 +711,230 @@ func (a Adapter) deleteServiceBindingSecrets(serviceBindingSecretsToRemove []uns return nil } -func (a *Adapter) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error { +// pushDevfileKubernetesComponents gets the Kubernetes components from the Devfile and push them to the cluster +// adding the specified labels and ownerreference to them +func (o *DevClient) pushDevfileKubernetesComponents( + ctx context.Context, + parameters common.PushParameters, + labels map[string]string, + mode string, + reference metav1.OwnerReference, +) ([]devfilev1.Component, error) { + var ( + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + + // fetch the "kubernetes inlined components" to create them on cluster + // from odo standpoint, these components contain yaml manifest of ServiceBinding + k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, false) + if err != nil { + return nil, fmt.Errorf("error while trying to fetch service(s) from devfile: %w", err) + } + + // validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster + err = component.ValidateResourcesExist(o.kubernetesClient, parameters.Devfile, k8sComponents, path) + if err != nil { + return nil, err + } + + // Set the annotations for the component type + annotations := make(map[string]string) + odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())) + + // create the Kubernetes objects from the manifest and delete the ones not in the devfile + err = service.PushKubernetesResources(o.kubernetesClient, parameters.Devfile, k8sComponents, labels, annotations, path, mode, reference) + if err != nil { + return nil, fmt.Errorf("failed to create Kubernetes resources associated with the component: %w", err) + } + return k8sComponents, nil +} + +func (o *DevClient) updatePVCsOwnerReferences(ctx context.Context, ownerReference metav1.OwnerReference) error { + var ( + componentName = odocontext.GetComponentName(ctx) + ) + + // list the latest state of the PVCs + pvcs, err := o.kubernetesClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) + if err != nil { + return err + } + + // update the owner reference of the PVCs with the deployment + for i := range pvcs { + if pvcs[i].OwnerReferences != nil || pvcs[i].DeletionTimestamp != nil { + continue + } + err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { + return o.kubernetesClient.UpdateStorageOwnerReference(&pvcs[i], ownerRef) + }) + if err != nil { + return err + } + } + return nil +} + +func (o *DevClient) getPushDevfileCommands(parameters common.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { + pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) + if err != nil { + return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) + } + + if parameters.Debug { + pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) + if e != nil { + return nil, fmt.Errorf("debug command is not valid: %w", e) + } + pushDevfileCommands[devfilev1.DebugCommandGroupKind] = pushDevfileDebugCommands + } + + return pushDevfileCommands, nil +} + +func (o *DevClient) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error { containerPortsMapping := make(map[string][]int) for c, ports := range portsToFwd { for _, p := range ports { containerPortsMapping[c] = append(containerPortsMapping[c], p.TargetPort) } } - return port.CheckAppPortsListening(ctx, a.execClient, podName, containerPortsMapping, 1*time.Minute) + return port.CheckAppPortsListening(ctx, o.execClient, podName, containerPortsMapping, 1*time.Minute) +} + +// generateDeploymentObjectMeta generates a ObjectMeta object for the given deployment's name, labels and annotations +// if no deployment exists, it creates a new deployment name +func (o DevClient) generateDeploymentObjectMeta(ctx context.Context, deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) + if deployment != nil { + return generator.GetObjectMeta(deployment.Name, o.kubernetesClient.GetCurrentNamespace(), labels, annotations), nil + } else { + deploymentName, err := util.NamespaceKubernetesObject(componentName, appName) + if err != nil { + return metav1.ObjectMeta{}, err + } + return generator.GetObjectMeta(deploymentName, o.kubernetesClient.GetCurrentNamespace(), labels, annotations), nil + } } -// PushCommandsMap stores the commands to be executed as per their types. -type PushCommandsMap map[devfilev1.CommandGroupKind]devfilev1.Command +// buildVolumes: +// - (side effect on cluster) creates the PVC for the project sources if Epehemeral preference is false +// - (side effect on cluster) creates the PVCs for non-ephemeral volumes defined in the Devfile +// - (side effect on input parameters) adds volumeMounts to containers and initContainers for the PVCs and Ephemeral volumes +// - (side effect on input parameters) adds volumeMounts for automounted volumes +// => Returns the list of Volumes to add to the PodTemplate +func (o *DevClient) buildVolumes(ctx context.Context, parameters common.PushParameters, containers, initContainers []corev1.Container) ([]corev1.Volume, error) { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) + + runtime := component.GetComponentRuntimeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata()) + + storageClient := storagepkg.NewClient(componentName, appName, storagepkg.ClientOptions{ + Client: o.kubernetesClient, + Runtime: runtime, + }) + + // Create the PVC for the project sources, if not ephemeral + err := storage.HandleOdoSourceStorage(o.kubernetesClient, storageClient, componentName, o.prefClient.GetEphemeralSourceVolume()) + if err != nil { + return nil, err + } + + // Create PVCs for non-ephemeral Volumes defined in the Devfile + // and returns the Ephemeral volumes defined in the Devfile + ephemerals, err := storagepkg.Push(storageClient, parameters.Devfile) + if err != nil { + return nil, err + } + + // get all the PVCs from the cluster belonging to the component + // These PVCs have been created earlier with `storage.HandleOdoSourceStorage` and `storagepkg.Push` + pvcs, err := o.kubernetesClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) + if err != nil { + return nil, err + } + + var allVolumes []corev1.Volume + + // Get the name of the PVC for project sources + a map of (storageName => VolumeInfo) + // odoSourcePVCName will be empty when Ephemeral preference is true + odoSourcePVCName, volumeNameToVolInfo, err := storage.GetVolumeInfos(pvcs) + if err != nil { + return nil, err + } + + // Add the volumes for the projects source and the Odo-specific directory + odoMandatoryVolumes := utils.GetOdoContainerVolumes(odoSourcePVCName) + allVolumes = append(allVolumes, odoMandatoryVolumes...) + + // Add the volumeMounts for the project sources volume and the Odo-specific volume into the containers + utils.AddOdoProjectVolume(containers) + utils.AddOdoMandatoryVolume(containers) + + // Get PVC volumes and Volume Mounts + pvcVolumes, err := storage.GetPersistentVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, volumeNameToVolInfo, parsercommon.DevfileOptions{}) + if err != nil { + return nil, err + } + allVolumes = append(allVolumes, pvcVolumes...) + + ephemeralVolumes, err := storage.GetEphemeralVolumesAndVolumeMounts(parameters.Devfile, containers, initContainers, ephemerals, parsercommon.DevfileOptions{}) + if err != nil { + return nil, err + } + allVolumes = append(allVolumes, ephemeralVolumes...) + + automountVolumes, err := storage.GetAutomountVolumes(o.configAutomountClient, containers, initContainers) + if err != nil { + return nil, err + } + allVolumes = append(allVolumes, automountVolumes...) + + return allVolumes, nil +} + +func (o *DevClient) createOrUpdateServiceForComponent(ctx context.Context, svc *corev1.Service, ownerReference metav1.OwnerReference) error { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + ) + oldSvc, err := o.kubernetesClient.GetOneService(componentName, appName, true) + originOwnerReferences := svc.OwnerReferences + if err != nil { + // no old service was found, create a new one + if len(svc.Spec.Ports) > 0 { + err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { + svc.OwnerReferences = append(originOwnerReferences, ownerReference) + _, err = o.kubernetesClient.CreateService(*svc) + return err + }) + if err != nil { + return err + } + klog.V(2).Infof("Successfully created Service for component %s", componentName) + } + return nil + } + if len(svc.Spec.Ports) > 0 { + svc.Spec.ClusterIP = oldSvc.Spec.ClusterIP + svc.ResourceVersion = oldSvc.GetResourceVersion() + err = o.kubernetesClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { + svc.OwnerReferences = append(originOwnerReferences, ownerRef) + _, err = o.kubernetesClient.UpdateService(*svc) + return err + }) + if err != nil { + return err + } + klog.V(2).Infof("Successfully update Service for component %s", componentName) + return nil + } + // delete the old existing service if the component currently doesn't expose any ports + return o.kubernetesClient.DeleteService(oldSvc.Name) +} diff --git a/pkg/devfile/adapters/kubernetes/component/adapter_test.go b/pkg/dev/kubedev/push_test.go similarity index 97% rename from pkg/devfile/adapters/kubernetes/component/adapter_test.go rename to pkg/dev/kubedev/push_test.go index c24f45269e1..4188c453c31 100644 --- a/pkg/devfile/adapters/kubernetes/component/adapter_test.go +++ b/pkg/dev/kubedev/push_test.go @@ -1,4 +1,4 @@ -package component +package kubedev import ( "context" @@ -14,7 +14,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/redhat-developer/odo/pkg/configAutomount" - "github.com/redhat-developer/odo/pkg/devfile/adapters" + "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/preference" "github.com/redhat-developer/odo/pkg/testingutil" @@ -131,12 +131,12 @@ func TestCreateOrUpdateComponent(t *testing.T) { fakePrefClient.EXPECT().GetEphemeralSourceVolume().AnyTimes() fakeConfigAutomount := configAutomount.NewMockClient(ctrl) fakeConfigAutomount.EXPECT().GetAutomountingVolumes().AnyTimes() - componentAdapter := NewKubernetesAdapter(fkclient, fakePrefClient, nil, nil, nil, nil, fakeConfigAutomount, nil) + client := NewDevClient(fkclient, fakePrefClient, nil, nil, nil, nil, nil, nil, nil, fakeConfigAutomount) ctx := context.Background() ctx = odocontext.WithApplication(ctx, "app") ctx = odocontext.WithComponentName(ctx, "my-component") ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile") - _, _, err := componentAdapter.createOrUpdateComponent(ctx, adapters.PushParameters{ + _, _, err := client.createOrUpdateComponent(ctx, common.PushParameters{ Devfile: devObj, }, tt.running, libdevfile.DevfileCommands{}, nil) @@ -243,8 +243,8 @@ func TestAdapter_generateDeploymentObjectMeta(t *testing.T) { fakeClient, _ := kclient.FakeNew() fakeClient.Namespace = "project-0" - a := Adapter{ - kubeClient: fakeClient, + a := DevClient{ + kubernetesClient: fakeClient, } ctx := context.Background() ctx = odocontext.WithApplication(ctx, "app") @@ -439,8 +439,8 @@ func TestAdapter_deleteRemoteResources(t *testing.T) { if tt.fields.kubeClientCustomizer != nil { tt.fields.kubeClientCustomizer(kubeClient) } - a := Adapter{ - kubeClient: kubeClient, + a := DevClient{ + kubernetesClient: kubeClient, } if err := a.deleteRemoteResources(tt.args.objectsToRemove); (err != nil) != tt.wantErr { t.Errorf("deleteRemoteResources() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/devfile/adapters/kubernetes/storage/utils.go b/pkg/dev/kubedev/storage/utils.go similarity index 100% rename from pkg/devfile/adapters/kubernetes/storage/utils.go rename to pkg/dev/kubedev/storage/utils.go diff --git a/pkg/devfile/adapters/kubernetes/storage/utils_test.go b/pkg/dev/kubedev/storage/utils_test.go similarity index 100% rename from pkg/devfile/adapters/kubernetes/storage/utils_test.go rename to pkg/dev/kubedev/storage/utils_test.go diff --git a/pkg/devfile/adapters/kubernetes/utils/utils.go b/pkg/dev/kubedev/utils/utils.go similarity index 100% rename from pkg/devfile/adapters/kubernetes/utils/utils.go rename to pkg/dev/kubedev/utils/utils.go diff --git a/pkg/devfile/adapters/kubernetes/utils/utils_test.go b/pkg/dev/kubedev/utils/utils_test.go similarity index 100% rename from pkg/devfile/adapters/kubernetes/utils/utils_test.go rename to pkg/dev/kubedev/utils/utils_test.go diff --git a/pkg/dev/podmandev/pod.go b/pkg/dev/podmandev/pod.go index df26ba9e597..3402dd92811 100644 --- a/pkg/dev/podmandev/pod.go +++ b/pkg/dev/podmandev/pod.go @@ -14,7 +14,7 @@ import ( "github.com/redhat-developer/odo/pkg/api" "github.com/redhat-developer/odo/pkg/component" - "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/utils" + "github.com/redhat-developer/odo/pkg/dev/kubedev/utils" "github.com/redhat-developer/odo/pkg/labels" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/odo/commonflags" diff --git a/pkg/dev/podmandev/podmandev.go b/pkg/dev/podmandev/podmandev.go index 3827d9de416..5f67a13e02c 100644 --- a/pkg/dev/podmandev/podmandev.go +++ b/pkg/dev/podmandev/podmandev.go @@ -16,7 +16,6 @@ import ( "github.com/redhat-developer/odo/pkg/dev" "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/devfile" - "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/exec" "github.com/redhat-developer/odo/pkg/libdevfile" @@ -157,7 +156,7 @@ func (o *DevClient) syncFiles(ctx context.Context, options dev.StartOptions, pod CompInfo: compInfo, ForcePush: true, - Files: adapters.GetSyncFilesFromAttributes(devfileCmd), + Files: common.GetSyncFilesFromAttributes(devfileCmd), } execRequired, err := o.syncClient.SyncFiles(ctx, syncParams) if err != nil { @@ -185,7 +184,7 @@ func (o *DevClient) checkVolumesFree(pod *corev1.Pod) error { return nil } -func (o *DevClient) watchHandler(ctx context.Context, pushParams adapters.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { +func (o *DevClient) watchHandler(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { printWarningsOnDevfileChanges(ctx, watchParams) startOptions := dev.StartOptions{ diff --git a/pkg/dev/podmandev/reconcile.go b/pkg/dev/podmandev/reconcile.go index 01a654a83f1..4946118ed10 100644 --- a/pkg/dev/podmandev/reconcile.go +++ b/pkg/dev/podmandev/reconcile.go @@ -17,7 +17,7 @@ import ( "github.com/redhat-developer/odo/pkg/component" envcontext "github.com/redhat-developer/odo/pkg/config/context" "github.com/redhat-developer/odo/pkg/dev" - "github.com/redhat-developer/odo/pkg/devfile/adapters" + "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/devfile/image" "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/log" @@ -145,7 +145,7 @@ func (o *DevClient) reconcile( // Port-forwarding is enabled by executing dedicated socat commands err = o.portForwardClient.StartPortForwarding(ctx, *devfileObj, componentName, options.Debug, options.RandomPorts, out, errOut, fwPorts) if err != nil { - return adapters.NewErrPortForward(err) + return common.NewErrPortForward(err) } } // else port-forwarding is done via the main container ports in the pod spec diff --git a/pkg/devfile/adapters/kubernetes/component/interface.go b/pkg/devfile/adapters/kubernetes/component/interface.go deleted file mode 100644 index 40b3a3c88d5..00000000000 --- a/pkg/devfile/adapters/kubernetes/component/interface.go +++ /dev/null @@ -1,13 +0,0 @@ -package component - -import ( - "context" - - "github.com/redhat-developer/odo/pkg/devfile/adapters" - "github.com/redhat-developer/odo/pkg/watch" -) - -// ComponentAdapter defines the functions that platform-specific adapters must implement -type ComponentAdapter interface { - Push(ctx context.Context, parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) error -} diff --git a/pkg/devfile/adapters/kubernetes/component/push.go b/pkg/devfile/adapters/kubernetes/component/push.go deleted file mode 100644 index b4ecf1390e5..00000000000 --- a/pkg/devfile/adapters/kubernetes/component/push.go +++ /dev/null @@ -1,170 +0,0 @@ -package component - -import ( - "context" - "fmt" - "path/filepath" - "reflect" - - devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/library/v2/pkg/devfile/parser" - appsv1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" - - "github.com/redhat-developer/odo/pkg/component" - "github.com/redhat-developer/odo/pkg/devfile/adapters" - "github.com/redhat-developer/odo/pkg/devfile/image" - "github.com/redhat-developer/odo/pkg/kclient" - odolabels "github.com/redhat-developer/odo/pkg/labels" - "github.com/redhat-developer/odo/pkg/libdevfile" - odocontext "github.com/redhat-developer/odo/pkg/odo/context" - "github.com/redhat-developer/odo/pkg/service" - "github.com/redhat-developer/odo/pkg/testingutil/filesystem" - "github.com/redhat-developer/odo/pkg/watch" -) - -// getComponentDeployment returns the deployment associated with the component, if deployed -// and indicate if the deployment has been found -func (a *Adapter) getComponentDeployment(ctx context.Context) (*appsv1.Deployment, bool, error) { - var ( - componentName = odocontext.GetComponentName(ctx) - appName = odocontext.GetApplication(ctx) - ) - - // Get the Dev deployment: - // Since `odo deploy` can theoretically deploy a deployment as well with the same instance name - // we make sure that we are retrieving the deployment with the Dev mode, NOT Deploy. - selectorLabels := odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode, true) - deployment, err := a.kubeClient.GetOneDeploymentFromSelector(selectorLabels) - - if err != nil { - if _, ok := err.(*kclient.DeploymentNotFoundError); !ok { - return nil, false, fmt.Errorf("unable to determine if component %s exists: %w", componentName, err) - } - } - componentExists := deployment != nil - return deployment, componentExists, nil -} - -func (a *Adapter) buildPushAutoImageComponents(ctx context.Context, fs filesystem.Filesystem, devfileObj parser.DevfileObj, compStatus *watch.ComponentStatus) error { - components, err := libdevfile.GetImageComponentsToPushAutomatically(devfileObj) - if err != nil { - return err - } - - for _, c := range components { - if c.Image == nil { - return fmt.Errorf("component %q should be an Image Component", c.Name) - } - alreadyApplied, ok := compStatus.ImageComponentsAutoApplied[c.Name] - if ok && reflect.DeepEqual(*c.Image, alreadyApplied) { - klog.V(1).Infof("Skipping image component %q; already applied and not changed", c.Name) - continue - } - err = image.BuildPushSpecificImage(ctx, fs, c, true) - if err != nil { - return err - } - compStatus.ImageComponentsAutoApplied[c.Name] = *c.Image - } - - // Remove keys that might no longer be valid - devfileHasCompFn := func(n string) bool { - for _, c := range components { - if c.Name == n { - return true - } - } - return false - } - for n := range compStatus.ImageComponentsAutoApplied { - if !devfileHasCompFn(n) { - delete(compStatus.ImageComponentsAutoApplied, n) - } - } - - return nil -} - -// pushDevfileKubernetesComponents gets the Kubernetes components from the Devfile and push them to the cluster -// adding the specified labels and ownerreference to them -func (a *Adapter) pushDevfileKubernetesComponents( - ctx context.Context, - parameters adapters.PushParameters, - labels map[string]string, - mode string, - reference metav1.OwnerReference, -) ([]devfilev1.Component, error) { - var ( - devfilePath = odocontext.GetDevfilePath(ctx) - path = filepath.Dir(devfilePath) - ) - - // fetch the "kubernetes inlined components" to create them on cluster - // from odo standpoint, these components contain yaml manifest of ServiceBinding - k8sComponents, err := libdevfile.GetK8sAndOcComponentsToPush(parameters.Devfile, false) - if err != nil { - return nil, fmt.Errorf("error while trying to fetch service(s) from devfile: %w", err) - } - - // validate if the GVRs represented by Kubernetes inlined components are supported by the underlying cluster - err = component.ValidateResourcesExist(a.kubeClient, parameters.Devfile, k8sComponents, path) - if err != nil { - return nil, err - } - - // Set the annotations for the component type - annotations := make(map[string]string) - odolabels.SetProjectType(annotations, component.GetComponentTypeFromDevfileMetadata(parameters.Devfile.Data.GetMetadata())) - - // create the Kubernetes objects from the manifest and delete the ones not in the devfile - err = service.PushKubernetesResources(a.kubeClient, parameters.Devfile, k8sComponents, labels, annotations, path, mode, reference) - if err != nil { - return nil, fmt.Errorf("failed to create Kubernetes resources associated with the component: %w", err) - } - return k8sComponents, nil -} - -func (a *Adapter) getPushDevfileCommands(parameters adapters.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { - pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) - if err != nil { - return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) - } - - if parameters.Debug { - pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) - if e != nil { - return nil, fmt.Errorf("debug command is not valid: %w", e) - } - pushDevfileCommands[devfilev1.DebugCommandGroupKind] = pushDevfileDebugCommands - } - - return pushDevfileCommands, nil -} - -func (a *Adapter) updatePVCsOwnerReferences(ctx context.Context, ownerReference metav1.OwnerReference) error { - var ( - componentName = odocontext.GetComponentName(ctx) - ) - - // list the latest state of the PVCs - pvcs, err := a.kubeClient.ListPVCs(fmt.Sprintf("%v=%v", "component", componentName)) - if err != nil { - return err - } - - // update the owner reference of the PVCs with the deployment - for i := range pvcs { - if pvcs[i].OwnerReferences != nil || pvcs[i].DeletionTimestamp != nil { - continue - } - err = a.kubeClient.TryWithBlockOwnerDeletion(ownerReference, func(ownerRef metav1.OwnerReference) error { - return a.kubeClient.UpdateStorageOwnerReference(&pvcs[i], ownerRef) - }) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml b/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml new file mode 100644 index 00000000000..206682178dc --- /dev/null +++ b/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml @@ -0,0 +1,20 @@ +commands: +- exec: + commandLine: GOCACHE=${PROJECT_SOURCE}/.cache go build main.go + component: runtime + group: + isDefault: true + kind: build + workingDir: ${PROJECT_SOURCE} + id: build +- exec: + commandLine: ./main + component: runtime + group: + isDefault: true + kind: run + workingDir: ${PROJECT_SOURCE} + id: run +schemaVersion: 2.1.0 +metadata: + name: parent diff --git a/pkg/libdevfile/testdata/parent-devfile-components-only.yaml b/pkg/libdevfile/testdata/parent-devfile-components-only.yaml new file mode 100644 index 00000000000..74d64ff35bf --- /dev/null +++ b/pkg/libdevfile/testdata/parent-devfile-components-only.yaml @@ -0,0 +1,15 @@ +components: +- container: + endpoints: + - name: http + targetPort: 8080 + image: quay.io/devfile/golang:latest + memoryLimit: 1024Mi + mountSources: true + name: runtime +- kubernetes: + uri: "manifest.yaml" + name: kube-cmp +metadata: + name: my-go-app +schemaVersion: 2.1.0 diff --git a/pkg/libdevfile/testdata/parent-devfile-empty.yaml b/pkg/libdevfile/testdata/parent-devfile-empty.yaml new file mode 100644 index 00000000000..a3e59f1af10 --- /dev/null +++ b/pkg/libdevfile/testdata/parent-devfile-empty.yaml @@ -0,0 +1,3 @@ +metadata: + name: my-go-app +schemaVersion: 2.1.0 diff --git a/pkg/libdevfile/testdata/parent-devfile.yaml b/pkg/libdevfile/testdata/parent-devfile.yaml new file mode 100644 index 00000000000..08824612b9b --- /dev/null +++ b/pkg/libdevfile/testdata/parent-devfile.yaml @@ -0,0 +1,32 @@ +commands: +- exec: + commandLine: GOCACHE=${PROJECT_SOURCE}/.cache go build main.go + component: runtime + group: + isDefault: true + kind: build + workingDir: ${PROJECT_SOURCE} + id: build +- exec: + commandLine: ./main + component: runtime + group: + isDefault: true + kind: run + workingDir: ${PROJECT_SOURCE} + id: run +components: +- container: + endpoints: + - name: http + targetPort: 8080 + image: quay.io/devfile/golang:latest + memoryLimit: 1024Mi + mountSources: true + name: runtime +- kubernetes: + uri: "manifest.yaml" + name: kube-cmp +metadata: + name: my-go-app +schemaVersion: 2.1.0 diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index f95a0cc1efc..bb7d1b46ff0 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -11,8 +11,8 @@ import ( "time" "github.com/redhat-developer/odo/pkg/api" + "github.com/redhat-developer/odo/pkg/dev/common" - "github.com/redhat-developer/odo/pkg/devfile/adapters" "github.com/redhat-developer/odo/pkg/kclient" "github.com/redhat-developer/odo/pkg/labels" "github.com/redhat-developer/odo/pkg/libdevfile" @@ -64,7 +64,7 @@ type WatchParameters struct { // Custom function that can be used to push detected changes to remote pod. For more info about what each of the parameters to this function, please refer, pkg/component/component.go#PushLocal // WatchHandler func(kclient.ClientInterface, string, string, string, io.Writer, []string, []string, bool, []string, bool) error // Custom function that can be used to push detected changes to remote devfile pod. For more info about what each of the parameters to this function, please refer, pkg/devfile/adapters/interface.go#PlatformAdapter - DevfileWatchHandler func(context.Context, adapters.PushParameters, WatchParameters, *ComponentStatus) error + DevfileWatchHandler func(context.Context, common.PushParameters, WatchParameters, *ComponentStatus) error // Parameter whether or not to show build logs Show bool // DevfileBuildCmd takes the build command through the command line and overwrites devfile build command @@ -462,7 +462,7 @@ func (o *WatchClient) processEvents( klog.V(4).Infof("Copying files %s to pod", changedFiles) - pushParams := adapters.PushParameters{ + pushParams := common.PushParameters{ WatchFiles: changedFiles, WatchDeletedFiles: deletedPaths, IgnoredFiles: parameters.FileIgnores, @@ -572,5 +572,5 @@ func PrintInfoMessage(out io.Writer, path string, watchFiles bool, promptMessage } func isFatal(err error) bool { - return errors.As(err, &adapters.ErrPortForward{}) + return errors.As(err, &common.ErrPortForward{}) } From 840eb35ef8ecef65bff26ae011eb5a591aa85d7b Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 14:57:16 +0200 Subject: [PATCH 04/11] Rename Push to reconcile and split in 2 parts: components and innreloop --- pkg/dev/kubedev/{push.go => components.go} | 244 +++------------------ pkg/dev/kubedev/innerloop.go | 216 ++++++++++++++++++ pkg/dev/kubedev/kubedev.go | 20 +- pkg/dev/kubedev/push_test.go | 24 +- pkg/dev/kubedev/reconcile.go | 26 +++ 5 files changed, 294 insertions(+), 236 deletions(-) rename pkg/dev/kubedev/{push.go => components.go} (77%) create mode 100644 pkg/dev/kubedev/innerloop.go create mode 100644 pkg/dev/kubedev/reconcile.go diff --git a/pkg/dev/kubedev/push.go b/pkg/dev/kubedev/components.go similarity index 77% rename from pkg/dev/kubedev/push.go rename to pkg/dev/kubedev/components.go index d4ac11753a7..22f6b17ad96 100644 --- a/pkg/dev/kubedev/push.go +++ b/pkg/dev/kubedev/components.go @@ -7,7 +7,8 @@ import ( "path/filepath" "reflect" "strings" - "time" + + "golang.org/x/sync/errgroup" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/devfile/library/v2/pkg/devfile/generator" @@ -15,7 +16,6 @@ import ( parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem" dfutil "github.com/devfile/library/v2/pkg/util" - "golang.org/x/sync/errgroup" "github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/dev/common" @@ -27,10 +27,8 @@ import ( "github.com/redhat-developer/odo/pkg/libdevfile" "github.com/redhat-developer/odo/pkg/log" odocontext "github.com/redhat-developer/odo/pkg/odo/context" - "github.com/redhat-developer/odo/pkg/port" "github.com/redhat-developer/odo/pkg/service" storagepkg "github.com/redhat-developer/odo/pkg/storage" - "github.com/redhat-developer/odo/pkg/sync" "github.com/redhat-developer/odo/pkg/testingutil/filesystem" "github.com/redhat-developer/odo/pkg/util" "github.com/redhat-developer/odo/pkg/watch" @@ -44,27 +42,23 @@ import ( "k8s.io/utils/pointer" ) -// Push updates the component if a matching component exists or creates one if it doesn't exist -// Once the component has started, it will sync the source code to it. -// The componentStatus will be modified to reflect the status of the component when the function returns -func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) (err error) { - +// createComponents creates the components into the cluster +// returns true if the pod is created +func (o *DevClient) createComponents(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) (bool, error) { var ( appName = odocontext.GetApplication(ctx) componentName = odocontext.GetComponentName(ctx) - devfilePath = odocontext.GetDevfilePath(ctx) - path = filepath.Dir(devfilePath) ) // preliminary checks - err = dfutil.ValidateK8sResourceName("component name", componentName) + err := dfutil.ValidateK8sResourceName("component name", componentName) if err != nil { - return err + return false, err } err = dfutil.ValidateK8sResourceName("component namespace", o.kubernetesClient.GetCurrentNamespace()) if err != nil { - return err + return false, err } if componentStatus.State == watch.StateSyncOutdated { @@ -75,12 +69,13 @@ func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, klog.V(4).Infof("component state: %q\n", componentStatus.State) err = o.buildPushAutoImageComponents(ctx, o.filesystem, parameters.Devfile, componentStatus) if err != nil { - return err + return false, err } - deployment, deploymentExists, err := o.getComponentDeployment(ctx) + var deployment *appsv1.Deployment + deployment, o.deploymentExists, err = o.getComponentDeployment(ctx) if err != nil { - return err + return false, err } if componentStatus.State != watch.StateWaitDeployment && componentStatus.State != watch.StateReady { @@ -92,13 +87,13 @@ func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, labels := odolabels.GetLabels(componentName, appName, runtime, odolabels.ComponentDevMode, false) var updated bool - deployment, updated, err = o.createOrUpdateComponent(ctx, parameters, deploymentExists, libdevfile.DevfileCommands{ + deployment, updated, err = o.createOrUpdateComponent(ctx, parameters, o.deploymentExists, libdevfile.DevfileCommands{ BuildCmd: parameters.DevfileBuildCmd, RunCmd: parameters.DevfileRunCmd, DebugCmd: parameters.DevfileDebugCmd, }, deployment) if err != nil { - return fmt.Errorf("unable to create or update component: %w", err) + return false, fmt.Errorf("unable to create or update component: %w", err) } ownerReference := generator.GetOwnerReference(deployment) @@ -107,12 +102,12 @@ func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, objectsToRemove, serviceBindingSecretsToRemove, err := o.getRemoteResourcesNotPresentInDevfile(ctx, parameters, selector) if err != nil { - return fmt.Errorf("unable to determine resources to delete: %w", err) + return false, fmt.Errorf("unable to determine resources to delete: %w", err) } err = o.deleteRemoteResources(objectsToRemove) if err != nil { - return fmt.Errorf("unable to delete remote resources: %w", err) + return false, fmt.Errorf("unable to delete remote resources: %w", err) } // this is mainly useful when the Service Binding Operator is not installed; @@ -120,212 +115,56 @@ func (o *DevClient) Push(ctx context.Context, parameters common.PushParameters, if len(serviceBindingSecretsToRemove) != 0 { err = o.deleteServiceBindingSecrets(serviceBindingSecretsToRemove, deployment) if err != nil { - return fmt.Errorf("unable to delete service binding secrets: %w", err) + return false, fmt.Errorf("unable to delete service binding secrets: %w", err) } } // Create all the K8s components defined in the devfile _, err = o.pushDevfileKubernetesComponents(ctx, parameters, labels, odolabels.ComponentDevMode, ownerReference) if err != nil { - return err + return false, err } err = o.updatePVCsOwnerReferences(ctx, ownerReference) if err != nil { - return err + return false, err } if updated { klog.V(4).Infof("Deployment has been updated to generation %d. Waiting new event...\n", deployment.GetGeneration()) componentStatus.State = watch.StateWaitDeployment - return nil + return false, nil } numberReplicas := deployment.Status.ReadyReplicas if numberReplicas != 1 { klog.V(4).Infof("Deployment has %d ready replicas. Waiting new event...\n", numberReplicas) componentStatus.State = watch.StateWaitDeployment - return nil + return false, nil } injected, err := o.bindingClient.CheckServiceBindingsInjectionDone(componentName, appName) if err != nil { - return err + return false, err } if !injected { klog.V(4).Infof("Waiting for all service bindings to be injected...\n") - return errors.New("some servicebindings are not injected") + return false, errors.New("some servicebindings are not injected") } // Check if endpoints changed in Devfile portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.Debug) if err != nil { - return err + return false, err } - portsChanged := !reflect.DeepEqual(portsToForward, o.portForwardClient.GetForwardedPorts()) + o.portsChanged = !reflect.DeepEqual(portsToForward, o.portForwardClient.GetForwardedPorts()) - if componentStatus.State == watch.StateReady && !portsChanged { + if componentStatus.State == watch.StateReady && !o.portsChanged { // If the deployment is already in Ready State, no need to continue - return nil - } - - // Now the Deployment has a Ready replica, we can get the Pod to work inside it - pod, err := o.kubernetesClient.GetPodUsingComponentName(componentName) - if err != nil { - return fmt.Errorf("unable to get pod for component %s: %w", componentName, err) - } - - // Find at least one pod with the source volume mounted, error out if none can be found - containerName, syncFolder, err := common.GetFirstContainerWithSourceVolume(pod.Spec.Containers) - if err != nil { - return fmt.Errorf("error while retrieving container from pod %s with a mounted project volume: %w", pod.GetName(), err) - } - - s := log.Spinner("Syncing files into the container") - defer s.End(false) - - // Get commands - pushDevfileCommands, err := o.getPushDevfileCommands(parameters) - if err != nil { - return fmt.Errorf("failed to validate devfile build and run commands: %w", err) - } - - podChanged := componentStatus.State == watch.StateWaitDeployment - - // Get a sync adapter. Check if project files have changed and sync accordingly - compInfo := sync.ComponentInfo{ - ComponentName: componentName, - ContainerName: containerName, - PodName: pod.GetName(), - SyncFolder: syncFolder, - } - - cmdKind := devfilev1.RunCommandGroupKind - cmdName := parameters.DevfileRunCmd - if parameters.Debug { - cmdKind = devfilev1.DebugCommandGroupKind - cmdName = parameters.DevfileDebugCmd - } - - syncParams := sync.SyncParameters{ - Path: path, - WatchFiles: parameters.WatchFiles, - WatchDeletedFiles: parameters.WatchDeletedFiles, - IgnoredFiles: parameters.IgnoredFiles, - DevfileScanIndexForWatch: parameters.DevfileScanIndexForWatch, - - CompInfo: compInfo, - ForcePush: !deploymentExists || podChanged, - Files: common.GetSyncFilesFromAttributes(pushDevfileCommands[cmdKind]), - } - - execRequired, err := o.syncClient.SyncFiles(ctx, syncParams) - if err != nil { - componentStatus.State = watch.StateReady - return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err) - } - s.End(true) - - // PostStart events from the devfile will only be executed when the component - // didn't previously exist - if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) { - err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) - if err != nil { - return err - } - } - componentStatus.PostStartEventsDone = true - - cmd, err := libdevfile.ValidateAndGetCommand(parameters.Devfile, cmdName, cmdKind) - if err != nil { - return err - } - - commandType, err := parsercommon.GetCommandType(cmd) - if err != nil { - return err - } - var running bool - var isComposite bool - cmdHandler := runHandler{ - fs: o.filesystem, - execClient: o.execClient, - kubeClient: o.kubernetesClient, - appName: appName, - componentName: componentName, - devfile: parameters.Devfile, - path: path, - podName: pod.GetName(), - ctx: ctx, - } - - if commandType == devfilev1.ExecCommandType { - running, err = cmdHandler.IsRemoteProcessForCommandRunning(ctx, cmd, pod.Name) - if err != nil { - return err - } - } else if commandType == devfilev1.CompositeCommandType { - // this handler will run each command in this composite command individually, - // and will determine whether each command is running or not. - isComposite = true - } else { - return fmt.Errorf("unsupported type %q for Devfile command %s, only exec and composite are handled", - commandType, cmd.Id) - } - - cmdHandler.componentExists = running || isComposite - - klog.V(4).Infof("running=%v, execRequired=%v", - running, execRequired) - - if isComposite || !running || execRequired { - // Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command, - // the handler we pass will be called for each command in that composite command. - doExecuteBuildCommand := func() error { - execHandler := component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, - "Building your application in container", parameters.Show) - return libdevfile.Build(ctx, parameters.Devfile, parameters.DevfileBuildCmd, execHandler) - } - if running { - if cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) { - if err = doExecuteBuildCommand(); err != nil { - return err - } - } - } else { - if err = doExecuteBuildCommand(); err != nil { - return err - } - } - err = libdevfile.ExecuteCommandByNameAndKind(ctx, parameters.Devfile, cmdName, cmdKind, &cmdHandler, false) - if err != nil { - return err - } - } - - if podChanged || portsChanged { - o.portForwardClient.StopPortForwarding(ctx, componentName) - } - - // Check that the application is actually listening on the ports declared in the Devfile, so we are sure that port-forwarding will work - appReadySpinner := log.Spinner("Waiting for the application to be ready") - err = o.checkAppPorts(ctx, pod.Name, portsToForward) - appReadySpinner.End(err == nil) - if err != nil { - log.Warningf("Port forwarding might not work correctly: %v", err) - log.Warning("Running `odo logs --follow` might help in identifying the problem.") - fmt.Fprintln(log.GetStdout()) + return false, nil } - - err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) - if err != nil { - return common.NewErrPortForward(err) - } - componentStatus.EndpointsForwarded = o.portForwardClient.GetForwardedPorts() - - componentStatus.State = watch.StateReady - return nil + return true, nil } func (o *DevClient) buildPushAutoImageComponents(ctx context.Context, fs filesystem.Filesystem, devfileObj parser.DevfileObj, compStatus *watch.ComponentStatus) error { @@ -776,33 +615,6 @@ func (o *DevClient) updatePVCsOwnerReferences(ctx context.Context, ownerReferenc return nil } -func (o *DevClient) getPushDevfileCommands(parameters common.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { - pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) - if err != nil { - return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) - } - - if parameters.Debug { - pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) - if e != nil { - return nil, fmt.Errorf("debug command is not valid: %w", e) - } - pushDevfileCommands[devfilev1.DebugCommandGroupKind] = pushDevfileDebugCommands - } - - return pushDevfileCommands, nil -} - -func (o *DevClient) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error { - containerPortsMapping := make(map[string][]int) - for c, ports := range portsToFwd { - for _, p := range ports { - containerPortsMapping[c] = append(containerPortsMapping[c], p.TargetPort) - } - } - return port.CheckAppPortsListening(ctx, o.execClient, podName, containerPortsMapping, 1*time.Minute) -} - // generateDeploymentObjectMeta generates a ObjectMeta object for the given deployment's name, labels and annotations // if no deployment exists, it creates a new deployment name func (o DevClient) generateDeploymentObjectMeta(ctx context.Context, deployment *appsv1.Deployment, labels map[string]string, annotations map[string]string) (metav1.ObjectMeta, error) { diff --git a/pkg/dev/kubedev/innerloop.go b/pkg/dev/kubedev/innerloop.go new file mode 100644 index 00000000000..9e2d4e8af06 --- /dev/null +++ b/pkg/dev/kubedev/innerloop.go @@ -0,0 +1,216 @@ +package kubedev + +import ( + "context" + "fmt" + "path/filepath" + "time" + + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" + + "github.com/redhat-developer/odo/pkg/component" + "github.com/redhat-developer/odo/pkg/dev/common" + "github.com/redhat-developer/odo/pkg/libdevfile" + "github.com/redhat-developer/odo/pkg/log" + odocontext "github.com/redhat-developer/odo/pkg/odo/context" + "github.com/redhat-developer/odo/pkg/port" + "github.com/redhat-developer/odo/pkg/sync" + "github.com/redhat-developer/odo/pkg/util" + "github.com/redhat-developer/odo/pkg/watch" + + "k8s.io/klog" +) + +func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) error { + var ( + appName = odocontext.GetApplication(ctx) + componentName = odocontext.GetComponentName(ctx) + devfilePath = odocontext.GetDevfilePath(ctx) + path = filepath.Dir(devfilePath) + ) + + // Now the Deployment has a Ready replica, we can get the Pod to work inside it + pod, err := o.kubernetesClient.GetPodUsingComponentName(componentName) + if err != nil { + return fmt.Errorf("unable to get pod for component %s: %w", componentName, err) + } + + // Find at least one pod with the source volume mounted, error out if none can be found + containerName, syncFolder, err := common.GetFirstContainerWithSourceVolume(pod.Spec.Containers) + if err != nil { + return fmt.Errorf("error while retrieving container from pod %s with a mounted project volume: %w", pod.GetName(), err) + } + + s := log.Spinner("Syncing files into the container") + defer s.End(false) + + // Get commands + pushDevfileCommands, err := o.getPushDevfileCommands(parameters) + if err != nil { + return fmt.Errorf("failed to validate devfile build and run commands: %w", err) + } + + podChanged := componentStatus.State == watch.StateWaitDeployment + + // Get a sync adapter. Check if project files have changed and sync accordingly + compInfo := sync.ComponentInfo{ + ComponentName: componentName, + ContainerName: containerName, + PodName: pod.GetName(), + SyncFolder: syncFolder, + } + + cmdKind := devfilev1.RunCommandGroupKind + cmdName := parameters.DevfileRunCmd + if parameters.Debug { + cmdKind = devfilev1.DebugCommandGroupKind + cmdName = parameters.DevfileDebugCmd + } + + syncParams := sync.SyncParameters{ + Path: path, + WatchFiles: parameters.WatchFiles, + WatchDeletedFiles: parameters.WatchDeletedFiles, + IgnoredFiles: parameters.IgnoredFiles, + DevfileScanIndexForWatch: parameters.DevfileScanIndexForWatch, + + CompInfo: compInfo, + ForcePush: !o.deploymentExists || podChanged, + Files: common.GetSyncFilesFromAttributes(pushDevfileCommands[cmdKind]), + } + + execRequired, err := o.syncClient.SyncFiles(ctx, syncParams) + if err != nil { + componentStatus.State = watch.StateReady + return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err) + } + s.End(true) + + // PostStart events from the devfile will only be executed when the component + // didn't previously exist + if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) { + err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, "Executing post-start command in container", parameters.Show)) + if err != nil { + return err + } + } + componentStatus.PostStartEventsDone = true + + cmd, err := libdevfile.ValidateAndGetCommand(parameters.Devfile, cmdName, cmdKind) + if err != nil { + return err + } + + commandType, err := parsercommon.GetCommandType(cmd) + if err != nil { + return err + } + var running bool + var isComposite bool + cmdHandler := runHandler{ + fs: o.filesystem, + execClient: o.execClient, + kubeClient: o.kubernetesClient, + appName: appName, + componentName: componentName, + devfile: parameters.Devfile, + path: path, + podName: pod.GetName(), + ctx: ctx, + } + + if commandType == devfilev1.ExecCommandType { + running, err = cmdHandler.IsRemoteProcessForCommandRunning(ctx, cmd, pod.Name) + if err != nil { + return err + } + } else if commandType == devfilev1.CompositeCommandType { + // this handler will run each command in this composite command individually, + // and will determine whether each command is running or not. + isComposite = true + } else { + return fmt.Errorf("unsupported type %q for Devfile command %s, only exec and composite are handled", + commandType, cmd.Id) + } + + cmdHandler.componentExists = running || isComposite + + klog.V(4).Infof("running=%v, execRequired=%v", + running, execRequired) + + if isComposite || !running || execRequired { + // Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command, + // the handler we pass will be called for each command in that composite command. + doExecuteBuildCommand := func() error { + execHandler := component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, + "Building your application in container", parameters.Show) + return libdevfile.Build(ctx, parameters.Devfile, parameters.DevfileBuildCmd, execHandler) + } + if running { + if cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) { + if err = doExecuteBuildCommand(); err != nil { + return err + } + } + } else { + if err = doExecuteBuildCommand(); err != nil { + return err + } + } + err = libdevfile.ExecuteCommandByNameAndKind(ctx, parameters.Devfile, cmdName, cmdKind, &cmdHandler, false) + if err != nil { + return err + } + } + + if podChanged || o.portsChanged { + o.portForwardClient.StopPortForwarding(ctx, componentName) + } + + // Check that the application is actually listening on the ports declared in the Devfile, so we are sure that port-forwarding will work + appReadySpinner := log.Spinner("Waiting for the application to be ready") + err = o.checkAppPorts(ctx, pod.Name, o.portsToForward) + appReadySpinner.End(err == nil) + if err != nil { + log.Warningf("Port forwarding might not work correctly: %v", err) + log.Warning("Running `odo logs --follow` might help in identifying the problem.") + fmt.Fprintln(log.GetStdout()) + } + + err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) + if err != nil { + return common.NewErrPortForward(err) + } + componentStatus.EndpointsForwarded = o.portForwardClient.GetForwardedPorts() + + componentStatus.State = watch.StateReady + return nil +} + +func (o *DevClient) getPushDevfileCommands(parameters common.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { + pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) + if err != nil { + return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) + } + + if parameters.Debug { + pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) + if e != nil { + return nil, fmt.Errorf("debug command is not valid: %w", e) + } + pushDevfileCommands[devfilev1.DebugCommandGroupKind] = pushDevfileDebugCommands + } + + return pushDevfileCommands, nil +} + +func (o *DevClient) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error { + containerPortsMapping := make(map[string][]int) + for c, ports := range portsToFwd { + for _, p := range ports { + containerPortsMapping[c] = append(containerPortsMapping[c], p.TargetPort) + } + } + return port.CheckAppPortsListening(ctx, o.execClient, podName, containerPortsMapping, 1*time.Minute) +} diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 4f0d5234834..70ed10f6aec 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" "github.com/redhat-developer/odo/pkg/binding" _delete "github.com/redhat-developer/odo/pkg/component/delete" @@ -13,6 +13,7 @@ import ( "github.com/redhat-developer/odo/pkg/dev" "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/devfile" + "github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/exec" "github.com/redhat-developer/odo/pkg/kclient" odocontext "github.com/redhat-developer/odo/pkg/odo/context" @@ -20,11 +21,9 @@ import ( "github.com/redhat-developer/odo/pkg/preference" "github.com/redhat-developer/odo/pkg/sync" "github.com/redhat-developer/odo/pkg/testingutil/filesystem" + "github.com/redhat-developer/odo/pkg/watch" "k8s.io/klog" - - "github.com/redhat-developer/odo/pkg/devfile/location" - "github.com/redhat-developer/odo/pkg/watch" ) const ( @@ -45,6 +44,13 @@ type DevClient struct { execClient exec.Client deleteClient _delete.Client configAutomountClient configAutomount.Client + + // deploymentExists is true when the deployment is already created when calling createComponents + deploymentExists bool + // portsChanged is true of ports have changed since the last call to createComponents + portsChanged bool + // portsToForward lists the port to forward during inner loop (TODO move port forward to createComponents) + portsToForward map[string][]devfilev1.Endpoint } var _ dev.Client = (*DevClient)(nil) @@ -100,9 +106,9 @@ func (o *DevClient) Start( klog.V(4).Infoln("Creating inner-loop resources for the component") componentStatus := watch.ComponentStatus{ - ImageComponentsAutoApplied: make(map[string]v1alpha2.ImageComponent), + ImageComponentsAutoApplied: make(map[string]devfilev1.ImageComponent), } - err := o.Push(ctx, pushParameters, &componentStatus) + err := o.reconcile(ctx, pushParameters, &componentStatus) if err != nil { return err } @@ -136,7 +142,7 @@ func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams com pushParams.Devfile = devObj - err = o.Push(ctx, pushParams, componentStatus) + err = o.reconcile(ctx, pushParams, componentStatus) if err != nil { return fmt.Errorf("watch command was unable to push component: %w", err) } diff --git a/pkg/dev/kubedev/push_test.go b/pkg/dev/kubedev/push_test.go index 4188c453c31..82d59b30069 100644 --- a/pkg/dev/kubedev/push_test.go +++ b/pkg/dev/kubedev/push_test.go @@ -5,33 +5,31 @@ import ( "errors" "testing" - "github.com/devfile/library/v2/pkg/devfile/generator" - "github.com/devfile/library/v2/pkg/devfile/parser/data" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/redhat-developer/odo/pkg/configAutomount" - "github.com/redhat-developer/odo/pkg/dev/common" - "github.com/redhat-developer/odo/pkg/libdevfile" - "github.com/redhat-developer/odo/pkg/preference" - "github.com/redhat-developer/odo/pkg/testingutil" - "github.com/redhat-developer/odo/pkg/util" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" + "github.com/devfile/library/v2/pkg/devfile/generator" devfileParser "github.com/devfile/library/v2/pkg/devfile/parser" + "github.com/devfile/library/v2/pkg/devfile/parser/data" + "github.com/redhat-developer/odo/pkg/configAutomount" + "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/kclient" odolabels "github.com/redhat-developer/odo/pkg/labels" + "github.com/redhat-developer/odo/pkg/libdevfile" odocontext "github.com/redhat-developer/odo/pkg/odo/context" + "github.com/redhat-developer/odo/pkg/preference" odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil" + "github.com/redhat-developer/odo/pkg/util" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ktesting "k8s.io/client-go/testing" ) @@ -87,7 +85,7 @@ func TestCreateOrUpdateComponent(t *testing.T) { var comp devfilev1.Component if tt.componentType != "" { odolabels.SetProjectType(deployment.Annotations, string(tt.componentType)) - comp = testingutil.GetFakeContainerComponent("component") + comp = odoTestingUtil.GetFakeContainerComponent("component") } devObj := devfileParser.DevfileObj{ Data: func() data.DevfileData { diff --git a/pkg/dev/kubedev/reconcile.go b/pkg/dev/kubedev/reconcile.go new file mode 100644 index 00000000000..fab6ca1b0c7 --- /dev/null +++ b/pkg/dev/kubedev/reconcile.go @@ -0,0 +1,26 @@ +package kubedev + +import ( + "context" + + "github.com/redhat-developer/odo/pkg/dev/common" + "github.com/redhat-developer/odo/pkg/watch" +) + +// reconcile updates the component if a matching component exists or creates one if it doesn't exist +// Once the component has started, it will sync the source code to it. +// The componentStatus will be modified to reflect the status of the component when the function returns +func (o *DevClient) reconcile(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) (err error) { + + // podOK indicates if the pod is ready to use for the inner loop + var podOK bool + podOK, err = o.createComponents(ctx, parameters, componentStatus) + if err != nil { + return err + } + if !podOK { + return nil + } + + return o.innerloop(ctx, parameters, componentStatus) +} From c90605a67cd53bd572a533c3ef56f03f87e052b0 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 15:42:14 +0200 Subject: [PATCH 05/11] Pass out ans errout as startOptions --- pkg/dev/interface.go | 8 +++++--- pkg/dev/kubedev/kubedev.go | 9 +++------ pkg/dev/mock.go | 8 ++++---- pkg/dev/podmandev/podmandev.go | 13 +++++-------- pkg/odo/cli/dev/dev.go | 4 ++-- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pkg/dev/interface.go b/pkg/dev/interface.go index 03318ee4c3b..9578babb570 100644 --- a/pkg/dev/interface.go +++ b/pkg/dev/interface.go @@ -2,8 +2,9 @@ package dev import ( "context" - "github.com/redhat-developer/odo/pkg/api" "io" + + "github.com/redhat-developer/odo/pkg/api" ) type StartOptions struct { @@ -31,6 +32,9 @@ type StartOptions struct { ForwardLocalhost bool // Variables to override in the Devfile Variables map[string]string + + Out io.Writer + ErrOut io.Writer } type Client interface { @@ -39,8 +43,6 @@ type Client interface { // It logs messages and errors to out and errOut. Start( ctx context.Context, - out io.Writer, - errOut io.Writer, options StartOptions, ) error diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 70ed10f6aec..278f9d678aa 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -3,7 +3,6 @@ package kubedev import ( "context" "fmt" - "io" devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -83,8 +82,6 @@ func NewDevClient( func (o *DevClient) Start( ctx context.Context, - out io.Writer, - errOut io.Writer, options dev.StartOptions, ) error { klog.V(4).Infoln("Creating new adapter") @@ -100,7 +97,7 @@ func (o *DevClient) Start( DevfileRunCmd: options.RunCommand, RandomPorts: options.RandomPorts, CustomForwardedPorts: options.CustomForwardedPorts, - ErrOut: errOut, + ErrOut: options.ErrOut, Devfile: *devfileObj, } @@ -125,11 +122,11 @@ func (o *DevClient) Start( CustomForwardedPorts: options.CustomForwardedPorts, WatchFiles: options.WatchFiles, WatchCluster: true, - ErrOut: errOut, + ErrOut: options.ErrOut, PromptMessage: promptMessage, } - return o.watchClient.WatchAndPush(out, watchParameters, ctx, componentStatus) + return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) } // RegenerateAdapterAndPush get the new devfile and pushes the files to remote pod diff --git a/pkg/dev/mock.go b/pkg/dev/mock.go index c687fe98985..c615af8e388 100644 --- a/pkg/dev/mock.go +++ b/pkg/dev/mock.go @@ -50,15 +50,15 @@ func (mr *MockClientMockRecorder) CleanupResources(ctx, out interface{}) *gomock } // Start mocks base method. -func (m *MockClient) Start(ctx context.Context, out, errOut io.Writer, options StartOptions) error { +func (m *MockClient) Start(ctx context.Context, options StartOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Start", ctx, out, errOut, options) + ret := m.ctrl.Call(m, "Start", ctx, options) ret0, _ := ret[0].(error) return ret0 } // Start indicates an expected call of Start. -func (mr *MockClientMockRecorder) Start(ctx, out, errOut, options interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) Start(ctx, options interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), ctx, out, errOut, options) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), ctx, options) } diff --git a/pkg/dev/podmandev/podmandev.go b/pkg/dev/podmandev/podmandev.go index 5f67a13e02c..9f3812e9d55 100644 --- a/pkg/dev/podmandev/podmandev.go +++ b/pkg/dev/podmandev/podmandev.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "io" "path/filepath" "strings" @@ -76,8 +75,6 @@ func NewDevClient( func (o *DevClient) Start( ctx context.Context, - out io.Writer, - errOut io.Writer, options dev.StartOptions, ) error { var ( @@ -89,12 +86,12 @@ func (o *DevClient) Start( } ) - err := o.reconcile(ctx, out, errOut, options, &componentStatus) + err := o.reconcile(ctx, options.Out, options.ErrOut, options, &componentStatus) if err != nil { return err } - watch.PrintInfoMessage(out, path, options.WatchFiles, promptMessage) + watch.PrintInfoMessage(options.Out, path, options.WatchFiles, promptMessage) watchParameters := watch.WatchParameters{ DevfileWatchHandler: o.watchHandler, @@ -109,12 +106,12 @@ func (o *DevClient) Start( CustomForwardedPorts: options.CustomForwardedPorts, WatchFiles: options.WatchFiles, WatchCluster: false, - Out: out, - ErrOut: errOut, + Out: options.Out, + ErrOut: options.ErrOut, PromptMessage: promptMessage, } - return o.watchClient.WatchAndPush(out, watchParameters, ctx, componentStatus) + return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) } // syncFiles syncs the local source files in path into the pod's source volume diff --git a/pkg/odo/cli/dev/dev.go b/pkg/odo/cli/dev/dev.go index 2e35f481471..4a24fc8e6ff 100644 --- a/pkg/odo/cli/dev/dev.go +++ b/pkg/odo/cli/dev/dev.go @@ -238,8 +238,6 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { return o.clientset.DevClient.Start( o.ctx, - o.out, - o.errOut, dev.StartOptions{ IgnorePaths: o.ignorePaths, Debug: o.debugFlag, @@ -251,6 +249,8 @@ func (o *DevOptions) Run(ctx context.Context) (err error) { ForwardLocalhost: o.forwardLocalhostFlag, Variables: variables, CustomForwardedPorts: o.forwardedPorts, + Out: o.out, + ErrOut: o.errOut, }, ) } From 8442a0e85d6af6626fb7ca9f427c6686471e69b3 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 15:50:45 +0200 Subject: [PATCH 06/11] Embed StartOptions into PushParameters --- pkg/dev/common/types.go | 22 +++++++--------------- pkg/dev/kubedev/components.go | 8 ++++---- pkg/dev/kubedev/innerloop.go | 18 +++++++++--------- pkg/dev/kubedev/kubedev.go | 9 +-------- pkg/watch/watch.go | 10 ++-------- 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/pkg/dev/common/types.go b/pkg/dev/common/types.go index c92aa00f8c1..cca5956cf53 100644 --- a/pkg/dev/common/types.go +++ b/pkg/dev/common/types.go @@ -1,25 +1,17 @@ package common import ( - "io" - "github.com/devfile/library/v2/pkg/devfile/parser" - "github.com/redhat-developer/odo/pkg/api" + "github.com/redhat-developer/odo/pkg/dev" ) // PushParameters is a struct containing the parameters to be used when pushing to a devfile component type PushParameters struct { + StartOptions dev.StartOptions + Devfile parser.DevfileObj - WatchFiles []string // Optional: WatchFiles is the list of changed files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine changed files - WatchDeletedFiles []string // Optional: WatchDeletedFiles is the list of deleted files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine deleted files - IgnoredFiles []string // IgnoredFiles is the list of files to not push up to a component - Show bool // Show tells whether the devfile command output should be shown on stdout - DevfileBuildCmd string // DevfileBuildCmd takes the build command through the command line and overwrites devfile build command - DevfileRunCmd string // DevfileRunCmd takes the run command through the command line and overwrites devfile run command - DevfileDebugCmd string // DevfileDebugCmd takes the debug command through the command line and overwrites the devfile debug command - DevfileScanIndexForWatch bool // DevfileScanIndexForWatch is true if watch's push should regenerate the index file during SyncFiles, false otherwise. See 'pkg/sync/adapter.go' for details - Debug bool // Runs the component in debug mode - RandomPorts bool // True to forward containers ports on local random ports - CustomForwardedPorts []api.ForwardedPort // Optional: CustomForwardedPorts configuration to be used to customize the port forwarding; if nil, we automatically select ports - ErrOut io.Writer // Writer to output forwarded port information + WatchFiles []string // Optional: WatchFiles is the list of changed files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine changed files + WatchDeletedFiles []string // Optional: WatchDeletedFiles is the list of deleted files detected by odo watch. If empty or nil, odo will check .odo/odo-file-index.json to determine deleted files + Show bool // Show tells whether the devfile command output should be shown on stdout + DevfileScanIndexForWatch bool // DevfileScanIndexForWatch is true if watch's push should regenerate the index file during SyncFiles, false otherwise. See 'pkg/sync/adapter.go' for details } diff --git a/pkg/dev/kubedev/components.go b/pkg/dev/kubedev/components.go index 22f6b17ad96..b51f778b208 100644 --- a/pkg/dev/kubedev/components.go +++ b/pkg/dev/kubedev/components.go @@ -88,9 +88,9 @@ func (o *DevClient) createComponents(ctx context.Context, parameters common.Push var updated bool deployment, updated, err = o.createOrUpdateComponent(ctx, parameters, o.deploymentExists, libdevfile.DevfileCommands{ - BuildCmd: parameters.DevfileBuildCmd, - RunCmd: parameters.DevfileRunCmd, - DebugCmd: parameters.DevfileDebugCmd, + BuildCmd: parameters.StartOptions.BuildCommand, + RunCmd: parameters.StartOptions.RunCommand, + DebugCmd: parameters.StartOptions.DebugCommand, }, deployment) if err != nil { return false, fmt.Errorf("unable to create or update component: %w", err) @@ -154,7 +154,7 @@ func (o *DevClient) createComponents(ctx context.Context, parameters common.Push } // Check if endpoints changed in Devfile - portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.Debug) + portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.StartOptions.Debug) if err != nil { return false, err } diff --git a/pkg/dev/kubedev/innerloop.go b/pkg/dev/kubedev/innerloop.go index 9e2d4e8af06..3b49fc12ed0 100644 --- a/pkg/dev/kubedev/innerloop.go +++ b/pkg/dev/kubedev/innerloop.go @@ -62,17 +62,17 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet } cmdKind := devfilev1.RunCommandGroupKind - cmdName := parameters.DevfileRunCmd - if parameters.Debug { + cmdName := parameters.StartOptions.RunCommand + if parameters.StartOptions.Debug { cmdKind = devfilev1.DebugCommandGroupKind - cmdName = parameters.DevfileDebugCmd + cmdName = parameters.StartOptions.DebugCommand } syncParams := sync.SyncParameters{ Path: path, WatchFiles: parameters.WatchFiles, WatchDeletedFiles: parameters.WatchDeletedFiles, - IgnoredFiles: parameters.IgnoredFiles, + IgnoredFiles: parameters.StartOptions.IgnorePaths, DevfileScanIndexForWatch: parameters.DevfileScanIndexForWatch, CompInfo: compInfo, @@ -145,7 +145,7 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet doExecuteBuildCommand := func() error { execHandler := component.NewExecHandler(o.kubernetesClient, o.execClient, appName, componentName, pod.Name, "Building your application in container", parameters.Show) - return libdevfile.Build(ctx, parameters.Devfile, parameters.DevfileBuildCmd, execHandler) + return libdevfile.Build(ctx, parameters.Devfile, parameters.StartOptions.BuildCommand, execHandler) } if running { if cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) { @@ -178,7 +178,7 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet fmt.Fprintln(log.GetStdout()) } - err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.Debug, parameters.RandomPorts, log.GetStdout(), parameters.ErrOut, parameters.CustomForwardedPorts) + err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts) if err != nil { return common.NewErrPortForward(err) } @@ -189,13 +189,13 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet } func (o *DevClient) getPushDevfileCommands(parameters common.PushParameters) (map[devfilev1.CommandGroupKind]devfilev1.Command, error) { - pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.DevfileBuildCmd, parameters.DevfileRunCmd) + pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(parameters.Devfile, parameters.StartOptions.BuildCommand, parameters.StartOptions.RunCommand) if err != nil { return nil, fmt.Errorf("failed to validate devfile build and run commands: %w", err) } - if parameters.Debug { - pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.DevfileDebugCmd, devfilev1.DebugCommandGroupKind) + if parameters.StartOptions.Debug { + pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(parameters.Devfile, parameters.StartOptions.DebugCommand, devfilev1.DebugCommandGroupKind) if e != nil { return nil, fmt.Errorf("debug command is not valid: %w", e) } diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 278f9d678aa..ad3402a69c6 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -91,14 +91,7 @@ func (o *DevClient) Start( ) pushParameters := common.PushParameters{ - IgnoredFiles: options.IgnorePaths, - Debug: options.Debug, - DevfileBuildCmd: options.BuildCommand, - DevfileRunCmd: options.RunCommand, - RandomPorts: options.RandomPorts, - CustomForwardedPorts: options.CustomForwardedPorts, - ErrOut: options.ErrOut, - Devfile: *devfileObj, + Devfile: *devfileObj, } klog.V(4).Infoln("Creating inner-loop resources for the component") diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index bb7d1b46ff0..781a349900a 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -463,17 +463,11 @@ func (o *WatchClient) processEvents( klog.V(4).Infof("Copying files %s to pod", changedFiles) pushParams := common.PushParameters{ + // TODO StartOptions: parameters.StartOptions, + WatchFiles: changedFiles, WatchDeletedFiles: deletedPaths, - IgnoredFiles: parameters.FileIgnores, - DevfileBuildCmd: parameters.DevfileBuildCmd, - DevfileRunCmd: parameters.DevfileRunCmd, - DevfileDebugCmd: parameters.DevfileDebugCmd, DevfileScanIndexForWatch: !hasFirstSuccessfulPushOccurred, - Debug: parameters.Debug, - RandomPorts: parameters.RandomPorts, - CustomForwardedPorts: parameters.CustomForwardedPorts, - ErrOut: parameters.ErrOut, } oldStatus := *componentStatus err := parameters.DevfileWatchHandler(ctx, pushParams, parameters, componentStatus) From d292c76ecb555263ad8a5f1d3d9fc40e22bbff32 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 16:04:00 +0200 Subject: [PATCH 07/11] Embed StartOptions into WatchParameters --- pkg/dev/kubedev/kubedev.go | 18 ++++--------- pkg/dev/podmandev/podmandev.go | 38 ++++++--------------------- pkg/watch/watch.go | 47 ++++++++-------------------------- pkg/watch/watch_test.go | 7 ++++- 4 files changed, 30 insertions(+), 80 deletions(-) diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index ad3402a69c6..33a0ca662a4 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -105,18 +105,10 @@ func (o *DevClient) Start( klog.V(4).Infoln("Successfully created inner-loop resources") watchParameters := watch.WatchParameters{ - DevfileWatchHandler: o.regenerateAdapterAndPush, - FileIgnores: options.IgnorePaths, - Debug: options.Debug, - DevfileBuildCmd: options.BuildCommand, - DevfileRunCmd: options.RunCommand, - Variables: options.Variables, - RandomPorts: options.RandomPorts, - CustomForwardedPorts: options.CustomForwardedPorts, - WatchFiles: options.WatchFiles, - WatchCluster: true, - ErrOut: options.ErrOut, - PromptMessage: promptMessage, + StartOptions: options, + DevfileWatchHandler: o.regenerateAdapterAndPush, + WatchCluster: true, + PromptMessage: promptMessage, } return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) @@ -125,7 +117,7 @@ func (o *DevClient) Start( // RegenerateAdapterAndPush get the new devfile and pushes the files to remote pod func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { - devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), watchParams.Variables) + devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), watchParams.StartOptions.Variables) if err != nil { return fmt.Errorf("unable to generate component from watch parameters: %w", err) } diff --git a/pkg/dev/podmandev/podmandev.go b/pkg/dev/podmandev/podmandev.go index 9f3812e9d55..2c4f3d896df 100644 --- a/pkg/dev/podmandev/podmandev.go +++ b/pkg/dev/podmandev/podmandev.go @@ -94,21 +94,10 @@ func (o *DevClient) Start( watch.PrintInfoMessage(options.Out, path, options.WatchFiles, promptMessage) watchParameters := watch.WatchParameters{ - DevfileWatchHandler: o.watchHandler, - FileIgnores: options.IgnorePaths, - Debug: options.Debug, - DevfileBuildCmd: options.BuildCommand, - DevfileRunCmd: options.RunCommand, - Variables: options.Variables, - RandomPorts: options.RandomPorts, - IgnoreLocalhost: options.IgnoreLocalhost, - ForwardLocalhost: options.ForwardLocalhost, - CustomForwardedPorts: options.CustomForwardedPorts, - WatchFiles: options.WatchFiles, - WatchCluster: false, - Out: options.Out, - ErrOut: options.ErrOut, - PromptMessage: promptMessage, + StartOptions: options, + DevfileWatchHandler: o.watchHandler, + WatchCluster: false, + PromptMessage: promptMessage, } return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) @@ -184,25 +173,14 @@ func (o *DevClient) checkVolumesFree(pod *corev1.Pod) error { func (o *DevClient) watchHandler(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { printWarningsOnDevfileChanges(ctx, watchParams) - startOptions := dev.StartOptions{ - IgnorePaths: watchParams.FileIgnores, - Debug: watchParams.Debug, - BuildCommand: watchParams.DevfileBuildCmd, - RunCommand: watchParams.DevfileRunCmd, - RandomPorts: watchParams.RandomPorts, - IgnoreLocalhost: watchParams.IgnoreLocalhost, - ForwardLocalhost: watchParams.ForwardLocalhost, - CustomForwardedPorts: watchParams.CustomForwardedPorts, - WatchFiles: watchParams.WatchFiles, - Variables: watchParams.Variables, - } - return o.reconcile(ctx, watchParams.Out, watchParams.ErrOut, startOptions, componentStatus) + startOptions := watchParams.StartOptions + return o.reconcile(ctx, startOptions.Out, startOptions.ErrOut, startOptions, componentStatus) } func printWarningsOnDevfileChanges(ctx context.Context, parameters watch.WatchParameters) { var warning string currentDevfile := odocontext.GetDevfileObj(ctx) - newDevfile, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), parameters.Variables) + newDevfile, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), parameters.StartOptions.Variables) if err != nil { warning = fmt.Sprintf("error while reading the Devfile. Please restart 'odo dev' if you made any changes to the Devfile. Error message is: %v", err) } else { @@ -227,6 +205,6 @@ func printWarningsOnDevfileChanges(ctx context.Context, parameters watch.WatchPa } } if warning != "" { - log.Fwarning(parameters.Out, warning+"\n") + log.Fwarning(parameters.StartOptions.Out, warning+"\n") } } diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index 781a349900a..f0eddcca70f 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -10,7 +10,7 @@ import ( "reflect" "time" - "github.com/redhat-developer/odo/pkg/api" + "github.com/redhat-developer/odo/pkg/dev" "github.com/redhat-developer/odo/pkg/dev/common" "github.com/redhat-developer/odo/pkg/kclient" @@ -59,44 +59,19 @@ func NewWatchClient(kubeClient kclient.ClientInterface) *WatchClient { // WatchParameters is designed to hold the controllables and attributes that the watch function works on type WatchParameters struct { - // List/Slice of files/folders in component source, the updates to which need not be pushed to component deployed pod - FileIgnores []string + StartOptions dev.StartOptions + // Custom function that can be used to push detected changes to remote pod. For more info about what each of the parameters to this function, please refer, pkg/component/component.go#PushLocal // WatchHandler func(kclient.ClientInterface, string, string, string, io.Writer, []string, []string, bool, []string, bool) error // Custom function that can be used to push detected changes to remote devfile pod. For more info about what each of the parameters to this function, please refer, pkg/devfile/adapters/interface.go#PlatformAdapter DevfileWatchHandler func(context.Context, common.PushParameters, WatchParameters, *ComponentStatus) error // Parameter whether or not to show build logs Show bool - // DevfileBuildCmd takes the build command through the command line and overwrites devfile build command - DevfileBuildCmd string - // DevfileRunCmd takes the run command through the command line and overwrites devfile run command - DevfileRunCmd string - // DevfileDebugCmd takes the debug command through the command line and overwrites the devfile debug command - DevfileDebugCmd string - // Debug indicates if the debug command should be started after sync, or the run command by default - Debug bool // DebugPort indicates which debug port to use for pushing after sync DebugPort int - // Variables override Devfile variables - Variables map[string]string - // RandomPorts is true to forward containers ports on local random ports - RandomPorts bool - // Optional: sCustomForwardedPorts configuration to be used to customize the port forwarding; if nil, we automatically select ports - CustomForwardedPorts []api.ForwardedPort - - // IgnoreLocalhost indicates whether to proceed with port-forwarding regardless of any container ports being bound to the container loopback interface. - // Applicable to Podman only. - IgnoreLocalhost bool - // WatchFiles indicates to watch for file changes and sync changes to the container - WatchFiles bool - // ForwardLocalhost indicates whether to try to make port-forwarding work with container apps listening on the loopback interface. - ForwardLocalhost bool + // WatchCluster indicates to watch Cluster-related objects (Deployment, Pod, etc) WatchCluster bool - // ErrOut is a Writer to output forwarded port information - Out io.Writer - // ErrOut is a Writer to output forwarded port information - ErrOut io.Writer // PromptMessage PromptMessage string } @@ -119,11 +94,11 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct appName = odocontext.GetApplication(ctx) ) - klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", path, componentName, parameters.FileIgnores) + klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", path, componentName, parameters.StartOptions.IgnorePaths) var err error - if parameters.WatchFiles { - o.sourcesWatcher, err = getFullSourcesWatcher(path, parameters.FileIgnores) + if parameters.StartOptions.WatchFiles { + o.sourcesWatcher, err = getFullSourcesWatcher(path, parameters.StartOptions.IgnorePaths) if err != nil { return err } @@ -155,7 +130,7 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct if err != nil { return err } - if parameters.WatchFiles { + if parameters.StartOptions.WatchFiles { var devfileFiles []string devfileFiles, err = libdevfile.GetReferencedLocalFiles(*devfileObj) if err != nil { @@ -250,7 +225,7 @@ func (o *WatchClient) eventWatcher( var changedFiles, deletedPaths []string if !o.forceSync { // first find the files that have changed (also includes the ones newly created) or deleted - changedFiles, deletedPaths = evaluateChangesHandler(events, path, parameters.FileIgnores, o.sourcesWatcher) + changedFiles, deletedPaths = evaluateChangesHandler(events, path, parameters.StartOptions.IgnorePaths, o.sourcesWatcher) // process the changes and sync files with remote pod if len(changedFiles) == 0 && len(deletedPaths) == 0 { continue @@ -488,7 +463,7 @@ func (o *WatchClient) processEvents( fmt.Fprintf(out, "Updated Kubernetes config\n") } } else { - if parameters.WatchFiles { + if parameters.StartOptions.WatchFiles { fmt.Fprintf(out, "%s - %s\n\n", PushErrorString, err.Error()) } else { return nil, err @@ -501,7 +476,7 @@ func (o *WatchClient) processEvents( if oldStatus.State != StateReady && componentStatus.State == StateReady || !reflect.DeepEqual(oldStatus.EndpointsForwarded, componentStatus.EndpointsForwarded) { - PrintInfoMessage(out, path, parameters.WatchFiles, parameters.PromptMessage) + PrintInfoMessage(out, path, parameters.StartOptions.WatchFiles, parameters.PromptMessage) } return nil, nil } diff --git a/pkg/watch/watch_test.go b/pkg/watch/watch_test.go index b6809e10f72..42db2fecfb2 100644 --- a/pkg/watch/watch_test.go +++ b/pkg/watch/watch_test.go @@ -12,6 +12,7 @@ import ( "github.com/fsnotify/fsnotify" + "github.com/redhat-developer/odo/pkg/dev" odocontext "github.com/redhat-developer/odo/pkg/odo/context" ) @@ -91,7 +92,11 @@ func Test_eventWatcher(t *testing.T) { { name: "Case 3: Delete file, no error", args: args{ - parameters: WatchParameters{FileIgnores: []string{"file1"}}, + parameters: WatchParameters{ + StartOptions: dev.StartOptions{ + IgnorePaths: []string{"file1"}, + }, + }, }, wantOut: "Pushing files...\n\nchangedFiles [] deletedPaths [file1 file2]\n", wantErr: true, From be1ad9426b261789740eff709959c609b703bab6 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 17:44:28 +0200 Subject: [PATCH 08/11] Fix passing startoptions --- pkg/dev/kubedev/kubedev.go | 3 ++- pkg/watch/watch.go | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index 33a0ca662a4..c1ba8d1b5b8 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -91,7 +91,8 @@ func (o *DevClient) Start( ) pushParameters := common.PushParameters{ - Devfile: *devfileObj, + StartOptions: options, + Devfile: *devfileObj, } klog.V(4).Infoln("Creating inner-loop resources for the component") diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index f0eddcca70f..7b63b615fbf 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -438,8 +438,7 @@ func (o *WatchClient) processEvents( klog.V(4).Infof("Copying files %s to pod", changedFiles) pushParams := common.PushParameters{ - // TODO StartOptions: parameters.StartOptions, - + StartOptions: parameters.StartOptions, WatchFiles: changedFiles, WatchDeletedFiles: deletedPaths, DevfileScanIndexForWatch: !hasFirstSuccessfulPushOccurred, From ea53ac3c32f1d4b7c9b30ca4c6ca940720702504 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 18:08:43 +0200 Subject: [PATCH 09/11] Deduplicate options (out, ...) --- pkg/dev/kubedev/kubedev.go | 6 +++--- pkg/dev/podmandev/podmandev.go | 18 ++++++++---------- pkg/dev/podmandev/reconcile.go | 9 +++------ pkg/watch/interface.go | 3 +-- pkg/watch/mock.go | 9 ++++----- pkg/watch/watch.go | 28 ++++++++++++++-------------- pkg/watch/watch_test.go | 9 +++++---- 7 files changed, 38 insertions(+), 44 deletions(-) diff --git a/pkg/dev/kubedev/kubedev.go b/pkg/dev/kubedev/kubedev.go index c1ba8d1b5b8..961c96bd7fc 100644 --- a/pkg/dev/kubedev/kubedev.go +++ b/pkg/dev/kubedev/kubedev.go @@ -112,13 +112,13 @@ func (o *DevClient) Start( PromptMessage: promptMessage, } - return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) + return o.watchClient.WatchAndPush(ctx, watchParameters, componentStatus) } // RegenerateAdapterAndPush get the new devfile and pushes the files to remote pod -func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { +func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams common.PushParameters, componentStatus *watch.ComponentStatus) error { - devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), watchParams.StartOptions.Variables) + devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), pushParams.StartOptions.Variables) if err != nil { return fmt.Errorf("unable to generate component from watch parameters: %w", err) } diff --git a/pkg/dev/podmandev/podmandev.go b/pkg/dev/podmandev/podmandev.go index 2c4f3d896df..7db0c996049 100644 --- a/pkg/dev/podmandev/podmandev.go +++ b/pkg/dev/podmandev/podmandev.go @@ -86,7 +86,7 @@ func (o *DevClient) Start( } ) - err := o.reconcile(ctx, options.Out, options.ErrOut, options, &componentStatus) + err := o.reconcile(ctx, options, &componentStatus) if err != nil { return err } @@ -100,7 +100,7 @@ func (o *DevClient) Start( PromptMessage: promptMessage, } - return o.watchClient.WatchAndPush(options.Out, watchParameters, ctx, componentStatus) + return o.watchClient.WatchAndPush(ctx, watchParameters, componentStatus) } // syncFiles syncs the local source files in path into the pod's source volume @@ -170,17 +170,15 @@ func (o *DevClient) checkVolumesFree(pod *corev1.Pod) error { return nil } -func (o *DevClient) watchHandler(ctx context.Context, pushParams common.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error { - printWarningsOnDevfileChanges(ctx, watchParams) - - startOptions := watchParams.StartOptions - return o.reconcile(ctx, startOptions.Out, startOptions.ErrOut, startOptions, componentStatus) +func (o *DevClient) watchHandler(ctx context.Context, pushParams common.PushParameters, componentStatus *watch.ComponentStatus) error { + printWarningsOnDevfileChanges(ctx, pushParams.StartOptions) + return o.reconcile(ctx, pushParams.StartOptions, componentStatus) } -func printWarningsOnDevfileChanges(ctx context.Context, parameters watch.WatchParameters) { +func printWarningsOnDevfileChanges(ctx context.Context, options dev.StartOptions) { var warning string currentDevfile := odocontext.GetDevfileObj(ctx) - newDevfile, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), parameters.StartOptions.Variables) + newDevfile, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(""), options.Variables) if err != nil { warning = fmt.Sprintf("error while reading the Devfile. Please restart 'odo dev' if you made any changes to the Devfile. Error message is: %v", err) } else { @@ -205,6 +203,6 @@ func printWarningsOnDevfileChanges(ctx context.Context, parameters watch.WatchPa } } if warning != "" { - log.Fwarning(parameters.StartOptions.Out, warning+"\n") + log.Fwarning(options.Out, warning+"\n") } } diff --git a/pkg/dev/podmandev/reconcile.go b/pkg/dev/podmandev/reconcile.go index 4946118ed10..ccfab4b7dda 100644 --- a/pkg/dev/podmandev/reconcile.go +++ b/pkg/dev/podmandev/reconcile.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "io" "path/filepath" "strings" "time" @@ -32,8 +31,6 @@ import ( func (o *DevClient) reconcile( ctx context.Context, - out io.Writer, - errOut io.Writer, options dev.StartOptions, componentStatus *watch.ComponentStatus, ) error { @@ -130,7 +127,7 @@ func (o *DevClient) reconcile( if err != nil { log.Warningf("Port forwarding might not work correctly: %v", err) log.Warning("Running `odo logs --follow --platform podman` might help in identifying the problem.") - fmt.Fprintln(out) + fmt.Fprintln(options.Out) } // By default, Podman will not forward to container applications listening on the loopback interface. @@ -143,7 +140,7 @@ func (o *DevClient) reconcile( if options.ForwardLocalhost { // Port-forwarding is enabled by executing dedicated socat commands - err = o.portForwardClient.StartPortForwarding(ctx, *devfileObj, componentName, options.Debug, options.RandomPorts, out, errOut, fwPorts) + err = o.portForwardClient.StartPortForwarding(ctx, *devfileObj, componentName, options.Debug, options.RandomPorts, options.Out, options.ErrOut, fwPorts) if err != nil { return common.NewErrPortForward(err) } @@ -151,7 +148,7 @@ func (o *DevClient) reconcile( for _, fwPort := range fwPorts { s := fmt.Sprintf("Forwarding from %s:%d -> %d", fwPort.LocalAddress, fwPort.LocalPort, fwPort.ContainerPort) - fmt.Fprintf(out, " - %s", log.SboldColor(color.FgGreen, s)) + fmt.Fprintf(options.Out, " - %s", log.SboldColor(color.FgGreen, s)) } err = o.stateClient.SetForwardedPorts(ctx, fwPorts) if err != nil { diff --git a/pkg/watch/interface.go b/pkg/watch/interface.go index 544c0e052c2..1abff35149a 100644 --- a/pkg/watch/interface.go +++ b/pkg/watch/interface.go @@ -2,7 +2,6 @@ package watch import ( "context" - "io" ) type Client interface { @@ -11,5 +10,5 @@ type Client interface { // componentStatus is a variable to store the status of the component, and that will be exchanged between // parts of code (unfortunately, tthere is no place to store the status of the component in some Kubernetes resource // as it is generally done for a Kubernetes resource) - WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error + WatchAndPush(ctx context.Context, parameters WatchParameters, componentStatus ComponentStatus) error } diff --git a/pkg/watch/mock.go b/pkg/watch/mock.go index 5b615b9ab95..49d9756db4b 100644 --- a/pkg/watch/mock.go +++ b/pkg/watch/mock.go @@ -6,7 +6,6 @@ package watch import ( context "context" - io "io" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -36,15 +35,15 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { } // WatchAndPush mocks base method. -func (m *MockClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error { +func (m *MockClient) WatchAndPush(ctx context.Context, parameters WatchParameters, componentStatus ComponentStatus) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WatchAndPush", out, parameters, ctx, componentStatus) + ret := m.ctrl.Call(m, "WatchAndPush", ctx, parameters, componentStatus) ret0, _ := ret[0].(error) return ret0 } // WatchAndPush indicates an expected call of WatchAndPush. -func (mr *MockClientMockRecorder) WatchAndPush(out, parameters, ctx, componentStatus interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) WatchAndPush(ctx, parameters, componentStatus interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchAndPush", reflect.TypeOf((*MockClient)(nil).WatchAndPush), out, parameters, ctx, componentStatus) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchAndPush", reflect.TypeOf((*MockClient)(nil).WatchAndPush), ctx, parameters, componentStatus) } diff --git a/pkg/watch/watch.go b/pkg/watch/watch.go index 7b63b615fbf..5e75167c2c0 100644 --- a/pkg/watch/watch.go +++ b/pkg/watch/watch.go @@ -64,7 +64,7 @@ type WatchParameters struct { // Custom function that can be used to push detected changes to remote pod. For more info about what each of the parameters to this function, please refer, pkg/component/component.go#PushLocal // WatchHandler func(kclient.ClientInterface, string, string, string, io.Writer, []string, []string, bool, []string, bool) error // Custom function that can be used to push detected changes to remote devfile pod. For more info about what each of the parameters to this function, please refer, pkg/devfile/adapters/interface.go#PlatformAdapter - DevfileWatchHandler func(context.Context, common.PushParameters, WatchParameters, *ComponentStatus) error + DevfileWatchHandler func(context.Context, common.PushParameters, *ComponentStatus) error // Parameter whether or not to show build logs Show bool // DebugPort indicates which debug port to use for pushing after sync @@ -83,9 +83,9 @@ type evaluateChangesFunc func(events []fsnotify.Event, path string, fileIgnores // processEventsFunc processes the events received on the watcher. It uses the WatchParameters to trigger watch handler and writes to out // It returns a Duration after which to recall in case of error -type processEventsFunc func(ctx context.Context, changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error) +type processEventsFunc func(ctx context.Context, parameters WatchParameters, changedFiles, deletedPaths []string, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error) -func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error { +func (o *WatchClient) WatchAndPush(ctx context.Context, parameters WatchParameters, componentStatus ComponentStatus) error { var ( devfileObj = odocontext.GetDevfileObj(ctx) devfilePath = odocontext.GetDevfilePath(ctx) @@ -152,14 +152,14 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct return err } if isForbidden { - log.Fwarning(out, "Unable to watch Events resource, warning Events won't be displayed") + log.Fwarning(parameters.StartOptions.Out, "Unable to watch Events resource, warning Events won't be displayed") } } else { o.warningsWatcher = NewNoOpWatcher() } - o.keyWatcher = getKeyWatcher(ctx, out) - return o.eventWatcher(ctx, parameters, out, evaluateFileChanges, o.processEvents, componentStatus) + o.keyWatcher = getKeyWatcher(ctx, parameters.StartOptions.Out) + return o.eventWatcher(ctx, parameters, evaluateFileChanges, o.processEvents, componentStatus) } // eventWatcher loops till the context's Done channel indicates it to stop looping, at which point it performs cleanup. @@ -168,7 +168,6 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct func (o *WatchClient) eventWatcher( ctx context.Context, parameters WatchParameters, - out io.Writer, evaluateChangesHandler evaluateChangesFunc, processEventsHandler processEventsFunc, componentStatus ComponentStatus, @@ -179,6 +178,7 @@ func (o *WatchClient) eventWatcher( path = filepath.Dir(devfilePath) componentName = odocontext.GetComponentName(ctx) appName = odocontext.GetApplication(ctx) + out = parameters.StartOptions.Out ) expBackoff := NewExpBackoff() @@ -234,7 +234,7 @@ func (o *WatchClient) eventWatcher( componentStatus.State = StateSyncOutdated fmt.Fprintf(out, "Pushing files...\n\n") - retry, err := processEventsHandler(ctx, changedFiles, deletedPaths, parameters, out, &componentStatus, expBackoff) + retry, err := processEventsHandler(ctx, parameters, changedFiles, deletedPaths, &componentStatus, expBackoff) o.forceSync = false if err != nil { return err @@ -272,7 +272,7 @@ func (o *WatchClient) eventWatcher( } case <-deployTimer.C: - retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff) + retry, err := processEventsHandler(ctx, parameters, nil, nil, &componentStatus, expBackoff) if err != nil { return err } @@ -288,7 +288,7 @@ func (o *WatchClient) eventWatcher( case <-devfileTimer.C: fmt.Fprintf(out, "Updating Component...\n\n") - retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff) + retry, err := processEventsHandler(ctx, parameters, nil, nil, &componentStatus, expBackoff) if err != nil { return err } @@ -300,7 +300,7 @@ func (o *WatchClient) eventWatcher( } case <-retryTimer.C: - retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff) + retry, err := processEventsHandler(ctx, parameters, nil, nil, &componentStatus, expBackoff) if err != nil { return err } @@ -418,15 +418,15 @@ func evaluateFileChanges(events []fsnotify.Event, path string, fileIgnores []str func (o *WatchClient) processEvents( ctx context.Context, - changedFiles, deletedPaths []string, parameters WatchParameters, - out io.Writer, + changedFiles, deletedPaths []string, componentStatus *ComponentStatus, backoff *ExpBackoff, ) (*time.Duration, error) { var ( devfilePath = odocontext.GetDevfilePath(ctx) path = filepath.Dir(devfilePath) + out = parameters.StartOptions.Out ) for _, file := range removeDuplicates(append(changedFiles, deletedPaths...)) { @@ -444,7 +444,7 @@ func (o *WatchClient) processEvents( DevfileScanIndexForWatch: !hasFirstSuccessfulPushOccurred, } oldStatus := *componentStatus - err := parameters.DevfileWatchHandler(ctx, pushParams, parameters, componentStatus) + err := parameters.DevfileWatchHandler(ctx, pushParams, componentStatus) if err != nil { if isFatal(err) { return nil, err diff --git a/pkg/watch/watch_test.go b/pkg/watch/watch_test.go index 42db2fecfb2..c72eb12715a 100644 --- a/pkg/watch/watch_test.go +++ b/pkg/watch/watch_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "fmt" - "io" "testing" "time" @@ -43,8 +42,8 @@ func evaluateChangesHandler(events []fsnotify.Event, path string, fileIgnores [] return changedFiles, deletedPaths } -func processEventsHandler(ctx context.Context, changedFiles, deletedPaths []string, _ WatchParameters, out io.Writer, componentStatus *ComponentStatus, backo *ExpBackoff) (*time.Duration, error) { - fmt.Fprintf(out, "changedFiles %s deletedPaths %s\n", changedFiles, deletedPaths) +func processEventsHandler(ctx context.Context, params WatchParameters, changedFiles, deletedPaths []string, componentStatus *ComponentStatus, backo *ExpBackoff) (*time.Duration, error) { + fmt.Fprintf(params.StartOptions.Out, "changedFiles %s deletedPaths %s\n", changedFiles, deletedPaths) return nil, nil } @@ -149,7 +148,9 @@ func Test_eventWatcher(t *testing.T) { devfileWatcher: fileWatcher, keyWatcher: make(chan byte), } - err := o.eventWatcher(ctx, tt.args.parameters, out, evaluateChangesHandler, processEventsHandler, componentStatus) + tt.args.parameters.StartOptions.Out = out + + err := o.eventWatcher(ctx, tt.args.parameters, evaluateChangesHandler, processEventsHandler, componentStatus) if (err != nil) != tt.wantErr { t.Errorf("eventWatcher() error = %v, wantErr %v", err, tt.wantErr) return From dc6dbc8d99c6c27cf13aeee1ace8ccaf3c08ea0a Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Fri, 21 Apr 2023 18:27:06 +0200 Subject: [PATCH 10/11] Revert adding unwanted files --- .../parent-devfile-commands-only.yaml | 20 ------------ .../parent-devfile-components-only.yaml | 15 --------- .../testdata/parent-devfile-empty.yaml | 3 -- pkg/libdevfile/testdata/parent-devfile.yaml | 32 ------------------- 4 files changed, 70 deletions(-) delete mode 100644 pkg/libdevfile/testdata/parent-devfile-commands-only.yaml delete mode 100644 pkg/libdevfile/testdata/parent-devfile-components-only.yaml delete mode 100644 pkg/libdevfile/testdata/parent-devfile-empty.yaml delete mode 100644 pkg/libdevfile/testdata/parent-devfile.yaml diff --git a/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml b/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml deleted file mode 100644 index 206682178dc..00000000000 --- a/pkg/libdevfile/testdata/parent-devfile-commands-only.yaml +++ /dev/null @@ -1,20 +0,0 @@ -commands: -- exec: - commandLine: GOCACHE=${PROJECT_SOURCE}/.cache go build main.go - component: runtime - group: - isDefault: true - kind: build - workingDir: ${PROJECT_SOURCE} - id: build -- exec: - commandLine: ./main - component: runtime - group: - isDefault: true - kind: run - workingDir: ${PROJECT_SOURCE} - id: run -schemaVersion: 2.1.0 -metadata: - name: parent diff --git a/pkg/libdevfile/testdata/parent-devfile-components-only.yaml b/pkg/libdevfile/testdata/parent-devfile-components-only.yaml deleted file mode 100644 index 74d64ff35bf..00000000000 --- a/pkg/libdevfile/testdata/parent-devfile-components-only.yaml +++ /dev/null @@ -1,15 +0,0 @@ -components: -- container: - endpoints: - - name: http - targetPort: 8080 - image: quay.io/devfile/golang:latest - memoryLimit: 1024Mi - mountSources: true - name: runtime -- kubernetes: - uri: "manifest.yaml" - name: kube-cmp -metadata: - name: my-go-app -schemaVersion: 2.1.0 diff --git a/pkg/libdevfile/testdata/parent-devfile-empty.yaml b/pkg/libdevfile/testdata/parent-devfile-empty.yaml deleted file mode 100644 index a3e59f1af10..00000000000 --- a/pkg/libdevfile/testdata/parent-devfile-empty.yaml +++ /dev/null @@ -1,3 +0,0 @@ -metadata: - name: my-go-app -schemaVersion: 2.1.0 diff --git a/pkg/libdevfile/testdata/parent-devfile.yaml b/pkg/libdevfile/testdata/parent-devfile.yaml deleted file mode 100644 index 08824612b9b..00000000000 --- a/pkg/libdevfile/testdata/parent-devfile.yaml +++ /dev/null @@ -1,32 +0,0 @@ -commands: -- exec: - commandLine: GOCACHE=${PROJECT_SOURCE}/.cache go build main.go - component: runtime - group: - isDefault: true - kind: build - workingDir: ${PROJECT_SOURCE} - id: build -- exec: - commandLine: ./main - component: runtime - group: - isDefault: true - kind: run - workingDir: ${PROJECT_SOURCE} - id: run -components: -- container: - endpoints: - - name: http - targetPort: 8080 - image: quay.io/devfile/golang:latest - memoryLimit: 1024Mi - mountSources: true - name: runtime -- kubernetes: - uri: "manifest.yaml" - name: kube-cmp -metadata: - name: my-go-app -schemaVersion: 2.1.0 From c9c4258e3d9c1cba893dafadaae44d229deb6734 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Mon, 24 Apr 2023 15:09:44 +0200 Subject: [PATCH 11/11] Fix wait app ready --- pkg/dev/kubedev/components.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/dev/kubedev/components.go b/pkg/dev/kubedev/components.go index b51f778b208..38f1cafc963 100644 --- a/pkg/dev/kubedev/components.go +++ b/pkg/dev/kubedev/components.go @@ -154,11 +154,11 @@ func (o *DevClient) createComponents(ctx context.Context, parameters common.Push } // Check if endpoints changed in Devfile - portsToForward, err := libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.StartOptions.Debug) + o.portsToForward, err = libdevfile.GetDevfileContainerEndpointMapping(parameters.Devfile, parameters.StartOptions.Debug) if err != nil { return false, err } - o.portsChanged = !reflect.DeepEqual(portsToForward, o.portForwardClient.GetForwardedPorts()) + o.portsChanged = !reflect.DeepEqual(o.portsToForward, o.portForwardClient.GetForwardedPorts()) if componentStatus.State == watch.StateReady && !o.portsChanged { // If the deployment is already in Ready State, no need to continue