From f55683f7ac50a08f7bd68c11374b98ae2348082c Mon Sep 17 00:00:00 2001 From: Matthew Plachter Date: Wed, 16 Aug 2023 14:31:51 -0400 Subject: [PATCH] Add Basic OTEL Tracing to TFBuddy (#26) * add otel tracing to tfbuddy Signed-off-by: Matt Plachter * change over from fmt to log Signed-off-by: Matt Plachter * fix trace names Signed-off-by: Matt Plachter --------- Signed-off-by: Matt Plachter --- Earthfile | 2 +- Tiltfile | 5 +- cmd/root.go | 29 ++- cmd/tfc_hook_handler.go | 16 +- cmd/tfc_lock.go | 4 +- cmd/tfc_unlock.go | 4 +- go.mod | 45 ++-- go.sum | 160 ++++++++------ internal/telemetry/helpers.go | 33 +++ internal/telemetry/telemetry.go | 207 +++++++++++++++++++ localdev/Dockerfile.dlv | 4 +- localdev/manifests/deployment.yaml | 8 + localdev/ngrok/job.yaml | 16 ++ main.go | 8 +- manifests/base/deployment.yaml | 8 + pkg/const.go | 6 + pkg/gitlab_hooks/comment_actions.go | 65 ++++-- pkg/gitlab_hooks/comment_actions_test.go | 34 +-- pkg/gitlab_hooks/handler.go | 38 +++- pkg/gitlab_hooks/hook_worker.go | 6 +- pkg/gitlab_hooks/mr_actions.go | 13 +- pkg/gitlab_hooks/stream_msgs.go | 67 ++++-- pkg/hooks/server.go | 5 + pkg/mocks/helpers.go | 10 +- pkg/mocks/mock_runstream.go | 89 ++++++-- pkg/mocks/mock_tfc_api.go | 16 +- pkg/mocks/mock_tfc_trigger.go | 17 +- pkg/mocks/mock_vcs.go | 99 ++++----- pkg/runstream/interfaces.go | 16 +- pkg/runstream/run_event.go | 29 ++- pkg/runstream/run_metadata.go | 3 +- pkg/runstream/run_polling.go | 55 +++-- pkg/tfc_api/api_client.go | 40 +++- pkg/tfc_api/api_driven_runs.go | 7 +- pkg/tfc_hooks/notification.go | 18 +- pkg/tfc_hooks/polling.go | 16 +- pkg/tfc_trigger/interfaces.go | 6 +- pkg/tfc_trigger/project_config.go | 9 +- pkg/tfc_trigger/tfc_trigger.go | 124 +++++++---- pkg/tfc_trigger/tfc_trigger_test.go | 70 ++++--- pkg/tfc_utils/ci_job_lock.go | 6 +- pkg/tfc_utils/ci_job_run_status.go | 29 +-- pkg/tfc_utils/ci_job_runner.go | 3 +- pkg/vcs/github/client.go | 94 ++++++--- pkg/vcs/github/hooks/github_hooks_handler.go | 19 +- pkg/vcs/github/hooks/stream_types.go | 57 +++-- pkg/vcs/github/hooks/stream_worker.go | 35 ++-- pkg/vcs/github/run_events_worker.go | 15 +- pkg/vcs/gitlab/client.go | 75 +++++-- pkg/vcs/gitlab/git_actions.go | 19 +- pkg/vcs/gitlab/mr_comment_updater.go | 34 ++- pkg/vcs/gitlab/mr_status_updater.go | 49 +++-- pkg/vcs/gitlab/run_status_updater.go | 10 +- pkg/vcs/interfaces.go | 28 +-- 54 files changed, 1366 insertions(+), 514 deletions(-) create mode 100644 internal/telemetry/helpers.go create mode 100644 internal/telemetry/telemetry.go create mode 100644 localdev/ngrok/job.yaml create mode 100644 pkg/const.go diff --git a/Earthfile b/Earthfile index a7ba18f..bcdbb54 100644 --- a/Earthfile +++ b/Earthfile @@ -48,7 +48,7 @@ build-binary: WORKDIR /src COPY . /src - RUN GOARM=${VARIANT#v} go build -ldflags "-X main.GitCommit=$GIT_COMMIT -X main.GitTag=$GIT_TAG" -o tfbuddy + RUN GOARM=${VARIANT#v} go build -ldflags "-X github.com/zapier/tfbuddy/pkg.GitCommit=$GIT_COMMIT -X github.com/zapier/tfbuddy/pkg.GitTag=$GIT_TAG" -o tfbuddy SAVE ARTIFACT tfbuddy build-docker: diff --git a/Tiltfile b/Tiltfile index c9df878..048cbd0 100644 --- a/Tiltfile +++ b/Tiltfile @@ -180,10 +180,11 @@ test_go( labels=["tfbuddy"] ) -build_cmd='CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags="all=-N -l" -o build/tfbuddy ./' +arch="arm64" if str(local("uname -m")).strip('\n') == "arm64" else "amd64" +build_cmd='CGO_ENABLED=0 GOOS=linux GOARCH={} go build -gcflags="all=-N -l" -o build/tfbuddy ./' local_resource( 'go-build', - build_cmd, + build_cmd.format(arch), deps=[ './main.go', './go.mod', diff --git a/cmd/root.go b/cmd/root.go index f0e01e9..d09c5f3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,13 +1,19 @@ package cmd import ( + "context" "fmt" + "log" "os" + "strconv" + "strings" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/zapier/tfbuddy/internal/logging" + "github.com/zapier/tfbuddy/internal/telemetry" + "github.com/zapier/tfbuddy/pkg" "github.com/spf13/viper" ) @@ -33,7 +39,7 @@ func resolveLogLevel() zerolog.Level { lvl, err := zerolog.ParseLevel(logLevel) if err != nil { - fmt.Println("could not parse log level, defaulting to 'info'") + log.Println("could not parse log level, defaulting to 'info'") lvl = zerolog.InfoLevel } return lvl @@ -51,6 +57,25 @@ func init() { rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "v", "info", "Set the log output level (info, debug, trace)") } +func initTelemetry(ctx context.Context) (*telemetry.OperatorTelemetry, error) { + enableOtel, _ := strconv.ParseBool(os.Getenv("TFBUDDY_OTEL_ENABLED")) + otelHost := os.Getenv("TFBUDDY_OTEL_COLLECTOR_HOST") + otelPort := os.Getenv("TFBUDDY_OTEL_COLLECTOR_PORT") + + opts := telemetry.Options{ + Enabled: enableOtel, + Host: otelHost, + Port: otelPort, + Version: pkg.GitTag, + CommitSHA: pkg.GitCommit, + } + log.Printf("enabled: %v\thost: %s\tport: %s\n", enableOtel, otelHost, otelPort) + + log.Printf("OpenTelemetry Opts: %+v\n", opts) + + return telemetry.Init(ctx, "tfbuddy", opts) +} + // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { @@ -67,6 +92,8 @@ func initConfig() { viper.SetConfigName(".tfbuddy") } + viper.SetEnvPrefix("TFBUDDY") + viper.EnvKeyReplacer(strings.NewReplacer("-", "_")) viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. diff --git a/cmd/tfc_hook_handler.go b/cmd/tfc_hook_handler.go index 888c855..54dc23a 100644 --- a/cmd/tfc_hook_handler.go +++ b/cmd/tfc_hook_handler.go @@ -1,13 +1,17 @@ package cmd import ( + "context" + "github.com/spf13/cobra" - "github.com/zapier/tfbuddy/internal/logging" "github.com/zapier/tfbuddy/pkg/hooks" ) var gitlabToken string +var otelEnabled bool +var otelCollectorHost string +var otelCollectorPort string // tfcHookHandlerCmd represents the run command var tfcHookHandlerCmd = &cobra.Command{ @@ -15,8 +19,16 @@ var tfcHookHandlerCmd = &cobra.Command{ Short: "Start a hooks handler for Gitlab & Terraform cloud.", Long: ``, Run: func(cmd *cobra.Command, args []string) { - logging.SetupLogOutput(resolveLogLevel()) + ctx := context.Background() + + t, err := initTelemetry(ctx) + if err != nil { + panic(err) + } + defer t.Shutdown() + hooks.StartServer() + }, PreRunE: func(cmd *cobra.Command, args []string) error { return nil diff --git a/cmd/tfc_lock.go b/cmd/tfc_lock.go index bdfe6c1..526f896 100644 --- a/cmd/tfc_lock.go +++ b/cmd/tfc_lock.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "os" @@ -14,7 +15,8 @@ var tfcLockCmd = &cobra.Command{ Short: "Lock a Terraform workspace.", Long: ``, Run: func(cmd *cobra.Command, args []string) { - tfc_utils.LockUnlockWorkspace(tfcToken, tfcWorkspace, true, "Locked from MR") + ctx := context.Background() + tfc_utils.LockUnlockWorkspace(ctx, tfcToken, tfcWorkspace, true, "Locked from MR") }, PreRunE: func(cmd *cobra.Command, args []string) error { if tfcWorkspace == "" { diff --git a/cmd/tfc_unlock.go b/cmd/tfc_unlock.go index f99a318..b9d9e30 100644 --- a/cmd/tfc_unlock.go +++ b/cmd/tfc_unlock.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "fmt" "os" @@ -14,7 +15,8 @@ var tfcUnlockCmd = &cobra.Command{ Short: "Unlock a Terraform workspace.", Long: ``, Run: func(cmd *cobra.Command, args []string) { - tfc_utils.LockUnlockWorkspace(tfcToken, tfcWorkspace, false, "") + ctx := context.Background() + tfc_utils.LockUnlockWorkspace(ctx, tfcToken, tfcWorkspace, false, "") }, PreRunE: func(cmd *cobra.Command, args []string) error { if tfcWorkspace == "" { diff --git a/go.mod b/go.mod index 54f7b41..e5e375b 100644 --- a/go.mod +++ b/go.mod @@ -18,19 +18,29 @@ require ( github.com/jessevdk/go-flags v1.5.0 github.com/kr/pretty v0.3.1 github.com/labstack/echo-contrib v0.13.0 - github.com/labstack/echo/v4 v4.9.1 + github.com/labstack/echo/v4 v4.10.2 github.com/nats-io/nats-server/v2 v2.9.8 - github.com/nats-io/nats.go v1.21.0 + github.com/nats-io/nats.go v1.25.0 github.com/prometheus/client_golang v1.14.0 github.com/rs/zerolog v1.28.0 github.com/rzajac/zltest v0.12.0 - github.com/sl1pm4t/gongs v0.0.0-20221205005205-6f4e6d147fab + github.com/sl1pm4t/gongs v0.0.0-20230501190600-06976a7fac23 github.com/spf13/cobra v1.6.1 - github.com/spf13/viper v1.14.0 + github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.4 github.com/xanzy/go-gitlab v0.77.0 github.com/ziflex/lecho/v3 v3.3.0 - golang.org/x/oauth2 v0.3.0 + go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.40.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0 + go.opentelemetry.io/otel v1.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 + go.opentelemetry.io/otel/metric v0.37.0 + go.opentelemetry.io/otel/sdk v1.14.0 + go.opentelemetry.io/otel/sdk/metric v0.37.0 + go.opentelemetry.io/otel/trace v1.14.0 + golang.org/x/oauth2 v0.4.0 + google.golang.org/grpc v1.53.0 gopkg.in/dealancer/validate.v2 v2.1.0 gopkg.in/errgo.v2 v2.1.0 gopkg.in/yaml.v2 v2.4.0 @@ -48,9 +58,12 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/go-slug v0.11.1 // indirect @@ -67,14 +80,13 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nats-io/jwt/v2 v2.3.0 // indirect - github.com/nats-io/nkeys v0.3.0 // indirect + github.com/nats-io/nkeys v0.4.4 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -88,20 +100,25 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zclconf/go-cty v1.13.2 // indirect - golang.org/x/crypto v0.5.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + golang.org/x/crypto v0.8.0 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.5.0 // indirect + golang.org/x/net v0.9.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.5.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 55db58b..b99e850 100644 --- a/go.sum +++ b/go.sum @@ -41,16 +41,15 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230201104953-d1d05f4e2bfb h1:Vx1Bw/nGULx+FuY7Sw+8ZDpOx9XOdA+mOfo678SqkbU= github.com/ProtonMail/go-crypto v0.0.0-20230201104953-d1d05f4e2bfb/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= @@ -66,6 +65,8 @@ github.com/cbrgm/githubevents v1.7.0/go.mod h1:hmV8xPmd//BWSRXJNUBkT/fwRkJpeC1e6 github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -78,6 +79,11 @@ github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSb github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -94,11 +100,14 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -114,11 +123,17 @@ github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4B github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -131,7 +146,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -161,7 +175,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v49 v49.0.0 h1:vSz1fnOeGztFxDe48q0RCOrd8Cg4o8INcZBPVpGPNaY= @@ -186,25 +200,20 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-slug v0.10.1 h1:05SCRWCBpCxOeP7stQHvMgOz0raCBCekaytu8Rg/RZ4= -github.com/hashicorp/go-slug v0.10.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= github.com/hashicorp/go-slug v0.11.1 h1:c6lLdQnlhUWbS5I7hw8SvfymoFuy6EmiFDedy6ir994= github.com/hashicorp/go-slug v0.11.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= -github.com/hashicorp/go-tfe v1.16.0 h1:B4yEfNNHuCiBjXXci+UiE5MsScAM+pfXwDXhBdNmOOg= -github.com/hashicorp/go-tfe v1.16.0/go.mod h1:77snluBqtTTvMrY0w/mxQA5jlHQ8NT44AqQ8UdrPf0o= github.com/hashicorp/go-tfe v1.30.0 h1:vEieLxZ0Xly4+njypVwHH0RcUip7za1p6Pw52iqLOAY= github.com/hashicorp/go-tfe v1.30.0/go.mod h1:z0182DGE/63AKUaWblUVBIrt+xdSmsuuXg5AoxGqDF4= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -213,8 +222,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0= github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= -github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= -github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA= github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb h1:tsEKRC3PU9rMw18w/uAptoijhgG4EvlA5kfJPtwrMDk= @@ -230,11 +237,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -249,11 +253,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/labstack/echo-contrib v0.13.0 h1:bzSG0SpuZZd7BmJLvsWtPfU23W0Enh3K0tok3aENVKA= github.com/labstack/echo-contrib v0.13.0/go.mod h1:IF9+MJu22ADOZEHD+bAV67XMIO3vNXUy7Naz/ABPHEs= -github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= -github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= @@ -268,32 +271,27 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/nats-server/v2 v2.9.8 h1:jgxZsv+A3Reb3MgwxaINcNq/za8xZInKhDg9Q0cGN1o= github.com/nats-io/nats-server/v2 v2.9.8/go.mod h1:AB6hAnGZDlYfqb7CTAm66ZKMZy9DpfierY1/PbpvI2g= -github.com/nats-io/nats.go v1.21.0 h1:kQiWyQMMMIPjDR7NanrLhTnRUxWgU04yrzmYdq9JxCU= -github.com/nats-io/nats.go v1.21.0/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA= -github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE= +github.com/nats-io/nats.go v1.25.0/go.mod h1:D2WALIhz7V8M0pH8Scx8JZXlg6Oqz5VG+nQkK8nJdvg= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= @@ -313,6 +311,7 @@ github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8u github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -324,15 +323,15 @@ github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6us github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rzajac/zltest v0.12.0 h1:9WPX0UhhXG66iuRT9+jYSl9SAGyl+PmKsC4+UGKLJVU= github.com/rzajac/zltest v0.12.0/go.mod h1:wZSsCw1RyFaEIfUOCUw8DiicX4U6yB1IZdOg8Uj0yDI= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= -github.com/sl1pm4t/gongs v0.0.0-20221205005205-6f4e6d147fab h1:3L36gw7ypx0vJzAr7N9RygLuAtbgAujXiSLpV4sj+0o= -github.com/sl1pm4t/gongs v0.0.0-20221205005205-6f4e6d147fab/go.mod h1:D/23VJHsiC8ig5Nj1PgmEnmk0nZlMbkondiK9e4vlp4= +github.com/sl1pm4t/gongs v0.0.0-20230501190600-06976a7fac23 h1:nq323lhNbO2fNvuEMFDP+MXrYUcvvNi2Yvq3Q3V6lCA= +github.com/sl1pm4t/gongs v0.0.0-20230501190600-06976a7fac23/go.mod h1:D/23VJHsiC8ig5Nj1PgmEnmk0nZlMbkondiK9e4vlp4= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -343,8 +342,8 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -357,20 +356,16 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/go-gitlab v0.77.0 h1:UrbGlxkWVCbkpa6Fk6cM8ARh+rLACWemkJnsawT7t98= github.com/xanzy/go-gitlab v0.77.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -381,13 +376,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= -github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/ziflex/lecho/v3 v3.3.0 h1:Z6KnMf0ubJX93W8Np37DBIZalFubYDq0a92hv3S/9CY= github.com/ziflex/lecho/v3 v3.3.0/go.mod h1:VyOQDbC51eP3iJ4NdcyQbhmTqUZiapn7zJ3oHknCmXU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -396,6 +386,35 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.40.0 h1:uieC4MjrlpHeEtapTOpix710ykBLqXQqZqN6DpnvOvw= +go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.40.0/go.mod h1:9jOfbttH75jtbNJR3xGi1Bs+gN9IcQzj2iDqN9zi8Jg= +go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0 h1:Qf1GuR3QFxTNqDhfuw9XuJMkOOyRUwWP9NdFakk3RXM= +go.opentelemetry.io/contrib/instrumentation/runtime v0.40.0/go.mod h1:zmll4G8j5zRZeFURG6t/N7SOl7M5kUHQfV5UVqTaQFI= +go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0 h1:22J9c9mxNAZugv86zhwjBnER0DbO0VVpW9Oo/j3jBBQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.37.0/go.mod h1:QD8SSO9fgtBOvXYpcX5NXW+YnDJByTnh7a/9enQWFmw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0 h1:CI6DSdsSkJxX1rsfPSQ0SciKx6klhdDRBXqKb+FwXG8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.37.0/go.mod h1:WLBYPrz8srktckhCjFaau4VHSfGaMuqoKSXwpzaiRZg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM= +go.opentelemetry.io/otel/metric v0.37.0 h1:pHDQuLQOZwYD+Km0eb657A25NaRzy0a+eLyKfDXedEs= +go.opentelemetry.io/otel/metric v0.37.0/go.mod h1:DmdaHfGt54iV6UKxsV9slj2bBRJcKC1B1uvDLIioc1s= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= +go.opentelemetry.io/otel/sdk/metric v0.37.0 h1:haYBBtZZxiI3ROwSmkZnI+d0+AVzBWeviuYQDeBWosU= +go.opentelemetry.io/otel/sdk/metric v0.37.0/go.mod h1:mO2WV1AZKKwhwHTV3AKOoIEb9LbUaENZDuGUQd+j4A0= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -409,8 +428,8 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -449,7 +468,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -485,8 +503,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -496,8 +514,9 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -510,8 +529,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -568,13 +585,13 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -585,8 +602,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -642,8 +659,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -698,6 +715,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -711,6 +729,9 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -724,9 +745,15 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -739,11 +766,11 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -759,6 +786,7 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/telemetry/helpers.go b/internal/telemetry/helpers.go new file mode 100644 index 0000000..32fc7cd --- /dev/null +++ b/internal/telemetry/helpers.go @@ -0,0 +1,33 @@ +package telemetry + +import ( + "context" + + "go.opentelemetry.io/otel/trace" +) + +type otelSpanInfo struct { + spanID trace.SpanID + traceID trace.TraceID +} + +func GetOtelSpanInfoFromContext(ctx context.Context) otelSpanInfo { + s := trace.SpanFromContext(ctx) + + return otelSpanInfo{ + spanID: s.SpanContext().SpanID(), + traceID: s.SpanContext().TraceID(), + } +} + +func (o otelSpanInfo) SpanIDValid() bool { + return o.spanID.IsValid() +} + +func (o otelSpanInfo) SpanID() string { + return o.spanID.String() +} + +func (o otelSpanInfo) TraceID() string { + return o.traceID.String() +} diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go new file mode 100644 index 0000000..9250ea1 --- /dev/null +++ b/internal/telemetry/telemetry.go @@ -0,0 +1,207 @@ +package telemetry + +import ( + "context" + "fmt" + "time" + + "github.com/rs/zerolog/log" + + "go.opentelemetry.io/contrib/instrumentation/runtime" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + mGlobal "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +const DefaultMetricInterval = 2 + +type BaseTelemetry struct { + c context.Context + traceProvider *sdktrace.TracerProvider + metric *metric.MeterProvider +} + +func (bt *BaseTelemetry) Shutdown() { + if bt.traceProvider != nil { + _ = bt.traceProvider.Shutdown(bt.c) + } + if bt.metric != nil { + _ = bt.metric.Shutdown(bt.c) + } +} + +type OperatorTelemetry struct { + *BaseTelemetry +} + +func (ot *OperatorTelemetry) StartMetricCollectors() error { + log.Debug().Msg("Starting runtime instrumentation") + err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second * DefaultMetricInterval)) + if err != nil { + log.Error().Err(err).Msg("runtime instrumentation failure") + return err + } + return nil +} + +type Options struct { + Enabled bool + Host string + Port string + Version string + CommitSHA string +} + +func Init(ctx context.Context, serviceName string, opts Options) (*OperatorTelemetry, error) { + log.Info().Msg("Initializing telemetry") + bt := &OperatorTelemetry{ + BaseTelemetry: &BaseTelemetry{ + c: ctx, + }} + + res, err := resource.New(ctx, + resource.WithAttributes( + semconv.ServiceNameKey.String(serviceName), + semconv.ServiceVersionKey.String(opts.Version), + attribute.String("SHA", opts.CommitSHA), + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + err = bt.initProviders(res, opts.Enabled, opts.Host, opts.Port) + if err != nil { + return bt, err + } + + // setup propagator + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + + return bt, nil +} + +func createGRPCConn(ctx context.Context, enabled bool, otelHost string, otelPort string) (*grpc.ClientConn, error) { + if !enabled { + log.Info().Msg("otel disabled") + return nil, nil + } + + conn, err := grpc.DialContext(ctx, + fmt.Sprintf("%s:%s", otelHost, otelPort), + grpc.WithTransportCredentials( + insecure.NewCredentials(), + ), + ) + if err != nil { + log.Error().Err(err).Msg("unable to dial grpc") + return conn, err + } + + log.Debug().Str("host", otelHost).Str("port", otelPort).Msg("grpc conn created") + + return conn, err +} + +func (bt *BaseTelemetry) initProviders(res *resource.Resource, enabled bool, otelHost string, otelPort string) error { + conn, err := createGRPCConn(bt.c, enabled, otelHost, otelPort) + if err != nil { + return err + } + + err = bt.initTrace(conn, res) + if err != nil { + return err + } + + err = bt.initMetric(conn, res) + if err != nil { + return err + } + + return nil +} + +func (bt *BaseTelemetry) initTrace(conn *grpc.ClientConn, res *resource.Resource) error { + if conn == nil { + return nil + } + + err := bt.initOTLPTrace(conn, res) + if err != nil { + log.Error().Err(err).Msg("unable to init tracer") + } + log.Debug().Msg("tracer initialized") + return nil +} + +func (bt *BaseTelemetry) initMetric(conn *grpc.ClientConn, res *resource.Resource) error { + if conn == nil { + return nil + } + + err := bt.initOTLPMetric(conn, res) + if err != nil { + log.Error().Err(err).Msg("unable to init metrics") + return err + } + log.Debug().Msg("metric provider initialized") + return nil +} + +func (bt *BaseTelemetry) initOTLPTrace(conn *grpc.ClientConn, res *resource.Resource) error { + // tracer grpc client + traceExporter, err := otlptracegrpc.New(bt.c, + otlptracegrpc.WithInsecure(), + otlptracegrpc.WithGRPCConn(conn), + ) + if err != nil { + return err + } + + spanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter) + bt.traceProvider = sdktrace.NewTracerProvider( + // allow sampling to be configurable + // sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(res), + sdktrace.WithSpanProcessor(spanProcessor), + ) + + otel.SetTracerProvider(bt.traceProvider) + + return nil +} + +func (bt *BaseTelemetry) initOTLPMetric(conn *grpc.ClientConn, res *resource.Resource) error { + mClient, err := otlpmetricgrpc.New( + bt.c, + otlpmetricgrpc.WithInsecure(), + otlpmetricgrpc.WithGRPCConn(conn), + ) + if err != nil { + return fmt.Errorf("failed to create metric client: %w", err) + } + + otelReader := metric.NewPeriodicReader( + mClient, + metric.WithInterval(DefaultMetricInterval*time.Second), + ) + + bt.metric = metric.NewMeterProvider( + metric.WithResource(res), + metric.WithReader(otelReader), + ) + + mGlobal.SetMeterProvider(bt.metric) + + return nil +} diff --git a/localdev/Dockerfile.dlv b/localdev/Dockerfile.dlv index 3a614e0..1a7d96e 100644 --- a/localdev/Dockerfile.dlv +++ b/localdev/Dockerfile.dlv @@ -1,4 +1,4 @@ -FROM golang:1.19 as localdev +FROM golang:1.19.8 as localdev RUN apt update && apt install -y ca-certificates RUN go install github.com/go-delve/delve/cmd/dlv@latest @@ -6,4 +6,4 @@ RUN go install github.com/go-delve/delve/cmd/dlv@latest WORKDIR /app COPY build/tfbuddy /app/tfbuddy -CMD dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec /app/tfbuddy tfc handler +CMD dlv --listen=:2345 --api-version=2 --headless=true exec /app/tfbuddy tfc handler diff --git a/localdev/manifests/deployment.yaml b/localdev/manifests/deployment.yaml index 2ba0315..165bac0 100644 --- a/localdev/manifests/deployment.yaml +++ b/localdev/manifests/deployment.yaml @@ -23,6 +23,14 @@ spec: env: - name: TFBUDDY_NATS_SERVICE_URL value: nats://nats:4222 + - name: TFBUDDY_OTEL_ENABLED + value: "true" + - name: TFBUDDY_OTEL_COLLECTOR_PORT + value: "4317" + - name: TFBUDDY_OTEL_COLLECTOR_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP envFrom: - configMapRef: name: tfbuddy-config diff --git a/localdev/ngrok/job.yaml b/localdev/ngrok/job.yaml new file mode 100644 index 0000000..ab1943d --- /dev/null +++ b/localdev/ngrok/job.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: wait-ngrok-url +spec: + template: + spec: + containers: + - name: curl + image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest + command: [ + "/bin/bash", "-c", + "curl http://ngrok:4040/api/tunnels | jq --raw-output '.tunnels[0].public_url' | grep ngrok" + ] + restartPolicy: OnFailure + backoffLimit: 10 diff --git a/main.go b/main.go index 0777e93..d9cc64b 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,10 @@ import ( "fmt" "github.com/zapier/tfbuddy/cmd" -) - -var ( - GitTag = "" - GitCommit = "" + "github.com/zapier/tfbuddy/pkg" ) func main() { - fmt.Println("Starting TFBuddy:", GitTag, GitCommit) + fmt.Println("Starting TFBuddy:", pkg.GitTag, pkg.GitCommit) cmd.Execute() } diff --git a/manifests/base/deployment.yaml b/manifests/base/deployment.yaml index c85b80e..ed24939 100644 --- a/manifests/base/deployment.yaml +++ b/manifests/base/deployment.yaml @@ -38,6 +38,14 @@ spec: value: "debug" - name: TFBUDDY_NATS_SERVICE_URL value: nats://tfbuddy-nats:4222 + - name: TFBUDDY_OTEL_ENABLED + value: "false" + - name: TFBUDDY_OTEL_COLLECTOR_PORT + value: "4317" + - name: TFBUDDY_OTEL_COLLECTOR_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP image: ghcr.io/zapier/tfbuddy:latest imagePullPolicy: Always livenessProbe: diff --git a/pkg/const.go b/pkg/const.go new file mode 100644 index 0000000..89b3d0e --- /dev/null +++ b/pkg/const.go @@ -0,0 +1,6 @@ +package pkg + +var ( + GitTag = "" + GitCommit = "" +) diff --git a/pkg/gitlab_hooks/comment_actions.go b/pkg/gitlab_hooks/comment_actions.go index ac199d8..35acf6e 100644 --- a/pkg/gitlab_hooks/comment_actions.go +++ b/pkg/gitlab_hooks/comment_actions.go @@ -1,6 +1,7 @@ package gitlab_hooks import ( + "context" "fmt" "github.com/rs/zerolog/log" @@ -9,11 +10,15 @@ import ( "github.com/zapier/tfbuddy/pkg/comment_actions" "github.com/zapier/tfbuddy/pkg/tfc_trigger" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" ) // processNoteEvent processes GitLab Webhooks for Note events // In the Gitlab API, MR comments are called Notes -func (w *GitlabEventWorker) processNoteEvent(event vcs.MRCommentEvent) (projectName string, err error) { +func (w *GitlabEventWorker) processNoteEvent(ctx context.Context, event vcs.MRCommentEvent) (projectName string, err error) { + ctx, span := otel.Tracer("hooks").Start(ctx, "ProcessNoteEvent") + defer span.End() proj := event.GetProject().GetPathWithNamespace() if !allow_list.IsGitlabProjectAllowed(proj) { @@ -24,7 +29,7 @@ func (w *GitlabEventWorker) processNoteEvent(event vcs.MRCommentEvent) (projectN opts, err := comment_actions.ParseCommentCommand(event.GetAttributes().GetNote()) if err != nil { if err == comment_actions.ErrOtherTFTool { - w.postMessageToMergeRequest(event, "Use tfc to interact with tfbuddy") + w.postMessageToMergeRequest(ctx, event, "Use tfc to interact with tfbuddy") } if err == comment_actions.ErrNotTFCCommand || err == comment_actions.ErrOtherTFTool { gitlabWebHookIgnored.WithLabelValues("comment", "not-tfc-command", proj).Inc() @@ -52,17 +57,27 @@ func (w *GitlabEventWorker) processNoteEvent(event vcs.MRCommentEvent) (projectN trigger.SetMergeRequestDiscussionID(event.GetAttributes().GetDiscussionID()) } + span.SetAttributes( + attribute.String("agent", opts.Args.Agent), + attribute.String("command", opts.Args.Command), + attribute.String("branch", opts.TriggerOpts.Branch), + attribute.String("commit_sha", opts.TriggerOpts.CommitSHA), + attribute.String("project_name", opts.TriggerOpts.ProjectNameWithNamespace), + attribute.Int("merge_request_iid", opts.TriggerOpts.MergeRequestIID), + attribute.String("vcs_provider", opts.TriggerOpts.VcsProvider), + ) + // TODO: support additional commands and arguments (e.g. destroy, refresh, lock, unlock) // TODO: this should be refactored and be agnostic to the VCS type switch opts.Args.Command { case "apply": log.Info().Msg("Got TFC apply command") - if !w.checkApproval(event) { - w.postMessageToMergeRequest(event, ":no_entry: Apply failed. Merge Request requires approval.") + if !w.checkApproval(ctx, event) { + w.postMessageToMergeRequest(ctx, event, ":no_entry: Apply failed. Merge Request requires approval.") return proj, nil } - if !w.checkForMergeConflicts(event) { - w.postMessageToMergeRequest(event, ":no_entry: Apply failed. Merge Request has conflicts that need to be resolved.") + if !w.checkForMergeConflicts(ctx, event) { + w.postMessageToMergeRequest(ctx, event, ":no_entry: Apply failed. Merge Request has conflicts that need to be resolved.") return proj, nil } case "lock": @@ -74,48 +89,58 @@ func (w *GitlabEventWorker) processNoteEvent(event vcs.MRCommentEvent) (projectN default: return proj, nil } - executedWorkspaces, tfError := trigger.TriggerTFCEvents() + executedWorkspaces, tfError := trigger.TriggerTFCEvents(ctx) if tfError == nil && executedWorkspaces != nil { if len(executedWorkspaces.Errored) > 0 { for _, failedWS := range executedWorkspaces.Errored { - w.postMessageToMergeRequest(event, fmt.Sprintf(":no_entry: %s could not be run because: %s", failedWS.Name, failedWS.Error)) + w.postMessageToMergeRequest(ctx, event, fmt.Sprintf(":no_entry: %s could not be run because: %s", failedWS.Name, failedWS.Error)) } return proj, nil } } if tfError != nil { - w.postMessageToMergeRequest(event, fmt.Sprintf(":no_entry: could not be run because: %s", tfError.Error())) + w.postMessageToMergeRequest(ctx, event, fmt.Sprintf(":no_entry: could not be run because: %s", tfError.Error())) } return proj, tfError } -func (w *GitlabEventWorker) checkApproval(event vcs.MRCommentEvent) bool { +func (w *GitlabEventWorker) checkApproval(ctx context.Context, event vcs.MRCommentEvent) bool { + ctx, span := otel.Tracer("hooks").Start(ctx, "checkApproval") + defer span.End() + mrIID := event.GetMR().GetInternalID() proj := event.GetProject().GetPathWithNamespace() - approvals, err := w.gl.GetMergeRequestApprovals(mrIID, proj) + approvals, err := w.gl.GetMergeRequestApprovals(ctx, mrIID, proj) if err != nil { - w.postErrorToMergeRequest(event, fmt.Errorf("could not get MergeRequest from GitlabAPI: %v", err)) + w.postErrorToMergeRequest(ctx, event, fmt.Errorf("could not get MergeRequest from GitlabAPI: %v", err)) return false } return approvals.IsApproved() } -func (w *GitlabEventWorker) checkForMergeConflicts(event vcs.MRCommentEvent) bool { +func (w *GitlabEventWorker) checkForMergeConflicts(ctx context.Context, event vcs.MRCommentEvent) bool { + ctx, span := otel.Tracer("hooks").Start(ctx, "CheckForMergeConflicts") + defer span.End() + mrIID := event.GetMR().GetInternalID() proj := event.GetProject().GetPathWithNamespace() - mr, err := w.gl.GetMergeRequest(mrIID, proj) + mr, err := w.gl.GetMergeRequest(ctx, mrIID, proj) if err != nil { - w.postErrorToMergeRequest(event, fmt.Errorf("could not get MergeRequest from GitlabAPI: %v", err)) + w.postErrorToMergeRequest(ctx, event, fmt.Errorf("could not get MergeRequest from GitlabAPI: %v", err)) return false } // fail if the MR has conflicts only. return !mr.HasConflicts() } -func (w *GitlabEventWorker) postMessageToMergeRequest(event vcs.MRCommentEvent, msg string) { +func (w *GitlabEventWorker) postMessageToMergeRequest(ctx context.Context, event vcs.MRCommentEvent, msg string) { + ctx, span := otel.Tracer("GitlabHooks").Start(context.Background(), "postMessageToMergeRequest") + defer span.End() + if err := w.gl.CreateMergeRequestComment( + ctx, event.GetMR().GetInternalID(), event.GetProject().GetPathWithNamespace(), msg, @@ -124,6 +149,10 @@ func (w *GitlabEventWorker) postMessageToMergeRequest(event vcs.MRCommentEvent, } } -func (w *GitlabEventWorker) postErrorToMergeRequest(event vcs.MRCommentEvent, err error) { - w.postMessageToMergeRequest(event, fmt.Sprintf(":fire:
Error: %v", err)) +func (w *GitlabEventWorker) postErrorToMergeRequest(ctx context.Context, event vcs.MRCommentEvent, err error) { + ctx, span := otel.Tracer("GitlabHooks").Start(context.Background(), "postErrorToMergeRequest") + defer span.End() + span.RecordError(err) + + w.postMessageToMergeRequest(ctx, event, fmt.Sprintf(":fire:
Error: %v", err)) } diff --git a/pkg/gitlab_hooks/comment_actions_test.go b/pkg/gitlab_hooks/comment_actions_test.go index c36da68..9ae4cbc 100644 --- a/pkg/gitlab_hooks/comment_actions_test.go +++ b/pkg/gitlab_hooks/comment_actions_test.go @@ -1,6 +1,7 @@ package gitlab_hooks import ( + "context" "fmt" "os" "testing" @@ -116,8 +117,9 @@ func TestProcessNoteEventPlanError(t *testing.T) { defer os.Unsetenv(allow_list.GitlabProjectAllowListEnv) mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() + mockGitClient := mocks.NewMockGitClient(mockCtrl) - mockGitClient.EXPECT().CreateMergeRequestComment(101, "zapier/service-tf-buddy", ":no_entry: could not be run because: something went wrong") + mockGitClient.EXPECT().CreateMergeRequestComment(gomock.Any(), 101, "zapier/service-tf-buddy", ":no_entry: could not be run because: something went wrong") mockApiClient := mocks.NewMockApiClient(mockCtrl) mockStreamClient := mocks.NewMockStreamClient(mockCtrl) mockProject := mocks.NewMockProject(mockCtrl) @@ -143,7 +145,7 @@ func TestProcessNoteEventPlanError(t *testing.T) { mockMREvent.EXPECT().GetMR().Return(mockSimpleMR).Times(3) mockTFCTrigger := mocks.NewMockTrigger(mockCtrl) - mockTFCTrigger.EXPECT().TriggerTFCEvents().Return(nil, fmt.Errorf("something went wrong")) + mockTFCTrigger.EXPECT().TriggerTFCEvents(gomock.Any()).Return(nil, fmt.Errorf("something went wrong")) client := &GitlabEventWorker{ gl: mockGitClient, @@ -154,7 +156,7 @@ func TestProcessNoteEventPlanError(t *testing.T) { }, } - proj, err := client.processNoteEvent(mockMREvent) + proj, err := client.processNoteEvent(context.Background(), mockMREvent) if err == nil { t.Error("expected error") return @@ -186,7 +188,9 @@ func TestProcessNoteEventPanicHandling(t *testing.T) { return nil }, } - err := client.processNoteEventStreamMsg(&NoteEventMsg{}) + err := client.processNoteEventStreamMsg(&NoteEventMsg{ + Context: context.Background(), + }) if err != nil { t.Error("unexpected error", err) return @@ -224,7 +228,7 @@ func TestProcessNoteEventPlan(t *testing.T) { mockMREvent.EXPECT().GetMR().Return(mockSimpleMR).Times(2) mockTFCTrigger := mocks.NewMockTrigger(mockCtrl) - mockTFCTrigger.EXPECT().TriggerTFCEvents().Return(&tfc_trigger.TriggeredTFCWorkspaces{ + mockTFCTrigger.EXPECT().TriggerTFCEvents(gomock.Any()).Return(&tfc_trigger.TriggeredTFCWorkspaces{ Executed: []string{"service-tf-buddy"}, }, nil) @@ -237,7 +241,7 @@ func TestProcessNoteEventPlan(t *testing.T) { }, } - proj, err := client.processNoteEvent(mockMREvent) + proj, err := client.processNoteEvent(context.Background(), mockMREvent) if err != nil { t.Fatal(err) } @@ -253,7 +257,7 @@ func TestProcessNoteEventPlanFailedWorkspace(t *testing.T) { defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{}, t) - testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy could not be run because: could not fetch upstream").Return(nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(gomock.Any(), 101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy could not be run because: could not fetch upstream").Return(nil) mockLastCommit := mocks.NewMockCommit(mockCtrl) @@ -274,7 +278,7 @@ func TestProcessNoteEventPlanFailedWorkspace(t *testing.T) { mockTFCTrigger := mocks.NewMockTrigger(mockCtrl) // mockTFCTrigger.EXPECT().GetConfig().Return(testSuite.MockTriggerConfig).Times(2) - mockTFCTrigger.EXPECT().TriggerTFCEvents().Return(&tfc_trigger.TriggeredTFCWorkspaces{ + mockTFCTrigger.EXPECT().TriggerTFCEvents(gomock.Any()).Return(&tfc_trigger.TriggeredTFCWorkspaces{ Errored: []*tfc_trigger.ErroredWorkspace{{ Name: "service-tf-buddy", Error: "could not fetch upstream", @@ -293,7 +297,7 @@ func TestProcessNoteEventPlanFailedWorkspace(t *testing.T) { }, } - proj, err := worker.processNoteEvent(mockMREvent) + proj, err := worker.processNoteEvent(context.Background(), mockMREvent) if err != nil { t.Fatal(err) } @@ -310,8 +314,8 @@ func TestProcessNoteEventPlanFailedMultipleWorkspaces(t *testing.T) { testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{}, t) gomock.InOrder( - testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy could not be run because: could not fetch upstream").Return(nil), - testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy-staging could not be run because: workspace has been modified on target branch").Return(nil), + testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(gomock.Any(), 101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy could not be run because: could not fetch upstream").Return(nil), + testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(gomock.Any(), 101, testSuite.MetaData.ProjectNameNS, ":no_entry: service-tf-buddy-staging could not be run because: workspace has been modified on target branch").Return(nil), ) mockLastCommit := mocks.NewMockCommit(mockCtrl) mockLastCommit.EXPECT().GetSHA().Return("abvc12345") @@ -328,7 +332,7 @@ func TestProcessNoteEventPlanFailedMultipleWorkspaces(t *testing.T) { mockMREvent.EXPECT().GetMR().Return(testSuite.MockGitMR).AnyTimes() mockTFCTrigger := mocks.NewMockTrigger(mockCtrl) - mockTFCTrigger.EXPECT().TriggerTFCEvents().Return(&tfc_trigger.TriggeredTFCWorkspaces{ + mockTFCTrigger.EXPECT().TriggerTFCEvents(gomock.Any()).Return(&tfc_trigger.TriggeredTFCWorkspaces{ Errored: []*tfc_trigger.ErroredWorkspace{{ Name: "service-tf-buddy", Error: "could not fetch upstream", @@ -351,7 +355,7 @@ func TestProcessNoteEventPlanFailedMultipleWorkspaces(t *testing.T) { }, } - proj, err := client.processNoteEvent(mockMREvent) + proj, err := client.processNoteEvent(context.Background(), mockMREvent) if err != nil { t.Fatal(err) } @@ -392,7 +396,7 @@ func TestProcessNoteEventNoErrorNoRuns(t *testing.T) { mockMREvent.EXPECT().GetMR().Return(mockSimpleMR).Times(2) mockTFCTrigger := mocks.NewMockTrigger(mockCtrl) - mockTFCTrigger.EXPECT().TriggerTFCEvents().Return(nil, nil) + mockTFCTrigger.EXPECT().TriggerTFCEvents(gomock.Any()).Return(nil, nil) client := &GitlabEventWorker{ gl: mockGitClient, @@ -403,7 +407,7 @@ func TestProcessNoteEventNoErrorNoRuns(t *testing.T) { }, } - proj, err := client.processNoteEvent(mockMREvent) + proj, err := client.processNoteEvent(context.Background(), mockMREvent) if err != nil { t.Fatal(err) } diff --git a/pkg/gitlab_hooks/handler.go b/pkg/gitlab_hooks/handler.go index e31e363..82c8c3d 100644 --- a/pkg/gitlab_hooks/handler.go +++ b/pkg/gitlab_hooks/handler.go @@ -1,12 +1,16 @@ package gitlab_hooks import ( + "context" "net/http" "os" "github.com/prometheus/client_golang/prometheus" "github.com/sl1pm4t/gongs" "github.com/zapier/tfbuddy/pkg/hooks_stream" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/labstack/echo/v4" nats "github.com/nats-io/nats.go" @@ -91,32 +95,45 @@ func (h *GitlabHooksHandler) handler(c echo.Context) error { labels["eventType"] = string(eventType) switch eventType { case gogitlab.EventTypeMergeRequest: + ctx, span := otel.Tracer("GitlabHandler").Start(c.Request().Context(), "Gitlab - MergeRequestHook") + defer span.End() + log.Info().Msg("processing GitLab Merge Request event") event, err := getGitlabEventBody[gogitlab.MergeEvent](c) - if checkError(err, "could not decode merge request event") { + if checkError(ctx, err, "could not decode merge request event") { break } + + span.SetAttributes( + attribute.String("project", event.Project.PathWithNamespace), + attribute.String("action", event.ObjectAttributes.Action), + attribute.Int("mergeRequestID", event.ObjectAttributes.IID), + ) + msg := &MergeRequestEventMsg{ GitlabHookEvent: GitlabHookEvent{}, - payload: event, + Payload: event, } proj = event.Project.PathWithNamespace - _, err = h.mrStream.Publish(msg) - checkError(err, "could not publish merge request event to stream") + _, err = h.mrStream.Publish(ctx, msg) + checkError(ctx, err, "could not publish merge request event to stream") case gogitlab.EventTypeNote: + ctx, span := otel.Tracer("GitlabHandler").Start(c.Request().Context(), "Gitlab - EventTypeNote") + defer span.End() + log.Info().Msg("processing GitLab Note/Comment event") event, err := getNoteEventBody(c) - if checkError(err, "could not decode Note/Comment event") { + if checkError(ctx, err, "could not decode Note/Comment event") { break } - proj = event.payload.GetProject().GetPathWithNamespace() - _, err = h.notesStream.Publish(event) - checkError(err, "could not publish note event to stream") + proj = event.Payload.GetProject().GetPathWithNamespace() + _, err = h.notesStream.Publish(ctx, event) + checkError(ctx, err, "could not publish note event to stream") default: log.Info().Msgf("Ignoring Gitlab Event type: %s", eventType) @@ -159,14 +176,15 @@ func getNoteEventBody(c echo.Context) (*NoteEventMsg, error) { ne := &NoteEventMsg{ GitlabHookEvent: GitlabHookEvent{}, - payload: mrCommentEvt, + Payload: mrCommentEvt, } return ne, nil } -func checkError(err error, detail string) bool { +func checkError(ctx context.Context, err error, detail string) bool { if err != nil { log.Error().Err(err).Msg(detail) + trace.SpanFromContext(ctx).RecordError(err, trace.WithAttributes(attribute.String("detail", detail))) return true } return false diff --git a/pkg/gitlab_hooks/hook_worker.go b/pkg/gitlab_hooks/hook_worker.go index ca11823..1411f84 100644 --- a/pkg/gitlab_hooks/hook_worker.go +++ b/pkg/gitlab_hooks/hook_worker.go @@ -8,6 +8,7 @@ import ( "github.com/zapier/tfbuddy/pkg/tfc_trigger" "github.com/zapier/tfbuddy/pkg/utils" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" ) type GitlabEventWorker struct { @@ -39,6 +40,9 @@ func NewGitlabEventWorker(h *GitlabHooksHandler, js nats.JetStreamContext) *Gitl } func (w *GitlabEventWorker) processNoteEventStreamMsg(msg *NoteEventMsg) error { + ctx, span := otel.Tracer("hooks").Start(msg.Context, "ProcessNoteEventStreamMsg") + defer span.End() + var noteErr error defer func() { if r := recover(); r != nil { @@ -46,7 +50,7 @@ func (w *GitlabEventWorker) processNoteEventStreamMsg(msg *NoteEventMsg) error { noteErr = nil } }() - _, noteErr = w.processNoteEvent(msg) + _, noteErr = w.processNoteEvent(ctx, msg) return utils.EmitPermanentError(noteErr, func(err error) { log.Error().Msgf("got permanent error processing Note event: %s", err.Error()) diff --git a/pkg/gitlab_hooks/mr_actions.go b/pkg/gitlab_hooks/mr_actions.go index fe82ef1..5c3e72b 100644 --- a/pkg/gitlab_hooks/mr_actions.go +++ b/pkg/gitlab_hooks/mr_actions.go @@ -6,15 +6,20 @@ import ( gogitlab "github.com/xanzy/go-gitlab" "github.com/zapier/tfbuddy/pkg/allow_list" "github.com/zapier/tfbuddy/pkg/tfc_trigger" + "go.opentelemetry.io/otel" ) func (w *GitlabEventWorker) processMergeRequestEvent(msg *MergeRequestEventMsg) (projectName string, err error) { + ctx, span := otel.Tracer("GitlabHandler").Start(msg.Context, "processMergeRequestEvent") + defer span.End() + log.Trace().Msg("processMergeRequestEvent()") labels := prometheus.Labels{} labels["eventType"] = string(gogitlab.EventTypeMergeRequest) - event := msg.payload + event := msg.Payload + projectName = event.Project.PathWithNamespace labels["project"] = projectName if !allow_list.IsGitlabProjectAllowed(event.Project.PathWithNamespace) { @@ -41,17 +46,17 @@ func (w *GitlabEventWorker) processMergeRequestEvent(msg *MergeRequestEventMsg) trigger := tfc_trigger.NewTFCTrigger(w.gl, w.tfc, w.runstream, cfg) switch event.ObjectAttributes.Action { case "open", "reopen": - _, err := trigger.TriggerTFCEvents() + _, err := trigger.TriggerTFCEvents(ctx) return projectName, err case "update": if event.ObjectAttributes.OldRev != "" && event.ObjectAttributes.OldRev != event.ObjectAttributes.LastCommit.ID { - _, err := trigger.TriggerTFCEvents() + _, err := trigger.TriggerTFCEvents(ctx) return projectName, err } case "merge", "close": - return projectName, trigger.TriggerCleanupEvent() + return projectName, trigger.TriggerCleanupEvent(ctx) default: labels["reason"] = "unhandled-action" gitlabWebHookIgnored.With(labels).Inc() diff --git a/pkg/gitlab_hooks/stream_msgs.go b/pkg/gitlab_hooks/stream_msgs.go index c3ac5cb..05cd805 100644 --- a/pkg/gitlab_hooks/stream_msgs.go +++ b/pkg/gitlab_hooks/stream_msgs.go @@ -1,6 +1,7 @@ package gitlab_hooks import ( + "context" "encoding/json" "fmt" @@ -8,6 +9,10 @@ import ( "github.com/zapier/tfbuddy/pkg/hooks_stream" "github.com/zapier/tfbuddy/pkg/vcs" "github.com/zapier/tfbuddy/pkg/vcs/gitlab" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" ) const GitlabHooksSubject = "gitlab" @@ -31,42 +36,51 @@ func (e *GitlabHookEvent) GetPlatform() string { type NoteEventMsg struct { GitlabHookEvent - payload *gitlab.GitlabMergeCommentEvent + Payload *gitlab.GitlabMergeCommentEvent `json:"payload"` + Carrier propagation.MapCarrier `json:"Carrier"` + Context context.Context } -func (e *NoteEventMsg) GetId() string { - return e.payload.GetDiscussionID() +func (e *NoteEventMsg) GetId(ctx context.Context) string { + return e.Payload.GetDiscussionID() } func (e *NoteEventMsg) DecodeEventData(b []byte) error { - d := &gitlab.GitlabMergeCommentEvent{} - err := json.Unmarshal(b, d) + err := json.Unmarshal(b, e) if err != nil { return err } - e.payload = d + e.Context = otel.GetTextMapPropagator().Extract(context.Background(), e.Carrier) return nil } -func (e *NoteEventMsg) EncodeEventData() []byte { - b, _ := json.Marshal(e.payload) +func (e *NoteEventMsg) EncodeEventData(ctx context.Context) []byte { + ctx, span := otel.Tracer("hooks").Start(ctx, "encode_event_data", + trace.WithAttributes( + attribute.String("event_type", "NoteEvent"), + attribute.String("vcs", "gitlab"), + )) + defer span.End() + e.Carrier = make(map[string]string) + otel.GetTextMapPropagator().Inject(ctx, e.Carrier) + b, _ := json.Marshal(e) return b } func (e *NoteEventMsg) GetProject() vcs.Project { - return e.payload.GetProject() + return e.Payload.GetProject() } func (e *NoteEventMsg) GetMR() vcs.MR { - return e.payload + return e.Payload } func (e *NoteEventMsg) GetAttributes() vcs.MRAttributes { - return e.payload.GetAttributes() + return e.Payload.GetAttributes() } func (e *NoteEventMsg) GetLastCommit() vcs.Commit { - return e.payload.GetLastCommit() + return e.Payload.GetLastCommit() } // ---------------------------------------------- @@ -78,32 +92,41 @@ func mrEventsStreamSubject() string { type MergeRequestEventMsg struct { GitlabHookEvent - payload *gogitlab.MergeEvent + Payload *gogitlab.MergeEvent `json:"payload"` + Carrier propagation.MapCarrier `json:"Carrier"` + Context context.Context } -func (e *MergeRequestEventMsg) GetId() string { - return fmt.Sprintf("%d-%s", e.payload.ObjectAttributes.ID, e.payload.ObjectAttributes.Action) +func (e *MergeRequestEventMsg) GetId(ctx context.Context) string { + return fmt.Sprintf("%d-%s", e.Payload.ObjectAttributes.ID, e.Payload.ObjectAttributes.Action) } func (e *MergeRequestEventMsg) DecodeEventData(b []byte) error { - d := &gogitlab.MergeEvent{} - err := json.Unmarshal(b, d) + err := json.Unmarshal(b, e) if err != nil { return err } - e.payload = d + e.Context = otel.GetTextMapPropagator().Extract(context.Background(), e.Carrier) return nil } -func (e *MergeRequestEventMsg) EncodeEventData() []byte { - b, _ := json.Marshal(e.payload) +func (e *MergeRequestEventMsg) EncodeEventData(ctx context.Context) []byte { + ctx, span := otel.Tracer("hooks").Start(ctx, "encode_event_data", + trace.WithAttributes( + attribute.String("event_type", "MergeRequestEventMsg"), + attribute.String("vcs", "gitlab"), + )) + defer span.End() + e.Carrier = make(map[string]string) + otel.GetTextMapPropagator().Inject(ctx, e.Carrier) + b, _ := json.Marshal(e) return b } -func (e *MergeRequestEventMsg) GetType() string { +func (e *MergeRequestEventMsg) GetType(ctx context.Context) string { return "MergeRequestEventMsg" } func (e *MergeRequestEventMsg) GetPayload() interface{} { - return *e.payload + return *e.Payload } diff --git a/pkg/hooks/server.go b/pkg/hooks/server.go index e75c7cc..a9dc4a7 100644 --- a/pkg/hooks/server.go +++ b/pkg/hooks/server.go @@ -10,6 +10,7 @@ import ( "github.com/zapier/tfbuddy/pkg/hooks_stream" "github.com/zapier/tfbuddy/pkg/vcs/github" "github.com/ziflex/lecho/v3" + "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" "github.com/zapier/tfbuddy/pkg/gitlab_hooks" tfnats "github.com/zapier/tfbuddy/pkg/nats" @@ -53,6 +54,10 @@ func StartServer() { tfc := tfc_api.NewTFCClient() hooksGroup := e.Group("/hooks") + + // add otel middleware to hooks group + hooksGroup.Use(otelecho.Middleware("tfbuddy")) + hooksGroup.Use(middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) { log.Trace().RawJSON("body", reqBody).Msg("Received hook request") })) diff --git a/pkg/mocks/helpers.go b/pkg/mocks/helpers.go index 6d639a2..6757cf8 100644 --- a/pkg/mocks/helpers.go +++ b/pkg/mocks/helpers.go @@ -164,11 +164,11 @@ func (ts *TestSuite) InitTestSuite() { ts.MockGitDisc.EXPECT().GetDiscussionID().Return("201").AnyTimes() ts.MockGitDisc.EXPECT().GetMRNotes().Return([]vcs.MRNote{ts.MockMRNote}).AnyTimes() - ts.MockGitClient.EXPECT().GetMergeRequest(ts.MetaData.MRIID, ts.MetaData.ProjectNameNS).Return(ts.MockGitMR, nil).AnyTimes() - ts.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(ts.MetaData.MRIID, ts.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil).AnyTimes() - ts.MockGitClient.EXPECT().GetRepoFile(ts.MetaData.ProjectNameNS, ".tfbuddy.yaml", ts.MetaData.SourceBranch).Return(ts.MetaData.TFBuddyConfig, nil).AnyTimes() - ts.MockGitClient.EXPECT().CloneMergeRequest(ts.MetaData.ProjectNameNS, gomock.Any(), gomock.Any()).Return(ts.MockGitRepo, nil).AnyTimes() - ts.MockGitClient.EXPECT().CreateMergeRequestDiscussion(ts.MetaData.MRIID, ts.MetaData.ProjectNameNS, &RegexMatcher{regex: regexp.MustCompile("Starting TFC apply for Workspace: `([A-z\\-]){1,}/([A-z\\-]){1,}`.")}).Return(ts.MockGitDisc, nil).AnyTimes() + ts.MockGitClient.EXPECT().GetMergeRequest(gomock.Any(), ts.MetaData.MRIID, ts.MetaData.ProjectNameNS).Return(ts.MockGitMR, nil).AnyTimes() + ts.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), ts.MetaData.MRIID, ts.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil).AnyTimes() + ts.MockGitClient.EXPECT().GetRepoFile(gomock.Any(), ts.MetaData.ProjectNameNS, ".tfbuddy.yaml", ts.MetaData.SourceBranch).Return(ts.MetaData.TFBuddyConfig, nil).AnyTimes() + ts.MockGitClient.EXPECT().CloneMergeRequest(gomock.Any(), ts.MetaData.ProjectNameNS, gomock.Any(), gomock.Any()).Return(ts.MockGitRepo, nil).AnyTimes() + ts.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), ts.MetaData.MRIID, ts.MetaData.ProjectNameNS, &RegexMatcher{regex: regexp.MustCompile("Starting TFC apply for Workspace: `([A-z\\-]){1,}/([A-z\\-]){1,}`.")}).Return(ts.MockGitDisc, nil).AnyTimes() ts.MockApiClient.EXPECT().GetWorkspaceByName(gomock.Any(), gomock.Any(), gomock.Any()).Return(&tfe.Workspace{ID: "service-tfbuddy"}, nil).AnyTimes() ts.MockApiClient.EXPECT().GetTagsByQuery(gomock.Any(), gomock.Any(), "tfbuddylock").AnyTimes() diff --git a/pkg/mocks/mock_runstream.go b/pkg/mocks/mock_runstream.go index de67f0b..bba93a9 100644 --- a/pkg/mocks/mock_runstream.go +++ b/pkg/mocks/mock_runstream.go @@ -5,6 +5,7 @@ package mocks import ( + context "context" reflect "reflect" time "time" @@ -93,17 +94,17 @@ func (mr *MockStreamClientMockRecorder) NewTFRunPollingTask(meta, delay interfac } // PublishTFRunEvent mocks base method. -func (m *MockStreamClient) PublishTFRunEvent(re runstream.RunEvent) error { +func (m *MockStreamClient) PublishTFRunEvent(ctx context.Context, re runstream.RunEvent) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PublishTFRunEvent", re) + ret := m.ctrl.Call(m, "PublishTFRunEvent", ctx, re) ret0, _ := ret[0].(error) return ret0 } // PublishTFRunEvent indicates an expected call of PublishTFRunEvent. -func (mr *MockStreamClientMockRecorder) PublishTFRunEvent(re interface{}) *gomock.Call { +func (mr *MockStreamClientMockRecorder) PublishTFRunEvent(ctx, re interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishTFRunEvent", reflect.TypeOf((*MockStreamClient)(nil).PublishTFRunEvent), re) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishTFRunEvent", reflect.TypeOf((*MockStreamClient)(nil).PublishTFRunEvent), ctx, re) } // SubscribeTFRunEvents mocks base method. @@ -159,6 +160,20 @@ func (m *MockRunEvent) EXPECT() *MockRunEventMockRecorder { return m.recorder } +// GetContext mocks base method. +func (m *MockRunEvent) GetContext() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContext") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// GetContext indicates an expected call of GetContext. +func (mr *MockRunEventMockRecorder) GetContext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContext", reflect.TypeOf((*MockRunEvent)(nil).GetContext)) +} + // GetMetadata mocks base method. func (m *MockRunEvent) GetMetadata() runstream.RunMetadata { m.ctrl.T.Helper() @@ -201,6 +216,30 @@ func (mr *MockRunEventMockRecorder) GetRunID() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRunID", reflect.TypeOf((*MockRunEvent)(nil).GetRunID)) } +// SetCarrier mocks base method. +func (m *MockRunEvent) SetCarrier(arg0 map[string]string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetCarrier", arg0) +} + +// SetCarrier indicates an expected call of SetCarrier. +func (mr *MockRunEventMockRecorder) SetCarrier(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCarrier", reflect.TypeOf((*MockRunEvent)(nil).SetCarrier), arg0) +} + +// SetContext mocks base method. +func (m *MockRunEvent) SetContext(arg0 context.Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetContext", arg0) +} + +// SetContext indicates an expected call of SetContext. +func (mr *MockRunEventMockRecorder) SetContext(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContext", reflect.TypeOf((*MockRunEvent)(nil).SetContext), arg0) +} + // SetMetadata mocks base method. func (m *MockRunEvent) SetMetadata(arg0 runstream.RunMetadata) { m.ctrl.T.Helper() @@ -413,6 +452,20 @@ func (mr *MockRunPollingTaskMockRecorder) Completed() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Completed", reflect.TypeOf((*MockRunPollingTask)(nil).Completed)) } +// GetContext mocks base method. +func (m *MockRunPollingTask) GetContext() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetContext") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// GetContext indicates an expected call of GetContext. +func (mr *MockRunPollingTaskMockRecorder) GetContext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContext", reflect.TypeOf((*MockRunPollingTask)(nil).GetContext)) +} + // GetLastStatus mocks base method. func (m *MockRunPollingTask) GetLastStatus() string { m.ctrl.T.Helper() @@ -456,31 +509,43 @@ func (mr *MockRunPollingTaskMockRecorder) GetRunMetaData() *gomock.Call { } // Reschedule mocks base method. -func (m *MockRunPollingTask) Reschedule() error { +func (m *MockRunPollingTask) Reschedule(ctx context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reschedule") + ret := m.ctrl.Call(m, "Reschedule", ctx) ret0, _ := ret[0].(error) return ret0 } // Reschedule indicates an expected call of Reschedule. -func (mr *MockRunPollingTaskMockRecorder) Reschedule() *gomock.Call { +func (mr *MockRunPollingTaskMockRecorder) Reschedule(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reschedule", reflect.TypeOf((*MockRunPollingTask)(nil).Reschedule)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reschedule", reflect.TypeOf((*MockRunPollingTask)(nil).Reschedule), ctx) } // Schedule mocks base method. -func (m *MockRunPollingTask) Schedule() error { +func (m *MockRunPollingTask) Schedule(ctx context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Schedule") + ret := m.ctrl.Call(m, "Schedule", ctx) ret0, _ := ret[0].(error) return ret0 } // Schedule indicates an expected call of Schedule. -func (mr *MockRunPollingTaskMockRecorder) Schedule() *gomock.Call { +func (mr *MockRunPollingTaskMockRecorder) Schedule(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schedule", reflect.TypeOf((*MockRunPollingTask)(nil).Schedule), ctx) +} + +// SetCarrier mocks base method. +func (m *MockRunPollingTask) SetCarrier(arg0 map[string]string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetCarrier", arg0) +} + +// SetCarrier indicates an expected call of SetCarrier. +func (mr *MockRunPollingTaskMockRecorder) SetCarrier(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schedule", reflect.TypeOf((*MockRunPollingTask)(nil).Schedule)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCarrier", reflect.TypeOf((*MockRunPollingTask)(nil).SetCarrier), arg0) } // SetLastStatus mocks base method. diff --git a/pkg/mocks/mock_tfc_api.go b/pkg/mocks/mock_tfc_api.go index 294d361..87f5791 100644 --- a/pkg/mocks/mock_tfc_api.go +++ b/pkg/mocks/mock_tfc_api.go @@ -51,18 +51,18 @@ func (mr *MockApiClientMockRecorder) AddTags(ctx, workspace, prefix, value inter } // CreateRunFromSource mocks base method. -func (m *MockApiClient) CreateRunFromSource(opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { +func (m *MockApiClient) CreateRunFromSource(ctx context.Context, opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateRunFromSource", opts) + ret := m.ctrl.Call(m, "CreateRunFromSource", ctx, opts) ret0, _ := ret[0].(*tfe.Run) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateRunFromSource indicates an expected call of CreateRunFromSource. -func (mr *MockApiClientMockRecorder) CreateRunFromSource(opts interface{}) *gomock.Call { +func (mr *MockApiClientMockRecorder) CreateRunFromSource(ctx, opts interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRunFromSource", reflect.TypeOf((*MockApiClient)(nil).CreateRunFromSource), opts) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRunFromSource", reflect.TypeOf((*MockApiClient)(nil).CreateRunFromSource), ctx, opts) } // GetPlanOutput mocks base method. @@ -81,18 +81,18 @@ func (mr *MockApiClientMockRecorder) GetPlanOutput(id interface{}) *gomock.Call } // GetRun mocks base method. -func (m *MockApiClient) GetRun(id string) (*tfe.Run, error) { +func (m *MockApiClient) GetRun(ctx context.Context, id string) (*tfe.Run, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRun", id) + ret := m.ctrl.Call(m, "GetRun", ctx, id) ret0, _ := ret[0].(*tfe.Run) ret1, _ := ret[1].(error) return ret0, ret1 } // GetRun indicates an expected call of GetRun. -func (mr *MockApiClientMockRecorder) GetRun(id interface{}) *gomock.Call { +func (mr *MockApiClientMockRecorder) GetRun(ctx, id interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRun", reflect.TypeOf((*MockApiClient)(nil).GetRun), id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRun", reflect.TypeOf((*MockApiClient)(nil).GetRun), ctx, id) } // GetTagsByQuery mocks base method. diff --git a/pkg/mocks/mock_tfc_trigger.go b/pkg/mocks/mock_tfc_trigger.go index 6e906c5..83c3a8a 100644 --- a/pkg/mocks/mock_tfc_trigger.go +++ b/pkg/mocks/mock_tfc_trigger.go @@ -5,6 +5,7 @@ package mocks import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -199,30 +200,30 @@ func (mr *MockTriggerMockRecorder) SetMergeRequestRootNoteID(id interface{}) *go } // TriggerCleanupEvent mocks base method. -func (m *MockTrigger) TriggerCleanupEvent() error { +func (m *MockTrigger) TriggerCleanupEvent(arg0 context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TriggerCleanupEvent") + ret := m.ctrl.Call(m, "TriggerCleanupEvent", arg0) ret0, _ := ret[0].(error) return ret0 } // TriggerCleanupEvent indicates an expected call of TriggerCleanupEvent. -func (mr *MockTriggerMockRecorder) TriggerCleanupEvent() *gomock.Call { +func (mr *MockTriggerMockRecorder) TriggerCleanupEvent(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerCleanupEvent", reflect.TypeOf((*MockTrigger)(nil).TriggerCleanupEvent)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerCleanupEvent", reflect.TypeOf((*MockTrigger)(nil).TriggerCleanupEvent), arg0) } // TriggerTFCEvents mocks base method. -func (m *MockTrigger) TriggerTFCEvents() (*tfc_trigger.TriggeredTFCWorkspaces, error) { +func (m *MockTrigger) TriggerTFCEvents(arg0 context.Context) (*tfc_trigger.TriggeredTFCWorkspaces, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TriggerTFCEvents") + ret := m.ctrl.Call(m, "TriggerTFCEvents", arg0) ret0, _ := ret[0].(*tfc_trigger.TriggeredTFCWorkspaces) ret1, _ := ret[1].(error) return ret0, ret1 } // TriggerTFCEvents indicates an expected call of TriggerTFCEvents. -func (mr *MockTriggerMockRecorder) TriggerTFCEvents() *gomock.Call { +func (mr *MockTriggerMockRecorder) TriggerTFCEvents(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerTFCEvents", reflect.TypeOf((*MockTrigger)(nil).TriggerTFCEvents)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TriggerTFCEvents", reflect.TypeOf((*MockTrigger)(nil).TriggerTFCEvents), arg0) } diff --git a/pkg/mocks/mock_vcs.go b/pkg/mocks/mock_vcs.go index ee502b1..f96810f 100644 --- a/pkg/mocks/mock_vcs.go +++ b/pkg/mocks/mock_vcs.go @@ -5,6 +5,7 @@ package mocks import ( + context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -35,184 +36,184 @@ func (m *MockGitClient) EXPECT() *MockGitClientMockRecorder { } // AddMergeRequestDiscussionReply mocks base method. -func (m *MockGitClient) AddMergeRequestDiscussionReply(mrIID int, project, discussionID, comment string) (vcs.MRNote, error) { +func (m *MockGitClient) AddMergeRequestDiscussionReply(ctx context.Context, mrIID int, project, discussionID, comment string) (vcs.MRNote, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddMergeRequestDiscussionReply", mrIID, project, discussionID, comment) + ret := m.ctrl.Call(m, "AddMergeRequestDiscussionReply", ctx, mrIID, project, discussionID, comment) ret0, _ := ret[0].(vcs.MRNote) ret1, _ := ret[1].(error) return ret0, ret1 } // AddMergeRequestDiscussionReply indicates an expected call of AddMergeRequestDiscussionReply. -func (mr *MockGitClientMockRecorder) AddMergeRequestDiscussionReply(mrIID, project, discussionID, comment interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) AddMergeRequestDiscussionReply(ctx, mrIID, project, discussionID, comment interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMergeRequestDiscussionReply", reflect.TypeOf((*MockGitClient)(nil).AddMergeRequestDiscussionReply), mrIID, project, discussionID, comment) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMergeRequestDiscussionReply", reflect.TypeOf((*MockGitClient)(nil).AddMergeRequestDiscussionReply), ctx, mrIID, project, discussionID, comment) } // CloneMergeRequest mocks base method. -func (m *MockGitClient) CloneMergeRequest(arg0 string, arg1 vcs.MR, arg2 string) (vcs.GitRepo, error) { +func (m *MockGitClient) CloneMergeRequest(arg0 context.Context, arg1 string, arg2 vcs.MR, arg3 string) (vcs.GitRepo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CloneMergeRequest", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CloneMergeRequest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(vcs.GitRepo) ret1, _ := ret[1].(error) return ret0, ret1 } -func (m *MockGitClient) GetOldRunUrls(mrIID int, project string, rootNoteID int) (string, error) { +func (m *MockGitClient) GetOldRunUrls(ctx context.Context, mrIID int, project string, rootNoteID int) (string, error) { return "", nil } // CloneMergeRequest indicates an expected call of CloneMergeRequest. -func (mr *MockGitClientMockRecorder) CloneMergeRequest(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) CloneMergeRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloneMergeRequest", reflect.TypeOf((*MockGitClient)(nil).CloneMergeRequest), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloneMergeRequest", reflect.TypeOf((*MockGitClient)(nil).CloneMergeRequest), arg0, arg1, arg2, arg3) } // CreateMergeRequestComment mocks base method. -func (m *MockGitClient) CreateMergeRequestComment(id int, fullPath, comment string) error { +func (m *MockGitClient) CreateMergeRequestComment(ctx context.Context, id int, fullPath, comment string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateMergeRequestComment", id, fullPath, comment) + ret := m.ctrl.Call(m, "CreateMergeRequestComment", ctx, id, fullPath, comment) ret0, _ := ret[0].(error) return ret0 } // CreateMergeRequestComment indicates an expected call of CreateMergeRequestComment. -func (mr *MockGitClientMockRecorder) CreateMergeRequestComment(id, fullPath, comment interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) CreateMergeRequestComment(ctx, id, fullPath, comment interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestComment", reflect.TypeOf((*MockGitClient)(nil).CreateMergeRequestComment), id, fullPath, comment) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestComment", reflect.TypeOf((*MockGitClient)(nil).CreateMergeRequestComment), ctx, id, fullPath, comment) } // CreateMergeRequestDiscussion mocks base method. -func (m *MockGitClient) CreateMergeRequestDiscussion(mrID int, fullPath, comment string) (vcs.MRDiscussionNotes, error) { +func (m *MockGitClient) CreateMergeRequestDiscussion(ctx context.Context, mrID int, fullPath, comment string) (vcs.MRDiscussionNotes, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateMergeRequestDiscussion", mrID, fullPath, comment) + ret := m.ctrl.Call(m, "CreateMergeRequestDiscussion", ctx, mrID, fullPath, comment) ret0, _ := ret[0].(vcs.MRDiscussionNotes) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateMergeRequestDiscussion indicates an expected call of CreateMergeRequestDiscussion. -func (mr *MockGitClientMockRecorder) CreateMergeRequestDiscussion(mrID, fullPath, comment interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) CreateMergeRequestDiscussion(ctx, mrID, fullPath, comment interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestDiscussion", reflect.TypeOf((*MockGitClient)(nil).CreateMergeRequestDiscussion), mrID, fullPath, comment) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMergeRequestDiscussion", reflect.TypeOf((*MockGitClient)(nil).CreateMergeRequestDiscussion), ctx, mrID, fullPath, comment) } // GetMergeRequest mocks base method. -func (m *MockGitClient) GetMergeRequest(arg0 int, arg1 string) (vcs.DetailedMR, error) { +func (m *MockGitClient) GetMergeRequest(arg0 context.Context, arg1 int, arg2 string) (vcs.DetailedMR, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMergeRequest", arg0, arg1) + ret := m.ctrl.Call(m, "GetMergeRequest", arg0, arg1, arg2) ret0, _ := ret[0].(vcs.DetailedMR) ret1, _ := ret[1].(error) return ret0, ret1 } // GetMergeRequest indicates an expected call of GetMergeRequest. -func (mr *MockGitClientMockRecorder) GetMergeRequest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) GetMergeRequest(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequest", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequest", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequest), arg0, arg1, arg2) } // GetMergeRequestApprovals mocks base method. -func (m *MockGitClient) GetMergeRequestApprovals(arg0 int, arg1 string) (vcs.MRApproved, error) { +func (m *MockGitClient) GetMergeRequestApprovals(ctx context.Context, id int, project string) (vcs.MRApproved, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMergeRequestApprovals", arg0, arg1) + ret := m.ctrl.Call(m, "GetMergeRequestApprovals", ctx, id, project) ret0, _ := ret[0].(vcs.MRApproved) ret1, _ := ret[1].(error) return ret0, ret1 } // GetMergeRequestApprovals indicates an expected call of GetMergeRequestApprovals. -func (mr *MockGitClientMockRecorder) GetMergeRequestApprovals(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) GetMergeRequestApprovals(ctx, id, project interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequestApprovals", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequestApprovals), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequestApprovals", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequestApprovals), ctx, id, project) } // GetMergeRequestModifiedFiles mocks base method. -func (m *MockGitClient) GetMergeRequestModifiedFiles(mrIID int, projectID string) ([]string, error) { +func (m *MockGitClient) GetMergeRequestModifiedFiles(ctx context.Context, mrIID int, projectID string) ([]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMergeRequestModifiedFiles", mrIID, projectID) + ret := m.ctrl.Call(m, "GetMergeRequestModifiedFiles", ctx, mrIID, projectID) ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } // GetMergeRequestModifiedFiles indicates an expected call of GetMergeRequestModifiedFiles. -func (mr *MockGitClientMockRecorder) GetMergeRequestModifiedFiles(mrIID, projectID interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) GetMergeRequestModifiedFiles(ctx, mrIID, projectID interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequestModifiedFiles", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequestModifiedFiles), mrIID, projectID) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMergeRequestModifiedFiles", reflect.TypeOf((*MockGitClient)(nil).GetMergeRequestModifiedFiles), ctx, mrIID, projectID) } // GetPipelinesForCommit mocks base method. -func (m *MockGitClient) GetPipelinesForCommit(projectWithNS, commitSHA string) ([]vcs.ProjectPipeline, error) { +func (m *MockGitClient) GetPipelinesForCommit(ctx context.Context, projectWithNS, commitSHA string) ([]vcs.ProjectPipeline, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPipelinesForCommit", projectWithNS, commitSHA) + ret := m.ctrl.Call(m, "GetPipelinesForCommit", ctx, projectWithNS, commitSHA) ret0, _ := ret[0].([]vcs.ProjectPipeline) ret1, _ := ret[1].(error) return ret0, ret1 } // GetPipelinesForCommit indicates an expected call of GetPipelinesForCommit. -func (mr *MockGitClientMockRecorder) GetPipelinesForCommit(projectWithNS, commitSHA interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) GetPipelinesForCommit(ctx, projectWithNS, commitSHA interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPipelinesForCommit", reflect.TypeOf((*MockGitClient)(nil).GetPipelinesForCommit), projectWithNS, commitSHA) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPipelinesForCommit", reflect.TypeOf((*MockGitClient)(nil).GetPipelinesForCommit), ctx, projectWithNS, commitSHA) } // GetRepoFile mocks base method. -func (m *MockGitClient) GetRepoFile(arg0, arg1, arg2 string) ([]byte, error) { +func (m *MockGitClient) GetRepoFile(arg0 context.Context, arg1, arg2, arg3 string) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRepoFile", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "GetRepoFile", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // GetRepoFile indicates an expected call of GetRepoFile. -func (mr *MockGitClientMockRecorder) GetRepoFile(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) GetRepoFile(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepoFile", reflect.TypeOf((*MockGitClient)(nil).GetRepoFile), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepoFile", reflect.TypeOf((*MockGitClient)(nil).GetRepoFile), arg0, arg1, arg2, arg3) } // ResolveMergeRequestDiscussion mocks base method. -func (m *MockGitClient) ResolveMergeRequestDiscussion(arg0 string, arg1 int, arg2 string) error { +func (m *MockGitClient) ResolveMergeRequestDiscussion(arg0 context.Context, arg1 string, arg2 int, arg3 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResolveMergeRequestDiscussion", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "ResolveMergeRequestDiscussion", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // ResolveMergeRequestDiscussion indicates an expected call of ResolveMergeRequestDiscussion. -func (mr *MockGitClientMockRecorder) ResolveMergeRequestDiscussion(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) ResolveMergeRequestDiscussion(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveMergeRequestDiscussion", reflect.TypeOf((*MockGitClient)(nil).ResolveMergeRequestDiscussion), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveMergeRequestDiscussion", reflect.TypeOf((*MockGitClient)(nil).ResolveMergeRequestDiscussion), arg0, arg1, arg2, arg3) } // SetCommitStatus mocks base method. -func (m *MockGitClient) SetCommitStatus(projectWithNS, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { +func (m *MockGitClient) SetCommitStatus(ctx context.Context, projectWithNS, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetCommitStatus", projectWithNS, commitSHA, status) + ret := m.ctrl.Call(m, "SetCommitStatus", ctx, projectWithNS, commitSHA, status) ret0, _ := ret[0].(vcs.CommitStatus) ret1, _ := ret[1].(error) return ret0, ret1 } // SetCommitStatus indicates an expected call of SetCommitStatus. -func (mr *MockGitClientMockRecorder) SetCommitStatus(projectWithNS, commitSHA, status interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) SetCommitStatus(ctx, projectWithNS, commitSHA, status interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCommitStatus", reflect.TypeOf((*MockGitClient)(nil).SetCommitStatus), projectWithNS, commitSHA, status) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCommitStatus", reflect.TypeOf((*MockGitClient)(nil).SetCommitStatus), ctx, projectWithNS, commitSHA, status) } // UpdateMergeRequestDiscussionNote mocks base method. -func (m *MockGitClient) UpdateMergeRequestDiscussionNote(mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { +func (m *MockGitClient) UpdateMergeRequestDiscussionNote(ctx context.Context, mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateMergeRequestDiscussionNote", mrIID, noteID, project, discussionID, comment) + ret := m.ctrl.Call(m, "UpdateMergeRequestDiscussionNote", ctx, mrIID, noteID, project, discussionID, comment) ret0, _ := ret[0].(vcs.MRNote) ret1, _ := ret[1].(error) return ret0, ret1 } // UpdateMergeRequestDiscussionNote indicates an expected call of UpdateMergeRequestDiscussionNote. -func (mr *MockGitClientMockRecorder) UpdateMergeRequestDiscussionNote(mrIID, noteID, project, discussionID, comment interface{}) *gomock.Call { +func (mr *MockGitClientMockRecorder) UpdateMergeRequestDiscussionNote(ctx, mrIID, noteID, project, discussionID, comment interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMergeRequestDiscussionNote", reflect.TypeOf((*MockGitClient)(nil).UpdateMergeRequestDiscussionNote), mrIID, noteID, project, discussionID, comment) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMergeRequestDiscussionNote", reflect.TypeOf((*MockGitClient)(nil).UpdateMergeRequestDiscussionNote), ctx, mrIID, noteID, project, discussionID, comment) } // MockGitRepo is a mock of GitRepo interface. diff --git a/pkg/runstream/interfaces.go b/pkg/runstream/interfaces.go index 37874a9..1d57f24 100644 --- a/pkg/runstream/interfaces.go +++ b/pkg/runstream/interfaces.go @@ -1,12 +1,15 @@ package runstream -import "time" +import ( + "context" + "time" +) //go:generate mockgen -source interfaces.go -destination=../mocks/mock_runstream.go -package=mocks github.com/zapier/tfbuddy/pkg/runstream type StreamClient interface { HealthCheck() error - PublishTFRunEvent(re RunEvent) error + PublishTFRunEvent(ctx context.Context, re RunEvent) error AddRunMeta(rmd RunMetadata) error GetRunMeta(runID string) (RunMetadata, error) NewTFRunPollingTask(meta RunMetadata, delay time.Duration) RunPollingTask @@ -16,6 +19,9 @@ type StreamClient interface { type RunEvent interface { GetRunID() string + GetContext() context.Context + SetContext(context.Context) + SetCarrier(map[string]string) GetNewStatus() string GetMetadata() RunMetadata SetMetadata(RunMetadata) @@ -35,10 +41,12 @@ type RunMetadata interface { } type RunPollingTask interface { - Schedule() error - Reschedule() error + Schedule(ctx context.Context) error + Reschedule(ctx context.Context) error Completed() error GetRunID() string + GetContext() context.Context + SetCarrier(map[string]string) GetLastStatus() string SetLastStatus(string) GetRunMetaData() RunMetadata diff --git a/pkg/runstream/run_event.go b/pkg/runstream/run_event.go index 7f50b76..25abef5 100644 --- a/pkg/runstream/run_event.go +++ b/pkg/runstream/run_event.go @@ -1,6 +1,7 @@ package runstream import ( + "context" "encoding/json" "errors" "fmt" @@ -9,6 +10,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/nats-io/nats.go" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) const RunEventsStreamName = "RUN_EVENTS" @@ -20,11 +23,22 @@ type TFRunEvent struct { Workspace string NewStatus string Metadata RunMetadata + Carrier propagation.MapCarrier `json:"Carrier"` + context context.Context } func (e *TFRunEvent) GetRunID() string { return e.RunID } +func (e *TFRunEvent) GetContext() context.Context { + return e.context +} +func (e *TFRunEvent) SetContext(ctx context.Context) { + e.context = ctx +} +func (e *TFRunEvent) SetCarrier(carrier map[string]string) { + e.Carrier = carrier +} func (e *TFRunEvent) GetNewStatus() string { return e.NewStatus } @@ -34,14 +48,19 @@ func (e *TFRunEvent) GetMetadata() RunMetadata { func (e *TFRunEvent) SetMetadata(meta RunMetadata) { e.Metadata = meta } -func (s *Stream) PublishTFRunEvent(re RunEvent) error { + +func (s *Stream) PublishTFRunEvent(ctx context.Context, re RunEvent) error { + ctx, span := otel.Tracer("TF").Start(ctx, "PublishTFRunEvent") + defer span.End() + re.SetContext(ctx) + rmd, err := s.waitForTFRunMetadata(re) if err != nil { log.Error().Err(err).Msg("unable to publish TF Run Event: could not get RunMetadata") return err } - b, err := encodeTFRunEvent(re) + b, err := encodeTFRunEvent(ctx, re) if err != nil { return err } @@ -153,9 +172,13 @@ func decodeTFRunEvent(b []byte) (RunEvent, error) { run := &TFRunEvent{} run.Metadata = &TFRunMetadata{} err := json.Unmarshal(b, &run) + run.context = otel.GetTextMapPropagator().Extract(context.Background(), run.Carrier) return run, err } -func encodeTFRunEvent(run RunEvent) ([]byte, error) { +func encodeTFRunEvent(ctx context.Context, run RunEvent) ([]byte, error) { + carrier := propagation.MapCarrier(map[string]string{}) + otel.GetTextMapPropagator().Inject(ctx, carrier) + run.SetCarrier(carrier) return json.Marshal(run) } diff --git a/pkg/runstream/run_metadata.go b/pkg/runstream/run_metadata.go index 12f49b4..956cb4b 100644 --- a/pkg/runstream/run_metadata.go +++ b/pkg/runstream/run_metadata.go @@ -2,8 +2,9 @@ package runstream import ( "encoding/json" - "github.com/nats-io/nats.go" "time" + + "github.com/nats-io/nats.go" ) // ensure type complies with interface diff --git a/pkg/runstream/run_polling.go b/pkg/runstream/run_polling.go index 95a3e02..548bc86 100644 --- a/pkg/runstream/run_polling.go +++ b/pkg/runstream/run_polling.go @@ -1,12 +1,16 @@ package runstream import ( + "context" "encoding/json" "fmt" "time" "github.com/nats-io/nats.go" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" ) const RunPollingStreamNameV0 = "RUN_POLLING" @@ -32,6 +36,8 @@ type TFRunPollingTask struct { // Revision is the NATS KV entry revision Revision uint64 + ctx context.Context + Carrier propagation.MapCarrier `json:"Carrier"` } func (s *Stream) NewTFRunPollingTask(meta RunMetadata, delay time.Duration) RunPollingTask { @@ -51,14 +57,14 @@ func (s *Stream) NewTFRunPollingTask(meta RunMetadata, delay time.Duration) RunP } } -func (task *TFRunPollingTask) Schedule() error { - return task.stream.addTFRunPollingTask(task) +func (task *TFRunPollingTask) Schedule(ctx context.Context) error { + return task.stream.addTFRunPollingTask(ctx, task) } -func (task *TFRunPollingTask) Reschedule() error { +func (task *TFRunPollingTask) Reschedule(ctx context.Context) error { task.NextPoll = time.Now().Add(TaskPollingDelayDefault) task.Processing = false - return task.update() + return task.update(ctx) } func (task *TFRunPollingTask) Completed() error { @@ -67,6 +73,12 @@ func (task *TFRunPollingTask) Completed() error { func (task *TFRunPollingTask) GetRunID() string { return task.RunMetadata.GetRunID() } +func (task *TFRunPollingTask) GetContext() context.Context { + return task.ctx +} +func (task *TFRunPollingTask) SetCarrier(carrier map[string]string) { + task.Carrier = carrier +} func (task *TFRunPollingTask) GetLastStatus() string { return task.LastStatus } @@ -76,9 +88,16 @@ func (task *TFRunPollingTask) GetRunMetaData() RunMetadata { func (task *TFRunPollingTask) SetLastStatus(status string) { task.LastStatus = status } -func (task *TFRunPollingTask) update() error { +func (task *TFRunPollingTask) update(ctx context.Context) error { + ctx, span := otel.Tracer("terraform").Start(ctx, "update") + defer span.End() + task.LastUpdate = time.Now() - b, _ := encodeTFRunPollingTask(task) + span.SetAttributes( + attribute.String("runID", task.GetRunID()), + ) + + b, _ := encodeTFRunPollingTask(ctx, task) rev, err := task.stream.pollingKV.Update(pollingKVKey(task), b, task.Revision) if err != nil { // TODO: are there are errors we need to handle? @@ -89,8 +108,11 @@ func (task *TFRunPollingTask) update() error { return nil } -func (s *Stream) addTFRunPollingTask(task *TFRunPollingTask) error { - b, err := encodeTFRunPollingTask(task) +func (s *Stream) addTFRunPollingTask(ctx context.Context, task *TFRunPollingTask) error { + ctx, span := otel.Tracer("terraform").Start(ctx, "addTFRunPollingTask") + defer span.End() + + b, err := encodeTFRunPollingTask(ctx, task) if err != nil { return err } @@ -131,19 +153,21 @@ func (s *Stream) startPollingTaskDispatcher() { continue } if !task.Processing && time.Now().After(task.NextPoll) { + ctx, span := otel.Tracer("terraform").Start(task.GetContext(), "polling") // set & get processing status task.Processing = true - err := task.update() + err := task.update(ctx) if err == nil { // dispatch task and wait for response - b, _ := encodeTFRunPollingTask(task) + b, _ := encodeTFRunPollingTask(ctx, task) log.Debug().Str("runID", task.GetRunID()).Msg("enqueuing polling task") if _, err := s.js.PublishAsync(pollingStreamKey(task), b); err != nil { + span.RecordError(err) log.Error().Err(err).Msg("could not queue polling task") continue } - } + span.End() } } @@ -204,6 +228,8 @@ func (s *Stream) decodeTFRunPollingTaskKVEntry(entry nats.KeyValueEntry) (*TFRun log.Error().Err(err).Msg("unexpected error while decoding TF Run Polling Task KV entry") } + run.ctx = otel.GetTextMapPropagator().Extract(context.Background(), run.Carrier) + // backwards compat // TODO: remove once upgraded if run.RunMetadata.GetRunID() == "" { @@ -225,10 +251,15 @@ func (s *Stream) decodeTFRunPollingTask(b []byte) (*TFRunPollingTask, error) { err := json.Unmarshal(b, &run) run.stream = s + ctx := context.Background() + run.ctx = otel.GetTextMapPropagator().Extract(ctx, run.Carrier) + return run, err } -func encodeTFRunPollingTask(run *TFRunPollingTask) ([]byte, error) { +func encodeTFRunPollingTask(ctx context.Context, run *TFRunPollingTask) ([]byte, error) { + run.Carrier = make(map[string]string) + otel.GetTextMapPropagator().Inject(ctx, run.Carrier) return json.Marshal(run) } diff --git a/pkg/tfc_api/api_client.go b/pkg/tfc_api/api_client.go index 062a1ac..4e29e5e 100644 --- a/pkg/tfc_api/api_client.go +++ b/pkg/tfc_api/api_client.go @@ -7,15 +7,18 @@ import ( "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) //go:generate mockgen -source api_client.go -destination=../mocks/mock_tfc_api.go -package=mocks github.com/zapier/tfbuddy/pkg/tfc_api type ApiClient interface { GetPlanOutput(id string) ([]byte, error) - GetRun(id string) (*tfe.Run, error) + GetRun(ctx context.Context, id string) (*tfe.Run, error) GetWorkspaceByName(ctx context.Context, org, name string) (*tfe.Workspace, error) GetWorkspaceById(ctx context.Context, id string) (*tfe.Workspace, error) - CreateRunFromSource(opts *ApiRunOptions) (*tfe.Run, error) + CreateRunFromSource(ctx context.Context, opts *ApiRunOptions) (*tfe.Run, error) LockUnlockWorkspace(ctx context.Context, workspace string, reason string, tag string, lock bool) error AddTags(ctx context.Context, workspace string, prefix string, value string) error RemoveTagsByQuery(ctx context.Context, workspace string, query string) error @@ -45,9 +48,12 @@ func NewTFCClient() ApiClient { return &TFCClient{Client: tfcClient} } -func (t *TFCClient) GetRun(id string) (*tfe.Run, error) { +func (t *TFCClient) GetRun(ctx context.Context, id string) (*tfe.Run, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetTFRun", trace.WithAttributes(attribute.String("run_id", id))) + defer span.End() + run, err := t.Client.Runs.ReadWithOptions( - context.Background(), + ctx, id, &tfe.RunReadOptions{ Include: []tfe.RunIncludeOpt{tfe.RunPlan, tfe.RunWorkspace, tfe.RunConfigVer, tfe.RunApply}, @@ -73,10 +79,14 @@ func (t *TFCClient) GetPlanOutput(id string) ([]byte, error) { } func (t *TFCClient) GetWorkspaceById(ctx context.Context, id string) (*tfe.Workspace, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetTFWorkspaceById", trace.WithAttributes(attribute.String("run_id", id))) + defer span.End() return t.Client.Workspaces.ReadByID(ctx, id) } func (t *TFCClient) GetWorkspaceByName(ctx context.Context, org, name string) (*tfe.Workspace, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetTFWorkspaceByName", trace.WithAttributes(attribute.String("name", name), attribute.String("org", org))) + defer span.End() return t.Client.Workspaces.ReadWithOptions( ctx, org, @@ -86,7 +96,11 @@ func (t *TFCClient) GetWorkspaceByName(ctx context.Context, org, name string) (* } func (t *TFCClient) LockUnlockWorkspace(ctx context.Context, workspaceID string, reason string, tag string, lock bool) error { - + ctx, span := otel.Tracer("TFC").Start(ctx, "LockUnlockWorkspace", trace.WithAttributes( + attribute.String("workspaceID", workspaceID), + attribute.Bool("lock", lock), + )) + defer span.End() LockOptions := tfe.WorkspaceLockOptions{Reason: &reason} TagPrefix := "gl-lock" @@ -129,9 +143,15 @@ func (t *TFCClient) LockUnlockWorkspace(ctx context.Context, workspaceID string, // The tags take the format of prefix dash value, which is just a convention and not required by terraform cloud for naming format. // The tag, however, will be lowercased by terraform cloud, and in any retrieval operations. func (t *TFCClient) AddTags(ctx context.Context, workspace string, prefix string, value string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "AddTags", trace.WithAttributes( + attribute.String("workspace", workspace), + )) + defer span.End() LockTag := &tfe.Tag{ Name: fmt.Sprintf("%s-%s", prefix, value), } + span.SetAttributes(attribute.String("tag", LockTag.Name)) + AddTagsOptions := tfe.WorkspaceAddTagsOptions{ Tags: []*tfe.Tag{LockTag}, } @@ -147,6 +167,11 @@ func (t *TFCClient) AddTags(ctx context.Context, workspace string, prefix string // RemoveTagsByQuery removes all tags matching a query from a terraform cloud workspace. It returns an error if one is returned fom searching or removing tags.// Note: the query will match anywhere in the tag, so common substrings should be avoided. func (t *TFCClient) RemoveTagsByQuery(ctx context.Context, workspace string, query string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "RemoveTagsByQuery", trace.WithAttributes( + attribute.String("workspace", workspace), + )) + defer span.End() + taglist, err := t.GetTagsByQuery(ctx, workspace, query) if err != nil { log.Error().Err(err) @@ -179,6 +204,11 @@ func (t *TFCClient) RemoveTagsByQuery(ctx context.Context, workspace string, que // GetTagsByQuery returns a list of values of tags on a terraform workspace matching the query string. // It operates on strings reporesenting the value of the tag and internally converts it to and from the upstreams tag struct as needed. Attempting to query tags based on their tag ID will not match the tag. func (t *TFCClient) GetTagsByQuery(ctx context.Context, workspace string, query string) ([]string, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetTagsByQuery", trace.WithAttributes( + attribute.String("workspace", workspace), + )) + defer span.End() + ListTagOptions := &tfe.WorkspaceTagListOptions{ Query: &query, } diff --git a/pkg/tfc_api/api_driven_runs.go b/pkg/tfc_api/api_driven_runs.go index f74ea0c..4ad2b41 100644 --- a/pkg/tfc_api/api_driven_runs.go +++ b/pkg/tfc_api/api_driven_runs.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" ) type ApiRunOptions struct { @@ -28,8 +29,10 @@ type ApiRunOptions struct { } // CreateRunFromSource creates a new Terraform Cloud run from source files -func (c *TFCClient) CreateRunFromSource(opts *ApiRunOptions) (*tfe.Run, error) { - ctx := context.Background() +func (c *TFCClient) CreateRunFromSource(ctx context.Context, opts *ApiRunOptions) (*tfe.Run, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "CreateRunFromSource") + defer span.End() + log := log.With().Str("workspace", opts.Workspace).Logger() ws, err := c.GetWorkspaceByName(ctx, opts.Organization, opts.Workspace) diff --git a/pkg/tfc_hooks/notification.go b/pkg/tfc_hooks/notification.go index cba6aa6..2e91865 100644 --- a/pkg/tfc_hooks/notification.go +++ b/pkg/tfc_hooks/notification.go @@ -1,6 +1,7 @@ package tfc_hooks import ( + "context" "encoding/json" "fmt" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/rs/zerolog/log" "github.com/zapier/tfbuddy/pkg/runstream" "github.com/zapier/tfbuddy/pkg/tfc_api" + "go.opentelemetry.io/otel" ) var ( @@ -67,6 +69,9 @@ func NewNotificationHandler(api tfc_api.ApiClient, stream runstream.StreamClient func (h *NotificationHandler) Handler() func(c echo.Context) error { return func(c echo.Context) error { + ctx, span := otel.Tracer("TFBuddy").Start(c.Request().Context(), "NotificationHandler") + defer span.End() + labels := prometheus.Labels{ "status": "processed", } @@ -81,7 +86,7 @@ func (h *NotificationHandler) Handler() func(c echo.Context) error { log.Debug().Str("event", pretty.Sprint(event)) // do something with event - h.processNotification(&event) + h.processNotification(ctx, &event) tfcNotificationsReceived.With(labels).Inc() return c.String(http.StatusOK, "OK") @@ -108,13 +113,17 @@ type NotificationPayload struct { } `json:"notifications"` } -func (h *NotificationHandler) processNotification(n *NotificationPayload) { +func (h *NotificationHandler) processNotification(ctx context.Context, n *NotificationPayload) { + ctx, span := otel.Tracer("TFBuddy").Start(ctx, "ProcessNotification") + defer span.End() + log.Debug().Interface("NotificationPayload", *n).Msg("processNotification()") if n.RunId == "" { return } - run, err := h.api.GetRun(n.RunId) + run, err := h.api.GetRun(ctx, n.RunId) if err != nil { + span.RecordError(err) log.Error().Err(err) } runJson, _ := json.Marshal(run) @@ -127,13 +136,14 @@ func (h *NotificationHandler) processNotification(n *NotificationPayload) { "organization": n.OrganizationName, "workspace": n.WorkspaceName, } - err = h.stream.PublishTFRunEvent(&runstream.TFRunEvent{ + err = h.stream.PublishTFRunEvent(ctx, &runstream.TFRunEvent{ Organization: n.OrganizationName, Workspace: n.WorkspaceName, RunID: n.RunId, NewStatus: string(n.Notifications[0].RunStatus), }) if err != nil { + span.RecordError(err) tfcNotificationPublishFailed.With(labels).Inc() } else { tfcNotificationPublishSuccess.With(labels).Inc() diff --git a/pkg/tfc_hooks/polling.go b/pkg/tfc_hooks/polling.go index 1710ad2..ac0df45 100644 --- a/pkg/tfc_hooks/polling.go +++ b/pkg/tfc_hooks/polling.go @@ -4,14 +4,18 @@ import ( "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" "github.com/zapier/tfbuddy/pkg/runstream" + "go.opentelemetry.io/otel" ) // pollingStreamCallback processes TFC run polling tasks for speculative plans. We do not receive webhook notifications // for speculative plans, so they need to be polled instead. func (p *NotificationHandler) pollingStreamCallback(task runstream.RunPollingTask) bool { + ctx, span := otel.Tracer("TFC").Start(task.GetContext(), "PollingStreamCallback") + defer span.End() + log.Debug().Interface("task", task).Msg("TFC Run Polling Callback()") - run, err := p.api.GetRun(task.GetRunID()) + run, err := p.api.GetRun(ctx, task.GetRunID()) if err != nil { log.Error().Err(err).Str("runID", task.GetRunID()).Msg("could not get run") return false @@ -24,18 +28,24 @@ func (p *NotificationHandler) pollingStreamCallback(task runstream.RunPollingTas if string(run.Status) != task.GetLastStatus() { // Publish new RunEvent - err = p.stream.PublishTFRunEvent(&runstream.TFRunEvent{ + err = p.stream.PublishTFRunEvent(ctx, &runstream.TFRunEvent{ Organization: run.Workspace.Organization.Name, Workspace: run.Workspace.Name, RunID: run.ID, NewStatus: string(run.Status), }) + if err != nil { + span.RecordError(err) + log.Error().Err(err).Str("runID", task.GetRunID()).Msg("could not publish run event") + return false + } + } if isRunning(run) { // queue another polling task task.SetLastStatus(string(run.Status)) - if err := task.Reschedule(); err != nil { + if err := task.Reschedule(ctx); err != nil { log.Error().Err(err).Msg("could not reschedule TFC run polling task") } diff --git a/pkg/tfc_trigger/interfaces.go b/pkg/tfc_trigger/interfaces.go index ce9a638..82501a5 100644 --- a/pkg/tfc_trigger/interfaces.go +++ b/pkg/tfc_trigger/interfaces.go @@ -1,9 +1,11 @@ package tfc_trigger +import "context" + //go:generate mockgen -source interfaces.go -destination=../mocks/mock_tfc_trigger.go -package=mocks github.com/zapier/tfbuddy/pkg/tfc_trigger type Trigger interface { - TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) - TriggerCleanupEvent() error + TriggerTFCEvents(context.Context) (*TriggeredTFCWorkspaces, error) + TriggerCleanupEvent(context.Context) error GetAction() TriggerAction GetBranch() string GetCommitSHA() string diff --git a/pkg/tfc_trigger/project_config.go b/pkg/tfc_trigger/project_config.go index 56c693d..3610b7f 100644 --- a/pkg/tfc_trigger/project_config.go +++ b/pkg/tfc_trigger/project_config.go @@ -1,6 +1,7 @@ package tfc_trigger import ( + "context" "errors" "fmt" "os" @@ -12,6 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/zapier/tfbuddy/pkg/utils" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" "gopkg.in/dealancer/validate.v2" "gopkg.in/yaml.v2" ) @@ -91,11 +93,14 @@ type TFCWorkspace struct { TriggerDirs []string `yaml:"triggerDirs"` } -func getProjectConfigFile(gl vcs.GitClient, trigger *TFCTrigger) (*ProjectConfig, error) { +func getProjectConfigFile(ctx context.Context, gl vcs.GitClient, trigger *TFCTrigger) (*ProjectConfig, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "getProjectConfigFile") + defer span.End() + branches := []string{trigger.GetBranch(), "master", "main"} for _, branch := range branches { log.Debug().Msg(fmt.Sprintf("considering branch %s", branch)) - b, err := gl.GetRepoFile(trigger.GetProjectNameWithNamespace(), ProjectConfigFilename, branch) + b, err := gl.GetRepoFile(ctx, trigger.GetProjectNameWithNamespace(), ProjectConfigFilename, branch) if err != nil { log.Info().Err(err).Msg(fmt.Sprintf("no file on branch %s", branch)) continue diff --git a/pkg/tfc_trigger/tfc_trigger.go b/pkg/tfc_trigger/tfc_trigger.go index 6975bb6..cf7eaed 100644 --- a/pkg/tfc_trigger/tfc_trigger.go +++ b/pkg/tfc_trigger/tfc_trigger.go @@ -12,6 +12,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel" "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" @@ -197,7 +198,10 @@ var ( ErrWorkspaceUnlocked = errors.New("workspace is already unlocked") ) -func FindLockingMR(tags []string, thisMR string) string { +func FindLockingMR(ctx context.Context, tags []string, thisMR string) string { + ctx, span := otel.Tracer("TFC").Start(ctx, "FindLockingMR") + defer span.End() + for _, tag := range tags { tag = strings.TrimSpace(tag) log.Debug().Msg(fmt.Sprintf("processing tag: '%s'", tag)) @@ -222,22 +226,29 @@ func FindLockingMR(tags []string, thisMR string) string { // handleError both logs an error and reports it back to the Merge Request via an MR comment. // the returned error is identical to the input parameter as a convenience -func (t *TFCTrigger) handleError(err error, msg string) error { +func (t *TFCTrigger) handleError(ctx context.Context, err error, msg string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "handleError") + defer span.End() + span.RecordError(err) + log.Error().Err(err).Msg(msg) - if err := t.gl.CreateMergeRequestComment(t.GetMergeRequestIID(), t.GetProjectNameWithNamespace(), fmt.Sprintf("Error: %s: %v", msg, err)); err != nil { + if err := t.gl.CreateMergeRequestComment(ctx, t.GetMergeRequestIID(), t.GetProjectNameWithNamespace(), fmt.Sprintf("Error: %s: %v", msg, err)); err != nil { log.Error().Err(err).Msg("could not post error to Gitlab MR") } return err } // / postUpdate puts a message on a relevant MR -func (t *TFCTrigger) postUpdate(msg string) error { - return t.gl.CreateMergeRequestComment(t.GetMergeRequestIID(), t.GetProjectNameWithNamespace(), msg) +func (t *TFCTrigger) postUpdate(ctx context.Context, msg string) error { + return t.gl.CreateMergeRequestComment(ctx, t.GetMergeRequestIID(), t.GetProjectNameWithNamespace(), msg) } -func (t *TFCTrigger) getLockingMR(workspace string) string { - tags, err := t.tfc.GetTagsByQuery(context.Background(), workspace, tfPrefix) +func (t *TFCTrigger) getLockingMR(ctx context.Context, workspace string) string { + ctx, span := otel.Tracer("TFC").Start(ctx, "getLockingMR") + defer span.End() + + tags, err := t.tfc.GetTagsByQuery(ctx, workspace, tfPrefix) if err != nil { log.Error().Err(err) } @@ -245,12 +256,15 @@ func (t *TFCTrigger) getLockingMR(workspace string) string { log.Info().Msg("no tags returned") return "" } - lockingMR := FindLockingMR(tags, fmt.Sprintf("%d", t.GetMergeRequestIID())) + lockingMR := FindLockingMR(ctx, tags, fmt.Sprintf("%d", t.GetMergeRequestIID())) return lockingMR } -func (t *TFCTrigger) getTriggeredWorkspaces(modifiedFiles []string) ([]*TFCWorkspace, error) { - cfg, err := getProjectConfigFile(t.gl, t) +func (t *TFCTrigger) getTriggeredWorkspaces(ctx context.Context, modifiedFiles []string) ([]*TFCWorkspace, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "getTriggeredWorkspaces") + defer span.End() + + cfg, err := getProjectConfigFile(ctx, t.gl, t) if err != nil { if t.GetTriggerSource() == CommentTrigger { return nil, fmt.Errorf("could not read .tfbuddy.yml file for this repo. %w", err) @@ -290,7 +304,10 @@ type TriggeredTFCWorkspaces struct { Executed []string } -func (t *TFCTrigger) getModifiedWorkspaceBetweenMergeBaseTargetBranch(mr vcs.MR, repo vcs.GitRepo) (map[string]struct{}, error) { +func (t *TFCTrigger) getModifiedWorkspaceBetweenMergeBaseTargetBranch(ctx context.Context, mr vcs.MR, repo vcs.GitRepo) (map[string]struct{}, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "getModifiedWorkspaceBetweenMergeBaseTargetBranch") + defer span.End() + modifiedWSMap := make(map[string]struct{}, 0) // update the cloned repo to have the latest commits from target branch (usually main) err := repo.FetchUpstreamBranch(mr.GetTargetBranch()) @@ -314,7 +331,7 @@ func (t *TFCTrigger) getModifiedWorkspaceBetweenMergeBaseTargetBranch(mr vcs.MR, if len(targetModifiedFiles) > 0 { // use the same logic to find triggeredWorkspaces based on files modified between when the source branch was // forked and the current HEAD of the target branch - targetBranchWorkspaces, err := t.getTriggeredWorkspaces(targetModifiedFiles) + targetBranchWorkspaces, err := t.getTriggeredWorkspaces(ctx, targetModifiedFiles) if err != nil { return modifiedWSMap, fmt.Errorf("could not find modified workspaces for target branch. %w", err) } @@ -324,36 +341,44 @@ func (t *TFCTrigger) getModifiedWorkspaceBetweenMergeBaseTargetBranch(mr vcs.MR, } return modifiedWSMap, err } -func (t *TFCTrigger) getTriggeredWorkspacesForRequest(mr vcs.MR) ([]*TFCWorkspace, error) { +func (t *TFCTrigger) getTriggeredWorkspacesForRequest(ctx context.Context, mr vcs.MR) ([]*TFCWorkspace, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "getTriggeredWorkspacesForRequest") + defer span.End() - mrModifiedFiles, err := t.gl.GetMergeRequestModifiedFiles(mr.GetInternalID(), t.GetProjectNameWithNamespace()) + mrModifiedFiles, err := t.gl.GetMergeRequestModifiedFiles(ctx, mr.GetInternalID(), t.GetProjectNameWithNamespace()) if err != nil { return nil, fmt.Errorf("failed to get a list of modified files. %w", err) } log.Debug().Strs("modifiedFiles", mrModifiedFiles).Msg("modified files") - return t.getTriggeredWorkspaces(mrModifiedFiles) + return t.getTriggeredWorkspaces(ctx, mrModifiedFiles) } // cloneGitRepo will clone the git repo for a specific MR and returns the temp path to be cleaned up later -func (t *TFCTrigger) cloneGitRepo(mr vcs.MR) (vcs.GitRepo, error) { +func (t *TFCTrigger) cloneGitRepo(ctx context.Context, mr vcs.MR) (vcs.GitRepo, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "cloneGitRepo") + defer span.End() + safeProj := strings.ReplaceAll(t.GetProjectNameWithNamespace(), "/", "-") cloneDir, err := ioutil.TempDir("", fmt.Sprintf("%s-%d-*", safeProj, t.GetMergeRequestIID())) if err != nil { return nil, fmt.Errorf("could not create tmp directory. %w", err) } - repo, err := t.gl.CloneMergeRequest(t.GetProjectNameWithNamespace(), mr, cloneDir) + repo, err := t.gl.CloneMergeRequest(ctx, t.GetProjectNameWithNamespace(), mr, cloneDir) if err != nil { return nil, utils.CreatePermanentError(err) } return repo, nil } -func (t *TFCTrigger) TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) { - mr, err := t.gl.GetMergeRequest(t.GetMergeRequestIID(), t.GetProjectNameWithNamespace()) +func (t *TFCTrigger) TriggerTFCEvents(ctx context.Context) (*TriggeredTFCWorkspaces, error) { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "TriggerTFCEvents") + defer span.End() + + mr, err := t.gl.GetMergeRequest(ctx, t.GetMergeRequestIID(), t.GetProjectNameWithNamespace()) if err != nil { return nil, fmt.Errorf("could not read MergeRequest data from Gitlab API. %w", err) } - triggeredWorkspaces, err := t.getTriggeredWorkspacesForRequest(mr) + triggeredWorkspaces, err := t.getTriggeredWorkspacesForRequest(ctx, mr) if err != nil { return nil, fmt.Errorf("could not read triggered workspaces. %w", err) } @@ -363,15 +388,15 @@ func (t *TFCTrigger) TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) { } if len(triggeredWorkspaces) > 0 { - repo, err := t.cloneGitRepo(mr) + repo, err := t.cloneGitRepo(ctx, mr) if err != nil { return nil, err } defer os.Remove(repo.GetLocalDirectory()) - modifiedWSMap, err := t.getModifiedWorkspaceBetweenMergeBaseTargetBranch(mr, repo) + modifiedWSMap, err := t.getModifiedWorkspaceBetweenMergeBaseTargetBranch(ctx, mr, repo) if err != nil { - err = t.postUpdate(":warning: Could not identify modified workspaces on target branch. Please review the plan carefully for unrelated changes.") + err = t.postUpdate(ctx, ":warning: Could not identify modified workspaces on target branch. Please review the plan carefully for unrelated changes.") if err != nil { log.Error().Err(err).Msg("could not update MR with message") } @@ -395,7 +420,7 @@ func (t *TFCTrigger) TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) { }) continue } - if err := t.triggerRunForWorkspace(cfgWS, mr, repo.GetLocalDirectory()); err != nil { + if err := t.triggerRunForWorkspace(ctx, cfgWS, mr, repo.GetLocalDirectory()); err != nil { log.Error().Err(err).Msg("could not trigger Run for Workspace") workspaceStatus.Errored = append(workspaceStatus.Errored, &ErroredWorkspace{ Name: cfgWS.Name, @@ -408,7 +433,7 @@ func (t *TFCTrigger) TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) { } else if t.GetTriggerSource() == CommentTrigger { log.Error().Err(ErrNoChangesDetected) - t.postUpdate(ErrNoChangesDetected.Error()) + t.postUpdate(ctx, ErrNoChangesDetected.Error()) return nil, nil } else { @@ -419,41 +444,44 @@ func (t *TFCTrigger) TriggerTFCEvents() (*TriggeredTFCWorkspaces, error) { return workspaceStatus, nil } -func (t *TFCTrigger) TriggerCleanupEvent() error { - mr, err := t.gl.GetMergeRequest(t.GetMergeRequestIID(), t.GetProjectNameWithNamespace()) +func (t *TFCTrigger) TriggerCleanupEvent(ctx context.Context) error { + ctx, span := otel.Tracer("GitlabHandler").Start(ctx, "TriggerCleanupEvent") + defer span.End() + + mr, err := t.gl.GetMergeRequest(ctx, t.GetMergeRequestIID(), t.GetProjectNameWithNamespace()) if err != nil { return fmt.Errorf("could not read MergeRequest data from Gitlab API. %w", err) } var wsNames []string - cfg, err := getProjectConfigFile(t.gl, t) + cfg, err := getProjectConfigFile(ctx, t.gl, t) if err != nil { return fmt.Errorf("ignoring cleanup trigger for project, missing .tfbuddy.yaml. %w", err) } tag := fmt.Sprintf("%s-%d", tfPrefix, mr.GetInternalID()) for _, cfgWS := range cfg.Workspaces { - ws, err := t.tfc.GetWorkspaceByName(context.Background(), + ws, err := t.tfc.GetWorkspaceByName(ctx, cfgWS.Organization, cfgWS.Name) if err != nil { - t.handleError(err, "error getting workspace") + t.handleError(ctx, err, "error getting workspace") } - tags, err := t.tfc.GetTagsByQuery(context.Background(), + tags, err := t.tfc.GetTagsByQuery(ctx, ws.ID, tag, ) if err != nil { - t.handleError(err, "error getting tags") + t.handleError(ctx, err, "error getting tags") } if len(tags) != 0 { - err = t.tfc.RemoveTagsByQuery(context.Background(), ws.ID, tag) + err = t.tfc.RemoveTagsByQuery(ctx, ws.ID, tag) if err != nil { - t.handleError(err, "Error removing locking tag from workspace") + t.handleError(ctx, err, "Error removing locking tag from workspace") continue } wsNames = append(wsNames, cfgWS.Name) } } - _, err = t.gl.CreateMergeRequestDiscussion(mr.GetInternalID(), + _, err = t.gl.CreateMergeRequestDiscussion(ctx, mr.GetInternalID(), t.GetProjectNameWithNamespace(), fmt.Sprintf("Released locks for workspaces: %s", strings.Join(wsNames, ",")), ) @@ -487,12 +515,15 @@ func (t *TFCTrigger) LockUnlockWorkspace(ws *tfe.Workspace, mr vcs.DetailedMR, l return ErrWorkspaceUnlocked } -func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.DetailedMR, cloneDir string) error { +func (t *TFCTrigger) triggerRunForWorkspace(ctx context.Context, cfgWS *TFCWorkspace, mr vcs.DetailedMR, cloneDir string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "triggerRunForWorkspace") + defer span.End() + org := cfgWS.Organization wsName := cfgWS.Name // retrieve TFC workspace details, so we can sanity check this request. - ws, err := t.tfc.GetWorkspaceByName(context.Background(), org, wsName) + ws, err := t.tfc.GetWorkspaceByName(ctx, org, wsName) if err != nil { return fmt.Errorf("could not get Workspace from TFC API. %w", err) } @@ -509,7 +540,7 @@ func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.Detailed if err != nil { return fmt.Errorf("error modifying the TFC lock on the workspace. %w", err) } - _, err := t.gl.CreateMergeRequestDiscussion(mr.GetInternalID(), + _, err := t.gl.CreateMergeRequestDiscussion(ctx, mr.GetInternalID(), t.GetProjectNameWithNamespace(), fmt.Sprintf("Successfully %sed Workspace `%s/%s`", t.GetAction(), org, wsName), ) @@ -534,13 +565,13 @@ func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.Detailed // If the workspace is locked tell the user and don't queue a run // Otherwise, TFC wil queue an apply, which might put them out of order if isApply { - lockingMR := t.getLockingMR(ws.ID) + lockingMR := t.getLockingMR(ctx, ws.ID) if ws.Locked { return fmt.Errorf("refusing to Apply changes to a locked workspace. %w", err) } else if lockingMR != "" { return fmt.Errorf("workspace is locked by another MR! %s", lockingMR) } else { - err = t.tfc.AddTags(context.Background(), + err = t.tfc.AddTags(ctx, ws.ID, tfPrefix, fmt.Sprintf("%d", t.GetMergeRequestIID()), @@ -551,7 +582,7 @@ func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.Detailed } } // create a new Merge Request discussion thread where status updates will be nested - disc, err := t.gl.CreateMergeRequestDiscussion(mr.GetInternalID(), + disc, err := t.gl.CreateMergeRequestDiscussion(ctx, mr.GetInternalID(), t.GetProjectNameWithNamespace(), fmt.Sprintf("Starting TFC %v for Workspace: `%s/%s`.", t.GetAction(), org, wsName), ) @@ -566,7 +597,7 @@ func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.Detailed } // create new TFC run - run, err := t.tfc.CreateRunFromSource(&tfc_api.ApiRunOptions{ + run, err := t.tfc.CreateRunFromSource(ctx, &tfc_api.ApiRunOptions{ IsApply: isApply, Path: pkgDir, Message: fmt.Sprintf("MR [!%d]: %s", t.GetMergeRequestIID(), mr.GetTitle()), @@ -587,10 +618,13 @@ func (t *TFCTrigger) triggerRunForWorkspace(cfgWS *TFCWorkspace, mr vcs.Detailed Bool("speculative", run.ConfigurationVersion.Speculative). Msg("created TFC run") - return t.publishRunToStream(run) + return t.publishRunToStream(ctx, run) } -func (t *TFCTrigger) publishRunToStream(run *tfe.Run) error { +func (t *TFCTrigger) publishRunToStream(ctx context.Context, run *tfe.Run) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "publishRunToStream") + defer span.End() + rmd := &runstream.TFRunMetadata{ RunID: run.ID, Organization: run.Workspace.Organization.Name, @@ -612,7 +646,7 @@ func (t *TFCTrigger) publishRunToStream(run *tfe.Run) error { if run.ConfigurationVersion.Speculative { // TFC doesn't send Notification webhooks for speculative plans, so we need to poll for updates. task := t.runstream.NewTFRunPollingTask(rmd, 1*time.Second) - err := task.Schedule() + err := task.Schedule(ctx) if err != nil { return fmt.Errorf("failed to create TFC plan polling task. Updates may not be posted to MR. %w", err) diff --git a/pkg/tfc_trigger/tfc_trigger_test.go b/pkg/tfc_trigger/tfc_trigger_test.go index 0f216be..3c5acac 100644 --- a/pkg/tfc_trigger/tfc_trigger_test.go +++ b/pkg/tfc_trigger/tfc_trigger_test.go @@ -1,6 +1,7 @@ package tfc_trigger_test import ( + "context" "fmt" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/zapier/tfbuddy/pkg/mocks" "github.com/zapier/tfbuddy/pkg/tfc_api" "github.com/zapier/tfbuddy/pkg/tfc_trigger" + "go.opentelemetry.io/otel" ) func TestTriggerAction_String(t *testing.T) { @@ -83,7 +85,7 @@ func TestFindLockingMR(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tfc_trigger.FindLockingMR(tt.tags, tt.MR) + got := tfc_trigger.FindLockingMR(context.Background(), tt.tags, tt.MR) if got != tt.want { t.Fatalf("didn't match got: %s, want: %s", got, tt.want) } @@ -103,8 +105,8 @@ func TestTFCEvents_SingleWorkspacePlan(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC plan for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).Return(&tfe.Run{ + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC plan for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).Return(&tfe.Run{ ID: "101", Workspace: &tfe.Workspace{Name: "service-tfbuddy", Organization: &tfe.Organization{Name: "zapier-test"}, @@ -112,7 +114,7 @@ func TestTFCEvents_SingleWorkspacePlan(t *testing.T) { ConfigurationVersion: &tfe.ConfigurationVersion{Speculative: true}}, nil) mockRunPollingTask := mocks.NewMockRunPollingTask(mockCtrl) - mockRunPollingTask.EXPECT().Schedule() + mockRunPollingTask.EXPECT().Schedule(gomock.Any()) testSuite.MockStreamClient.EXPECT().NewTFRunPollingTask(gomock.Any(), time.Second*1).Return(mockRunPollingTask) @@ -127,7 +129,7 @@ func TestTFCEvents_SingleWorkspacePlan(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + triggeredWS, err := trigger.TriggerTFCEvents(context.Background()) if err != nil { t.Fatal(err) } @@ -155,9 +157,9 @@ func TestTFCEvents_SingleWorkspacePlanError(t *testing.T) { defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC plan for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC plan for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).Return(nil, fmt.Errorf("could not create run from source")) + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("could not create run from source")) testSuite.InitTestSuite() @@ -173,7 +175,7 @@ func TestTFCEvents_SingleWorkspacePlanError(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + triggeredWS, err := trigger.TriggerTFCEvents(context.Background()) if err != nil { t.Fatal(err) } @@ -208,7 +210,7 @@ func TestTFCEvents_SingleWorkspaceApply(t *testing.T) { defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) testSuite.MockGitRepo.EXPECT().GetModifiedFileNamesBetweenCommits(testSuite.MetaData.CommonSHA, "main").Return([]string{}, nil) - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).Return(&tfe.Run{ + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).Return(&tfe.Run{ ID: "101", Workspace: &tfe.Workspace{Name: "service-tfbuddy", Organization: &tfe.Organization{Name: "zapier-test"}, @@ -230,7 +232,8 @@ func TestTFCEvents_SingleWorkspaceApply(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + ctx, _ := otel.Tracer("FAKE").Start(context.Background(), "TEST") + triggeredWS, err := trigger.TriggerTFCEvents(ctx) if err != nil { t.Fatal(err) return @@ -271,16 +274,16 @@ func TestTFCEvents_MultiWorkspaceApply(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) - testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf", "staging/terraform.tf"}, nil) + testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf", "staging/terraform.tf"}, nil) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy-staging`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy-staging`.").Return(testSuite.MockGitDisc, nil) - testSuite.MockApiClient.EXPECT().GetWorkspaceByName(gomock.Any(), "zapier-test", gomock.Any()).DoAndReturn(func(a interface{}, b, c string) (*tfe.Workspace, error) { + testSuite.MockApiClient.EXPECT().GetWorkspaceByName(gomock.Any(), "zapier-test", gomock.Any()).DoAndReturn(func(a interface{}, c, d string) (*tfe.Workspace, error) { return &tfe.Workspace{ID: c}, nil }).AnyTimes() - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).DoAndReturn(func(opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { return &tfe.Run{ ID: "101", Workspace: &tfe.Workspace{Name: opts.Workspace, @@ -303,7 +306,8 @@ func TestTFCEvents_MultiWorkspaceApply(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + ctx, _ := otel.Tracer("FAKE").Start(context.Background(), "TEST") + triggeredWS, err := trigger.TriggerTFCEvents(ctx) if err != nil { t.Fatal(err) return @@ -345,9 +349,9 @@ func TestTFCEvents_SingleWorkspaceApplyError(t *testing.T) { defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) - testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil) - testSuite.MockGitClient.EXPECT().CloneMergeRequest(testSuite.MetaData.ProjectNameNS, gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("could not clone repo")) - testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Error: could not clone repo: could not clone repo").MaxTimes(2) + testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil) + testSuite.MockGitClient.EXPECT().CloneMergeRequest(gomock.Any(), testSuite.MetaData.ProjectNameNS, gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("could not clone repo")) + testSuite.MockGitClient.EXPECT().CreateMergeRequestComment(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Error: could not clone repo: could not clone repo").MaxTimes(2) testSuite.InitTestSuite() @@ -363,8 +367,7 @@ func TestTFCEvents_SingleWorkspaceApplyError(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - - triggeredWS, err := trigger.TriggerTFCEvents() + triggeredWS, err := trigger.TriggerTFCEvents(context.Background()) if err == nil { t.Fatal("expected error to be returned") return @@ -401,16 +404,16 @@ func TestTFCEvents_MultiWorkspaceApplyError(t *testing.T) { defer mockCtrl.Finish() testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{ProjectConfig: ws}, t) - testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf", "staging/terraform.tf"}, nil) + testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf", "staging/terraform.tf"}, nil) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) - testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy-staging`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy`.").Return(testSuite.MockGitDisc, nil) + testSuite.MockGitClient.EXPECT().CreateMergeRequestDiscussion(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS, "Starting TFC apply for Workspace: `zapier-test/service-tfbuddy-staging`.").Return(testSuite.MockGitDisc, nil) testSuite.MockApiClient.EXPECT().GetWorkspaceByName(gomock.Any(), "zapier-test", gomock.Any()).DoAndReturn(func(a interface{}, b, c string) (*tfe.Workspace, error) { return &tfe.Workspace{ID: c}, nil }).AnyTimes() - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).DoAndReturn(func(opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, opts *tfc_api.ApiRunOptions) (*tfe.Run, error) { if opts.Workspace == "service-tfbuddy" { return nil, fmt.Errorf("api error with terraform cloud") } @@ -433,7 +436,8 @@ func TestTFCEvents_MultiWorkspaceApplyError(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + ctx, _ := otel.Tracer("FAKE").Start(context.Background(), "TEST") + triggeredWS, err := trigger.TriggerTFCEvents(ctx) if err != nil { t.Fatal(err) return @@ -464,7 +468,7 @@ func TestTFCEvents_WorkspaceApplyModifiedBothSrcDstBranches(t *testing.T) { testSuite := mocks.CreateTestSuite(mockCtrl, mocks.TestOverrides{}, t) testSuite.MockGitRepo.EXPECT().GetModifiedFileNamesBetweenCommits(testSuite.MetaData.CommonSHA, "main").Return([]string{"terraform.tf"}, nil) - testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil) + testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"main.tf"}, nil) mockStreamClient := mocks.NewMockStreamClient(mockCtrl) @@ -482,7 +486,8 @@ func TestTFCEvents_WorkspaceApplyModifiedBothSrcDstBranches(t *testing.T) { TriggerSource: tfc_trigger.CommentTrigger, }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, mockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + ctx, _ := otel.Tracer("FAKE").Start(context.Background(), "TEST") + triggeredWS, err := trigger.TriggerTFCEvents(ctx) if err != nil { t.Fatal(err) return @@ -522,10 +527,10 @@ func TestTFCEvents_MultiWorkspaceApplyModifiedBothSrcDstBranches(t *testing.T) { Dir: "production", }}}, }, t) - testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"/production/main.tf"}, nil).AnyTimes() + testSuite.MockGitClient.EXPECT().GetMergeRequestModifiedFiles(gomock.Any(), testSuite.MetaData.MRIID, testSuite.MetaData.ProjectNameNS).Return([]string{"/production/main.tf"}, nil).AnyTimes() testSuite.MockGitRepo.EXPECT().GetModifiedFileNamesBetweenCommits(testSuite.MetaData.CommonSHA, testSuite.MetaData.TargetBranch).Return([]string{"/staging/terraform.tf"}, nil).AnyTimes() - testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any()).Return(&tfe.Run{ + testSuite.MockApiClient.EXPECT().CreateRunFromSource(gomock.Any(), gomock.Any()).Return(&tfe.Run{ ID: "101", Workspace: &tfe.Workspace{Name: "service-tfbuddy", Organization: &tfe.Organization{Name: "zapier-test"}, @@ -533,7 +538,7 @@ func TestTFCEvents_MultiWorkspaceApplyModifiedBothSrcDstBranches(t *testing.T) { ConfigurationVersion: &tfe.ConfigurationVersion{Speculative: true}}, nil) mockRunPollingTask := mocks.NewMockRunPollingTask(mockCtrl) - mockRunPollingTask.EXPECT().Schedule() + mockRunPollingTask.EXPECT().Schedule(gomock.Any()) testSuite.MockStreamClient.EXPECT().NewTFRunPollingTask(gomock.Any(), time.Second*1).Return(mockRunPollingTask) testSuite.InitTestSuite() @@ -550,7 +555,8 @@ func TestTFCEvents_MultiWorkspaceApplyModifiedBothSrcDstBranches(t *testing.T) { CommitSHA: "abcd12233", }) trigger := tfc_trigger.NewTFCTrigger(testSuite.MockGitClient, testSuite.MockApiClient, testSuite.MockStreamClient, tCfg) - triggeredWS, err := trigger.TriggerTFCEvents() + ctx, _ := otel.Tracer("FAKE").Start(context.Background(), "TEST") + triggeredWS, err := trigger.TriggerTFCEvents(ctx) if err != nil { t.Fatal(err) return diff --git a/pkg/tfc_utils/ci_job_lock.go b/pkg/tfc_utils/ci_job_lock.go index 00735a2..cf4a0dd 100644 --- a/pkg/tfc_utils/ci_job_lock.go +++ b/pkg/tfc_utils/ci_job_lock.go @@ -5,10 +5,12 @@ import ( tfe "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" ) -func LockUnlockWorkspace(token, workspace string, lock bool, lockReason string) { - ctx := context.Background() +func LockUnlockWorkspace(ctx context.Context, token, workspace string, lock bool, lockReason string) { + ctx, span := otel.Tracer("TFC").Start(ctx, "LockUnlockWorkspace") + defer span.End() log.Info().Str("workspace", workspace).Msgf("LockUnlockWorkspace for workspace.") diff --git a/pkg/tfc_utils/ci_job_run_status.go b/pkg/tfc_utils/ci_job_run_status.go index b5266e5..d7a8d28 100644 --- a/pkg/tfc_utils/ci_job_run_status.go +++ b/pkg/tfc_utils/ci_job_run_status.go @@ -1,6 +1,7 @@ package tfc_utils import ( + "context" "fmt" "log" "os" @@ -11,6 +12,7 @@ import ( "github.com/zapier/tfbuddy/pkg/tfc_api" "github.com/zapier/tfbuddy/pkg/vcs/gitlab" + "go.opentelemetry.io/otel" tfe "github.com/hashicorp/go-tfe" gogitlab "github.com/xanzy/go-gitlab" @@ -43,10 +45,11 @@ const MR_RUN_DETAILS_FORMAT = ` func MonitorRunStatus() { glClient = gitlab.NewGitlabClient() tfcClient = tfc_api.NewTFCClient() + ctx := context.Background() projectID := os.Getenv("CI_PROJECT_ID") sha := os.Getenv("CI_COMMIT_SHA") - statuses := glClient.GetCommitStatuses(projectID, sha) + statuses := glClient.GetCommitStatuses(ctx, projectID, sha) commentBody := "" wg := sync.WaitGroup{} for _, s := range statuses { @@ -64,29 +67,29 @@ func MonitorRunStatus() { commentBody += fmt.Sprintf(MR_RUN_DETAILS_FORMAT, workspace, s.Status, s.TargetURL, s.TargetURL, description) st := s wg.Add(1) - go waitForRunCompletionOrFailure(&wg, st, workspace, runID) + go waitForRunCompletionOrFailure(ctx, &wg, st, workspace, runID) case "success": fallthrough case "failed": // get run summary - postRunSummary(s, workspace, runID) + postRunSummary(ctx, s, workspace, runID) } } } } - postCommentBody(commentBody) + postCommentBody(ctx, commentBody) wg.Wait() } -func postCommentBody(commentBody string) { +func postCommentBody(ctx context.Context, commentBody string) { if commentBody != "" { projectID := os.Getenv("CI_PROJECT_ID") mrIID, err := strconv.Atoi(os.Getenv("CI_MERGE_REQUEST_IID")) if err != nil { log.Printf("erroring posting comment: %v", err) } - glClient.CreateMergeRequestComment(mrIID, projectID, fmt.Sprintf(MR_COMMENT_FORMAT, commentBody)) + glClient.CreateMergeRequestComment(ctx, mrIID, projectID, fmt.Sprintf(MR_COMMENT_FORMAT, commentBody)) } } @@ -104,9 +107,9 @@ var failedPlanSummaryFormat = ` *Click Terraform Cloud URL to see detailed plan output* ` -func postRunSummary(commitStatus *gogitlab.CommitStatus, wsName, runID string) { +func postRunSummary(ctx context.Context, commitStatus *gogitlab.CommitStatus, wsName, runID string) { //run, _ := tfcClient.Client.Runs.ReadWithOptions(context.Background(), runID, &tfe.RunReadOptions{Include: "plan"}) - run, err := tfcClient.GetRun(runID) + run, err := tfcClient.GetRun(ctx, runID) if err != nil { log.Printf("err: %v\n", err) return @@ -122,10 +125,12 @@ func postRunSummary(commitStatus *gogitlab.CommitStatus, wsName, runID string) { } commentBody := fmt.Sprintf(MR_RUN_DETAILS_FORMAT, wsName, run.Status, commitStatus.TargetURL, commitStatus.TargetURL, description) - postCommentBody(commentBody) + postCommentBody(ctx, commentBody) } -func waitForRunCompletionOrFailure(wg *sync.WaitGroup, commitStatus *gogitlab.CommitStatus, wsName, runID string) { +func waitForRunCompletionOrFailure(ctx context.Context, wg *sync.WaitGroup, commitStatus *gogitlab.CommitStatus, wsName, runID string) { + ctx, span := otel.Tracer("TFC").Start(ctx, "waitForRunCompletionOrFailure") + defer span.End() defer wg.Done() attempts := 360 @@ -135,7 +140,7 @@ func waitForRunCompletionOrFailure(wg *sync.WaitGroup, commitStatus *gogitlab.Co time.Sleep(retryInterval) log.Println("Reading Run details.", runID) - run, err := tfcClient.GetRun(runID) + run, err := tfcClient.GetRun(ctx, runID) if err != nil { log.Printf("err: %v\n", err) } @@ -145,7 +150,7 @@ func waitForRunCompletionOrFailure(wg *sync.WaitGroup, commitStatus *gogitlab.Co continue } - postRunSummary(commitStatus, wsName, runID) + postRunSummary(ctx, commitStatus, wsName, runID) break } } diff --git a/pkg/tfc_utils/ci_job_runner.go b/pkg/tfc_utils/ci_job_runner.go index c0bad3d..b45caef 100644 --- a/pkg/tfc_utils/ci_job_runner.go +++ b/pkg/tfc_utils/ci_job_runner.go @@ -2,7 +2,6 @@ package tfc_utils import ( "context" - "fmt" "strings" tfe "github.com/hashicorp/go-tfe" @@ -149,5 +148,5 @@ func canCancelRun(run *tfe.Run) bool { } func printRunInfo(run *tfe.Run, title, wsName string) { - fmt.Printf(RunInfo, title, run.ID, run.Status, run.Source, run.Message, wsName, run.ID) + log.Printf(RunInfo, title, run.ID, run.Status, run.Source, run.Message, wsName, run.ID) } diff --git a/pkg/vcs/github/client.go b/pkg/vcs/github/client.go index e1a4b69..85d624c 100644 --- a/pkg/vcs/github/client.go +++ b/pkg/vcs/github/client.go @@ -18,6 +18,7 @@ import ( zgit "github.com/zapier/tfbuddy/pkg/git" "github.com/zapier/tfbuddy/pkg/utils" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" "golang.org/x/oauth2" ) @@ -53,8 +54,11 @@ func NewGithubClient() *Client { } } -func (c *Client) GetMergeRequestApprovals(id int, project string) (vcs.MRApproved, error) { - pr, err := c.GetPullRequest(project, id) +func (c *Client) GetMergeRequestApprovals(ctx context.Context, id int, project string) (vcs.MRApproved, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetMergeRequestApprovals") + defer span.End() + + pr, err := c.GetPullRequest(ctx, project, id) if err != nil { return nil, err } @@ -62,7 +66,10 @@ func (c *Client) GetMergeRequestApprovals(id int, project string) (vcs.MRApprove } // Go over all comments on a PR, trying to grab any old TFC run urls and deleting the bodies -func (c *Client) GetOldRunUrls(prID int, fullName string, rootCommentID int) (string, error) { +func (c *Client) GetOldRunUrls(ctx context.Context, prID int, fullName string, rootCommentID int) (string, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetOldRunURLs") + defer span.End() + log.Debug().Msg("pruneComments") projectParts, err := splitFullName(fullName) if err != nil { @@ -134,26 +141,38 @@ func (c *Client) GetOldRunUrls(prID int, fullName string, rootCommentID int) (st return oldRunBlock, nil } -func (c *Client) CreateMergeRequestComment(prID int, fullName string, comment string) error { - _, err := c.PostIssueComment(prID, fullName, comment) +func (c *Client) CreateMergeRequestComment(ctx context.Context, prID int, fullName string, comment string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "CreateMergeRequestComment") + defer span.End() + + _, err := c.PostIssueComment(ctx, prID, fullName, comment) return err } -func (c *Client) CreateMergeRequestDiscussion(prID int, fullName string, comment string) (vcs.MRDiscussionNotes, error) { +func (c *Client) CreateMergeRequestDiscussion(ctx context.Context, prID int, fullName string, comment string) (vcs.MRDiscussionNotes, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "CreateMergeRequestDiscussion") + defer span.End() + // GitHub doesn't support discussion threads AFAICT. - iss, err := c.PostIssueComment(prID, fullName, comment) + iss, err := c.PostIssueComment(ctx, prID, fullName, comment) return &GithubPRIssueComment{iss}, err } -func (c *Client) GetMergeRequest(prID int, fullName string) (vcs.DetailedMR, error) { - pr, err := c.GetPullRequest(fullName, prID) +func (c *Client) GetMergeRequest(ctx context.Context, prID int, fullName string) (vcs.DetailedMR, error) { + ctx, span := otel.Tracer("hooks").Start(ctx, "GetMergeRequest") + defer span.End() + + pr, err := c.GetPullRequest(ctx, fullName, prID) if err != nil { return nil, err } return pr, nil } -func (c *Client) GetRepoFile(fullName string, file string, ref string) ([]byte, error) { +func (c *Client) GetRepoFile(ctx context.Context, fullName string, file string, ref string) ([]byte, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetRepoFile") + defer span.End() + if ref == "" { ref = "HEAD" } @@ -176,8 +195,11 @@ func (c *Client) GetRepoFile(fullName string, file string, ref string) ([]byte, }, createBackOffWithRetries()) } -func (c *Client) GetMergeRequestModifiedFiles(prID int, fullName string) ([]string, error) { - pr, err := c.GetPullRequest(fullName, prID) +func (c *Client) GetMergeRequestModifiedFiles(ctx context.Context, prID int, fullName string) ([]string, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetMergeRequestModifiedFiles") + defer span.End() + + pr, err := c.GetPullRequest(ctx, fullName, prID) if err != nil { return nil, err } @@ -208,13 +230,16 @@ func (c *Client) GetMergeRequestModifiedFiles(prID int, fullName string) ([]stri const GITHUB_CLONE_DEPTH_ENV = "TFBUDDY_GITHUB_CLONE_DEPTH" -func (c *Client) CloneMergeRequest(project string, mr vcs.MR, dest string) (vcs.GitRepo, error) { +func (c *Client) CloneMergeRequest(ctx context.Context, project string, mr vcs.MR, dest string) (vcs.GitRepo, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "CloneMergeRequest") + defer span.End() + parts, err := splitFullName(project) if err != nil { return nil, err } - repo, _, err := c.client.Repositories.Get(context.Background(), parts[0], parts[1]) + repo, _, err := c.client.Repositories.Get(ctx, parts[0], parts[1]) if err != nil { return nil, utils.CreatePermanentError(err) } @@ -265,57 +290,69 @@ func (c *Client) CloneMergeRequest(project string, mr vcs.MR, dest string) (vcs. return zgit.NewRepository(gitRepo, auth, dest), nil } -func (c *Client) UpdateMergeRequestDiscussionNote(mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { +func (c *Client) UpdateMergeRequestDiscussionNote(ctx context.Context, mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { //TODO implement me //panic("implement me") return nil, nil } -func (c *Client) ResolveMergeRequestDiscussion(s string, i int, s2 string) error { +func (c *Client) ResolveMergeRequestDiscussion(ctx context.Context, s string, i int, s2 string) error { // This is a NoOp on GitHub return nil } -func (c *Client) AddMergeRequestDiscussionReply(prID int, fullName, discussionID, comment string) (vcs.MRNote, error) { +func (c *Client) AddMergeRequestDiscussionReply(ctx context.Context, prID int, fullName, discussionID, comment string) (vcs.MRNote, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "AddMergeRequestDiscussionReply") + defer span.End() + // GitHub doesn't support discussion threads AFAICT. - iss, err := c.PostIssueComment(prID, fullName, comment) + iss, err := c.PostIssueComment(ctx, prID, fullName, comment) return &IssueComment{iss}, err } -func (c *Client) SetCommitStatus(projectWithNS string, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { +func (c *Client) SetCommitStatus(ctx context.Context, projectWithNS string, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { //TODO implement me return nil, nil } -func (c *Client) GetPipelinesForCommit(projectWithNS string, commitSHA string) ([]vcs.ProjectPipeline, error) { +func (c *Client) GetPipelinesForCommit(ctx context.Context, projectWithNS string, commitSHA string) ([]vcs.ProjectPipeline, error) { //TODO implement me return nil, nil } -func (c *Client) GetIssue(owner *gogithub.User, repo string, issueId int) (*gogithub.Issue, error) { +func (c *Client) GetIssue(ctx context.Context, owner *gogithub.User, repo string, issueId int) (*gogithub.Issue, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetIssue") + defer span.End() + owName, err := ResolveOwnerName(owner) if err != nil { return nil, utils.CreatePermanentError(err) } return backoff.RetryWithData(func() (*gogithub.Issue, error) { - iss, _, err := c.client.Issues.Get(context.Background(), owName, repo, issueId) + iss, _, err := c.client.Issues.Get(ctx, owName, repo, issueId) return iss, utils.CreatePermanentError(err) }, createBackOffWithRetries()) } -func (c *Client) GetPullRequest(fullName string, prID int) (*GithubPR, error) { +func (c *Client) GetPullRequest(ctx context.Context, fullName string, prID int) (*GithubPR, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "GetPullRequest") + defer span.End() + parts, err := splitFullName(fullName) if err != nil { return nil, err } return backoff.RetryWithData(func() (*GithubPR, error) { - pr, _, err := c.client.PullRequests.Get(c.ctx, parts[0], parts[1], prID) + pr, _, err := c.client.PullRequests.Get(ctx, parts[0], parts[1], prID) return &GithubPR{pr}, utils.CreatePermanentError(err) }, createBackOffWithRetries()) } // PostIssueComment adds a comment to an existing Pull Request -func (c *Client) PostIssueComment(prId int, fullName string, body string) (*gogithub.IssueComment, error) { +func (c *Client) PostIssueComment(ctx context.Context, prId int, fullName string, body string) (*gogithub.IssueComment, error) { + ctx, span := otel.Tracer("TFC").Start(ctx, "PostIssueComment") + defer span.End() + projectParts, err := splitFullName(fullName) if err != nil { return nil, utils.CreatePermanentError(err) @@ -324,7 +361,7 @@ func (c *Client) PostIssueComment(prId int, fullName string, body string) (*gogi comment := &gogithub.IssueComment{ Body: String(body), } - iss, _, err := c.client.Issues.CreateComment(context.Background(), projectParts[0], projectParts[1], prId, comment) + iss, _, err := c.client.Issues.CreateComment(ctx, projectParts[0], projectParts[1], prId, comment) if err != nil { log.Error().Err(err).Msg("github client: could not post issue comment") } @@ -334,7 +371,10 @@ func (c *Client) PostIssueComment(prId int, fullName string, body string) (*gogi } // PostPullRequestComment adds a review comment to an existing PullRequest -func (c *Client) PostPullRequestComment(owner, repo string, prId int, body string) error { +func (c *Client) PostPullRequestComment(ctx context.Context, owner, repo string, prId int, body string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "PostPullRequestComment") + defer span.End() + // TODO: this is broken return backoff.Retry(func() error { comment := &gogithub.PullRequestComment{ diff --git a/pkg/vcs/github/hooks/github_hooks_handler.go b/pkg/vcs/github/hooks/github_hooks_handler.go index 1578bcd..67c20a5 100644 --- a/pkg/vcs/github/hooks/github_hooks_handler.go +++ b/pkg/vcs/github/hooks/github_hooks_handler.go @@ -1,7 +1,7 @@ package hooks import ( - "fmt" + "context" "os" "github.com/cbrgm/githubevents/githubevents" @@ -15,6 +15,9 @@ import ( "github.com/zapier/tfbuddy/pkg/tfc_api" "github.com/zapier/tfbuddy/pkg/tfc_trigger" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) type TriggerCreationFunc func( @@ -71,27 +74,37 @@ func NewGithubHooksHandler(vcs vcs.GitClient, tfc tfc_api.ApiClient, rs runstrea func (h *GithubHooksHandler) Handler(c echo.Context) error { err := h.ghEvents.HandleEventRequest(c.Request()) if err != nil { - fmt.Printf("error: %v\n", err) + log.Printf("error: %v\n", err) } return c.String(200, "NOK") } func onError(deliveryID string, eventName string, event interface{}, err error) error { + _, span := otel.Tracer("GithubEvents").Start(context.Background(), "Github - ErrorHookHandler") + defer span.End() + log.Warn().Str("deliveryID", deliveryID).Str("eventName", eventName).Err(err).Msg("GitHub hook handler error") lbl := prometheus.Labels{} lbl["event-type"] = eventName lbl["repository"] = "" + span.RecordError(err, trace.WithAttributes( + attribute.String("event-type", eventName), + attribute.String("deliveryID", deliveryID), + )) log.Error().Err(err).Msg("unexpected error while processing Github event") githubWebHookFailed.With(lbl).Inc() return nil } func (h *GithubHooksHandler) handleIssueCommentCreatedEvent(deliveryID string, eventName string, event *github.IssueCommentEvent) error { + ctx, span := otel.Tracer("GithubHandler").Start(context.Background(), "Github - HooksHandler") + defer span.End() + lbls := prometheus.Labels{ "eventType": eventName, "repository": *event.Repo.FullName, } - _, err := h.commentStream.Publish(&GithubIssueCommentEventMsg{payload: event}) + _, err := h.commentStream.Publish(ctx, &GithubIssueCommentEventMsg{Payload: event}) if err != nil { githubWebHookFailed.With(lbls).Inc() } diff --git a/pkg/vcs/github/hooks/stream_types.go b/pkg/vcs/github/hooks/stream_types.go index 44f27b4..436248f 100644 --- a/pkg/vcs/github/hooks/stream_types.go +++ b/pkg/vcs/github/hooks/stream_types.go @@ -1,12 +1,17 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/google/go-github/v49/github" "github.com/rs/zerolog/log" "github.com/zapier/tfbuddy/pkg/hooks_stream" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" ) // ---------------------------------------------------------------------------- @@ -24,26 +29,35 @@ func getGithubJetstreamSubject(evtType string) string { const PullRequestEventType = "PullRequestEvent" type PullRequestEventMsg struct { - payload *github.PullRequestEvent + Payload *github.PullRequestEvent `json:"payload"` + Carrier propagation.MapCarrier `json:"Carrier"` + Context context.Context } -func (e *PullRequestEventMsg) GetId() string { - return *e.payload.PullRequest.URL +func (e *PullRequestEventMsg) GetId(ctx context.Context) string { + return *e.Payload.PullRequest.URL } func (e *PullRequestEventMsg) DecodeEventData(b []byte) error { log.Debug().RawJSON("event_data", b).Msg("worker got PR event") - d := &github.PullRequestEvent{} - err := json.Unmarshal(b, d) + err := json.Unmarshal(b, e) if err != nil { return err } - e.payload = d + e.Context = otel.GetTextMapPropagator().Extract(context.Background(), e.Carrier) return nil } -func (e *PullRequestEventMsg) EncodeEventData() []byte { - b, _ := json.Marshal(e.payload) +func (e *PullRequestEventMsg) EncodeEventData(ctx context.Context) []byte { + ctx, span := otel.Tracer("hooks").Start(ctx, "encode_event_data", + trace.WithAttributes( + attribute.String("event_type", "PullRequestEvent"), + attribute.String("vcs", "github"), + )) + defer span.End() + e.Carrier = make(map[string]string) + otel.GetTextMapPropagator().Inject(ctx, e.Carrier) + b, _ := json.Marshal(e) return b } @@ -51,28 +65,37 @@ func (e *PullRequestEventMsg) EncodeEventData() []byte { const IssueCommentEvent = "IssueCommentEvent" type GithubIssueCommentEventMsg struct { - payload *github.IssueCommentEvent + Payload *github.IssueCommentEvent `json:"payload"` + Carrier propagation.MapCarrier `json:"Carrier"` + Context context.Context } -func (e *GithubIssueCommentEventMsg) GetId() string { - return fmt.Sprintf("%d", *e.payload.Comment.ID) +func (e *GithubIssueCommentEventMsg) GetId(ctx context.Context) string { + return fmt.Sprintf("%d", *e.Payload.Comment.ID) } func (e *GithubIssueCommentEventMsg) DecodeEventData(b []byte) error { log.Trace().RawJSON("event_data", b).Msg("decoding issue_comment event") - - p := &github.IssueCommentEvent{} - err := json.Unmarshal(b, p) + err := json.Unmarshal(b, e) if err != nil { log.Error().Err(err).Msg("could not decode Github IssueCommentEvent") return err } - e.payload = p + + e.Context = otel.GetTextMapPropagator().Extract(context.Background(), e.Carrier) return nil } -func (e *GithubIssueCommentEventMsg) EncodeEventData() []byte { - b, err := json.Marshal(e.payload) +func (e *GithubIssueCommentEventMsg) EncodeEventData(ctx context.Context) []byte { + ctx, span := otel.Tracer("hooks").Start(ctx, "encode_event_data", + trace.WithAttributes( + attribute.String("event_type", "issue_comment_event"), + attribute.String("vcs", "github"), + )) + defer span.End() + e.Carrier = make(map[string]string) + otel.GetTextMapPropagator().Inject(ctx, e.Carrier) + b, err := json.Marshal(e) if err != nil { log.Error().Err(err).Msg("could not encode github IssueCommentEvent") } diff --git a/pkg/vcs/github/hooks/stream_worker.go b/pkg/vcs/github/hooks/stream_worker.go index 0831728..cbfb907 100644 --- a/pkg/vcs/github/hooks/stream_worker.go +++ b/pkg/vcs/github/hooks/stream_worker.go @@ -1,6 +1,7 @@ package hooks import ( + "context" "errors" "fmt" @@ -11,9 +12,13 @@ import ( "github.com/zapier/tfbuddy/pkg/tfc_trigger" "github.com/zapier/tfbuddy/pkg/utils" "github.com/zapier/tfbuddy/pkg/vcs/github" + "go.opentelemetry.io/otel" ) func (h *GithubHooksHandler) processIssueCommentEvent(msg *GithubIssueCommentEventMsg) error { + ctx, span := otel.Tracer("hooks").Start(msg.Context, "processIssueCommentEvent") + defer span.End() + var commentErr error defer func() { if r := recover(); r != nil { @@ -21,17 +26,20 @@ func (h *GithubHooksHandler) processIssueCommentEvent(msg *GithubIssueCommentEve commentErr = nil } }() - commentErr = h.processIssueComment(msg) + commentErr = h.processIssueComment(ctx, msg) return utils.EmitPermanentError(commentErr, func(err error) { log.Error().Msgf("got a permanent error attempting to process comment event: %s", err.Error()) }) } -func (h *GithubHooksHandler) processIssueComment(msg *GithubIssueCommentEventMsg) error { - if msg == nil || msg.payload == nil { +func (h *GithubHooksHandler) processIssueComment(ctx context.Context, msg *GithubIssueCommentEventMsg) error { + ctx, span := otel.Tracer("hooks").Start(ctx, "processIssueComment") + defer span.End() + + if msg == nil || msg.Payload == nil { return errors.New("msg is nil") } - event := msg.payload + event := msg.Payload // Check if fullName is allowed log.Debug().Str("repo", *event.Repo.FullName).Msg("processIssueCommentEvent") @@ -44,7 +52,7 @@ func (h *GithubHooksHandler) processIssueComment(msg *GithubIssueCommentEventMsg opts, err := comment_actions.ParseCommentCommand(*event.Comment.Body) if err != nil { if err == comment_actions.ErrOtherTFTool { - h.postPullRequestComment(event, "Use 'tfc' to interact with TFBuddy") + h.postPullRequestComment(ctx, event, "Use 'tfc' to interact with TFBuddy") } if err == comment_actions.ErrNotTFCCommand || err == comment_actions.ErrOtherTFTool { githubWebHookIgnored.WithLabelValues( @@ -57,7 +65,7 @@ func (h *GithubHooksHandler) processIssueComment(msg *GithubIssueCommentEventMsg return err } - pr, err := h.vcs.GetMergeRequest(*event.Issue.Number, event.GetRepo().GetFullName()) + pr, err := h.vcs.GetMergeRequest(ctx, *event.Issue.Number, event.GetRepo().GetFullName()) if err != nil { log.Error().Err(err).Msg("could not process GitHub IssueCommentEvent") return err @@ -85,12 +93,12 @@ func (h *GithubHooksHandler) processIssueComment(msg *GithubIssueCommentEventMsg case "apply": log.Info().Msg("Got TFC apply command") if !pullReq.IsApproved() { - h.postPullRequestComment(event, ":no_entry: Apply failed. Pull Request requires approval.") + h.postPullRequestComment(ctx, event, ":no_entry: Apply failed. Pull Request requires approval.") return nil } if pullReq.HasConflicts() { - h.postPullRequestComment(event, ":no_entry: Apply failed. Pull Request has conflicts that need to be resolved.") + h.postPullRequestComment(ctx, event, ":no_entry: Apply failed. Pull Request has conflicts that need to be resolved.") return nil } case "lock": @@ -102,20 +110,23 @@ func (h *GithubHooksHandler) processIssueComment(msg *GithubIssueCommentEventMsg default: return fmt.Errorf("could not parse command") } - executedWorkspaces, tfError := trigger.TriggerTFCEvents() + executedWorkspaces, tfError := trigger.TriggerTFCEvents(ctx) if tfError == nil && len(executedWorkspaces.Errored) > 0 { for _, failedWS := range executedWorkspaces.Errored { - h.postPullRequestComment(event, fmt.Sprintf(":no_entry: %s could not be run because: %s", failedWS.Name, failedWS.Error)) + h.postPullRequestComment(ctx, event, fmt.Sprintf(":no_entry: %s could not be run because: %s", failedWS.Name, failedWS.Error)) } return nil } return tfError } -func (h *GithubHooksHandler) postPullRequestComment(event *gogithub.IssueCommentEvent, body string) error { +func (h *GithubHooksHandler) postPullRequestComment(ctx context.Context, event *gogithub.IssueCommentEvent, body string) error { + ctx, span := otel.Tracer("hooks").Start(ctx, "postPullRequestComment") + defer span.End() + log.Debug().Msg("postPullRequestComment") prID := event.GetIssue().GetNumber() log.Debug().Str("repo", event.GetRepo().GetFullName()).Int("PR", prID).Msg("postPullRequestComment") - return h.vcs.CreateMergeRequestComment(prID, event.GetRepo().GetFullName(), body) + return h.vcs.CreateMergeRequestComment(ctx, prID, event.GetRepo().GetFullName(), body) } diff --git a/pkg/vcs/github/run_events_worker.go b/pkg/vcs/github/run_events_worker.go index edb8869..d54ffc9 100644 --- a/pkg/vcs/github/run_events_worker.go +++ b/pkg/vcs/github/run_events_worker.go @@ -1,6 +1,7 @@ package github import ( + "context" "fmt" "github.com/hashicorp/go-tfe" @@ -9,6 +10,7 @@ import ( "github.com/zapier/tfbuddy/pkg/runstream" "github.com/zapier/tfbuddy/pkg/tfc_api" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" ) const runEventsConsumerDurableName = "github" @@ -43,26 +45,33 @@ func (w *RunEventsWorker) Close() { // eventStreamCallback processes TFC run notifications via the NATS stream func (w *RunEventsWorker) eventStreamCallback(re runstream.RunEvent) bool { + ctx, span := otel.Tracer("TFC").Start(re.GetContext(), "eventStreamCallback") + defer span.End() + log.Debug().Interface("TFRunEvent", re).Msg("Gitlab RunEventsWorker.eventStreamCallback()") - run, err := w.tfc.GetRun(re.GetRunID()) + run, err := w.tfc.GetRun(ctx, re.GetRunID()) if err != nil { + span.RecordError(err) log.Error().Err(err).Str("runID", re.GetRunID()).Msg("could not get run") return false } run.Status = tfe.RunStatus(re.GetNewStatus()) - w.postRunStatusComment(run, re.GetMetadata()) + w.postRunStatusComment(ctx, run, re.GetMetadata()) //w.updateCommitStatusForRun(run, re.GetMetadata()) return true } -func (w *RunEventsWorker) postRunStatusComment(run *tfe.Run, rmd runstream.RunMetadata) { +func (w *RunEventsWorker) postRunStatusComment(ctx context.Context, run *tfe.Run, rmd runstream.RunMetadata) { + ctx, span := otel.Tracer("TFC").Start(ctx, "postRunStatusComment") + defer span.End() commentBody, _, _ := comment_formatter.FormatRunStatusCommentBody(w.tfc, run, rmd) if commentBody != "" { w.client.CreateMergeRequestComment( + ctx, rmd.GetMRInternalID(), rmd.GetMRProjectNameWithNamespace(), fmt.Sprintf( diff --git a/pkg/vcs/gitlab/client.go b/pkg/vcs/gitlab/client.go index eeafc8f..5a88f26 100644 --- a/pkg/vcs/gitlab/client.go +++ b/pkg/vcs/gitlab/client.go @@ -1,6 +1,7 @@ package gitlab import ( + "context" "errors" "fmt" "net/url" @@ -12,6 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/zapier/tfbuddy/pkg/utils" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" gogitlab "github.com/xanzy/go-gitlab" ) @@ -57,7 +59,10 @@ func NewGitlabClient() *GitlabClient { return &GitlabClient{glClient, token, tokenUser} } -func (c *GitlabClient) ResolveMergeRequestDiscussion(projectWithNamespace string, mrIID int, discussionID string) error { +func (c *GitlabClient) ResolveMergeRequestDiscussion(ctx context.Context, projectWithNamespace string, mrIID int, discussionID string) error { + _, span := otel.Tracer("TFC").Start(ctx, "ResolveMergeRequestDiscussion") + defer span.End() + return backoff.Retry(func() error { _, _, err := c.client.Discussions.ResolveMergeRequestDiscussion(projectWithNamespace, mrIID, discussionID, &gogitlab.ResolveMergeRequestDiscussionOptions{Resolved: gogitlab.Bool(true)}) return utils.CreatePermanentError(err) @@ -95,13 +100,20 @@ func (gS *GitlabCommitStatus) Info() string { return fmt.Sprintf("%s %s %s", gS.Author.Username, gS.Name, gS.SHA) } -func (c *GitlabClient) SetCommitStatus(projectWithNS string, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { +func (c *GitlabClient) SetCommitStatus(ctx context.Context, projectWithNS string, commitSHA string, status vcs.CommitStatusOptions) (vcs.CommitStatus, error) { + _, span := otel.Tracer("TFC").Start(ctx, "SetCommitStatus") + defer span.End() + return backoff.RetryWithData(func() (vcs.CommitStatus, error) { commitStatus, _, err := c.client.Commits.SetCommitStatus(projectWithNS, commitSHA, status.(*GitlabCommitStatusOptions).SetCommitStatusOptions) return &GitlabCommitStatus{commitStatus}, utils.CreatePermanentError(err) }, createBackOffWithRetries()) } -func (c *GitlabClient) GetCommitStatuses(projectID, commitSHA string) []*gogitlab.CommitStatus { + +func (c *GitlabClient) GetCommitStatuses(ctx context.Context, projectID, commitSHA string) []*gogitlab.CommitStatus { + _, span := otel.Tracer("TFC").Start(ctx, "GetCommitStatuses") + defer span.End() + statuses, _, err := c.client.Commits.GetCommitStatuses(projectID, commitSHA, &gogitlab.GetCommitStatusesOptions{Stage: &glExternalStageName}) if err != nil { log.Fatal().Msgf("could not get commit statuses: %v\n", err) @@ -111,7 +123,10 @@ func (c *GitlabClient) GetCommitStatuses(projectID, commitSHA string) []*gogitla } // Crawl the comments on this MR for tfbuddy comments, grab any TFC urls out of them, and delete them. -func (c *GitlabClient) GetOldRunUrls(mrIID int, project string, rootNoteID int) (string, error) { +func (c *GitlabClient) GetOldRunUrls(ctx context.Context, mrIID int, project string, rootNoteID int) (string, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetOldRunURLs") + defer span.End() + log.Debug().Str("projectID", project).Int("mrIID", mrIID).Msg("pruning notes") notes, _, err := c.client.Notes.ListMergeRequestNotes(project, mrIID, &gogitlab.ListMergeRequestNotesOptions{}) if err != nil { @@ -177,7 +192,10 @@ func (c *GitlabClient) GetOldRunUrls(mrIID int, project string, rootNoteID int) } // CreateMergeRequestComment creates a comment on the merge request. -func (c *GitlabClient) CreateMergeRequestComment(mrIID int, projectID, comment string) error { +func (c *GitlabClient) CreateMergeRequestComment(ctx context.Context, mrIID int, projectID, comment string) error { + _, span := otel.Tracer("TFC").Start(ctx, "CreateMergeRequestComment") + defer span.End() + if comment != "" { return backoff.Retry(func() error { log.Debug().Str("projectID", projectID).Int("mrIID", mrIID).Msg("posting Gitlab comment") @@ -211,7 +229,10 @@ func (gn *GitlabMRNote) GetNoteID() int64 { return int64(gn.Note.ID) } -func (c *GitlabClient) CreateMergeRequestDiscussion(mrIID int, project, comment string) (vcs.MRDiscussionNotes, error) { +func (c *GitlabClient) CreateMergeRequestDiscussion(ctx context.Context, mrIID int, project, comment string) (vcs.MRDiscussionNotes, error) { + _, span := otel.Tracer("TFC").Start(ctx, "CreateMergeRequestDiscussion") + defer span.End() + if comment == "" { return nil, errors.New("comment is empty") } @@ -225,7 +246,10 @@ func (c *GitlabClient) CreateMergeRequestDiscussion(mrIID int, project, comment }, createBackOffWithRetries()) } -func (c *GitlabClient) UpdateMergeRequestDiscussionNote(mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { +func (c *GitlabClient) UpdateMergeRequestDiscussionNote(ctx context.Context, mrIID, noteID int, project, discussionID, comment string) (vcs.MRNote, error) { + _, span := otel.Tracer("TFC").Start(ctx, "UpdateMergeRequestDiscussionNote") + defer span.End() + if comment == "" { return nil, utils.CreatePermanentError(errors.New("comment is empty")) } @@ -244,7 +268,10 @@ func (c *GitlabClient) UpdateMergeRequestDiscussionNote(mrIID, noteID int, proje } // AddMergeRequestDiscussionReply creates a comment on the merge request. -func (c *GitlabClient) AddMergeRequestDiscussionReply(mrIID int, project, discussionID, comment string) (vcs.MRNote, error) { +func (c *GitlabClient) AddMergeRequestDiscussionReply(ctx context.Context, mrIID int, project, discussionID, comment string) (vcs.MRNote, error) { + _, span := otel.Tracer("TFC").Start(ctx, "AddMergeRequestDiscussionReply") + defer span.End() + if comment != "" { return backoff.RetryWithData(func() (vcs.MRNote, error) { log.Debug().Str("project", project).Int("mrIID", mrIID).Msg("posting Gitlab discussion reply") @@ -257,7 +284,10 @@ func (c *GitlabClient) AddMergeRequestDiscussionReply(mrIID int, project, discus } // ResolveMergeRequestDiscussionReply marks a discussion thread as resolved / unresolved. -func (c *GitlabClient) ResolveMergeRequestDiscussionReply(mrIID int, project, discussionID string, resolved bool) error { +func (c *GitlabClient) ResolveMergeRequestDiscussionReply(ctx context.Context, mrIID int, project, discussionID string, resolved bool) error { + _, span := otel.Tracer("TFC").Start(ctx, "ResolveMergeRequestDiscussionReply") + defer span.End() + return backoff.Retry(func() error { log.Debug().Str("project", project).Int("mrIID", mrIID).Msg("posting Gitlab discussion reply") _, _, err := c.client.Discussions.ResolveMergeRequestDiscussion(project, mrIID, discussionID, &gogitlab.ResolveMergeRequestDiscussionOptions{Resolved: gogitlab.Bool(resolved)}) @@ -266,7 +296,10 @@ func (c *GitlabClient) ResolveMergeRequestDiscussionReply(mrIID int, project, di } // GetRepoFile retrieves a single file from a Gitlab repository using the RepositoryFiles API -func (g *GitlabClient) GetRepoFile(project, file, ref string) ([]byte, error) { +func (g *GitlabClient) GetRepoFile(ctx context.Context, project, file, ref string) ([]byte, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetRepoFile") + defer span.End() + if ref == "" { ref = "HEAD" } @@ -278,7 +311,10 @@ func (g *GitlabClient) GetRepoFile(project, file, ref string) ([]byte, error) { // GetMergeRequestModifiedFiles returns the names of files that were modified in the merge request // relative to the repo root, e.g. parent/child/file.txt. -func (g *GitlabClient) GetMergeRequestModifiedFiles(mrIID int, projectID string) ([]string, error) { +func (g *GitlabClient) GetMergeRequestModifiedFiles(ctx context.Context, mrIID int, projectID string) ([]string, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetMergeRequestModifiedFiles") + defer span.End() + const maxPerPage = 100 return backoff.RetryWithData(func() ([]string, error) { var files []string @@ -352,8 +388,13 @@ type GitlabMRAuthor struct { func (ga *GitlabMRAuthor) GetUsername() string { return ga.Username } -func (g *GitlabClient) GetMergeRequest(mrIID int, project string) (vcs.DetailedMR, error) { +func (g *GitlabClient) GetMergeRequest(ctx context.Context, mrIID int, project string) (vcs.DetailedMR, error) { + ctx, span := otel.Tracer("hooks").Start(ctx, "GetMergeRequest") + defer span.End() + return backoff.RetryWithData(func() (vcs.DetailedMR, error) { + _, span := otel.Tracer("hooks").Start(ctx, "GetMergeRequest") + defer span.End() mr, _, err := g.client.MergeRequests.GetMergeRequest( project, mrIID, @@ -377,7 +418,10 @@ type GitlabMRApproval struct { func (gm *GitlabMRApproval) IsApproved() bool { return gm.Approved } -func (g *GitlabClient) GetMergeRequestApprovals(mrIID int, project string) (vcs.MRApproved, error) { +func (g *GitlabClient) GetMergeRequestApprovals(ctx context.Context, mrIID int, project string) (vcs.MRApproved, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetMergeRequestApprovals") + defer span.End() + return backoff.RetryWithData(func() (vcs.MRApproved, error) { approvals, _, err := g.client.MergeRequestApprovals.GetConfiguration( project, @@ -400,7 +444,10 @@ func (gP *GitlabPipeline) GetSource() string { func (gP *GitlabPipeline) GetID() int { return gP.ID } -func (g *GitlabClient) GetPipelinesForCommit(project, commitSHA string) ([]vcs.ProjectPipeline, error) { +func (g *GitlabClient) GetPipelinesForCommit(ctx context.Context, project, commitSHA string) ([]vcs.ProjectPipeline, error) { + _, span := otel.Tracer("TFC").Start(ctx, "GetPipelinesForCommit") + defer span.End() + return backoff.RetryWithData(func() ([]vcs.ProjectPipeline, error) { pipelines, _, err := g.client.Pipelines.ListProjectPipelines(project, &gogitlab.ListProjectPipelinesOptions{ SHA: gogitlab.String(commitSHA), diff --git a/pkg/vcs/gitlab/git_actions.go b/pkg/vcs/gitlab/git_actions.go index b8448c4..5a2a711 100644 --- a/pkg/vcs/gitlab/git_actions.go +++ b/pkg/vcs/gitlab/git_actions.go @@ -1,6 +1,7 @@ package gitlab import ( + "context" "os" "path/filepath" @@ -12,20 +13,26 @@ import ( "github.com/xanzy/go-gitlab" zgit "github.com/zapier/tfbuddy/pkg/git" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" "gopkg.in/errgo.v2/fmt/errors" ) const GITLAB_CLONE_DEPTH_ENV = "TFBUDDY_GITLAB_CLONE_DEPTH" // CloneMergeRequest performs a git clone of the target Gitlab project & merge request branch to the `dest` path. -func (c *GitlabClient) CloneMergeRequest(project string, mr vcs.MR, dest string) (vcs.GitRepo, error) { +func (c *GitlabClient) CloneMergeRequest(ctx context.Context, project string, mr vcs.MR, dest string) (vcs.GitRepo, error) { + _, span := otel.Tracer("TFC").Start(ctx, "CloneMergeRequest") + defer span.End() + proj, _, err := c.client.Projects.GetProject(project, &gitlab.GetProjectOptions{ License: gitlab.Bool(false), Statistics: gitlab.Bool(false), WithCustomAttributes: gitlab.Bool(false), }) if err != nil { - return nil, errors.Newf("could not clone MR - unable to read project details from Gitlab API: %v", err) + err = errors.Newf("could not clone MR - unable to read project details from Gitlab API: %v", err) + span.RecordError(err) + return nil, err } ref := plumbing.NewBranchReferenceName(mr.GetSourceBranch()) @@ -50,7 +57,9 @@ func (c *GitlabClient) CloneMergeRequest(project string, mr vcs.MR, dest string) }) if err != nil && err != git.ErrRepositoryAlreadyExists { - return nil, errors.Newf("could not clone MR: %v", err) + err = errors.Newf("could not clone MR: %v", err) + span.RecordError(err) + return nil, err } wt, _ := repo.Worktree() @@ -65,7 +74,9 @@ func (c *GitlabClient) CloneMergeRequest(project string, mr vcs.MR, dest string) Force: false, }) if err != nil && err != git.NoErrAlreadyUpToDate { - return nil, errors.Newf("could not pull MR: %v", err) + err = errors.Newf("could not pull MR: %v", err) + span.RecordError(err) + return nil, err } if log.Trace().Enabled() { diff --git a/pkg/vcs/gitlab/mr_comment_updater.go b/pkg/vcs/gitlab/mr_comment_updater.go index f731bc0..65d1eed 100644 --- a/pkg/vcs/gitlab/mr_comment_updater.go +++ b/pkg/vcs/gitlab/mr_comment_updater.go @@ -1,9 +1,11 @@ package gitlab import ( + "context" "fmt" "github.com/zapier/tfbuddy/pkg/comment_formatter" + "go.opentelemetry.io/otel" "github.com/hashicorp/go-tfe" "github.com/rs/zerolog/log" @@ -11,7 +13,9 @@ import ( "github.com/zapier/tfbuddy/pkg/runstream" ) -func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunMetadata) { +func (p *RunStatusUpdater) postRunStatusComment(ctx context.Context, run *tfe.Run, rmd runstream.RunMetadata) { + ctx, span := otel.Tracer("TFC").Start(ctx, "postRunStatusComment") + defer span.End() commentBody, topLevelNoteBody, resolveDiscussion := comment_formatter.FormatRunStatusCommentBody(p.tfc, run, rmd) @@ -19,7 +23,7 @@ func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunM var err error if run.Status == tfe.RunErrored || run.Status == tfe.RunCanceled || run.Status == tfe.RunDiscarded || run.Status == tfe.RunPlannedAndFinished { - oldUrls, err = p.client.GetOldRunUrls(rmd.GetMRInternalID(), rmd.GetMRProjectNameWithNamespace(), int(rmd.GetRootNoteID())) + oldUrls, err = p.client.GetOldRunUrls(ctx, rmd.GetMRInternalID(), rmd.GetMRProjectNameWithNamespace(), int(rmd.GetRootNoteID())) if err != nil { log.Error().Err(err).Msg("could not retrieve old run urls") } @@ -29,6 +33,7 @@ func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunM } //oldURLBlock := utils.CaptureSubstring(, prefix string, suffix string) if _, err := p.client.UpdateMergeRequestDiscussionNote( + ctx, rmd.GetMRInternalID(), int(rmd.GetRootNoteID()), rmd.GetMRProjectNameWithNamespace(), @@ -39,7 +44,7 @@ func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunM } if commentBody != "" { - p.postComment(fmt.Sprintf( + p.postComment(ctx, fmt.Sprintf( "Status: `%s`
%s", run.Status, commentBody), @@ -52,6 +57,7 @@ func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunM if resolveDiscussion { err := p.client.ResolveMergeRequestDiscussion( + ctx, rmd.GetMRProjectNameWithNamespace(), rmd.GetMRInternalID(), rmd.GetDiscussionID(), @@ -62,18 +68,21 @@ func (p *RunStatusUpdater) postRunStatusComment(run *tfe.Run, rmd runstream.RunM } } -func (p *RunStatusUpdater) postComment(commentBody, projectID string, mrIID int, discussionID string) error { +func (p *RunStatusUpdater) postComment(ctx context.Context, commentBody, projectID string, mrIID int, discussionID string) error { + ctx, span := otel.Tracer("TFC").Start(ctx, "postComment") + defer span.End() + content := fmt.Sprintf(MR_COMMENT_FORMAT, commentBody) if discussionID != "" { - _, err := p.client.AddMergeRequestDiscussionReply(mrIID, projectID, discussionID, content) + _, err := p.client.AddMergeRequestDiscussionReply(ctx, mrIID, projectID, discussionID, content) if err != nil { log.Error().Err(err).Msg("error posting Gitlab discussion reply") return err } return nil } else { - err := p.client.CreateMergeRequestComment(mrIID, projectID, content) + err := p.client.CreateMergeRequestComment(ctx, mrIID, projectID, content) if err != nil { log.Error().Err(err).Msg("error posting Gitlab comment to MR") return err @@ -82,19 +91,6 @@ func (p *RunStatusUpdater) postComment(commentBody, projectID string, mrIID int, } } -func hasChanges(plan *tfe.Plan) bool { - if plan.ResourceAdditions > 0 { - return true - } - if plan.ResourceDestructions > 0 { - return true - } - if plan.ResourceChanges > 0 { - return true - } - return false -} - const MR_COMMENT_FORMAT = ` ### Terraform Cloud %s diff --git a/pkg/vcs/gitlab/mr_status_updater.go b/pkg/vcs/gitlab/mr_status_updater.go index b8e2955..7ad8a66 100644 --- a/pkg/vcs/gitlab/mr_status_updater.go +++ b/pkg/vcs/gitlab/mr_status_updater.go @@ -1,6 +1,7 @@ package gitlab import ( + "context" "errors" "fmt" "time" @@ -10,56 +11,60 @@ import ( "github.com/rs/zerolog/log" gogitlab "github.com/xanzy/go-gitlab" "github.com/zapier/tfbuddy/pkg/runstream" + "go.opentelemetry.io/otel" ) // Sentinel error var errNoPipelineStatus = errors.New("nil pipeline status") -func (p *RunStatusUpdater) updateCommitStatusForRun(run *tfe.Run, rmd runstream.RunMetadata) { +func (p *RunStatusUpdater) updateCommitStatusForRun(ctx context.Context, run *tfe.Run, rmd runstream.RunMetadata) { + ctx, span := otel.Tracer("TFC").Start(ctx, "updateCommitStatusForRun") + defer span.End() + switch run.Status { // https://www.terraform.io/cloud-docs/api-docs/run#run-states case tfe.RunPending: // The initial status of a run once it has been created. if rmd.GetAction() == "plan" { - p.updateStatus(gogitlab.Pending, "plan", rmd) - p.updateStatus(gogitlab.Failed, "apply", rmd) + p.updateStatus(ctx, gogitlab.Pending, "plan", rmd) + p.updateStatus(ctx, gogitlab.Failed, "apply", rmd) } else { - p.updateStatus(gogitlab.Pending, "apply", rmd) + p.updateStatus(ctx, gogitlab.Pending, "apply", rmd) } case tfe.RunApplyQueued: // Once the changes in the plan have been confirmed, the run run will transition to apply_queued. // This status indicates that the run should start as soon as the backend services have available capacity. - p.updateStatus(gogitlab.Pending, "apply", rmd) + p.updateStatus(ctx, gogitlab.Pending, "apply", rmd) case tfe.RunApplying: // The applying phase of a run is in progress. - p.updateStatus(gogitlab.Running, "apply", rmd) + p.updateStatus(ctx, gogitlab.Running, "apply", rmd) case tfe.RunApplied: if len(run.TargetAddrs) > 0 { - p.updateStatus(gogitlab.Pending, "apply", rmd) + p.updateStatus(ctx, gogitlab.Pending, "apply", rmd) return } // The applying phase of a run has completed. - p.updateStatus(gogitlab.Success, "apply", rmd) + p.updateStatus(ctx, gogitlab.Success, "apply", rmd) case tfe.RunCanceled: // The run has been discarded. This is a final state. - p.updateStatus(gogitlab.Failed, rmd.GetAction(), rmd) + p.updateStatus(ctx, gogitlab.Failed, rmd.GetAction(), rmd) case tfe.RunDiscarded: // The run has been discarded. This is a final state. - p.updateStatus(gogitlab.Failed, "plan", rmd) - p.updateStatus(gogitlab.Failed, "apply", rmd) + p.updateStatus(ctx, gogitlab.Failed, "plan", rmd) + p.updateStatus(ctx, gogitlab.Failed, "apply", rmd) case tfe.RunErrored: // The run has errored. This is a final state. - p.updateStatus(gogitlab.Failed, rmd.GetAction(), rmd) + p.updateStatus(ctx, gogitlab.Failed, rmd.GetAction(), rmd) case tfe.RunPlanning: // The planning phase of a run is in progress. - p.updateStatus(gogitlab.Running, rmd.GetAction(), rmd) + p.updateStatus(ctx, gogitlab.Running, rmd.GetAction(), rmd) case tfe.RunPlanned: // this status is for Apply runs (as opposed to `RunPlannedAndFinished` below, so don't update the status. @@ -68,16 +73,16 @@ func (p *RunStatusUpdater) updateCommitStatusForRun(run *tfe.Run, rmd runstream. case tfe.RunPlannedAndFinished: // The completion of a run containing a plan only, or a run the produces a plan with no changes to apply. // This is a final state. - p.updateStatus(gogitlab.Success, rmd.GetAction(), rmd) + p.updateStatus(ctx, gogitlab.Success, rmd.GetAction(), rmd) if run.HasChanges { // TODO: is pending enough to block merging before apply? - p.updateStatus(gogitlab.Pending, "apply", rmd) + p.updateStatus(ctx, gogitlab.Pending, "apply", rmd) } case tfe.RunPolicySoftFailed: // A sentinel policy has soft failed for a plan-only run. This is a final state. // During the apply, the policy failure will need to be overriden. - p.updateStatus(gogitlab.Success, rmd.GetAction(), rmd) + p.updateStatus(ctx, gogitlab.Success, rmd.GetAction(), rmd) case tfe.RunPolicyChecked: // The sentinel policy checking phase of a run has completed. @@ -91,7 +96,10 @@ func (p *RunStatusUpdater) updateCommitStatusForRun(run *tfe.Run, rmd runstream. } -func (p *RunStatusUpdater) updateStatus(state gogitlab.BuildStateValue, action string, rmd runstream.RunMetadata) { +func (p *RunStatusUpdater) updateStatus(ctx context.Context, state gogitlab.BuildStateValue, action string, rmd runstream.RunMetadata) { + ctx, span := otel.Tracer("TFC").Start(ctx, "updateStatus") + defer span.End() + status := &gogitlab.SetCommitStatusOptions{ Name: statusName(rmd.GetWorkspace(), action), Context: statusName(rmd.GetWorkspace(), action), @@ -105,7 +113,7 @@ func (p *RunStatusUpdater) updateStatus(state gogitlab.BuildStateValue, action s var pipelineID *int getPipelineIDFn := func() error { log.Debug().Msg("getting pipeline status") - pipelineID := p.getLatestPipelineID(rmd) + pipelineID := p.getLatestPipelineID(ctx, rmd) if pipelineID == nil { return errNoPipelineStatus } @@ -123,6 +131,7 @@ func (p *RunStatusUpdater) updateStatus(state gogitlab.BuildStateValue, action s log.Debug().Interface("new_status", status).Msg("updating Gitlab commit status") cs, err := p.client.SetCommitStatus( + ctx, rmd.GetMRProjectNameWithNamespace(), rmd.GetCommitSHA(), &GitlabCommitStatusOptions{status}, @@ -161,8 +170,8 @@ func runUrlForTFRunMetadata(rmd runstream.RunMetadata) *string { )) } -func (p *RunStatusUpdater) getLatestPipelineID(rmd runstream.RunMetadata) *int { - pipelines, err := p.client.GetPipelinesForCommit(rmd.GetMRProjectNameWithNamespace(), rmd.GetCommitSHA()) +func (p *RunStatusUpdater) getLatestPipelineID(ctx context.Context, rmd runstream.RunMetadata) *int { + pipelines, err := p.client.GetPipelinesForCommit(ctx, rmd.GetMRProjectNameWithNamespace(), rmd.GetCommitSHA()) if err != nil { log.Error().Err(err).Msg("could not retrieve pipelines for commit") return nil diff --git a/pkg/vcs/gitlab/run_status_updater.go b/pkg/vcs/gitlab/run_status_updater.go index e15334a..f503346 100644 --- a/pkg/vcs/gitlab/run_status_updater.go +++ b/pkg/vcs/gitlab/run_status_updater.go @@ -6,6 +6,7 @@ import ( "github.com/zapier/tfbuddy/pkg/runstream" "github.com/zapier/tfbuddy/pkg/tfc_api" "github.com/zapier/tfbuddy/pkg/vcs" + "go.opentelemetry.io/otel" ) type RunStatusUpdater struct { @@ -38,16 +39,19 @@ func (p *RunStatusUpdater) Close() { // eventStreamCallback processes TFC run notifications via the NATS stream func (p *RunStatusUpdater) eventStreamCallback(re runstream.RunEvent) bool { + ctx, span := otel.Tracer("TFC").Start(re.GetContext(), "eventStreamCallback") + defer span.End() + log.Debug().Interface("TFRunEvent", re).Msg("Gitlab RunStatusUpdater.eventStreamCallback()") - run, err := p.tfc.GetRun(re.GetRunID()) + run, err := p.tfc.GetRun(ctx, re.GetRunID()) if err != nil { log.Error().Err(err).Str("runID", re.GetRunID()).Msg("could not get run") return false } run.Status = tfe.RunStatus(re.GetNewStatus()) - p.postRunStatusComment(run, re.GetMetadata()) - p.updateCommitStatusForRun(run, re.GetMetadata()) + p.postRunStatusComment(ctx, run, re.GetMetadata()) + p.updateCommitStatusForRun(ctx, run, re.GetMetadata()) return true } diff --git a/pkg/vcs/interfaces.go b/pkg/vcs/interfaces.go index 975955c..66870b7 100644 --- a/pkg/vcs/interfaces.go +++ b/pkg/vcs/interfaces.go @@ -1,21 +1,23 @@ package vcs +import "context" + //go:generate mockgen -source interfaces.go -destination=../mocks/mock_vcs.go -package=mocks github.com/zapier/tfbuddy/pkg/vcs type GitClient interface { - GetMergeRequestApprovals(id int, project string) (MRApproved, error) - CreateMergeRequestComment(id int, fullPath string, comment string) error - CreateMergeRequestDiscussion(mrID int, fullPath string, comment string) (MRDiscussionNotes, error) - GetMergeRequest(int, string) (DetailedMR, error) - GetRepoFile(string, string, string) ([]byte, error) - GetMergeRequestModifiedFiles(mrIID int, projectID string) ([]string, error) - CloneMergeRequest(string, MR, string) (GitRepo, error) - UpdateMergeRequestDiscussionNote(mrIID, noteID int, project, discussionID, comment string) (MRNote, error) - ResolveMergeRequestDiscussion(string, int, string) error - AddMergeRequestDiscussionReply(mrIID int, project, discussionID, comment string) (MRNote, error) - SetCommitStatus(projectWithNS string, commitSHA string, status CommitStatusOptions) (CommitStatus, error) - GetPipelinesForCommit(projectWithNS string, commitSHA string) ([]ProjectPipeline, error) - GetOldRunUrls(mrIID int, project string, rootCommentID int) (string, error) + GetMergeRequestApprovals(ctx context.Context, id int, project string) (MRApproved, error) + CreateMergeRequestComment(ctx context.Context, id int, fullPath string, comment string) error + CreateMergeRequestDiscussion(ctx context.Context, mrID int, fullPath string, comment string) (MRDiscussionNotes, error) + GetMergeRequest(context.Context, int, string) (DetailedMR, error) + GetRepoFile(context.Context, string, string, string) ([]byte, error) + GetMergeRequestModifiedFiles(ctx context.Context, mrIID int, projectID string) ([]string, error) + CloneMergeRequest(context.Context, string, MR, string) (GitRepo, error) + UpdateMergeRequestDiscussionNote(ctx context.Context, mrIID, noteID int, project, discussionID, comment string) (MRNote, error) + ResolveMergeRequestDiscussion(context.Context, string, int, string) error + AddMergeRequestDiscussionReply(ctx context.Context, mrIID int, project, discussionID, comment string) (MRNote, error) + SetCommitStatus(ctx context.Context, projectWithNS string, commitSHA string, status CommitStatusOptions) (CommitStatus, error) + GetPipelinesForCommit(ctx context.Context, projectWithNS string, commitSHA string) ([]ProjectPipeline, error) + GetOldRunUrls(ctx context.Context, mrIID int, project string, rootCommentID int) (string, error) } type GitRepo interface { FetchUpstreamBranch(string) error