Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b3d19ed
ENG-2804 add webflow changelog webhook handler to api server (#4189)
getinnocuous Mar 9, 2026
096a0a5
fix: pin Alpine to 3.23.3 and upgrade zlib to remediate CVE-2026-2218…
carabasdaniel Mar 9, 2026
76874cd
fix(deps): update module github.com/auth0/go-jwt-middleware/v2 to v3 …
DavidS-ovm Mar 9, 2026
db313ad
maint, add a BUGBOT rule to keep go.mod clean (#4198)
DavidS-ovm Mar 9, 2026
0aedf74
GitHub library upgrades (#4200)
DavidS-ovm Mar 10, 2026
de09874
feat: create Elastic SAN volume snapshot adapter (#4209)
Lionel-Wilson Mar 10, 2026
3303f9f
feat: implement NetworkDefaultSecurityRule adapter (#4216)
Lionel-Wilson Mar 10, 2026
1594265
feat: implement ComputeDiskAccessPrivateEndpointConnection adapter (#…
Lionel-Wilson Mar 10, 2026
4a7eb59
[ENG-3089] Relax bbolt cache durability to fix goroutine pileup (#4221)
DavidS-ovm Mar 11, 2026
274fb06
feat: implement ElasticSanPool adapter (#4218)
Lionel-Wilson Mar 11, 2026
99ce1dd
ENG-3047 Phase 1 — UTM Attribution on External Links (#4214)
getinnocuous Mar 11, 2026
0960861
Eng 3006 create an adapter for elasticsanvolumegroup (#4219)
Lionel-Wilson Mar 11, 2026
dbebe81
[ENG-3098] Phase 1: Backend -- Accumulate Partial Plans (#4233)
DavidS-ovm Mar 12, 2026
0c35108
[ENG-3104] Add --no-start flag to submit-plan and new start-analysis …
DavidS-ovm Mar 12, 2026
06f88f3
feat: add RFC 9728 resource_metadata to MCP 401 responses (#4234)
tphoney Mar 12, 2026
ebb631c
[ENG-3113] GitHub App PR commenting for change analysis results (#4231)
tphoney Mar 12, 2026
bd9a16a
Eng 3123 cli action comment wait (#4245)
DavidS-ovm Mar 12, 2026
7bf270b
Run go mod tidy
actions-user Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws-source/adapters/ecs-task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func TestTaskGetInputMapper(t *testing.T) {

if input == nil {
t.Fatal("input is nil")
return
}

if *input.Cluster != "test-ECSCluster-Bt4SqcM3CURk" {
Expand Down
4 changes: 2 additions & 2 deletions aws-source/build/package/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ARG BUILD_VERSION
ARG BUILD_COMMIT

# required for generating the version descriptor
RUN apk add --no-cache git
RUN apk upgrade --no-cache && apk add --no-cache git

WORKDIR /workspace

Expand All @@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/go/pkg \
--mount=type=cache,target=/root/.cache/go-build \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w -X github.com/overmindtech/cli/go/tracing.version=${BUILD_VERSION} -X github.com/overmindtech/cli/go/tracing.commit=${BUILD_COMMIT}" -o source aws-source/main.go

FROM alpine:3.23
FROM alpine:3.23.3
WORKDIR /
COPY --from=builder /workspace/source .
USER 65534:65534
Expand Down
1 change: 1 addition & 0 deletions cmd/auth_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func TestNewRetryableHTTPClientRespectsProxy(t *testing.T) {

if httpTransport == nil {
t.Fatal("Could not get http.Transport")
return
}

// Verify proxy function is set to ProxyFromEnvironment
Expand Down
51 changes: 6 additions & 45 deletions cmd/changes_get_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"slices"
"strings"
"time"

"connectrpc.com/connect"
"github.com/overmindtech/cli/go/sdp-go"
Expand Down Expand Up @@ -72,51 +71,12 @@ func GetChange(cmd *cobra.Command, args []string) error {
}

client := AuthenticatedChangesClient(ctx, oi)
fetch:
for {
changeRes, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
Msg: &sdp.GetChangeRequest{
UUID: changeUuid[:],
},
})
if err != nil || changeRes.Msg == nil || changeRes.Msg.GetChange() == nil {
return loggedError{
err: err,
fields: lf,
message: "failed to get change",
}
}
ch := changeRes.Msg.GetChange()
md := ch.GetMetadata()
if md == nil || md.GetChangeAnalysisStatus() == nil {
return loggedError{
err: fmt.Errorf("change metadata or change analysis status is nil"),
fields: lf,
message: "failed to get change",
}
}
status := md.GetChangeAnalysisStatus().GetStatus()
switch status {
case sdp.ChangeAnalysisStatus_STATUS_DONE, sdp.ChangeAnalysisStatus_STATUS_SKIPPED:
break fetch
case sdp.ChangeAnalysisStatus_STATUS_ERROR:
return loggedError{
err: fmt.Errorf("change analysis completed with error status"),
fields: lf,
message: "change analysis failed",
}
case sdp.ChangeAnalysisStatus_STATUS_UNSPECIFIED, sdp.ChangeAnalysisStatus_STATUS_INPROGRESS:
log.WithContext(ctx).WithFields(lf).WithField("status", status.String()).Info("Waiting for change analysis to complete")
}
time.Sleep(3 * time.Second)
if ctx.Err() != nil {
return loggedError{
err: ctx.Err(),
fields: lf,
message: "context cancelled",
}
if viper.GetBool("wait") {
if err := waitForChangeAnalysis(ctx, client, changeUuid, lf); err != nil {
return err
}
}

app, _ = strings.CutSuffix(app, "/")
// get the change
var format sdp.ChangeOutputFormat
Expand Down Expand Up @@ -184,7 +144,8 @@ func init() {
getChangeCmd.PersistentFlags().String("status", "CHANGE_STATUS_DEFINING", "The expected status of the change. Use this with --ticket-link to get the first change with that status for a given ticket link. Allowed values: CHANGE_STATUS_DEFINING (ready for analysis/analysis in progress), CHANGE_STATUS_HAPPENING (deployment in progress), CHANGE_STATUS_DONE (deployment completed)")

getChangeCmd.PersistentFlags().String("frontend", "", "The frontend base URL")
_ = submitPlanCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead.") // MarkDeprecated only errors if the flag doesn't exist, we fall back to using app
cobra.CheckErr(getChangeCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead."))
getChangeCmd.PersistentFlags().Bool("wait", true, "Wait for analysis to complete before returning. Set to false to return immediately with the current status.")
getChangeCmd.PersistentFlags().String("format", "json", "How to render the change. Possible values: json, markdown")
getChangeCmd.PersistentFlags().StringSlice("risk-levels", []string{"high", "medium", "low"}, "Only show changes with the specified risk levels. Allowed values: high, medium, low")
}
14 changes: 14 additions & 0 deletions cmd/changes_get_change_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import (
"github.com/overmindtech/cli/go/sdp-go"
)

func TestGetChangeCmdHasWaitFlag(t *testing.T) {
t.Parallel()

flag := getChangeCmd.PersistentFlags().Lookup("wait")
if flag == nil {
t.Error("Expected wait flag to be registered on get-change command")
return
}

if flag.DefValue != "true" {
t.Errorf("Expected wait flag default value to be 'true', got %q", flag.DefValue)
}
}

func TestValidateChangeStatus(t *testing.T) {
tests := []struct {
name string
Expand Down
50 changes: 4 additions & 46 deletions cmd/changes_get_signals.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cmd
import (
_ "embed"
"fmt"
"time"

"connectrpc.com/connect"
"github.com/overmindtech/cli/go/sdp-go"
Expand Down Expand Up @@ -55,51 +54,10 @@ func GetSignals(cmd *cobra.Command, args []string) error {
}

client := AuthenticatedChangesClient(ctx, oi)
fetch:
for {
changeRes, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
Msg: &sdp.GetChangeRequest{
UUID: changeUuid[:],
},
})
if err != nil || changeRes.Msg == nil || changeRes.Msg.GetChange() == nil {
return loggedError{
err: err,
fields: lf,
message: "failed to get change",
}
}
ch := changeRes.Msg.GetChange()
md := ch.GetMetadata()
if md == nil || md.GetChangeAnalysisStatus() == nil {
return loggedError{
err: fmt.Errorf("change metadata or change analysis status is nil"),
fields: lf,
message: "failed to get change",
}
}
status := md.GetChangeAnalysisStatus().GetStatus()
switch status {
case sdp.ChangeAnalysisStatus_STATUS_DONE, sdp.ChangeAnalysisStatus_STATUS_SKIPPED:
break fetch
case sdp.ChangeAnalysisStatus_STATUS_ERROR:
return loggedError{
err: fmt.Errorf("change analysis completed with error status"),
fields: lf,
message: "change analysis failed",
}
case sdp.ChangeAnalysisStatus_STATUS_UNSPECIFIED, sdp.ChangeAnalysisStatus_STATUS_INPROGRESS:
log.WithContext(ctx).WithFields(lf).WithField("status", status.String()).Info("Waiting for change analysis to complete")
}
time.Sleep(3 * time.Second)
if ctx.Err() != nil {
return loggedError{
err: ctx.Err(),
fields: lf,
message: "context cancelled",
}
}
if err := waitForChangeAnalysis(ctx, client, changeUuid, lf); err != nil {
return err
}

// get the change signals
var format sdp.ChangeOutputFormat
switch viper.GetString("format") {
Expand Down Expand Up @@ -140,6 +98,6 @@ func init() {
getSignalsCmd.PersistentFlags().String("status", "CHANGE_STATUS_DEFINING", "The expected status of the change. Use this with --ticket-link to get the first change with that status for a given ticket link. Allowed values: CHANGE_STATUS_DEFINING (ready for analysis/analysis in progress), CHANGE_STATUS_HAPPENING (deployment in progress), CHANGE_STATUS_DONE (deployment completed)")

getSignalsCmd.PersistentFlags().String("frontend", "", "The frontend base URL")
_ = getSignalsCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead.") // MarkDeprecated only errors if the flag doesn't exist, we fall back to using app
cobra.CheckErr(getSignalsCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead."))
getSignalsCmd.PersistentFlags().String("format", "json", "How to render the signals. Possible values: json, markdown")
}
106 changes: 106 additions & 0 deletions cmd/changes_start_analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cmd

import (
"fmt"
"strings"

"connectrpc.com/connect"
"github.com/overmindtech/cli/go/sdp-go"
"github.com/overmindtech/cli/go/tracing"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// startAnalysisCmd represents the start-analysis command
var startAnalysisCmd = &cobra.Command{
Use: "start-analysis {--ticket-link URL | --uuid ID | --change URL}",
Short: "Triggers analysis on a change with previously stored planned changes",
Long: `Triggers analysis on a change that has previously stored planned changes.

This command is used in multi-plan workflows (e.g., Atlantis parallel planning) where
multiple terraform plans are submitted independently using 'submit-plan --no-start',
and then analysis is triggered once all plans are submitted.

The change must be in DEFINING status and must have at least one planned change stored.`,
PreRun: PreRunSetup,
RunE: StartAnalysis,
}

func StartAnalysis(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

app := viper.GetString("app")

ctx, oi, _, err := login(ctx, cmd, []string{"changes:write", "sources:read"}, nil)
if err != nil {
return err
}

lf := log.Fields{}

changeUUID, err := getChangeUUIDAndCheckStatus(ctx, oi, sdp.ChangeStatus_CHANGE_STATUS_DEFINING, viper.GetString("ticket-link"), true)
if err != nil {
return loggedError{
err: err,
fields: lf,
message: "failed to identify change",
}
}

lf["change"] = changeUUID.String()

analysisConfig, err := buildAnalysisConfig(ctx, lf)
if err != nil {
return err
}

client := AuthenticatedChangesClient(ctx, oi)

resp, err := client.StartChangeAnalysis(ctx, &connect.Request[sdp.StartChangeAnalysisRequest]{
Msg: &sdp.StartChangeAnalysisRequest{
ChangeUUID: changeUUID[:],
ChangingItems: nil, // uses pre-stored items from AddPlannedChanges
BlastRadiusConfigOverride: analysisConfig.BlastRadiusConfig,
RoutineChangesConfigOverride: analysisConfig.RoutineChangesConfig,
GithubOrganisationProfileOverride: analysisConfig.GithubOrgProfile,
Knowledge: analysisConfig.KnowledgeFiles,
PostGithubComment: viper.GetBool("comment"),
},
})
if err != nil {
return loggedError{
err: err,
fields: lf,
message: "failed to start change analysis",
}
}

app, _ = strings.CutSuffix(app, "/")
changeUrl := fmt.Sprintf("%v/changes/%v?utm_source=cli&cli_version=%v", app, changeUUID, tracing.Version())
log.WithContext(ctx).WithFields(lf).WithField("change-url", changeUrl).Info("Change analysis started")

if viper.GetBool("comment") {
fmt.Printf("CHANGE_URL='%s'\n", changeUrl)
fmt.Printf("GITHUB_APP_ACTIVE='%v'\n", resp.Msg.GetGithubAppActive())
} else {
fmt.Println(changeUrl)
}

if viper.GetBool("wait") {
log.WithContext(ctx).WithFields(lf).Info("Waiting for analysis to complete")
return waitForChangeAnalysis(ctx, client, changeUUID, lf)
}

return nil
}

func init() {
changesCmd.AddCommand(startAnalysisCmd)

addAPIFlags(startAnalysisCmd)
addChangeUuidFlags(startAnalysisCmd)
addAnalysisFlags(startAnalysisCmd)

startAnalysisCmd.PersistentFlags().Bool("wait", false, "Wait for analysis to complete before returning.")
}
Loading