reference-architectures/cloud_deploy_flow/CloudFunctions/cloudDeployApprovals/main.go (108 lines of code) (raw):
package example
import (
// Importing necessary packages for the code
"context" // For managing request contexts
"encoding/json" // For JSON encoding and decoding
"fmt" // For formatted I/O
"log" // For logging messages
"strings" // For string manipulation
"time" // For time management
"cloud.google.com/go/deploy/apiv1/deploypb" // Google Cloud Deploy library for managing deployment processes
"cloud.google.com/go/pubsub" // Pub/Sub for messaging in Google Cloud
"github.com/GoogleCloudPlatform/functions-framework-go/functions" // Framework for creating Cloud Run Functions
"github.com/cloudevents/sdk-go/v2/event" // CloudEvents SDK for event handling
"github.com/codingconcepts/env" // For managing environment variables in Go
"google.golang.org/api/option"
)
// Struct for storing configuration data pulled from environment variables
type config struct {
// Environment variables for project ID, location, and topic ID, all required
ProjectId string `env:"PROJECTID" required:"true"`
Location string `env:"LOCATION" required:"true"`
SendTopicID string `env:"SENDTOPICID" required:"true"`
}
// Struct representing a Pub/Sub message payload
type PubsubMessage struct {
// Data contains the core message payload
Data []byte `json:"data"`
// Attributes store optional metadata as an ApprovalsData object
Attributes ApprovalsData `json:"attributes"`
// ID generated by server for tracking
MessageID string `json:"messageId"`
// PublishTime indicates when the message was published
PublishTime time.Time `json:"publishTime"`
// OrderingKey helps determine the order of message processing
OrderingKey string `json:"orderingKey"`
}
// Struct representing a complete Pub/Sub message
type Message struct {
Message PubsubMessage `json:"message"`
}
// Struct defining the expected structure of message attributes
type ApprovalsData struct {
Action string `json:"Action"` // Approval action (e.g., "Required")
Rollout string `json:"Rollout"` // Rollout ID
ReleaseId string `json:"ReleaseId"` // Release ID
RolloutId string `json:"RolloutId"` // Rollout ID
TargetId string `json:"TargetId"` // Target ID for deployment
Location string `json:"Location"` // Location for deployment
ProjectNumber string `json:"ProjectNumber"` // Project number
ManualApproval string `json:"manualApproval"` // Manual approval flag ("true"/"false")
}
// Struct for creating command messages for Pub/Sub
type CommandMessage struct {
Commmand string `json:"command"` // Command type, e.g., "ApproveRollout"
ApproveRollout deploypb.ApproveRolloutRequest `json:"approveRolloutRequest"` // Request details for approving rollout
}
// Global config variable for storing loaded environment variables
var c config
// Initialization function to register the Cloud Event and load environment variables
func init() {
functions.CloudEvent("cloudDeployApprovals", cloudDeployApprovals) // Registers the function as a CloudEvent handler
// Load environment variables into the config struct
if err := env.Set(&c); err != nil {
_ = fmt.Errorf("error getting env: %s", err) // Logs error if environment variables are missing
}
}
// Cloud Run Function to process deployment approval requests
func cloudDeployApprovals(ctx context.Context, e event.Event) error {
log.Printf("Deploy Approvals function invoked") // Logs function invocation
var msg Message // Struct to hold the decoded event data
err := json.Unmarshal(e.Data(), &msg) // Decodes the JSON event data into msg
if err != nil {
// If data is malformed, log the error and continue to avoid reprocessing
_ = fmt.Errorf("errored unmarshalling data: %v", err)
return nil
}
// Wait for 3 seconds as a demo delay before proceeding
log.Printf("Waiting 3 seconds to approve for demo")
time.Sleep(3 * time.Second)
// Extract the message attributes for easier access
var a = msg.Message.Attributes
log.Printf("A is: %v", a) // Logs the extracted attributes
// Check conditions: Action required, a valid Rollout ID, and manual approval
if a.Action == "Required" && a.Rollout != "" && strings.ToLower(a.ManualApproval) == "true" {
log.Printf("Creating Rollout and sending to pubsub") // Logs the action
// Constructs a command message to approve the rollout
var command = CommandMessage{
Commmand: "ApproveRollout",
ApproveRollout: deploypb.ApproveRolloutRequest{
Name: a.Rollout, // Rollout name as per received message
Approved: true, // Approves the rollout
},
}
// Sends the command to Pub/Sub
err = sendCommandPubSub(ctx, &command)
if err != nil {
_ = fmt.Errorf("failed to send pubsub command: %v", err) // Logs error if Pub/Sub message fails to send
return nil // Returns nil to acknowledge message even if there's an error
}
log.Printf("Deployment triggered successfully") // Logs success
}
return nil // Acknowledges successful message processing
}
// Function to publish messages to Pub/Sub
func sendCommandPubSub(ctx context.Context, m *CommandMessage) error {
// Initializes Pub/Sub client using the project ID from config
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) // Returns error if client creation fails
}
defer client.Close() // Ensures client is closed after message is sent
// Defines the target topic using config data
t := client.Topic(c.SendTopicID)
// Encodes the CommandMessage as JSON
jsonData, err := json.Marshal(m)
if err != nil {
return fmt.Errorf("json.Marshal: %v", err) // Returns error if JSON encoding fails
}
log.Printf("Sending message to PubSub") // Logs before publishing
// Publishes the message to the topic
result := t.Publish(ctx, &pubsub.Message{
Data: jsonData, // Publishes JSON data as message
})
// Waits for the publish result, logging ID or error if present
id, err := result.Get(ctx)
log.Printf("ID: %s, err: %v", id, err)
if err != nil {
fmt.Printf("Get: %v", err) // Logs any error on publish
return nil
}
log.Printf("Published a message; msg ID: %v\n", id) // Logs message ID if successful
return nil
}