github.com/jenkins-x/jx/v2@v2.1.155/pkg/tekton/metapipeline/clientfactory.go (about)

     1  package metapipeline
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    11  	jxclient "github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    12  	"github.com/jenkins-x/jx-logging/pkg/log"
    13  	"github.com/jenkins-x/jx/v2/pkg/apps"
    14  	"github.com/jenkins-x/jx/v2/pkg/config"
    15  	"github.com/jenkins-x/jx/v2/pkg/gits"
    16  	"github.com/jenkins-x/jx/v2/pkg/jxfactory"
    17  	"github.com/jenkins-x/jx/v2/pkg/kube"
    18  	"github.com/jenkins-x/jx/v2/pkg/tekton"
    19  	"github.com/jenkins-x/jx/v2/pkg/util"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  	tektonclient "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
    23  	corev1 "k8s.io/api/core/v1"
    24  	"k8s.io/client-go/kubernetes"
    25  	kubeclient "k8s.io/client-go/kubernetes"
    26  )
    27  
    28  const (
    29  	retryDuration      = time.Second * 30
    30  	defaultCheckoutDir = "source"
    31  )
    32  
    33  var (
    34  	logger = log.Logger().WithFields(logrus.Fields{"component": "meta-pipeline-client"})
    35  )
    36  
    37  // clientFactory implements the interface methods to create and apply the meta pipeline.
    38  type clientFactory struct {
    39  	jxClient     versioned.Interface
    40  	tektonClient tektonclient.Interface
    41  	kubeClient   kubernetes.Interface
    42  	ns           string
    43  
    44  	versionDir       string
    45  	versionStreamURL string
    46  	versionStreamRef string
    47  }
    48  
    49  // NewMetaPipelineClient creates a new client for the creation and application of meta pipelines.
    50  // The responsibility of the meta pipeline is to prepare the execution pipeline and to allow Apps to contribute
    51  // the this execution pipeline.
    52  func NewMetaPipelineClient() (Client, error) {
    53  	tektonClient, jxClient, kubeClient, ns, err := getClientsAndNamespace()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return NewMetaPipelineClientWithClientsAndNamespace(jxClient, tektonClient, kubeClient, ns)
    59  }
    60  
    61  // NewMetaPipelineClientWithClientsAndNamespace creates a new client for the creation and application of meta pipelines using the specified parameters.
    62  func NewMetaPipelineClientWithClientsAndNamespace(jxClient versioned.Interface, tektonClient tektonclient.Interface, kubeClient kubernetes.Interface, ns string) (Client, error) {
    63  	url, ref, err := versionStreamURLAndRef(jxClient, ns)
    64  	if err != nil {
    65  		return nil, errors.Wrap(err, "unable to determine versions stream URL and ref")
    66  	}
    67  
    68  	versionDir, err := cloneVersionStream(url, ref)
    69  	if err != nil {
    70  		return nil, errors.Wrap(err, "unable to clone version dir")
    71  	}
    72  
    73  	client := clientFactory{
    74  		jxClient:         jxClient,
    75  		tektonClient:     tektonClient,
    76  		kubeClient:       kubeClient,
    77  		ns:               ns,
    78  		versionDir:       versionDir,
    79  		versionStreamURL: url,
    80  		versionStreamRef: ref,
    81  	}
    82  
    83  	return &client, nil
    84  }
    85  
    86  // Create creates the Tekton CRDs needed for executing the pipeline as defined by the input parameters.
    87  func (c *clientFactory) Create(param PipelineCreateParam) (kube.PromoteStepActivityKey, tekton.CRDWrapper, error) {
    88  	err := c.cloneVersionStreamIfNeeded()
    89  	if err != nil {
    90  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, "unable to clone version stream")
    91  	}
    92  
    93  	gitInfo, err := gits.ParseGitURL(param.PullRef.SourceURL())
    94  	if err != nil {
    95  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, fmt.Sprintf("unable to determine needed git info from the specified git url '%s'", param.PullRef.SourceURL()))
    96  	}
    97  
    98  	podTemplates, err := c.getPodTemplates(apps.AppPodTemplateName)
    99  	if err != nil {
   100  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, "unable to retrieve pod templates")
   101  	}
   102  
   103  	branchIdentifier, err := c.determineBranchIdentifier(param.PipelineKind, param.PullRef)
   104  	if err != nil {
   105  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, "unable to create branch identifier")
   106  	}
   107  
   108  	pipelineName := tekton.PipelineResourceNameFromGitInfo(gitInfo, branchIdentifier, param.Context, tekton.MetaPipeline.String())
   109  	buildNumber, err := tekton.GenerateNextBuildNumber(c.tektonClient, c.jxClient, c.ns, gitInfo, branchIdentifier, retryDuration, param.Context, param.UseActivityForNextBuildNumber)
   110  	if err != nil {
   111  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, "unable to determine next build number")
   112  	}
   113  
   114  	logger.WithField("repo", gitInfo.URL).WithField("buildNumber", buildNumber).Debug("creating meta pipeline CRDs")
   115  
   116  	extendingApps, err := getExtendingApps(c.jxClient, c.ns)
   117  	if err != nil {
   118  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, err
   119  	}
   120  
   121  	crdCreationParams := CRDCreationParameters{
   122  		Namespace:           c.ns,
   123  		Context:             param.Context,
   124  		PipelineName:        pipelineName,
   125  		PipelineKind:        param.PipelineKind,
   126  		BuildNumber:         buildNumber,
   127  		BranchIdentifier:    branchIdentifier,
   128  		PullRef:             param.PullRef,
   129  		SourceDir:           defaultCheckoutDir,
   130  		PodTemplates:        podTemplates,
   131  		ServiceAccount:      param.ServiceAccount,
   132  		Labels:              param.Labels,
   133  		EnvVars:             param.EnvVariables,
   134  		DefaultImage:        param.DefaultImage,
   135  		Apps:                extendingApps,
   136  		VersionsDir:         c.versionDir,
   137  		GitInfo:             *gitInfo,
   138  		UseBranchAsRevision: param.UseBranchAsRevision,
   139  		NoReleasePrepare:    param.NoReleasePrepare,
   140  	}
   141  
   142  	return c.createActualCRDs(buildNumber, branchIdentifier, param.Context, param.PullRef, crdCreationParams)
   143  }
   144  
   145  func (c *clientFactory) createActualCRDs(buildNumber string, branchIdentifier string, context string, pullRef PullRef, parameters CRDCreationParameters) (kube.PromoteStepActivityKey, tekton.CRDWrapper, error) {
   146  	tektonCRDs, err := createMetaPipelineCRDs(parameters)
   147  	if err != nil {
   148  		return kube.PromoteStepActivityKey{}, tekton.CRDWrapper{}, errors.Wrap(err, "failed to generate Tekton CRDs for meta pipeline")
   149  	}
   150  
   151  	pr, _ := tekton.ParsePullRefs(pullRef.String())
   152  	pipelineActivity := tekton.GeneratePipelineActivity(buildNumber, branchIdentifier, &parameters.GitInfo, context, pr)
   153  
   154  	return *pipelineActivity, *tektonCRDs, nil
   155  }
   156  
   157  // Apply takes the given CRDs to process them, usually applying them to the cluster.
   158  func (c *clientFactory) Apply(pipelineActivity kube.PromoteStepActivityKey, crds tekton.CRDWrapper) error {
   159  	err := tekton.ApplyPipeline(c.jxClient, c.kubeClient, c.tektonClient, &crds, c.ns, &pipelineActivity)
   160  	if err != nil {
   161  		return errors.Wrapf(err, "failed to apply Tekton CRDs")
   162  	}
   163  	logger.WithField("pipeline", crds.PipelineRun().Name).Debug("applied tekton CRDs")
   164  	return nil
   165  }
   166  
   167  // Close cleans up the resources use by this client.
   168  func (c *clientFactory) Close() error {
   169  	return os.RemoveAll(c.versionDir)
   170  }
   171  
   172  func (c *clientFactory) getPodTemplates(containerName string) (map[string]*corev1.Pod, error) {
   173  	podTemplates, err := kube.LoadPodTemplates(c.kubeClient, c.ns)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	return podTemplates, nil
   179  }
   180  
   181  func (c *clientFactory) determineBranchIdentifier(pipelineType PipelineKind, pullRef PullRef) (string, error) {
   182  	var branch string
   183  	switch pipelineType {
   184  	case ReleasePipeline:
   185  		// no pull requests to merge, taking base branch name as identifier
   186  		branch = pullRef.baseBranch
   187  	case PullRequestPipeline:
   188  		if len(pullRef.pullRequests) == 0 {
   189  			return "", errors.New("pullrequest pipeline requested, but no pull requests specified")
   190  		}
   191  		branch = fmt.Sprintf("PR-%s", pullRef.PullRequests()[0].ID)
   192  	default:
   193  		branch = "unknown"
   194  	}
   195  	return branch, nil
   196  }
   197  
   198  func versionStreamURLAndRef(jxClient versioned.Interface, ns string) (string, string, error) {
   199  	devEnv, err := kube.GetDevEnvironment(jxClient, ns)
   200  	if err != nil {
   201  		return "", "", errors.Wrap(err, "unable to retrieve team environment")
   202  	}
   203  
   204  	if devEnv == nil {
   205  		return config.DefaultVersionsURL, config.DefaultVersionsRef, nil
   206  	}
   207  
   208  	teamSettings := devEnv.Spec.TeamSettings
   209  	url := teamSettings.VersionStreamURL
   210  	ref := teamSettings.VersionStreamRef
   211  	if url == "" {
   212  		url = config.DefaultVersionsURL
   213  	}
   214  	if ref == "" {
   215  		ref = config.DefaultVersionsRef
   216  	}
   217  
   218  	return url, ref, nil
   219  }
   220  
   221  func (c *clientFactory) cloneVersionStreamIfNeeded() error {
   222  	url, ref, err := versionStreamURLAndRef(c.jxClient, c.ns)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	if c.versionStreamURL != url || c.versionStreamRef != ref {
   228  		oldVersionStreamDir := c.versionDir
   229  		c.versionDir, err = cloneVersionStream(url, ref)
   230  		if err != nil {
   231  			return err
   232  		}
   233  		_ = os.RemoveAll(oldVersionStreamDir)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  func cloneVersionStream(url string, ref string) (string, error) {
   240  	dir, err := ioutil.TempDir("", "jx-version-repo-")
   241  	if err != nil {
   242  		return "", errors.Wrap(err, "unable to create temp dir for version stream")
   243  	}
   244  
   245  	logger.Debugf("cloning version stream url: %s ref: %s into %s", url, ref, dir)
   246  
   247  	// Not using GitCLi Clone/ShallowClone atm, since it does not work with tags.
   248  	// Once https://github.com/jenkins-x/jx/issues/5087 is resolved we should switch to that.
   249  	// As a quick hack is assumes that any ref with a '.' won't be a SHA.
   250  	if ref == "master" || strings.Contains(ref, ".") {
   251  		args := []string{"clone", "--depth", "1", "--branch", ref, url, "."}
   252  		cmd := util.Command{
   253  			Dir:  dir,
   254  			Name: "git",
   255  			Args: args,
   256  		}
   257  		output, err := cmd.RunWithoutRetry()
   258  		if err != nil {
   259  			return "", errors.Wrapf(err, "unable to clone version stream and checking out branch/tag: %s", output)
   260  		}
   261  	} else {
   262  		// assuming we deal with a SHA
   263  		args := []string{"clone", url, "."}
   264  		cmd := util.Command{
   265  			Dir:  dir,
   266  			Name: "git",
   267  			Args: args,
   268  		}
   269  		output, err := cmd.RunWithoutRetry()
   270  		if err != nil {
   271  			return "", errors.Wrapf(err, "unable to clone version stream: %s", output)
   272  		}
   273  
   274  		// Fetch PR refs before checking out the ref
   275  		args = []string{"fetch", "origin", ref}
   276  		cmd = util.Command{
   277  			Dir:  dir,
   278  			Name: "git",
   279  			Args: args,
   280  		}
   281  		output, err = cmd.RunWithoutRetry()
   282  		if err != nil {
   283  			return "", errors.Wrapf(err, "unable to fetch pull request refs for version stream: %s", output)
   284  		}
   285  
   286  		args = []string{"checkout", ref}
   287  		cmd = util.Command{
   288  			Dir:  dir,
   289  			Name: "git",
   290  			Args: args,
   291  		}
   292  		output, err = cmd.RunWithoutRetry()
   293  		if err != nil {
   294  			return "", errors.Wrapf(err, "unable checkout sha %s for version stream %s: %s", ref, url, output)
   295  		}
   296  	}
   297  
   298  	return dir, err
   299  }
   300  
   301  func getClientsAndNamespace() (tektonclient.Interface, jxclient.Interface, kubeclient.Interface, string, error) {
   302  	factory := jxfactory.NewFactory()
   303  
   304  	tektonClient, _, err := factory.CreateTektonClient()
   305  	if err != nil {
   306  		return nil, nil, nil, "", errors.Wrap(err, "unable to create Tekton client")
   307  	}
   308  
   309  	jxClient, _, err := factory.CreateJXClient()
   310  	if err != nil {
   311  		return nil, nil, nil, "", errors.Wrap(err, "unable to create JX client")
   312  	}
   313  
   314  	kubeClient, ns, err := factory.CreateKubeClient()
   315  	if err != nil {
   316  		return nil, nil, nil, "", errors.Wrap(err, "unable to create Kube client")
   317  	}
   318  	ns, _, err = kube.GetDevNamespace(kubeClient, ns)
   319  	if err != nil {
   320  		return nil, nil, nil, "", errors.Wrap(err, "unable to find the dev namespace")
   321  	}
   322  	return tektonClient, jxClient, kubeClient, ns, nil
   323  }