reference-architectures/cloud_deploy_flow/CloudFunctions/createRelease/main.go (139 lines of code) (raw):

package example import ( "context" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" deploy "cloud.google.com/go/deploy/apiv1" "cloud.google.com/go/deploy/apiv1/deploypb" "cloud.google.com/go/pubsub" "github.com/GoogleCloudPlatform/functions-framework-go/functions" "github.com/cloudevents/sdk-go/v2/event" "github.com/codingconcepts/env" "google.golang.org/api/option" ) // Config struct for storing environment variables type config struct { // Project and pipeline configurations ProjectId string `env:"PROJECTID" required:"true"` Location string `env:"LOCATION" required:"true"` Pipeline string `env:"PIPELINE" required:"true"` TriggerID string `env:"TRIGGER" required:"true"` SendTopicID string `env:"SENDTOPICID" required:"true"` } // Global configuration variable var c config // Initializes environment variables and registers the deployTrigger function func init() { // Register CloudEvent function with a name for deployment functions.CloudEvent("deployTrigger", deployTrigger) // Load environment variables into config struct if err := env.Set(&c); err != nil { _ = fmt.Errorf("error getting env: %s", err) } } // Structs for parsing Pub/Sub messages type PubSubMessage struct { Data []byte `json:"data"` } type MessagePublishedData struct { Message PubSubMessage } // CommandMessage struct defines the message sent to Pub/Sub type CommandMessage struct { Commmand string `json:"command"` CreateRelease deploypb.CreateReleaseRequest `json:"createReleaseRequest"` } // Main deployTrigger function is called upon a Pub/Sub trigger func deployTrigger(ctx context.Context, e event.Event) error { log.Printf("Deploy trigger function invoked") // Parse the Pub/Sub message data into MessagePublishedData struct var msg MessagePublishedData if err := e.DataAs(&msg); err != nil { return fmt.Errorf("event.DataAs: %w", err) } // Unmarshal CloudBuild data into BuildMessage struct log.Printf("Converting Byte to Struct Object") var buildNotification BuildMessage if err := json.Unmarshal(msg.Message.Data, &buildNotification); err != nil { return fmt.Errorf("error parsing JIRA notification: %v", err) } // Check for specific build criteria (trigger ID and status) log.Printf("Checking if proper build") if buildNotification.BuildTriggerID != c.TriggerID || buildNotification.Status != "SUCCESS" { log.Printf("Build trigger ID or status does not match, returning early") // Return nil to indicate successful processing without further actions return nil } // Extract relevant image information from the build notification log.Printf("Pulling relevant image") image := buildNotification.Artifacts.Images[0] log.Printf("Received Image from Cloud Build: %s", image) // Create a Cloud Deploy client for further interactions deployClient, err := deploy.NewCloudDeployClient(ctx, option.WithUserAgent("cloud-solutions/platform-engineering-cloud-deploy-pipeline-code-v1"), ) if err != nil { return fmt.Errorf("error creating Cloud Deploy client: %v", err) } defer deployClient.Close() // Construct the name of the delivery pipeline from environment variables pipelineName := fmt.Sprintf("projects/%s/locations/%s/deliveryPipelines/%s", c.ProjectId, c.Location, c.Pipeline) // Retrieve the delivery pipeline information pipeline, err := deployClient.GetDeliveryPipeline(ctx, &deploypb.GetDeliveryPipelineRequest{ Name: pipelineName, }) if err != nil { return fmt.Errorf("error getting delivery pipeline: %v", err) } // Generate a unique release ID with a random suffix randomID, err := generateRandomID(6) // Generate a random ID of 6 bytes (12 hex characters) if err != nil { log.Fatalf("Error generating random ID: %v", err) } releaseID := fmt.Sprintf("release-%s", randomID) // Set the release ID // Define a new release request with the image and pipeline details var command = CommandMessage{ Commmand: "CreateRelease", CreateRelease: deploypb.CreateReleaseRequest{ Parent: pipeline.Name, ReleaseId: releaseID, Release: &deploypb.Release{ // Configure the release with image details BuildArtifacts: []*deploypb.BuildArtifact{ { Tag: image, // Set the container image Image: "pizza", // Placeholder for substitution in run.yaml }, }, // Skaffold config details for deployment SkaffoldConfigUri: fmt.Sprintf("%s/%s.tar.gz", buildNotification.Substitutions.DeployGCS, buildNotification.Substitutions.CommitSha, ), SkaffoldConfigPath: "skaffold.yaml", // Path to Skaffold config file }, }, } // Send the command message to Pub/Sub for deployment err = sendCommandPubSub(ctx, &command) if err != nil { return fmt.Errorf("failed to send pubsub command: %v", err) } log.Printf("Deployment triggered successfully") return nil } // generateRandomID creates a random hexadecimal ID of specified length func generateRandomID(length int) (string, error) { // Create a byte slice of specified length bytes := make([]byte, length) // Fill the byte slice with random data if _, err := rand.Read(bytes); err != nil { return "", err } // Return the hexadecimal representation of random bytes return hex.EncodeToString(bytes), nil } // sendCommandPubSub publishes a CommandMessage to a specified Pub/Sub topic to trigger further actions. func sendCommandPubSub(ctx context.Context, m *CommandMessage) error { // Create a new Pub/Sub client to publish messages. client, err := pubsub.NewClient(ctx, c.ProjectId, option.WithUserAgent("cloud-solutions/platform-engineering-cloud-deploy-pipeline-code-v1"), ) if err != nil { return fmt.Errorf("pubsub.NewClient: %v", err) } defer client.Close() // Ensure client is closed when done // Define the topic for message publication based on environment configuration. t := client.Topic(c.SendTopicID) // Marshal the CommandMessage into JSON format for the Pub/Sub message data payload. jsonData, err := json.Marshal(m) if err != nil { return fmt.Errorf("json.Marshal: %v", err) } log.Printf("Sending message to PubSub") // Publish the JSON data as a Pub/Sub message and wait for the message ID. result := t.Publish(ctx, &pubsub.Message{ Data: jsonData, // Serialized JSON command message }) // Block until the result returns the server-generated ID for the published message. id, err := result.Get(ctx) log.Printf("ID: %s, err: %v", id, err) if err != nil { fmt.Printf("Get: %v", err) return nil } log.Printf("Published a message; msg ID: %v\n", id) return nil }