diff --git a/.golangci.yaml b/.golangci.yaml index d5d0487070e..e5707f4394f 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -88,9 +88,3 @@ issues: # Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint # See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331 text: 'SA1019: allComponents\[i\].RunningOn is deprecated' - - linters: - - staticcheck - # Workaround to exclude some 'staticcheck' messages, because line-based directive does not seem to work with golangci-lint - # See https://github.com/golangci/golangci-lint/issues/741#issuecomment-1017014331 - # TODO(feloy) Remove when https://github.com/devfile/library/pull/167 is merged - text: 'SA1019: generator.GetContainers is deprecated: in favor of GetPodTemplateSpec' diff --git a/go.mod b/go.mod index 58e31b3789e..05fa3bcf3b6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Xuanwo/go-locale v1.1.0 github.com/blang/semver v3.5.1+incompatible github.com/devfile/api/v2 v2.2.0 - github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257 + github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273 github.com/devfile/registry-support/registry-library v0.0.0-20221201200738-19293ac0b8ab github.com/fatih/color v1.14.1 diff --git a/go.sum b/go.sum index af599f846d1..f046e47277f 100644 --- a/go.sum +++ b/go.sum @@ -390,6 +390,8 @@ github.com/devfile/library v1.2.1-0.20220308191614-f0f7e11b17de/go.mod h1:GSPfJa github.com/devfile/library/v2 v2.0.1/go.mod h1:paJ0PARAVy0br13VpBEQ4fO3rZVDxWtooQ29+23PNBk= github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257 h1:BgnMyht1qUYqMRR1H2aAMIcEXb9ignawJbdSMrAZg8Y= github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0= +github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f h1:DRWf62j2diJCEPPumsKUkypNlyMV2/P6e3q6zcDT+WM= +github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f/go.mod h1:9mHgcxKzzFYRrnac8BRJ2gC6Ff1A2ZeZ4Iy73N6Vrp0= github.com/devfile/registry-support/index/generator v0.0.0-20220222194908-7a90a4214f3e/go.mod h1:iRPBxs+ZjfLEduVXpCCIOzdD2588Zv9OCs/CcXMcCCY= github.com/devfile/registry-support/index/generator v0.0.0-20220527155645-8328a8a883be/go.mod h1:1fyDJL+fPHtcrYA6yjSVWeLmXmjCNth0d5Rq1rvtryc= github.com/devfile/registry-support/index/generator v0.0.0-20221018203505-df96d34d4273 h1:DXENQSRTEDsk9com38njPg5511DD12HPIgzyFUErnpM= diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index ffb777bf3cc..d3e97fad8ea 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -12,7 +12,6 @@ import ( "github.com/devfile/library/v2/pkg/devfile/parser" "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" "k8s.io/utils/pointer" @@ -96,17 +95,28 @@ func (o *deployHandler) ApplyOpenShift(openshift v1alpha2.Component) error { // Execute will deploy the listed information in the `exec` section of devfile.yaml func (o *deployHandler) Execute(command v1alpha2.Command) error { - containerComps, err := generator.GetContainers(o.devfileObj, common.DevfileOptions{FilterByName: command.Exec.Component}) + policy, err := o.kubeClient.GetCurrentNamespacePolicy() if err != nil { return err } - if len(containerComps) != 1 { + podTemplateSpec, err := generator.GetPodTemplateSpec(o.devfileObj, generator.PodTemplateParams{ + Options: common.DevfileOptions{ + FilterByName: command.Exec.Component, + }, + PodSecurityAdmissionPolicy: policy, + }) + if err != nil { + return err + } + // Setting the restart policy to "never" so that pods are kept around after the job finishes execution; this is helpful in obtaining logs to debug. + podTemplateSpec.Spec.RestartPolicy = "Never" + + if len(podTemplateSpec.Spec.Containers) != 1 { return fmt.Errorf("could not find the component") } - containerComp := containerComps[0] - containerComp.Command = []string{"/bin/sh"} - containerComp.Args = getCmdline(command) + podTemplateSpec.Spec.Containers[0].Command = []string{"/bin/sh"} + podTemplateSpec.Spec.Containers[0].Args = getCmdline(command) // Create a Kubernetes Job and use the container image referenced by command.Exec.Component // Get the component for the command with command.Exec.Component @@ -123,13 +133,7 @@ func (o *deployHandler) Execute(command v1alpha2.Command) error { ObjectMeta: metav1.ObjectMeta{ Name: getJobName(), }, - PodTemplateSpec: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{containerComp}, - // Setting the restart policy to "never" so that pods are kept around after the job finishes execution; this is helpful in obtaining logs to debug. - RestartPolicy: "Never", - }, - }, + PodTemplateSpec: *podTemplateSpec, SpecParams: odogenerator.JobSpecParams{ CompletionMode: &completionMode, TTLSecondsAfterFinished: pointer.Int32(60), diff --git a/tests/examples/source/devfiles/nodejs/devfile-deploy-exec-long.yaml b/tests/examples/source/devfiles/nodejs/devfile-deploy-exec-long.yaml new file mode 100644 index 00000000000..f5311d0febc --- /dev/null +++ b/tests/examples/source/devfiles/nodejs/devfile-deploy-exec-long.yaml @@ -0,0 +1,74 @@ +commands: + - exec: + commandLine: npm install + component: runtime + group: + isDefault: true + kind: build + workingDir: /project + id: install + - exec: + commandLine: npm start + component: runtime + group: + isDefault: true + kind: run + workingDir: /project + id: run + - exec: + commandLine: npm run debug + component: runtime + group: + isDefault: true + kind: debug + workingDir: /project + id: debug + - exec: + commandLine: npm test + component: runtime + group: + isDefault: true + kind: test + workingDir: /project + id: test + - exec: + commandLine: sleep 20 + component: runtime + id: deploy-exec + - id: deploy + composite: + commands: + - deploy-exec + group: + kind: deploy + isDefault: true +components: + - container: + endpoints: + - name: http-3000 + targetPort: 3000 + image: registry.access.redhat.com/ubi8/nodejs-14:latest + memoryLimit: 1024Mi + mountSources: true + sourceMapping: /project + name: runtime +metadata: + description: Stack with Node.js 14 + displayName: Node.js Runtime + icon: https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg + language: javascript + name: nodejs-prj1-api-abhz + projectType: nodejs + tags: + - NodeJS + - Express + - ubi8 + version: 1.0.1 +schemaVersion: 2.2.0 +starterProjects: + - git: + remotes: + origin: https://github.com/odo-devfiles/nodejs-ex.git + name: nodejs-starter +variables: + CONTAINER_IMAGE: quay.io/unknown-account/myimage diff --git a/tests/helper/component_cluster.go b/tests/helper/component_cluster.go index 5ea0b84f5ab..2d8754ca059 100644 --- a/tests/helper/component_cluster.go +++ b/tests/helper/component_cluster.go @@ -5,6 +5,7 @@ import ( "fmt" . "github.com/onsi/gomega" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "github.com/redhat-developer/odo/pkg/labels" @@ -70,6 +71,19 @@ func (o *ClusterComponent) GetPodDef() *corev1.Pod { return &podDef } +func (o *ClusterComponent) GetJobDef() *batchv1.Job { + var jobDef batchv1.Job + var jobName string + Eventually(func() string { + jobName = o.cli.GetJobNameByComponent(o.name, o.namespace) + return jobName + }).Should(Not(BeEmpty())) + bufferOutput := o.cli.Run("get", "jobs", jobName, "-o", "json").Out.Contents() + err := json.Unmarshal(bufferOutput, &jobDef) + Expect(err).ToNot(HaveOccurred()) + return &jobDef +} + func (o *ClusterComponent) GetPodLogs() string { podName := o.cli.GetRunningPodNameByComponent(o.name, o.namespace) return string(o.cli.Run("-n", o.namespace, "logs", podName).Out.Contents()) diff --git a/tests/helper/component_interface.go b/tests/helper/component_interface.go index fa3b4e752d6..d54548a8f60 100644 --- a/tests/helper/component_interface.go +++ b/tests/helper/component_interface.go @@ -2,6 +2,7 @@ package helper import ( . "github.com/onsi/ginkgo/v2" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" ) @@ -22,6 +23,8 @@ type Component interface { GetLabels() map[string]string // GetPodDef returns the definition of the pod GetPodDef() *corev1.Pod + // GetJobDef returns the definition of the job + GetJobDef() *batchv1.Job // GetPodLogs returns logs for the pod GetPodLogs() string } diff --git a/tests/helper/component_podman.go b/tests/helper/component_podman.go index 08b5754e0f4..b6c238ceab2 100644 --- a/tests/helper/component_podman.go +++ b/tests/helper/component_podman.go @@ -9,6 +9,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/kubectl/pkg/scheme" @@ -110,6 +111,11 @@ func (o *PodmanComponent) GetPodDef() *corev1.Pod { return &pod } +func (o *PodmanComponent) GetJobDef() *batchv1.Job { + // Not implemented for Podman + panic("not implemented for Podman") +} + func (o *PodmanComponent) GetLabels() map[string]string { podName := fmt.Sprintf("%s-%s", o.componentName, o.app) cmd := exec.Command("podman", "pod", "inspect", podName, "--format", "json") diff --git a/tests/helper/helper_cli.go b/tests/helper/helper_cli.go index 3708868f831..c50bbbdd63c 100644 --- a/tests/helper/helper_cli.go +++ b/tests/helper/helper_cli.go @@ -14,6 +14,7 @@ type CliRunner interface { Exec(podName string, projectName string, args []string, expectedSuccess *bool) (string, string) CheckCmdOpInRemoteDevfilePod(podName string, containerName string, prjName string, cmd []string, checkOp func(cmdOp string, err error) bool) bool GetRunningPodNameByComponent(compName string, namespace string) string + GetJobNameByComponent(compName string, namespace string) string GetVolumeMountNamesandPathsFromContainer(deployName string, containerName, namespace string) string WaitAndCheckForExistence(resourceType, namespace string, timeoutMinutes int) bool GetServices(namespace string) string diff --git a/tests/helper/helper_cmd_wrapper.go b/tests/helper/helper_cmd_wrapper.go index dd64633a4e6..ec1bf07fb2c 100644 --- a/tests/helper/helper_cmd_wrapper.go +++ b/tests/helper/helper_cmd_wrapper.go @@ -114,6 +114,11 @@ func (cw *CmdWrapper) ShouldRun() *CmdWrapper { return cw } +func (cw *CmdWrapper) Should(f func(session *gexec.Session)) { + cw.Runner() + f(cw.session) +} + func (cw *CmdWrapper) WithTerminate(timeoutAfter time.Duration, stop chan bool) *CmdWrapper { cw.timeout = timeoutAfter * time.Second cw.stopChan = stop diff --git a/tests/helper/helper_kubectl.go b/tests/helper/helper_kubectl.go index 7ea07fffbeb..afa43433a6b 100644 --- a/tests/helper/helper_kubectl.go +++ b/tests/helper/helper_kubectl.go @@ -18,6 +18,7 @@ import ( const ( ResourceTypeDeployment = "deployment" ResourceTypePod = "pod" + ResourceTypeJob = "job" ResourceTypePVC = "pvc" ResourceTypeService = "service" ) @@ -91,6 +92,13 @@ func (kubectl KubectlRunner) GetRunningPodNameByComponent(compName string, names return strings.TrimSpace(stdOut) } +// GetJobNameByComponent executes kubectl command and returns the running job name +func (kubectl KubectlRunner) GetJobNameByComponent(compName string, namespace string) string { + selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName) + stdOut := Cmd(kubectl.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out() + return strings.TrimSpace(stdOut) +} + // GetPVCSize executes kubectl command and returns the bound storage size func (kubectl KubectlRunner) GetPVCSize(compName, storageName, namespace string) string { selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName) diff --git a/tests/helper/helper_oc.go b/tests/helper/helper_oc.go index 36b2f2f45e8..66e11b931c7 100644 --- a/tests/helper/helper_oc.go +++ b/tests/helper/helper_oc.go @@ -164,6 +164,13 @@ func (oc OcRunner) GetRunningPodNameByComponent(compName string, namespace strin return strings.TrimSpace(stdOut) } +// GetJobNameByComponent executes kubectl command and returns the running job name +func (oc OcRunner) GetJobNameByComponent(compName string, namespace string) string { + selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName) + stdOut := Cmd(oc.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out() + return strings.TrimSpace(stdOut) +} + // GetPVCSize executes oc command and returns the bound storage size func (oc OcRunner) GetPVCSize(compName, storageName, namespace string) string { selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName) diff --git a/tests/integration/cmd_devfile_deploy_test.go b/tests/integration/cmd_devfile_deploy_test.go index 5edbf27650e..9b5da30dfc7 100644 --- a/tests/integration/cmd_devfile_deploy_test.go +++ b/tests/integration/cmd_devfile_deploy_test.go @@ -11,6 +11,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" + "github.com/redhat-developer/odo/pkg/labels" segment "github.com/redhat-developer/odo/pkg/segment/context" "github.com/redhat-developer/odo/tests/helper" ) @@ -533,6 +535,18 @@ CMD ["npm", "start"] }) }) + It("should not set securitycontext for podsecurity admission on job's pod template", func() { + if os.Getenv("KUBERNETES") != "true" { + Skip("This is a Kubernetes specific scenario, skipping") + } + helper.Cmd("odo", "deploy").Should(func(session *gexec.Session) { + component := helper.NewComponent(cmpName, "app", labels.ComponentDeployMode, commonVar.Project, commonVar.CliRunner) + jobDef := component.GetJobDef() + Expect(jobDef.Spec.Template.Spec.SecurityContext.RunAsNonRoot).To(BeNil()) + Expect(jobDef.Spec.Template.Spec.SecurityContext.SeccompProfile).To(BeNil()) + }) + }) + } When("using a devfile name with length more than 63", func() { @@ -578,4 +592,35 @@ CMD ["npm", "start"] }) }) + + Context("deploying devfile with long-running exec", func() { + BeforeEach(func() { + helper.CopyExampleDevFile( + filepath.Join("source", "devfiles", "nodejs", "devfile-deploy-exec-long.yaml"), + path.Join(commonVar.Context, "devfile.yaml"), + helper.DevfileMetadataNameSetter(cmpName)) + }) + + When("pod security is enforced as restricted", func() { + BeforeEach(func() { + commonVar.CliRunner.SetLabelsOnNamespace( + commonVar.Project, + "pod-security.kubernetes.io/enforce=restricted", + "pod-security.kubernetes.io/enforce-version=latest", + ) + }) + + It("should set securitycontext for podsecurity admission on job's pod template", func() { + if os.Getenv("KUBERNETES") != "true" { + Skip("This is a Kubernetes specific scenario, skipping") + } + helper.Cmd("odo", "deploy").Should(func(session *gexec.Session) { + component := helper.NewComponent(cmpName, "app", labels.ComponentDeployMode, commonVar.Project, commonVar.CliRunner) + jobDef := component.GetJobDef() + Expect(*jobDef.Spec.Template.Spec.SecurityContext.RunAsNonRoot).To(BeTrue()) + Expect(string(jobDef.Spec.Template.Spec.SecurityContext.SeccompProfile.Type)).To(Equal("RuntimeDefault")) + }) + }) + }) + }) }) diff --git a/vendor/github.com/devfile/library/v2/pkg/devfile/generator/generators.go b/vendor/github.com/devfile/library/v2/pkg/devfile/generator/generators.go index 556366582fb..547433e5f15 100644 --- a/vendor/github.com/devfile/library/v2/pkg/devfile/generator/generators.go +++ b/vendor/github.com/devfile/library/v2/pkg/devfile/generator/generators.go @@ -1,5 +1,5 @@ // -// Copyright 2022 Red Hat, Inc. +// Copyright 2022-2023 Red Hat, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -238,6 +238,7 @@ func GetDeployment(devfileObj parser.DevfileObj, deployParams DeploymentParams) // PodTemplateParams is a struct that contains the required data to create a podtemplatespec object type PodTemplateParams struct { ObjectMeta metav1.ObjectMeta + Options common.DevfileOptions // PodSecurityAdmissionPolicy is the policy to be respected by the created pod // The pod will be patched, if necessary, to respect the policies PodSecurityAdmissionPolicy psaapi.Policy @@ -249,8 +250,9 @@ type PodTemplateParams struct { // - gets the init container for every preStart devfile event // - patches the pod template and containers to satisfy PodSecurityAdmissionPolicy // - patches the pod template and containers to apply pod and container overrides +// The containers included in the podTemplateSpec can be filtered using podTemplateParams.Options func GetPodTemplateSpec(devfileObj parser.DevfileObj, podTemplateParams PodTemplateParams) (*corev1.PodTemplateSpec, error) { - containers, err := GetContainers(devfileObj, common.DevfileOptions{}) + containers, err := GetContainers(devfileObj, podTemplateParams.Options) if err != nil { return nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 1f8feebf543..b8392854194 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -146,7 +146,7 @@ github.com/devfile/api/v2/pkg/utils/overriding github.com/devfile/api/v2/pkg/utils/unions github.com/devfile/api/v2/pkg/validation github.com/devfile/api/v2/pkg/validation/variables -# github.com/devfile/library/v2 v2.2.1-0.20230308185609-bd4a12f27257 +# github.com/devfile/library/v2 v2.2.1-0.20230323124903-d36e409ff94f ## explicit; go 1.15 github.com/devfile/library/v2/pkg/devfile github.com/devfile/library/v2/pkg/devfile/generator