Skip to content

Commit

Permalink
Implemented deployment pipeline success webhook.
Browse files Browse the repository at this point in the history
  • Loading branch information
sflanker committed Jan 22, 2025
1 parent 76f77ee commit b416c03
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 26 deletions.
13 changes: 8 additions & 5 deletions cmd/portage/cli/v0/pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,9 @@ func newRunCommand() *cobra.Command {
_ = viper.BindPFlag("codescan.coveragefile", codeScanCmd.Flags().Lookup("coverage-file"))

// run deploy
deployCmd := newBasicCommand("deploy", "Beta Feature: VALIDATION ONLY - run gatecheck validate on artifacts from previous pipelines", runDeploy)
deployCmd := newBasicCommand("deploy", "Beta Feature: VALIDATION ONLY - run gatecheck validate on artifacts from previous pipelines", runDeployExplicit)
deployCmd.Flags().String("gatecheck-config", "", "gatecheck configuration file")
deployCmd.Flags().Bool("submit", false, "submit artifacts to configured API endpoint")
_ = viper.BindPFlag("deploy.gatecheckconfigfilename", deployCmd.Flags().Lookup("gatecheck-config"))
_ = viper.BindPFlag("deploy.submit", deployCmd.Flags().Lookup("submit"))

// run image-delivery

Expand Down Expand Up @@ -135,12 +133,17 @@ func runDebug(cmd *cobra.Command, _ []string) error {
return debugPipeline(cmd.OutOrStdout(), cmd.ErrOrStderr(), dryRunEnabled)
}

func runDeploy(cmd *cobra.Command, _ []string) error {
func runDeployExplicit(cmd *cobra.Command, args []string) error {
return runDeploy(cmd, args, true)
}

func runDeploy(cmd *cobra.Command, _ []string, force bool) error {
dryRunEnabled, _ := cmd.Flags().GetBool("dry-run")
config := new(pipelines.Config)
if err := viper.Unmarshal(config); err != nil {
return err
}
config.Deploy.Enabled = true
return deployPipeline(cmd.OutOrStdout(), cmd.ErrOrStderr(), config, dryRunEnabled)
}

Expand Down Expand Up @@ -211,7 +214,7 @@ func runAll(cmd *cobra.Command, args []string) error {
return err
}

return runDeploy(cmd, args)
return runDeploy(cmd, args, false)
}

// Execution functions - Logic for command execution
Expand Down
11 changes: 8 additions & 3 deletions pkg/pipelines/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ type Config struct {
Deploy configDeploy `mapstructure:"deploy"`
}

type webhookConfig struct {
Url string `mapstructure:"url"`
AuthorizationVar string `mapstructure:"authorizationVar"`
}

type configImageBuild struct {
Enabled bool `mapstructure:"enabled"`
BuildDir string `mapstructure:"buildDir"`
Expand Down Expand Up @@ -65,9 +70,9 @@ type configImagePublish struct {
}

type configDeploy struct {
Enabled bool `mapstructure:"enabled"`
GatecheckConfigFilename string `mapstructure:"gatecheckConfigFilename"`
Submit bool `mapstructure:"submit"`
Enabled bool `mapstructure:"enabled"`
GatecheckConfigFilename string `mapstructure:"gatecheckConfigFilename"`
SuccessWebhooks []webhookConfig `mapstructure:"successWebhooks"`
}

// metaConfigField is used to map viper values to env variables and their associated default values
Expand Down
78 changes: 67 additions & 11 deletions pkg/pipelines/deploy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package pipelines

import (
"bytes"
_ "embed"
"errors"
"fmt"
"io"
"log/slog"
"mime/multipart"
"net/http"
"os"
"path"
"path/filepath"
"portage/pkg/shell"

"github.com/jarxorg/tree"
Expand Down Expand Up @@ -54,6 +58,7 @@ func (p *Deploy) Run() error {
slog.Warn("deployment pipeline disabled, skip.")
return nil
}

if err := p.preRun(); err != nil {
return errors.New("deploy Pipeline failed, pre-run error. See logs for details")
}
Expand Down Expand Up @@ -121,22 +126,73 @@ func (p *Deploy) Run() error {
return mkDeploymentError(err)
}

if p.config.Deploy.Submit {
configPath := gatecheckConfigPath
if p.config.Deploy.GatecheckConfigFilename != "" {
configPath = p.config.Deploy.GatecheckConfigFilename
for i, hook := range p.config.Deploy.SuccessWebhooks {
slog.Debug("submitting deployment success webhook", "webhook", hook, "index", i)

// Send a POST request with the bundle file in a multipart form
bundleFile, err := os.Open(p.runtime.bundleFilename)
if err != nil {
slog.Error("failed to open bundle file", "error", err)
return mkDeploymentError(err)
}
defer bundleFile.Close()

var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)

writer.WriteField("action", "deploy")
writer.WriteField("status", "success")

bundleFilePart, err := writer.CreateFormFile("bundle", filepath.Base(p.runtime.bundleFilename))
if err != nil {
slog.Error("failed to create form file", "error", err)
return mkDeploymentError(err)
}

_, err = io.Copy(bundleFilePart, bundleFile)
if err != nil {
slog.Error("failed to copy file content to form part", "error", err)
return mkDeploymentError(err)
}

err = writer.Close()
if err != nil {
slog.Error("failed to close multipart writer", "error", err)
return mkDeploymentError(err)
}

req, err := http.NewRequest("POST", hook.Url, &requestBody)
if err != nil {
slog.Error("failed to create HTTP request", "error", err)
return mkDeploymentError(err)
}

// Set the Content-Type header to the multipart writer's content type
req.Header.Set("Content-Type", writer.FormDataContentType())

if hook.AuthorizationVar != "" {
authValue := os.Getenv(hook.AuthorizationVar)
if authValue != "" {
req.Header.Set("Authorization", authValue)
} else {
slog.Warn("authorization environment variable is empty", "envVar", hook.AuthorizationVar)
}
}

err = shell.GatecheckSubmit(
shell.WithDryRun(p.DryRunEnabled),
shell.WithStderr(p.Stderr),
shell.WithStdout(p.Stdout),
shell.WithTargetFile(p.runtime.bundleFilename),
shell.WithConfigFile(configPath),
)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
slog.Error("failed to execute HTTP request", "error", err)
return mkDeploymentError(err)
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
slog.Error("webhook returned non-success status", "status", resp.StatusCode)
return fmt.Errorf("webhook request failed with status: %d", resp.StatusCode)
}

slog.Info("successfully submitted deployment success webhook", "webhook", hook)
}

return nil
Expand Down
7 changes: 0 additions & 7 deletions pkg/shell/gatecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,3 @@ func GatecheckValidate(options ...OptionFunc) error {
cmd := exec.Command("gatecheck", args...)
return run(cmd, o)
}

func GatecheckSubmit(options ...OptionFunc) error {
o := newOptions(options...)
// TODO: remove verbose once we are at MVP
cmd := exec.Command("gatecheck", "submit", "--config", o.configFilename, o.targetFilename, "--verbose")
return run(cmd, o)
}

0 comments on commit b416c03

Please sign in to comment.