custom-targets/helm/helm-deployer/render.go (107 lines of code) (raw):

// Copyright 2023 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // https://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "fmt" "path" "time" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/cloud-deploy-samples/custom-targets/util/clouddeploy" ) const ( // Path to use when downloading the source input archive file. srcArchivePath = "/workspace/archive.tgz" // Path to use when unarchiving the source input. srcPath = "/workspace/source" // Name of the archive uploaded at render time that will be downloaded at deploy time. renderedArchiveName = "helm-archive.tgz" ) var ( // Default chart path used if not provided as a deploy parameter. defaultChartPath = path.Join(srcPath, "mychart") ) // renderer implements the requestHandler interface for render requests. type renderer struct { req *clouddeploy.RenderRequest params *params gcsClient *storage.Client } // process processes a render request and uploads succeeded or failed results to GCS for Cloud Deploy. func (r *renderer) process(ctx context.Context) error { fmt.Println("Processing render request") res, err := r.render(ctx) if err != nil { fmt.Printf("Render failed: %v\n", err) rr := &clouddeploy.RenderResult{ ResultStatus: clouddeploy.RenderFailed, FailureMessage: err.Error(), Metadata: map[string]string{ clouddeploy.CustomTargetSourceMetadataKey: helmDeployerSampleName, clouddeploy.CustomTargetSourceSHAMetadataKey: clouddeploy.GitCommit, }, } fmt.Println("Uploading failed render results") rURI, err := r.req.UploadResult(ctx, r.gcsClient, rr) if err != nil { return fmt.Errorf("error uploading failed render results: %v", err) } fmt.Printf("Uploaded failed render results to %s\n", rURI) return err } fmt.Println("Uploading render results") rURI, err := r.req.UploadResult(ctx, r.gcsClient, res) if err != nil { return fmt.Errorf("error uploading render results: %v", err) } fmt.Printf("Uploaded render results to %s\n", rURI) return nil } // render performs the following steps: // 1. Run helm template for the provided helm chart to produce a manifest // 2. Upload the manifest to GCS to use as the Cloud Deploy Release inspector artifact. // 3. Upload the archived helm configuration to GCS so it can be used at deploy time. // // Returns either the render results or an error if the render failed. func (r *renderer) render(ctx context.Context) (*clouddeploy.RenderResult, error) { fmt.Printf("Downloading render input archive to %s and unarchiving to %s\n", srcArchivePath, srcPath) inURI, err := r.req.DownloadAndUnarchiveInput(ctx, r.gcsClient, srcArchivePath, srcPath) if err != nil { return nil, fmt.Errorf("unable to download and unarchive render input: %v", err) } fmt.Printf("Downloaded render input archive from %s\n", inURI) // If template lookup or template validatation is enabled then connect to the cluster at render time. if r.params.templateLookup || r.params.templateValidate { fmt.Printf("Helm template lookup or validate enabled. Setting up cluster credentials for %s\n", r.params.gkeCluster) if _, err := gcloudClusterCredentials(r.params.gkeCluster); err != nil { return nil, fmt.Errorf("unable to set up cluster credentials: %v", err) } fmt.Printf("Finished setting up cluster credentials for %s\n", r.params.gkeCluster) } // Use the pipeline ID as the helm release since this should be consistent. helmRelease := r.req.Pipeline chartPath := determineChartPath(r.params) hOpts := helmOptions{namespace: r.params.namespace} templateOut, err := helmTemplate(helmRelease, chartPath, &helmTemplateOptions{helmOptions: hOpts, lookup: r.params.templateLookup, validate: r.params.templateValidate}) if err != nil { return nil, fmt.Errorf("error running helm template: %v", err) } tBytes, err := time.Now().MarshalText() if err != nil { return nil, fmt.Errorf("unable to marshal current time: %v", err) } // Add a comment at the top of the manifest indicating that it's not used at deploy time. manifest := []byte(fmt.Sprintf("# Manifest generated at %s by helm template.\n# This manifest is not used when performing the deploy, instead the same helm chart used to produce this manifest is provided to helm upgrade.\n", tBytes)) manifest = append(manifest, templateOut...) fmt.Println("Uploading manifest from helm template") mURI, err := r.req.UploadArtifact(ctx, r.gcsClient, "manifest.yaml", &clouddeploy.GCSUploadContent{Data: manifest}) if err != nil { return nil, fmt.Errorf("error uploading manifest: %v", err) } fmt.Printf("Uploaded manifest from helm template to %s\n", mURI) fmt.Println("Uploading archived helm configuration for use at deploy time") ahURI, err := r.req.UploadArtifact(ctx, r.gcsClient, renderedArchiveName, &clouddeploy.GCSUploadContent{LocalPath: srcArchivePath}) if err != nil { return nil, fmt.Errorf("error uploading archived helm configuration: %v", err) } fmt.Printf("Uploaded archived helm configuration to %s\n", ahURI) rr := &clouddeploy.RenderResult{ ResultStatus: clouddeploy.RenderSucceeded, ManifestFile: mURI, Metadata: map[string]string{ clouddeploy.CustomTargetSourceMetadataKey: helmDeployerSampleName, clouddeploy.CustomTargetSourceSHAMetadataKey: clouddeploy.GitCommit, }, } return rr, nil } // determineChartPath determines the path to the helm chart based on the deploy parameters provided. func determineChartPath(params *params) string { // If a path to the helm chart is provided then use it, otherwise default to "mychart" directory. chartPath := defaultChartPath if len(params.configPath) != 0 { chartPath = path.Join(srcPath, params.configPath) } return chartPath }