...

Source file src/github.com/redhat-developer/odo/pkg/component/execute_new_container.go

Documentation: github.com/redhat-developer/odo/pkg/component

     1  package component
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    10  	"github.com/devfile/library/v2/pkg/devfile/generator"
    11  	"github.com/devfile/library/v2/pkg/devfile/parser"
    12  	"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
    13  
    14  	"github.com/redhat-developer/odo/pkg/configAutomount"
    15  	"github.com/redhat-developer/odo/pkg/dev/kubedev/storage"
    16  	"github.com/redhat-developer/odo/pkg/kclient"
    17  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    18  	odogenerator "github.com/redhat-developer/odo/pkg/libdevfile/generator"
    19  	"github.com/redhat-developer/odo/pkg/log"
    20  	"github.com/redhat-developer/odo/pkg/util"
    21  
    22  	batchv1 "k8s.io/api/batch/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/klog"
    25  	"k8s.io/utils/pointer"
    26  )
    27  
    28  func ExecuteInNewContainer(
    29  	ctx context.Context,
    30  	kubeClient kclient.ClientInterface,
    31  	configAutomountClient configAutomount.Client,
    32  	devfileObj parser.DevfileObj,
    33  	componentName string,
    34  	appName string,
    35  	command v1alpha2.Command,
    36  ) error {
    37  	policy, err := kubeClient.GetCurrentNamespacePolicy()
    38  	if err != nil {
    39  		return err
    40  	}
    41  	podTemplateSpec, err := generator.GetPodTemplateSpec(devfileObj, generator.PodTemplateParams{
    42  		Options: common.DevfileOptions{
    43  			FilterByName: command.Exec.Component,
    44  		},
    45  		PodSecurityAdmissionPolicy: policy,
    46  	})
    47  	if err != nil {
    48  		return err
    49  	}
    50  	// 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.
    51  	podTemplateSpec.Spec.RestartPolicy = "Never"
    52  
    53  	if len(podTemplateSpec.Spec.Containers) != 1 {
    54  		return fmt.Errorf("could not find the component")
    55  	}
    56  
    57  	podTemplateSpec.Spec.Containers[0].Command = []string{"/bin/sh"}
    58  	podTemplateSpec.Spec.Containers[0].Args = getJobCmdline(command)
    59  
    60  	volumes, err := storage.GetAutomountVolumes(configAutomountClient, podTemplateSpec.Spec.Containers, podTemplateSpec.Spec.InitContainers)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	podTemplateSpec.Spec.Volumes = volumes
    66  
    67  	// Create a Kubernetes Job and use the container image referenced by command.Exec.Component
    68  	// Get the component for the command with command.Exec.Component
    69  	getJobName := func() string {
    70  		maxLen := kclient.JobNameOdoMaxLength - len(command.Id)
    71  		// We ignore the error here because our component name or app name will never be empty; which are the only cases when an error might be raised.
    72  		name, _ := util.NamespaceKubernetesObjectWithTrim(componentName, appName, maxLen)
    73  		name += "-" + command.Id
    74  		return name
    75  	}
    76  	completionMode := batchv1.CompletionMode("Indexed")
    77  	jobParams := odogenerator.JobParams{
    78  		TypeMeta: generator.GetTypeMeta(kclient.JobsKind, kclient.JobsAPIVersion),
    79  		ObjectMeta: metav1.ObjectMeta{
    80  			Name: getJobName(),
    81  		},
    82  		PodTemplateSpec: *podTemplateSpec,
    83  		SpecParams: odogenerator.JobSpecParams{
    84  			CompletionMode:          &completionMode,
    85  			TTLSecondsAfterFinished: pointer.Int32(60),
    86  			BackOffLimit:            pointer.Int32(1),
    87  		},
    88  	}
    89  	job := odogenerator.GetJob(jobParams)
    90  	// Set labels and annotations
    91  	job.SetLabels(odolabels.GetLabels(componentName, appName, GetComponentRuntimeFromDevfileMetadata(devfileObj.Data.GetMetadata()), odolabels.ComponentDeployMode, false))
    92  	job.Annotations = map[string]string{}
    93  	odolabels.AddCommonAnnotations(job.Annotations)
    94  	odolabels.SetProjectType(job.Annotations, GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata()))
    95  
    96  	//	Make sure there are no existing jobs
    97  	checkAndDeleteExistingJob := func() {
    98  		items, dErr := kubeClient.ListJobs(odolabels.GetSelector(componentName, appName, odolabels.ComponentDeployMode, false))
    99  		if dErr != nil {
   100  			klog.V(4).Infof("failed to list jobs; cause: %s", dErr.Error())
   101  			return
   102  		}
   103  		jobName := getJobName()
   104  		for _, item := range items.Items {
   105  			if strings.Contains(item.Name, jobName) {
   106  				dErr = kubeClient.DeleteJob(item.Name)
   107  				if dErr != nil {
   108  					klog.V(4).Infof("failed to delete job %q; cause: %s", item.Name, dErr.Error())
   109  				}
   110  			}
   111  		}
   112  	}
   113  	checkAndDeleteExistingJob()
   114  
   115  	log.Sectionf("Executing command:")
   116  	spinner := log.Spinnerf("Executing command in container (command: %s)", command.Id)
   117  	defer spinner.End(false)
   118  
   119  	var createdJob *batchv1.Job
   120  	createdJob, err = kubeClient.CreateJob(job, "")
   121  	if err != nil {
   122  		return err
   123  	}
   124  	defer func() {
   125  		err = kubeClient.DeleteJob(createdJob.Name)
   126  		if err != nil {
   127  			klog.V(4).Infof("failed to delete job %q; cause: %s", createdJob.Name, err)
   128  		}
   129  	}()
   130  
   131  	var done = make(chan struct{}, 1)
   132  	// Print the tip to use `odo logs` if the command is still running after 1 minute
   133  	go func() {
   134  		select {
   135  		case <-time.After(1 * time.Minute):
   136  			log.Info("\nTip: Run `odo logs --deploy --follow` to get the logs of the command output.")
   137  		case <-done:
   138  			return
   139  		}
   140  	}()
   141  
   142  	// Wait for the command to complete execution
   143  	_, err = kubeClient.WaitForJobToComplete(createdJob)
   144  	done <- struct{}{}
   145  
   146  	spinner.End(err == nil)
   147  
   148  	if err != nil {
   149  		err = fmt.Errorf("failed to execute (command: %s)", command.Id)
   150  		// Print the job logs if the job failed
   151  		jobLogs, logErr := kubeClient.GetJobLogs(createdJob, command.Exec.Component)
   152  		if logErr != nil {
   153  			log.Warningf("failed to fetch the logs of execution; cause: %s", logErr)
   154  		}
   155  		fmt.Println("Execution output:")
   156  		_ = util.DisplayLog(false, jobLogs, log.GetStderr(), componentName, 100)
   157  	}
   158  
   159  	return err
   160  }
   161  
   162  func getJobCmdline(command v1alpha2.Command) []string {
   163  	// deal with environment variables
   164  	var cmdLine string
   165  	setEnvVariable := util.GetCommandStringFromEnvs(command.Exec.Env)
   166  
   167  	if setEnvVariable == "" {
   168  		cmdLine = command.Exec.CommandLine
   169  	} else {
   170  		cmdLine = setEnvVariable + " && " + command.Exec.CommandLine
   171  	}
   172  	var args []string
   173  	if command.Exec.WorkingDir != "" {
   174  		// since we are using /bin/sh -c, the command needs to be within a single double quote instance, for example "cd /tmp && pwd"
   175  		args = []string{"-c", "cd " + command.Exec.WorkingDir + " && " + cmdLine}
   176  	} else {
   177  		args = []string{"-c", cmdLine}
   178  	}
   179  	return args
   180  }
   181  

View as plain text