diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2564345..a63a5d04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: # Actually release the binaries including signing them release: - runs-on: depot-ubuntu-22.04-8 + runs-on: depot-ubuntu-22.04-32 if: ${{ github.event_name != 'pull_request' }} permissions: contents: write diff --git a/cmd/changes_get_change.go b/cmd/changes_get_change.go index d3205ea0..c2af8db7 100644 --- a/cmd/changes_get_change.go +++ b/cmd/changes_get_change.go @@ -38,7 +38,7 @@ var getChangeCmd = &cobra.Command{ // to reflect the latest version // // This allows us to update the assets without fear of breaking older comments -const assetVersion = "17c7fd2c365d4f4cdd8e414ca5148f825fa4febd" +const assetVersion = "476f6df5bc783c17b1d0513a43e0a0aa9c075588" // tag from v1.6.1 func GetChange(cmd *cobra.Command, args []string) error { ctx := cmd.Context() @@ -204,8 +204,10 @@ fetch: SeverityText string Title string Description string + RiskUrl string } type TemplateData struct { + BlastRadiusUrl string ChangeUrl string ExpectedChanges []TemplateItem UnmappedChanges []TemplateItem @@ -214,6 +216,7 @@ fetch: Risks []TemplateRisk // Path to the assets folder on github AssetPath string + TagsLine string } status := map[sdp.ItemDiffStatus]TemplateItem{ sdp.ItemDiffStatus_ITEM_DIFF_STATUS_UNSPECIFIED: { @@ -267,6 +270,7 @@ fetch: app, _ = strings.CutSuffix(app, "/") data := TemplateData{ ChangeUrl: fmt.Sprintf("%v/changes/%v", app, changeUuid.String()), + BlastRadiusUrl: fmt.Sprintf("%v/changes/%v/blast-radius", app, changeUuid.String()), ExpectedChanges: []TemplateItem{}, UnmappedChanges: []TemplateItem{}, BlastItems: int(changeRes.Msg.GetChange().GetMetadata().GetNumAffectedItems()), @@ -326,14 +330,20 @@ fetch: } for _, risk := range riskRes.Msg.GetChangeRiskMetadata().GetRisks() { + // parse the risk UUID to a string + riskUuid, _ := uuid.FromBytes(risk.GetUUID()) data.Risks = append(data.Risks, TemplateRisk{ SeverityAlt: severity[risk.GetSeverity()].SeverityAlt, SeverityIcon: severity[risk.GetSeverity()].SeverityIcon, SeverityText: severity[risk.GetSeverity()].SeverityText, Title: risk.GetTitle(), Description: risk.GetDescription(), + RiskUrl: fmt.Sprintf("%v/changes/%v/blast-radius?selectedRisk=%v&activeTab=risks", app, changeUuid.String(), riskUuid.String()), }) } + // get the tags in + data.TagsLine = getTagsLine(changeRes.Msg.GetChange().GetProperties().GetEnrichedTags().GetTagValue()) + data.TagsLine = strings.TrimSpace(data.TagsLine) tmpl, err := template.New("comment").Parse(commentTemplate) if err != nil { @@ -386,6 +396,32 @@ func renderRiskFilter(levels []sdp.Risk_Severity) string { return strings.Join(result, ", ") } +func getTagsLine(tags map[string]*sdp.TagValue) string { + autoTags := "" + userTags := "" + + for key, value := range tags { + if value.GetAutoTagValue() != nil { + suffix := "" + if value.GetAutoTagValue().GetValue() != "" { + suffix = fmt.Sprintf("|%s", value.GetAutoTagValue().GetValue()) + } + autoTags += fmt.Sprintf("`✨%s%s` ", key, suffix) + } else if value.GetUserTagValue() != nil { + suffix := "" + if value.GetUserTagValue().GetValue() != "" { + suffix = fmt.Sprintf("|%s", value.GetUserTagValue().GetValue()) + } + userTags += fmt.Sprintf("`%s%s` ", key, suffix) + } else { + // we should never get here, but just in case. + // its a tag jim, but not as we know it + userTags += fmt.Sprintf("`%s` ", key) + } + } + return autoTags + userTags +} + func init() { changesCmd.AddCommand(getChangeCmd) addAPIFlags(getChangeCmd) diff --git a/cmd/changes_get_change_test.go b/cmd/changes_get_change_test.go new file mode 100644 index 00000000..827be3a2 --- /dev/null +++ b/cmd/changes_get_change_test.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "testing" + + "github.com/overmindtech/cli/sdp-go" + "github.com/stretchr/testify/assert" +) + +func TestGetTagsLine(t *testing.T) { + tests := []struct { + name string + tags map[string]*sdp.TagValue + want string + }{ + { + name: "empty tags", + tags: map[string]*sdp.TagValue{}, + want: "", + }, + { + name: "key only", + tags: map[string]*sdp.TagValue{ + "key": {}, + }, + want: "`key` ", + }, + { + name: "auto tag without value", + tags: map[string]*sdp.TagValue{ + "autoTag": { + Value: &sdp.TagValue_AutoTagValue{ + AutoTagValue: &sdp.AutoTagValue{}, + }, + }, + }, + want: "`✨autoTag` ", + }, + { + name: "auto tag with value", + tags: map[string]*sdp.TagValue{ + "autoTag": { + Value: &sdp.TagValue_AutoTagValue{ + AutoTagValue: &sdp.AutoTagValue{ + Value: "value", + }, + }, + }, + }, + want: "`✨autoTag|value` ", + }, + { + name: "user tag without value", + tags: map[string]*sdp.TagValue{ + "userTag": { + Value: &sdp.TagValue_UserTagValue{ + UserTagValue: &sdp.UserTagValue{}, + }, + }, + }, + want: "`userTag` ", + }, + { + name: "user tag with value", + tags: map[string]*sdp.TagValue{ + "userTag": { + Value: &sdp.TagValue_UserTagValue{ + UserTagValue: &sdp.UserTagValue{ + Value: "value", + }, + }, + }, + }, + want: "`userTag|value` ", + }, + { + name: "mixed tags", + tags: map[string]*sdp.TagValue{ + "autoTag": { + Value: &sdp.TagValue_AutoTagValue{ + AutoTagValue: &sdp.AutoTagValue{ + Value: "value", + }, + }, + }, + "userTag": { + Value: &sdp.TagValue_UserTagValue{ + UserTagValue: &sdp.UserTagValue{ + Value: "value", + }, + }, + }, + }, + want: "`✨autoTag|value` `userTag|value` ", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getTagsLine(tt.tags) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/cmd/changes_list_changes.go b/cmd/changes_list_changes.go index 07d67653..4b52b61e 100644 --- a/cmd/changes_list_changes.go +++ b/cmd/changes_list_changes.go @@ -238,6 +238,18 @@ func printJson(_ context.Context, b []byte, prefix, id string, cmd *cobra.Comman return flagError{fmt.Sprintf("need --dir value to write to files\n\n%v", cmd.UsageString())} } + // attempt to create the directory + err := os.MkdirAll(dir, 0755) + if err != nil { + return loggedError{ + err: err, + fields: log.Fields{ + "output-dir": dir, + }, + message: "failed to create output directory", + } + } + // write the change to a file fileName := fmt.Sprintf("%v/%v-%v.json", dir, prefix, id) file, err := os.Create(fileName) diff --git a/cmd/comment.md b/cmd/comment.md index 500fa950..ee765d34 100644 --- a/cmd/comment.md +++ b/cmd/comment.md @@ -1,9 +1,27 @@ {{ $top := . -}} +# bannerbanner +{{ if .TagsLine -}} +{{ .TagsLine }} +{{ end -}} + +# warning Risks +{{ if not .Risks }} +Overmind has not identified any risks associated with this change. + +This could be due to the change being low risk with no impact on other parts of the system, or involving resources that Overmind currently does not support. +{{ else -}} +{{ range .Risks }} +## {{ .SeverityAlt }} {{ .Title }} [{{ .SeverityText }}] + +{{ .Description }} [Open Risk]({{ .RiskUrl }}) +{{ end }} +{{ end -}} + # mapped Expected Changes {{ range .ExpectedChanges -}}
-{{ .StatusAlt }} {{ .Type }} › {{ .Title }} +{{ .StatusAlt }} {{ .Type }} › {{ .Title }} {{ if .Diff -}} ```diff @@ -20,7 +38,7 @@ No expected changes found. {{ end }} {{ if .UnmappedChanges -}} -## unmapped Unmapped Changes +# unmapped Unmapped Changes > [!NOTE] > These changes couldn't be mapped to a discoverable cloud resource and therefore won't be included in the blast radius calculation. @@ -28,7 +46,7 @@ No expected changes found. {{ range .UnmappedChanges -}}
-{{ .StatusAlt }} {{ .Type }} › {{ .Title }} +{{ .StatusAlt }} {{ .Type }} › {{ .Title }} {{ if .Diff -}} @@ -44,23 +62,12 @@ No expected changes found. {{ end }} {{ end }} -# Blast Radius +# edges Blast Radius -| items Items | edges Edges | +| items Items | edges Edges | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | {{ .BlastItems }} | {{ .BlastEdges }} | -[Open in Overmind]({{ .ChangeUrl }}) - -# warning Risks -{{ if not .Risks }} -Overmind has not identified any risks associated with this change. +[Open Blast Radius]({{ .BlastRadiusUrl }}) -This could be due to the change being low risk with no impact on other parts of the system, or involving resources that Overmind currently does not support. -{{ else -}} -{{ range .Risks }} -## {{ .SeverityAlt }} {{ .Title }} [{{ .SeverityText }}] - -{{ .Description }} -{{ end }} -{{ end -}} +[Open in Overmind]({{ .ChangeUrl }}) diff --git a/cmd/root.go b/cmd/root.go index d332f767..086d217c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,7 +77,7 @@ func PreRunSetup(cmd *cobra.Command, args []string) { // set up tracing if honeycombApiKey := viper.GetString("honeycomb-api-key"); honeycombApiKey != "" { - if err := tracing.InitTracerWithUpstreams("cli", honeycombApiKey, ""); err != nil { + if err := tracing.InitTracerWithUpstreams("overmind-cli", honeycombApiKey, ""); err != nil { log.Fatal(err) } @@ -578,7 +578,7 @@ func getAPIKeyToken(ctx context.Context, oi sdp.OvermindInstance, apiKey string, if !ok { return nil, fmt.Errorf("authenticated successfully against %s, but your API key is missing this permission: '%v'", app, missing) } - pterm.Info.Println(fmt.Sprintf("Using Overmind API key for %s", app)) + log.WithField("app", app).Info("Using Overmind API key") return token, nil } diff --git a/sdp-go/changes.go b/sdp-go/changes.go index 430636d3..322a4ffd 100644 --- a/sdp-go/changes.go +++ b/sdp-go/changes.go @@ -354,7 +354,9 @@ func (cp *ChangeProperties) ToMap() map[string]any { "rawPlan": cp.GetRawPlan(), "codeChanges": cp.GetCodeChanges(), "repo": cp.GetRepo(), - "tags": cp.GetTags(), + "tags": cp.GetEnrichedTags(), + "autoTaggingRuleSource": cp.GetAutoTaggingRuleSource().ToMessage(), + "skippedAutoTags": cp.GetSkippedAutoTags(), } } @@ -429,6 +431,19 @@ func (s EndChangeResponse_State) ToMessage() string { } } +func (s ChangeProperties_AutoTaggingRuleSource) ToMessage() string { + switch s { + case ChangeProperties_AUTO_TAGGING_RULE_SOURCE_UNSPECIFIED: + return "unknown" + case ChangeProperties_AUTO_TAGGING_RULE_SOURCE_FILE: + return "file" + case ChangeProperties_AUTO_TAGGING_RULE_SOURCE_UI: + return "ui" + default: + return "unknown" + } +} + // allow custom auto tag rules to be passed on the cli, via a yaml file // // rules: