Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added assets/change.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/changed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/created.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/deleted.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/edge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/high.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/item.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/low.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/medium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/replaced.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/risks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions cmd/comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# <img width="24" alt="mapped" src="https://github.com/overmindtech/ovm-cli/assets/8799341/311e1bb0-e3da-4499-8db3-b97ec7674484"> Expected Changes

{{range .ExpectedChanges }}
<details>
<summary><img width="14" alt="{{ .StatusAlt }}" src="{{ .StatusIcon }}"> {{ .Type }} › {{ .Title }}</summary>

{{if .Diff }}
```diff
{{ .Diff }}
```
{{else}}
(no changed attributes)
{{end}}
</details>
{{else}}
No expected changes found.
{{end}}


## <img width="20" alt="unmapped" src="https://github.com/overmindtech/ovm-cli/assets/8799341/46002c31-0c19-45c5-92ac-2a339e25b00e"> Unmapped Changes

> [!NOTE]
> These changes couldn't be mapped to a real cloud resource and therefore won't be included in the blast radius calculation.


{{range .UnmappedChanges }}
<details>
<summary><img width="14" alt="{{ .StatusAlt }}" src="{{ .StatusIcon }}"> {{ .Type }} › {{ .Title }}</summary>

{{if .Diff }}
```diff
{{ .Diff }}
```
{{else}}
(no changed attributes)
{{end}}
</details>
{{else}}
No unmapped changes found.
{{end}}



# Blast Radius

| <img width="16" alt="mapped" src="https://github.com/overmindtech/ovm-cli/assets/8799341/311e1bb0-e3da-4499-8db3-b97ec7674484"> Items | <img width="16" alt="edge" src="https://github.com/overmindtech/ovm-cli/assets/8799341/437dcecd-117d-474d-a6fd-1aa241fb0fd0"> Edges |
|---|---|
| 75 | 97

[Open in Overmind]({{ .ChangeUrl }})



{{if .Risks }}
# <img width="24" alt="warning" src="https://github.com/overmindtech/ovm-cli/assets/8799341/fd3b183f-92b3-4aab-987d-40452f92bdbb"> Risks

{{range .Risks }}
## <img width="18" alt="{{ .SeverityAlt }}" src="{{ .SeverityIcon }}"> Impact on Target Groups [High]

The various target groups including \"944651592624.eu-west-2.elbv2-target-group.k8s-default-nats-4650f3a363\", \"944651592624.eu-west-2.elbv2-target-group.k8s-default-smartloo-fd2416d9f8\", etc., that work alongside the load balancer for traffic routing may be indirectly affected if the security group change causes networking issues. This is especially important if these target groups rely on different ports other than 8080 for health checks or for directing incoming requests to backend services.
{{end}}
{{end}}
200 changes: 176 additions & 24 deletions cmd/getchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,33 @@ package cmd

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
"os/signal"
"syscall"
"text/template"
"time"

"connectrpc.com/connect"
"github.com/google/uuid"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
diffspan "github.com/hexops/gotextdiff/span"
"github.com/overmindtech/ovm-cli/tracing"
"github.com/overmindtech/sdp-go"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"gopkg.in/yaml.v3"
)

//go:embed comment.md
var commentTemplate string

// getChangeCmd represents the get-change command
var getChangeCmd = &cobra.Command{
Use: "get-change {--uuid ID | --change https://app.overmind.tech/changes/c772d072-6b0b-4763-b7c5-ff5069beed4c}",
Expand Down Expand Up @@ -87,7 +96,7 @@ func GetChange(ctx context.Context, ready chan bool) int {
lf["uuid"] = changeUuid.String()

client := AuthenticatedChangesClient(ctx)
response, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
changeRes, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
Msg: &sdp.GetChangeRequest{
UUID: changeUuid[:],
},
Expand All @@ -99,44 +108,187 @@ func GetChange(ctx context.Context, ready chan bool) int {
return 1
}
log.WithContext(ctx).WithFields(log.Fields{
"change-uuid": uuid.UUID(response.Msg.Change.Metadata.UUID),
"change-created": response.Msg.Change.Metadata.CreatedAt.AsTime(),
"change-status": response.Msg.Change.Metadata.Status.String(),
"change-name": response.Msg.Change.Properties.Title,
"change-description": response.Msg.Change.Properties.Description,
"change-uuid": uuid.UUID(changeRes.Msg.Change.Metadata.UUID),
"change-created": changeRes.Msg.Change.Metadata.CreatedAt.AsTime(),
"change-status": changeRes.Msg.Change.Metadata.Status.String(),
"change-name": changeRes.Msg.Change.Properties.Title,
"change-description": changeRes.Msg.Change.Properties.Description,
}).Info("found change")

// diffRes, err := client.GetDiff(ctx, &connect.Request[sdp.GetDiffRequest]{
// Msg: &sdp.GetDiffRequest{
// ChangeUUID: changeUuid[:],
// },
// })
// if err != nil {
// log.WithContext(ctx).WithError(err).WithFields(log.Fields{
// "change-url": viper.GetString("change-url"),
// }).Error("failed to get change diff")
// return 1
// }
// log.WithContext(ctx).WithFields(log.Fields{
// "change-uuid": uuid.UUID(changeRes.Msg.Change.Metadata.UUID),
// }).Info("loaded change diff")

switch viper.GetString("format") {
case "json":
b, err := json.MarshalIndent(response.Msg.Change.ToMap(), "", " ")
b, err := json.MarshalIndent(changeRes.Msg.Change.ToMap(), "", " ")
if err != nil {
log.Errorf("Error rendering change: %v", err)
log.WithContext(ctx).WithField("input", fmt.Sprintf("%#v", changeRes.Msg.Change.ToMap())).WithError(err).Error("Error rendering change")
return 1
}

fmt.Println(string(b))
case "markdown":
changeUrl := fmt.Sprintf("%v/changes/%v", viper.GetString("frontend"), changeUuid.String())
if response.Msg.Change.Metadata.NumAffectedApps != 0 || response.Msg.Change.Metadata.NumAffectedItems != 0 {
// we have affected stuff
fmt.Printf(`## Blast Radius &nbsp; · &nbsp; [View in Overmind](%v) <img width="16" src="https://raw.githubusercontent.com/overmindtech/ovm-cli/main/assets/chainLink.png" alt="chain link icon" />
type TemplateItem struct {
StatusAlt string
StatusIcon string
Type string
Title string
Diff string
}
type TemplateRisk struct {
SeverityAlt string
SeverityIcon string
SeverityText string
Title string
Description string
}
type TemplateData struct {
ChangeUrl string
ExpectedChanges []TemplateItem
UnmappedChanges []TemplateItem
BlastItems int
BlastEdges int
Risks []TemplateRisk
}
status := map[sdp.ItemDiffStatus]TemplateItem{
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_UNSPECIFIED: {
StatusAlt: "unspecified",
StatusIcon: "",
},
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_UNCHANGED: {
StatusAlt: "unchanged",
StatusIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/item.png",
},
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_CREATED: {
StatusAlt: "created",
StatusIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/created.png",
},
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_UPDATED: {
StatusAlt: "updated",
StatusIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/changed.png",
},
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_DELETED: {
StatusAlt: "deleted",
StatusIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/deleted.png",
},
sdp.ItemDiffStatus_ITEM_DIFF_STATUS_REPLACED: {
StatusAlt: "replaced",
StatusIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/replaced.png",
},
}

> **Warning**
> Overmind identified potentially affected apps and items as a result of this pull request.
severity := map[sdp.Risk_Severity]TemplateRisk{
sdp.Risk_SEVERITY_UNSPECIFIED: {
SeverityAlt: "unspecified",
SeverityIcon: "",
SeverityText: "unspecified",
},
sdp.Risk_SEVERITY_LOW: {
SeverityAlt: "low",
SeverityIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/low.png",
SeverityText: "Low",
},
sdp.Risk_SEVERITY_MEDIUM: {
SeverityAlt: "medium",
SeverityIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/medium.png",
SeverityText: "Medium",
},
sdp.Risk_SEVERITY_HIGH: {
SeverityAlt: "high",
SeverityIcon: "https://raw.githubusercontent.com/overmindtech/ovm-cli/ac4feb1b9dd73b5c42c5a515d12517b551d2886b/assets/high.png",
SeverityText: "High",
},
}
data := TemplateData{
ChangeUrl: fmt.Sprintf("%v/changes/%v", viper.GetString("frontend"), changeUuid.String()),
ExpectedChanges: []TemplateItem{},
UnmappedChanges: []TemplateItem{},
BlastItems: 75,
BlastEdges: 97,
Risks: []TemplateRisk{},
}

<br>
for _, item := range changeRes.Msg.Change.Properties.PlannedChanges {
var before, after string
if item.Before != nil {
bb, err := yaml.Marshal(item.Before.Attributes.AttrStruct.AsMap())
if err != nil {
log.WithContext(ctx).WithError(err).Error("error marshalling 'before' attributes")
before = ""
} else {
before = string(bb)
}
}
if item.After != nil {
ab, err := yaml.Marshal(item.After.Attributes.AttrStruct.AsMap())
if err != nil {
log.WithContext(ctx).WithError(err).Error("error marshalling 'after' attributes")
after = ""
} else {
after = string(ab)
}
}
edits := myers.ComputeEdits(diffspan.URIFromPath("current"), before, after)
diff := fmt.Sprint(gotextdiff.ToUnified("current", "planned", before, edits))

| <img width="16" src="https://raw.githubusercontent.com/overmindtech/ovm-cli/main/assets/blastRadiusItems.png" alt="icon for blast radius items" />&nbsp;Affected items |
| -------------- |
| [%v items](%v) |
`, changeUrl, response.Msg.Change.Metadata.NumAffectedItems, changeUrl)
} else {
fmt.Printf(`## Blast Radius &nbsp; · &nbsp; [View in Overmind](%v) <img width="16" src="https://raw.githubusercontent.com/overmindtech/ovm-cli/main/assets/chainLink.png" alt="chain link icon" />
if item.Item != nil {
data.ExpectedChanges = append(data.ExpectedChanges, TemplateItem{
StatusAlt: status[item.Status].StatusAlt,
StatusIcon: status[item.Status].StatusIcon,
Type: item.Item.Type,
Title: item.Item.UniqueAttributeValue,
Diff: diff,
})
} else {
var typ, title string
if item.After != nil {
typ = item.After.Type
title = item.After.UniqueAttributeValue()
} else if item.Before != nil {
typ = item.Before.Type
title = item.Before.UniqueAttributeValue()
}
data.UnmappedChanges = append(data.ExpectedChanges, TemplateItem{
StatusAlt: status[item.Status].StatusAlt,
StatusIcon: status[item.Status].StatusIcon,
Type: typ,
Title: title,
Diff: diff,
})
}
}

> **✅ Checks complete**
> Overmind didn't identify any potentially affected apps and items as a result of this pull request.
for _, risk := range changeRes.Msg.Change.Metadata.Risks {
data.Risks = append(data.Risks, TemplateRisk{
SeverityAlt: severity[risk.Severity].SeverityAlt,
SeverityIcon: severity[risk.Severity].SeverityIcon,
SeverityText: severity[risk.Severity].SeverityText,
Title: risk.Title,
Description: risk.Description,
})
}

`, changeUrl)
tmpl, err := template.New("comment").Parse(commentTemplate)
if err != nil {
log.WithContext(ctx).WithError(err).Error("error parsing comment template")
return 1
}
err = tmpl.Execute(os.Stdout, data)
if err != nil {
log.WithContext(ctx).WithField("input", fmt.Sprintf("%#v", data)).WithError(err).Error("error rendering comment")
return 1
}
}

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
connectrpc.com/connect v1.12.0
github.com/getsentry/sentry-go v0.25.0
github.com/google/uuid v1.4.0
github.com/hexops/gotextdiff v1.0.3
github.com/jedib0t/go-pretty/v6 v6.4.9
github.com/mattn/go-isatty v0.0.20
github.com/overmindtech/sdp-go v0.57.0
Expand All @@ -26,6 +27,7 @@ require (
go.opentelemetry.io/otel/trace v1.19.0
golang.org/x/oauth2 v0.14.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand Down Expand Up @@ -76,6 +78,5 @@ require (
google.golang.org/grpc v1.58.3 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.10 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down