github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/step/syntax/step_syntax_effective.go (about)

     1  package syntax
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts/step"
    12  	"github.com/jenkins-x/jx/v2/pkg/tekton"
    13  	"k8s.io/client-go/kubernetes"
    14  
    15  	"github.com/jenkins-x/jx/v2/pkg/versionstream"
    16  
    17  	"github.com/jenkins-x/jx-logging/pkg/log"
    18  	"github.com/jenkins-x/jx/v2/pkg/cmd/helper"
    19  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts"
    20  	"github.com/jenkins-x/jx/v2/pkg/cmd/templates"
    21  	"github.com/jenkins-x/jx/v2/pkg/config"
    22  	"github.com/jenkins-x/jx/v2/pkg/gits"
    23  	"github.com/jenkins-x/jx/v2/pkg/jenkinsfile"
    24  	"github.com/jenkins-x/jx/v2/pkg/jenkinsfile/gitresolver"
    25  	"github.com/jenkins-x/jx/v2/pkg/kube"
    26  	"github.com/jenkins-x/jx/v2/pkg/tekton/syntax"
    27  	"github.com/jenkins-x/jx/v2/pkg/util"
    28  	"github.com/pkg/errors"
    29  	"github.com/spf13/cobra"
    30  	corev1 "k8s.io/api/core/v1"
    31  	"sigs.k8s.io/yaml"
    32  )
    33  
    34  // StepSyntaxEffectiveOptions contains the command line flags
    35  type StepSyntaxEffectiveOptions struct {
    36  	step.StepOptions
    37  
    38  	Pack              string
    39  	BuildPackURL      string
    40  	BuildPackRef      string
    41  	Context           string
    42  	CustomImage       string
    43  	DefaultImage      string
    44  	UseKaniko         bool
    45  	KanikoImage       string
    46  	ProjectID         string
    47  	DockerRegistry    string
    48  	DockerRegistryOrg string
    49  	SourceName        string
    50  	CustomEnvs        []string
    51  	OutputFile        string
    52  	ShortView         bool
    53  
    54  	ValidateInCluster bool
    55  
    56  	PodTemplates map[string]*corev1.Pod
    57  
    58  	GitInfo         *gits.GitRepository
    59  	VersionResolver *versionstream.VersionResolver
    60  }
    61  
    62  var (
    63  	stepSyntaxEffectiveLong = templates.LongDesc(`
    64  		Reads the appropriate jenkins-x.yml, depending on context, from the current directory, if one exists, and outputs an effective representation of the pipelines
    65  `)
    66  
    67  	stepSyntaxEffectiveExample = templates.Examples(`
    68  		# view the effective pipeline
    69  		jx step syntax effective
    70  
    71  		# view the short version of the effective pipeline
    72  		jx step syntax effective -s
    73  
    74  `)
    75  )
    76  
    77  // NewCmdStepSyntaxEffective Creates a new Command object
    78  func NewCmdStepSyntaxEffective(commonOpts *opts.CommonOptions) *cobra.Command {
    79  	options := &StepSyntaxEffectiveOptions{
    80  		StepOptions: step.StepOptions{
    81  			CommonOptions: commonOpts,
    82  		},
    83  	}
    84  
    85  	cmd := &cobra.Command{
    86  		Use:     "effective",
    87  		Short:   "Outputs an effective representation of the pipeline to be executed",
    88  		Long:    stepSyntaxEffectiveLong,
    89  		Example: stepSyntaxEffectiveExample,
    90  		Run: func(cmd *cobra.Command, args []string) {
    91  			options.Cmd = cmd
    92  			options.Args = args
    93  			err := options.Run()
    94  			helper.CheckErr(err)
    95  		},
    96  	}
    97  
    98  	cmd.Flags().StringArrayVarP(&options.CustomEnvs, "env", "e", nil, "List of custom environment variables to be applied to resources that are created")
    99  
   100  	options.addFlags(cmd)
   101  	return cmd
   102  }
   103  
   104  func (o *StepSyntaxEffectiveOptions) addFlags(cmd *cobra.Command) {
   105  	cmd.Flags().StringVarP(&o.OutDir, "output-dir", "", "", "The directory to write the output to as YAML. Defaults to STDOUT if neither --output-dir nor --output-file is specified.")
   106  	cmd.Flags().StringVarP(&o.OutputFile, "output-file", "", "", "The file to write the output to as YAML. If unspecified and --output-dir is specified, the filename defaults to 'jenkins-x[-context]-effective.yml'")
   107  	cmd.Flags().StringVarP(&o.Pack, "pack", "p", "", "The build pack name. If none is specified its discovered from the source code")
   108  	cmd.Flags().StringVarP(&o.BuildPackURL, "url", "u", "", "The URL for the build pack Git repository")
   109  	cmd.Flags().StringVarP(&o.BuildPackRef, "ref", "r", "", "The Git reference (branch,tag,sha) in the Git repository to use")
   110  	cmd.Flags().StringVarP(&o.Context, "context", "c", "", "The pipeline context if there are multiple separate pipelines for a given branch")
   111  	cmd.Flags().StringVarP(&o.ServiceAccount, "service-account", "", tekton.DefaultPipelineSA, "The Kubernetes ServiceAccount to use to run the pipeline")
   112  	cmd.Flags().StringVarP(&o.SourceName, "source", "", "source", "The name of the source repository")
   113  	cmd.Flags().StringVarP(&o.CustomImage, "image", "", "", "Specify a custom image to use for the steps which overrides the image in the PodTemplates")
   114  	cmd.Flags().StringVarP(&o.DefaultImage, "default-image", "", syntax.DefaultContainerImage, "Specify the docker image to use if there is no image specified for a step and there's no Pod Template")
   115  	cmd.Flags().BoolVarP(&o.UseKaniko, "use-kaniko", "", true, "Enables using kaniko directly for building docker images")
   116  	cmd.Flags().BoolVarP(&o.ShortView, "short", "s", false, "Use short concise output")
   117  	cmd.Flags().StringVarP(&o.KanikoImage, "kaniko-image", "", syntax.KanikoDockerImage, "The docker image for Kaniko")
   118  	cmd.Flags().StringVarP(&o.ProjectID, "project-id", "", "", "The cloud project ID. If not specified we default to the install project")
   119  	cmd.Flags().StringVarP(&o.DockerRegistry, "docker-registry", "", "", "The Docker Registry host name to use which is added as a prefix to docker images")
   120  	cmd.Flags().StringVarP(&o.DockerRegistryOrg, "docker-registry-org", "", "", "The Docker registry organisation. If blank the git repository owner is used")
   121  	cmd.Flags().BoolVarP(&o.ValidateInCluster, "validate-in-cluster", "", false, "Validate that resources referenced in the effective pipeline, such as volumes, exist in the current context cluster")
   122  }
   123  
   124  // Run implements this command
   125  func (o *StepSyntaxEffectiveOptions) Run() error {
   126  	settings, err := o.TeamSettings()
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	kubeClient, ns, err := o.KubeClientAndDevNamespace()
   132  	if err != nil {
   133  		return errors.Wrap(err, "unable to create Kube client")
   134  	}
   135  
   136  	if o.ProjectID == "" {
   137  		if !o.RemoteCluster {
   138  			data, err := kube.ReadInstallValues(kubeClient, ns)
   139  			if err != nil {
   140  				return errors.Wrapf(err, "failed to read install values from namespace %s", ns)
   141  			}
   142  			o.ProjectID = data["projectID"]
   143  		}
   144  		if o.ProjectID == "" {
   145  			o.ProjectID = "todo"
   146  		}
   147  	}
   148  	if o.DefaultImage == "" {
   149  		o.DefaultImage = syntax.DefaultContainerImage
   150  	}
   151  	if o.VersionResolver == nil {
   152  		o.VersionResolver, err = o.GetVersionResolver()
   153  		if err != nil {
   154  			return err
   155  		}
   156  	}
   157  	if o.KanikoImage == "" {
   158  		o.KanikoImage = syntax.KanikoDockerImage
   159  	}
   160  	o.KanikoImage, err = o.VersionResolver.ResolveDockerImage(o.KanikoImage)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if o.Verbose {
   165  		log.Logger().Info("setting up docker registry\n")
   166  	}
   167  
   168  	if o.DockerRegistry == "" {
   169  		data, err := kube.GetConfigMapData(kubeClient, kube.ConfigMapJenkinsDockerRegistry, ns)
   170  		if err != nil {
   171  			return fmt.Errorf("could not find ConfigMap %s in namespace %s: %s", kube.ConfigMapJenkinsDockerRegistry, ns, err)
   172  		}
   173  		o.DockerRegistry = data["docker.registry"]
   174  		if o.DockerRegistry == "" {
   175  			return util.MissingOption("docker-registry")
   176  		}
   177  	}
   178  
   179  	workingDir, err := os.Getwd()
   180  	if err != nil {
   181  		return err
   182  	}
   183  	o.GitInfo, err = o.FindGitInfo(workingDir)
   184  	if err != nil {
   185  		return errors.Wrapf(err, "failed to find git information from dir %s", workingDir)
   186  	}
   187  	projectConfig, projectConfigFile, err := o.LoadProjectConfig(workingDir)
   188  	if err != nil {
   189  		return errors.Wrapf(err, "failed to load project config in dir %s", workingDir)
   190  	}
   191  	if o.BuildPackURL == "" || o.BuildPackRef == "" {
   192  		if projectConfig.BuildPackGitURL != "" {
   193  			o.BuildPackURL = projectConfig.BuildPackGitURL
   194  		} else if o.BuildPackURL == "" {
   195  			o.BuildPackURL = settings.BuildPackURL
   196  		}
   197  		if projectConfig.BuildPackGitURef != "" {
   198  			o.BuildPackRef = projectConfig.BuildPackGitURef
   199  		} else if o.BuildPackRef == "" {
   200  			o.BuildPackRef = settings.BuildPackRef
   201  		}
   202  	}
   203  	if o.BuildPackURL == "" {
   204  		return util.MissingOption("url")
   205  	}
   206  	if o.BuildPackRef == "" {
   207  		return util.MissingOption("ref")
   208  	}
   209  
   210  	if o.Pack == "" {
   211  		o.Pack = projectConfig.BuildPack
   212  	}
   213  	if o.Pack == "" {
   214  		o.Pack, err = o.DiscoverBuildPack(workingDir, projectConfig, o.Pack)
   215  		if err != nil {
   216  			return errors.Wrapf(err, "failed to discover the build pack")
   217  		}
   218  	}
   219  
   220  	if o.Pack == "" {
   221  		return util.MissingOption("pack")
   222  	}
   223  
   224  	o.PodTemplates, err = kube.LoadPodTemplates(kubeClient, ns)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	packsDir, err := gitresolver.InitBuildPack(o.Git(), o.BuildPackURL, o.BuildPackRef)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	resolver, err := gitresolver.CreateResolver(packsDir, o.Git())
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	effectiveConfig, err := o.CreateEffectivePipeline(packsDir, projectConfig, projectConfigFile, resolver)
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	if o.ShortView {
   245  		effectiveConfig = o.makeConcisePipeline(effectiveConfig)
   246  	}
   247  
   248  	effectiveYaml, err := yaml.Marshal(effectiveConfig)
   249  	if err != nil {
   250  		return errors.Wrap(err, "failed to marshal effective pipeline")
   251  	}
   252  	if o.OutDir == "" && o.OutputFile == "" {
   253  		if o.ShortView {
   254  			for _, line := range strings.Split(string(effectiveYaml), "\n") {
   255  				prefix := "command: "
   256  				idx := strings.Index(line, prefix)
   257  				if idx >= 0 {
   258  					line = line[0:idx] + prefix + util.ColorInfo(line[idx+len(prefix):])
   259  				}
   260  				fmt.Printf("%s\n", line)
   261  			}
   262  		} else {
   263  			fmt.Printf("%s\n", effectiveYaml)
   264  		}
   265  	} else {
   266  		outputDir := o.OutDir
   267  		if outputDir == "" {
   268  			outputDir, err = os.Getwd()
   269  			if err != nil {
   270  				return errors.Wrap(err, "failed to get current directory")
   271  			}
   272  		}
   273  		outputFilename := o.OutputFile
   274  		if outputFilename == "" {
   275  			outputFilename = "jenkins-x"
   276  			if o.Context != "" {
   277  				outputFilename += "-" + o.Context
   278  			}
   279  			outputFilename += "-effective.yml"
   280  		}
   281  		outputFile := filepath.Join(outputDir, outputFilename)
   282  		err = ioutil.WriteFile(outputFile, effectiveYaml, util.DefaultWritePermissions)
   283  		if err != nil {
   284  			return errors.Wrapf(err, "failed to write effective pipeline to %s", outputFile)
   285  		}
   286  		log.Logger().Infof("Effective pipeline written to %s", outputFile)
   287  	}
   288  	return nil
   289  }
   290  
   291  // CreateEffectivePipeline takes a project config and generates the effective version of the pipeline for it, including
   292  // build packs, inheritance, overrides, defaults, etc.
   293  func (o *StepSyntaxEffectiveOptions) CreateEffectivePipeline(packsDir string, projectConfig *config.ProjectConfig, projectConfigFile string, resolver jenkinsfile.ImportFileResolver) (*config.ProjectConfig, error) {
   294  	name := o.Pack
   295  	packDir := filepath.Join(packsDir, name)
   296  
   297  	pipelineConfig := projectConfig.PipelineConfig
   298  	if name != "none" {
   299  		pipelineFile := filepath.Join(packDir, jenkinsfile.PipelineConfigFileName)
   300  		exists, err := util.FileExists(pipelineFile)
   301  		if err != nil {
   302  			return nil, errors.Wrapf(err, "failed to find build pack pipeline YAML: %s", pipelineFile)
   303  		}
   304  		if !exists {
   305  			return nil, fmt.Errorf("no build pack for %s exists at directory %s", name, packDir)
   306  		}
   307  		pipelineConfig, err = jenkinsfile.LoadPipelineConfig(pipelineFile, resolver, true, false)
   308  		if err != nil {
   309  			return nil, errors.Wrapf(err, "failed to load build pack pipeline YAML: %s", pipelineFile)
   310  		}
   311  
   312  		localPipelineConfig := projectConfig.PipelineConfig
   313  		if localPipelineConfig != nil {
   314  			err = localPipelineConfig.ExtendPipeline(pipelineConfig, false)
   315  			if err != nil {
   316  				return nil, errors.Wrapf(err, "failed to override PipelineConfig using configuration in file %s", projectConfigFile)
   317  			}
   318  			pipelineConfig = localPipelineConfig
   319  		}
   320  	} else {
   321  		pipelineConfig.PopulatePipelinesFromDefault()
   322  	}
   323  
   324  	if pipelineConfig == nil {
   325  		return nil, fmt.Errorf("failed to find PipelineConfig in file %s", projectConfigFile)
   326  	}
   327  
   328  	err := o.combineEnvVars(pipelineConfig)
   329  	if err != nil {
   330  		return nil, errors.Wrapf(err, "failed to combine env vars")
   331  	}
   332  
   333  	pipelines := pipelineConfig.Pipelines
   334  	// First, handle release.
   335  	if pipelines.Release != nil {
   336  		releaseLifecycles := pipelines.Release
   337  
   338  		// lets add a pre-step to setup the credentials
   339  		if releaseLifecycles.Setup == nil {
   340  			releaseLifecycles.Setup = &jenkinsfile.PipelineLifecycle{}
   341  		}
   342  		steps := []*syntax.Step{
   343  			{
   344  				Command: "jx step git credentials",
   345  				Name:    "jx-git-credentials",
   346  			},
   347  		}
   348  		releaseLifecycles.Setup.Steps = append(steps, releaseLifecycles.Setup.Steps...)
   349  		parsed, err := o.createPipelineForKind(jenkinsfile.PipelineKindRelease, releaseLifecycles, pipelines, projectConfig, pipelineConfig)
   350  		if err != nil {
   351  			return nil, errors.Wrapf(err, "failed to create effective pipeline for release")
   352  		}
   353  		pipelines.Release = &jenkinsfile.PipelineLifecycles{
   354  			Pipeline:   parsed,
   355  			SetVersion: releaseLifecycles.SetVersion,
   356  		}
   357  	}
   358  	if pipelines.PullRequest != nil {
   359  		prLifecycles := pipelines.PullRequest
   360  		parsed, err := o.createPipelineForKind(jenkinsfile.PipelineKindPullRequest, prLifecycles, pipelines, projectConfig, pipelineConfig)
   361  		if err != nil {
   362  			return nil, errors.Wrapf(err, "failed to create effective pipeline for pull request")
   363  		}
   364  		pipelines.PullRequest = &jenkinsfile.PipelineLifecycles{
   365  			Pipeline:   parsed,
   366  			SetVersion: prLifecycles.SetVersion,
   367  		}
   368  	}
   369  	if pipelines.Feature != nil {
   370  		featureLifecycles := pipelines.Feature
   371  		parsed, err := o.createPipelineForKind(jenkinsfile.PipelineKindFeature, featureLifecycles, pipelines, projectConfig, pipelineConfig)
   372  		if err != nil {
   373  			return nil, errors.Wrapf(err, "failed to create effective pipeline for pull request")
   374  		}
   375  		pipelines.Feature = &jenkinsfile.PipelineLifecycles{
   376  			Pipeline:   parsed,
   377  			SetVersion: featureLifecycles.SetVersion,
   378  		}
   379  	}
   380  
   381  	pipelineConfig.Pipelines = pipelines
   382  	projectConfig.PipelineConfig = pipelineConfig
   383  
   384  	return projectConfig, nil
   385  }
   386  
   387  func (o *StepSyntaxEffectiveOptions) createPipelineForKind(kind string, lifecycles *jenkinsfile.PipelineLifecycles, pipelines jenkinsfile.Pipelines, projectConfig *config.ProjectConfig, pipelineConfig *jenkinsfile.PipelineConfig) (*syntax.ParsedPipeline, error) {
   388  	var parsed *syntax.ParsedPipeline
   389  	var err error
   390  
   391  	if lifecycles != nil && lifecycles.Pipeline != nil {
   392  		parsed = lifecycles.Pipeline
   393  		if projectConfig.BuildPack == "" || projectConfig.BuildPack == "none" {
   394  			for _, override := range pipelines.Overrides {
   395  				if override.MatchesPipeline(kind) {
   396  					// If no step/steps, other overrides, or stage is specified, just remove the whole pipeline.
   397  					// TODO: This is probably pointless functionality.
   398  					if override.Step == nil && len(override.Steps) == 0 && !override.HasNonStepOverrides() && override.Stage == "" {
   399  						return nil, nil
   400  					}
   401  					parsed = syntax.ApplyStepOverridesToPipeline(parsed, override)
   402  				}
   403  			}
   404  		}
   405  	} else {
   406  		args := jenkinsfile.CreatePipelineArguments{
   407  			Lifecycles:        lifecycles,
   408  			PodTemplates:      o.PodTemplates,
   409  			CustomImage:       o.CustomImage,
   410  			DefaultImage:      o.DefaultImage,
   411  			WorkspaceDir:      o.getWorkspaceDir(),
   412  			GitHost:           o.GitInfo.Host,
   413  			GitName:           o.GitInfo.Name,
   414  			GitOrg:            o.GitInfo.Organisation,
   415  			ProjectID:         o.ProjectID,
   416  			DockerRegistry:    o.getDockerRegistry(projectConfig),
   417  			DockerRegistryOrg: o.GetDockerRegistryOrg(projectConfig, o.GitInfo),
   418  			KanikoImage:       o.KanikoImage,
   419  			UseKaniko:         o.UseKaniko,
   420  			// Make sure we don't inject the setversion steps
   421  			NoReleasePrepare: false,
   422  			StepCounter:      0,
   423  		}
   424  		parsed, _, err = pipelineConfig.CreatePipelineForBuildPack(args)
   425  		if err != nil {
   426  			return nil, errors.Wrapf(err, "Failed to generate pipeline from build pack")
   427  		}
   428  	}
   429  
   430  	// Replace placeholders in directories.
   431  	replacePlaceholderArgs := syntax.StepPlaceholderReplacementArgs{
   432  		WorkspaceDir:      o.getWorkspaceDir(),
   433  		GitName:           o.GitInfo.Name,
   434  		GitOrg:            o.GitInfo.Organisation,
   435  		GitHost:           o.GitInfo.Host,
   436  		ProjectID:         o.ProjectID,
   437  		DockerRegistry:    o.getDockerRegistry(projectConfig),
   438  		DockerRegistryOrg: o.GetDockerRegistryOrg(projectConfig, o.GitInfo),
   439  		KanikoImage:       o.KanikoImage,
   440  		UseKaniko:         o.UseKaniko,
   441  	}
   442  	parsed.ReplacePlaceholdersInStepAndStageDirs(replacePlaceholderArgs)
   443  	parsed.AddContainerEnvVarsToPipeline(pipelineConfig.Env)
   444  
   445  	if pipelineConfig.ContainerOptions != nil {
   446  		if parsed.Options == nil {
   447  			parsed.Options = &syntax.RootOptions{}
   448  		}
   449  		mergedContainer, err := syntax.MergeContainers(pipelineConfig.ContainerOptions, parsed.Options.ContainerOptions)
   450  		if err != nil {
   451  			return nil, errors.Wrapf(err, "Could not merge containerOptions from parent")
   452  		}
   453  		parsed.Options.ContainerOptions = mergedContainer
   454  	}
   455  
   456  	for _, override := range pipelines.Overrides {
   457  		if override.MatchesPipeline(kind) {
   458  			parsed = syntax.ApplyNonStepOverridesToPipeline(parsed, override)
   459  		}
   460  	}
   461  
   462  	var kubeClient kubernetes.Interface
   463  	var ns string
   464  
   465  	// If we're validating in the cluster, get the kubeClient. Otherwise it'll be nil and ignored.
   466  	if o.ValidateInCluster {
   467  		kubeClient, ns, err = o.KubeClientAndDevNamespace()
   468  		if err != nil {
   469  			return nil, errors.Wrap(err, "unable to create Kube client")
   470  		}
   471  	}
   472  
   473  	// TODO: Seeing weird behavior seemingly related to https://golang.org/doc/faq#nil_error
   474  	// if err is reused, maybe we need to switch return types (perhaps upstream in build-pipeline)?
   475  	ctx := context.Background()
   476  	if validateErr := parsed.ValidateInCluster(ctx, kubeClient, ns); validateErr != nil {
   477  		return nil, errors.Wrapf(validateErr, "validation failed for Pipeline")
   478  	}
   479  
   480  	// lets override any container options env vars from any custom injected env vars from the metapipeline client
   481  	if parsed != nil && parsed.Options != nil && parsed.Options.ContainerOptions != nil {
   482  		parsed.Options.ContainerOptions.Env = syntax.CombineEnv(pipelineConfig.Env, parsed.Options.ContainerOptions.Env)
   483  	}
   484  	return parsed, nil
   485  }
   486  
   487  func (o *StepSyntaxEffectiveOptions) combineEnvVars(projectConfig *jenkinsfile.PipelineConfig) error {
   488  	// add any custom env vars
   489  	envMap := make(map[string]corev1.EnvVar)
   490  	for _, e := range projectConfig.Env {
   491  		envMap[e.Name] = e
   492  	}
   493  	for _, customEnvVar := range o.CustomEnvs {
   494  		parts := strings.Split(customEnvVar, "=")
   495  		if len(parts) != 2 {
   496  			return errors.Errorf("expected 2 parts to env var but got %v", len(parts))
   497  		}
   498  		e := corev1.EnvVar{
   499  			Name:  parts[0],
   500  			Value: parts[1],
   501  		}
   502  		envMap[e.Name] = e
   503  	}
   504  	projectConfig.Env = syntax.EnvMapToSlice(envMap)
   505  	return nil
   506  }
   507  
   508  func (o *StepSyntaxEffectiveOptions) getWorkspaceDir() string {
   509  	return filepath.Join("/workspace", o.SourceName)
   510  }
   511  
   512  func (o *StepSyntaxEffectiveOptions) getDockerRegistry(projectConfig *config.ProjectConfig) string {
   513  	dockerRegistry := o.DockerRegistry
   514  	if dockerRegistry == "" {
   515  		dockerRegistry = o.GetDockerRegistry(projectConfig)
   516  	}
   517  	return dockerRegistry
   518  }
   519  
   520  // LoadProjectConfig loads the pipeline config from the given workingDir
   521  func (o *StepSyntaxEffectiveOptions) LoadProjectConfig(workingDir string) (*config.ProjectConfig, string, error) {
   522  	if o.Context != "" {
   523  		fileName := filepath.Join(workingDir, fmt.Sprintf("jenkins-x-%s.yml", o.Context))
   524  		exists, err := util.FileExists(fileName)
   525  		if err != nil {
   526  			return nil, fileName, errors.Wrapf(err, "failed to check if file exists %s", fileName)
   527  		}
   528  		if exists {
   529  			config, err := config.LoadProjectConfigFile(fileName)
   530  			return config, fileName, err
   531  		}
   532  	}
   533  	return config.LoadProjectConfig(workingDir)
   534  }
   535  
   536  func (o *StepSyntaxEffectiveOptions) makeConcisePipeline(projectConfig *config.ProjectConfig) *config.ProjectConfig {
   537  	for _, pipelines := range projectConfig.PipelineConfig.Pipelines.All() {
   538  		if pipelines != nil {
   539  			if pipelines.Pipeline != nil {
   540  				o.makeConciseStages(pipelines.Pipeline.Stages)
   541  			}
   542  		}
   543  	}
   544  	return projectConfig
   545  }
   546  
   547  func (o *StepSyntaxEffectiveOptions) makeConciseStages(stages []syntax.Stage) {
   548  	for i := range stages {
   549  		stage := &stages[i]
   550  		for j := range stage.Steps {
   551  			o.makeConciseStep(&stage.Steps[j])
   552  		}
   553  	}
   554  }
   555  
   556  func (o *StepSyntaxEffectiveOptions) makeConciseStep(step *syntax.Step) {
   557  	for _, child := range step.Steps {
   558  		o.makeConciseStep(child)
   559  	}
   560  	c := step.Command
   561  	if c == "" {
   562  		return
   563  	}
   564  	args := step.Arguments
   565  	if len(args) > 0 {
   566  		c = c + " " + strings.Join(args, " ")
   567  		step.Arguments = nil
   568  	}
   569  	step.Command = c
   570  }