package main import ( "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "log" "os" "os/exec" "path/filepath" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" ) type Config struct { RepoURL string RepoBranch string S3Bucket string S3Key string AWSRegion string } type Response struct { StatusCode int `json:"statusCode"` Headers map[string]string `json:"headers"` Body string `json:"body"` } var commandRunner = exec.Command // verifySignature computes an HMAC using the provided secret and compares it to the incoming signature. func verifySignature(secret, body, signatureHeader string) bool { // Assuming the header is in the format "sha256=" const prefix = "sha256=" if len(signatureHeader) < len(prefix) || signatureHeader[:len(prefix)] != prefix { return false } receivedSig := signatureHeader[len(prefix):] mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(body)) expectedSig := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(receivedSig), []byte(expectedSig)) } func handleRequest(ctx context.Context, event json.RawMessage) (Response, error) { // For demonstration, assume the event JSON includes a "body" and "headers" map. var req struct { Body string `json:"body"` Headers map[string]string `json:"headers"` } if err := json.Unmarshal(event, &req); err != nil { log.Printf("Error unmarshalling event: %v", err) return Response{StatusCode: 400, Headers: map[string]string{"Content-Type": "application/json"}, Body: "{\"message\":\"Bad Request\"}"}, err } secret := os.Getenv("WEBHOOK_SECRET") if secret == "" { log.Println("WEBHOOK_SECRET is not set") return Response{StatusCode: 500, Headers: map[string]string{"Content-Type": "application/json"}, Body: "{\"message\":\"Server configuration error\"}"}, fmt.Errorf("WEBHOOK_SECRET is not set") } signature := req.Headers["X-Hub-Signature-256"] // adjust this header name as appropriate. if signature == "" || !verifySignature(secret, req.Body, signature) { log.Println("Signature verification failed") return Response{StatusCode: 401, Headers: map[string]string{"Content-Type": "application/json"}, Body: "{\"message\":\"Unauthorized\"}"}, fmt.Errorf("signature verification failed") } // Call your existing process (for example, runDeploymentProcess) if err := runDeploymentProcess(ctx); err != nil { // Log the error; you may also report it via CloudWatch alarms log.Printf("Deployment process failed: %v", err) return Response{ StatusCode: 500, Headers: map[string]string{"Content-Type": "application/json"}, Body: fmt.Sprintf("{\"message\": \"Deployment process failed: %v\"}", err), }, err } return Response{ StatusCode: 200, Headers: map[string]string{"Content-Type": "application/json"}, Body: "{\"message\": \"Success\"}", }, nil } func main() { lambda.Start(handleRequest) } func runDeploymentProcess(ctx context.Context) error { cfg, err := loadConfig() if err != nil { log.Printf("Configuration error: %v", err) return err } // Create a unique temp directory for this run repoDir, err := os.MkdirTemp("", "repo-*") if err != nil { log.Printf("Error creating temporary directory: %v", err) return err } defer os.RemoveAll(repoDir) zipFilePath := filepath.Join(repoDir, "source.zip") // 1. Clone the repository if err := cloneRepository(ctx, cfg.RepoURL, cfg.RepoBranch, repoDir); err != nil { log.Printf("Failure in cloning: %v", err) return err } // 2. Create a ZIP archive of the repository if err := createZipArchive(ctx, repoDir, zipFilePath); err != nil { log.Printf("Failure in creating ZIP archive: %v", err) return err } // 3. Upload the ZIP file to S3 cfg_s3, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.AWSRegion)) if err != nil { log.Printf("Error loading configuration: %v", err) return err } s3Client := s3.NewFromConfig(cfg_s3) uploader := manager.NewUploader(s3Client) if err := uploadToS3WithUploader(ctx, zipFilePath, cfg.S3Bucket, cfg.S3Key, uploader); err != nil { log.Printf("Failure in uploading to S3: %v", err) return err } return nil } func loadConfig() (*Config, error) { repoURL := os.Getenv("REPO_URL") if repoURL == "" { return nil, fmt.Errorf("REPO_URL environment variable not set") } repoBranch := os.Getenv("REPO_BRANCH") if repoBranch == "" { repoBranch = "main" } s3Bucket := os.Getenv("S3_BUCKET") if s3Bucket == "" { return nil, fmt.Errorf("S3_BUCKET environment variable not set") } s3Key := os.Getenv("S3_KEY") if s3Key == "" { s3Key = "source.zip" } awsRegion := os.Getenv("AWS_REGION") if awsRegion == "" { awsRegion = "ap-northeast-1" } return &Config{ RepoURL: repoURL, RepoBranch: repoBranch, S3Bucket: s3Bucket, S3Key: s3Key, AWSRegion: awsRegion, }, nil } func cloneRepository(_ context.Context, repoURL, repoBranch, repoDir string) error { cloneCmd := commandRunner("git", "clone", "--branch", repoBranch, repoURL, repoDir) cloneCmd.Stdout = os.Stdout cloneCmd.Stderr = os.Stderr fmt.Printf("Cloning repository %s (branch %s)...\n", repoURL, repoBranch) if err := cloneCmd.Run(); err != nil { return fmt.Errorf("error cloning repository: %v", err) } fmt.Println("Repository cloned successfully.") return nil } func createZipArchive(_ context.Context, repoDir, zipFilePath string) error { zipCmd := commandRunner("zip", "-r", zipFilePath, ".") zipCmd.Dir = repoDir // Change to the cloned repo directory zipCmd.Stdout = os.Stdout zipCmd.Stderr = os.Stderr fmt.Println("Creating ZIP archive of the repository...") if err := zipCmd.Run(); err != nil { return fmt.Errorf("error creating ZIP archive: %v", err) } fmt.Printf("ZIP archive created at %s.\n", zipFilePath) return nil } type Uploader interface { Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*manager.Uploader)) (*manager.UploadOutput, error) } func uploadToS3WithUploader(ctx context.Context, zipPath, bucket, key string, uploader Uploader) error { // Open the ZIP file f, err := os.Open(zipPath) if err != nil { return fmt.Errorf("Error opening ZIP file: %v", err) } defer f.Close() // Upload the file to S3. fmt.Printf("Uploading %s to s3://%s/%s...\n", zipPath, bucket, key) result, err := uploader.Upload(ctx, &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), Body: f, }) if err != nil { return fmt.Errorf("failed to upload file: %v", err) } fmt.Printf("Successfully uploaded to %s\n", result.Location) return nil }