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, ¶meters.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 }