Skip to content

Commit 1c09e9c

Browse files
committed
Implement get-change command
1 parent 57ea657 commit 1c09e9c

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

cmd/getchange.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/url"
8+
"os"
9+
"os/signal"
10+
"path"
11+
"syscall"
12+
"time"
13+
14+
"github.com/bufbuild/connect-go"
15+
"github.com/google/uuid"
16+
"github.com/overmindtech/ovm-cli/tracing"
17+
"github.com/overmindtech/sdp-go"
18+
log "github.com/sirupsen/logrus"
19+
"github.com/spf13/cobra"
20+
"github.com/spf13/viper"
21+
"go.opentelemetry.io/otel/attribute"
22+
"go.opentelemetry.io/otel/trace"
23+
)
24+
25+
// getChangeCmd represents the get-change command
26+
var getChangeCmd = &cobra.Command{
27+
Use: "get-change {--uuid ID | --change https://app.overmind.tech/changes/c772d072-6b0b-4763-b7c5-ff5069beed4c}",
28+
Short: "Displays the contents of a change.",
29+
PreRun: func(cmd *cobra.Command, args []string) {
30+
// Bind these to viper
31+
err := viper.BindPFlags(cmd.Flags())
32+
if err != nil {
33+
log.WithError(err).Fatal("could not bind `get-change` flags")
34+
}
35+
},
36+
Run: func(cmd *cobra.Command, args []string) {
37+
sigs := make(chan os.Signal, 1)
38+
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
39+
40+
exitcode := GetChange(sigs, nil)
41+
tracing.ShutdownTracer()
42+
os.Exit(exitcode)
43+
},
44+
}
45+
46+
func GetChange(signals chan os.Signal, ready chan bool) int {
47+
timeout, err := time.ParseDuration(viper.GetString("timeout"))
48+
if err != nil {
49+
log.Errorf("invalid --timeout value '%v', error: %v", viper.GetString("timeout"), err)
50+
return 1
51+
}
52+
53+
var changeUuid uuid.UUID
54+
if viper.GetString("uuid") != "" {
55+
changeUuid, err = uuid.Parse(viper.GetString("uuid"))
56+
if err != nil {
57+
log.Errorf("invalid --uuid value '%v', error: %v", viper.GetString("uuid"), err)
58+
return 1
59+
}
60+
}
61+
62+
if viper.GetString("change") != "" {
63+
changeUrl, err := url.ParseRequestURI(viper.GetString("change"))
64+
if err != nil {
65+
log.Errorf("invalid --change value '%v', error: %v", viper.GetString("change"), err)
66+
return 1
67+
}
68+
changeUuid, err = uuid.Parse(path.Base(changeUrl.Path))
69+
if err != nil {
70+
log.Errorf("invalid --change value '%v', couldn't parse: %v", viper.GetString("change"), err)
71+
return 1
72+
}
73+
}
74+
75+
if changeUuid == uuid.Nil {
76+
log.Error("no change specified; use one of --uuid or --change")
77+
return 1
78+
}
79+
80+
ctx := context.Background()
81+
ctx, span := tracing.Tracer().Start(ctx, "CLI GetChange", trace.WithAttributes(
82+
attribute.String("om.config", fmt.Sprintf("%v", viper.AllSettings())),
83+
))
84+
defer span.End()
85+
86+
ctx, err = ensureToken(ctx, signals)
87+
if err != nil {
88+
log.WithContext(ctx).WithError(err).WithFields(log.Fields{
89+
"url": viper.GetString("url"),
90+
}).Error("failed to authenticate")
91+
return 1
92+
}
93+
94+
// apply a timeout to the main body of processing
95+
ctx, cancel := context.WithTimeout(ctx, timeout)
96+
defer cancel()
97+
98+
client := AuthenticatedChangesClient(ctx)
99+
response, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
100+
Msg: &sdp.GetChangeRequest{
101+
UUID: changeUuid[:],
102+
},
103+
})
104+
if err != nil {
105+
log.WithContext(ctx).WithError(err).WithFields(log.Fields{
106+
"change-url": viper.GetString("change-url"),
107+
}).Error("failed to get change")
108+
return 1
109+
}
110+
log.WithContext(ctx).WithFields(log.Fields{
111+
"change-uuid": uuid.UUID(response.Msg.Change.Metadata.UUID),
112+
"change-created": response.Msg.Change.Metadata.CreatedAt.AsTime(),
113+
"change-name": response.Msg.Change.Properties.Title,
114+
"change-description": response.Msg.Change.Properties.Description,
115+
}).Info("found change")
116+
117+
switch viper.GetString("format") {
118+
case "json":
119+
b, _ := json.MarshalIndent(response.Msg.Change, "", " ")
120+
fmt.Println(string(b))
121+
case "markdown":
122+
changeUrl := fmt.Sprintf("%v/changes/%v", viper.GetString("frontend"), changeUuid.String())
123+
if response.Msg.Change.Metadata.NumAffectedApps != 0 || response.Msg.Change.Metadata.NumAffectedItems != 0 {
124+
// we have affected stuff
125+
fmt.Printf(`## Blast Radius &nbsp; · &nbsp; [View in Overmind](%v) <img align="center" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPwSURBVHgB7VrdVdswGFVCBkg3cDdIJyBMUHyAZ5INyASlE6SdAPPKX+gENRPABmSDZgBIem9ipZ8VKZaR7fRwuOfk2LJk+V79fLoSKPWB3aKl/gNMJpMuLvzN4jielXl3ZwJAuo/L6Xw+5zUSWRSQ4vfr6OgoKaqncQEg3lssFmP8+h7Fpyg3Oj4+vncVaKsGcXd3xxb/7UmeiFqtFjRPvrkKNCaA5EE8UauxrpFA0EG73f6E4dLilWmUu5Tv4tm5S0QjQ0iQ15iCLOZr/OR6B4Qj9pYS8wPvHOCdVJarvQcc5A+2kSeQvyzH8voZBF2Y5WoVsIX81Of9TEQsHkU3NzeHskxtAkLJa7CnjDkxkPm1CKiKvIasC1FpX+ZVLqBq8kSn05HzpZut3EtUKqAO8oTFXqwFVBZG6yJPsMURgf7oNNcMfV9JD/iQl91eFi8vLz1Zt8wLFsAFB+R/yA+Y5BH6HtmCt7e3tAWRKgnUdyqSaS5PBQLExurfmLS1PP2MbsFDlH9kj3lWrzLBA51G/TmbESSAzpKkROUbY55pkP4uHnU53HxECDuxBN57qtRKoMKBSCauCXtycnIOcZ+VGL8cdtuGk8ULzfb29mKzXJAAfGC9qJhda8LibdgTF7ayNiMHjGwNFCQAhCKRfCoqb3ob7guyndkaDvJD1+4sdAh1BTmvvazpbV5fX3PmrAx5wksAJxxC4DN+Y/kc0WVNukycN9aMfVEHg0Ik8oZF++JCAWKRivA7kxMPrTUVRXvKE9LboBEifZ/1zk+1midDn019Z1umbYWVEwnj+QH5S+IQw7CYKg9wuKE3dTLXc9jAn+Fypjzh7AGXPZBlMH7lacHg6urKqxeM4VbqHMiEVYCvMUN8TzEEUp1GnPayCtLb4DtTFYANAZm3ScSjra4SAoZiMi9DYJEIw9s8qABsCJBLt/KwxMzj4ZN4RBHPrmMQ09ug1xIVgNx+4Pr6ug/CawFc/n39PCblAD0xlmsDz3nk+mBZpO4RaWIVgFwPGF2blNmMMORBwBeSYppzo4D8DN8bqUCYYbSvb4q8jQ2Z4JhRpoA8Mapit2bOgUjcF3obFzzIey1SPmjLD7lIvBV1kydkD8yMj795D5u9Xzt5Yi2ALS7NmSrhbUw0RZ7IzQGEwFTfZ96mNJokT5gCcmeQvt5Go2nyxMbBFn2/IOB9OLUL8oTNSgxF0svbcCOyC/KE9WgRloKnCKaXSbLFjUcbsyxK9bK5MjDKNkKecJ6NOkQUgVFs1BR5YuvhLv8aQoOm8kPDXhG8D611FfagDLxOp+k0cfmqVl5JLnCc5Ck2KJfc3KgdoPTxesi/BXzgPeIvmnKmx43NP2kAAAAASUVORK5CYII=" alt="chain link icon" />
126+
127+
> **Warning**
128+
> Overmind identified potentially affected apps and items as a result of this pull request.
129+
130+
<br>
131+
132+
| <img align="center" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAM0SURBVHgB7ZqxUhsxEIb/FZg0eQB6eAB6aJwUYVKEZMBFKmjyAFR5hlRp0qUxbQ4GSJEJk0zcmN4PEHo/ABUM2uyeQwY7htPZWs4H+mZc+Kyx7n6tpP1XByQSiUQikXisEErSed1aAdEqvF9jYBEGyE39hn4WaK+ZZX0YEixAp9V6igu/zaAt3CME3seCUyHOYUCQAPrw/oI/SuNlVAANomHXQgQX1EpGvqqHV1j7lnuAAYUCdF62Fu877Meh95CvP5EpjoAGmSg/CR5YQ2QKBfDwlYX+fzCvIjKFAlQ590chg213HoYQ49w7OhCVe2igb72nT4KJADJSfUmWPjSPsx5mnOgCMNEBNdC2SlxiE1UAefi950dZGzUimgA68uMefta9QxQBdM5r2N+89s87MG/J9gVLeLBTSbbI652NzVLeISwVLrwDGurw2jtUkUHmfUrf+QAEMLUAOvrNr9n3oYs18g5TC+CJTm9+r5t3mFoA+YPu0IWaeYfp14BGvgLf6LRe3mFqAUZX27p5hzi7QI1JAuCRY2qHy0Lkeh585vxgYfUOi475hVUKrcyEAFo3wJzY58Mv3TE/tzuvWuvSaNtCiMqnQF47eELvmodZ97Y2eaZ5SbusQkWm+jWAwxxc81vWdxIliEylAoz1EXegUSLTJWqVqVIBxEecoCRSYzxDRAoFYJ2jRjCV/2/2w6n3tBRHwIjbe2i4gAZdGCHzubRvIBd3KywUoHmU9bTeBwOc5yWURRIjRCRoEXRS7/tbeIyKrAErnTet4PO+n5IQxT4dChIgt7xyPm8RCf6K30v9rvChtNJEkg0iMsHboIogZe9PdElvpdx0Qs5FiQgi5AVUHd3b2vyQKPHz/NnibLD0O0JF/NrY6mBCeHCkdiJmaLA9ztESrvyyThVMyLPj/eZdv8+WG9QRZjE918Pi2WCIhkkFETxykgCIjKV3KEtI7hI/AmbIO2h5rahNdAEsvUNZ3IJrF7ZBZCy9Qxn0HkIqTSaLoJV3CEX7diPvK9zR1ob8HYFL7BDzJu4RHXlX4h0l4zxrYGJ8AzvO0RJ7m4NTgut74lNdf3QKIpFIJBKJRCKAPxZBM7U9oOuSAAAAAElFTkSuQmCC" alt="icon for blast radius items" /> &nbsp;Affected items |
133+
| ------------- |
134+
| [%v items](%v) |
135+
`, changeUrl, response.Msg.Change.Metadata.NumAffectedItems, changeUrl)
136+
} else {
137+
fmt.Printf(`## Blast Radius &nbsp; · &nbsp; [View in Overmind](%v) <img align="center" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPwSURBVHgB7VrdVdswGFVCBkg3cDdIJyBMUHyAZ5INyASlE6SdAPPKX+gENRPABmSDZgBIem9ipZ8VKZaR7fRwuOfk2LJk+V79fLoSKPWB3aKl/gNMJpMuLvzN4jielXl3ZwJAuo/L6Xw+5zUSWRSQ4vfr6OgoKaqncQEg3lssFmP8+h7Fpyg3Oj4+vncVaKsGcXd3xxb/7UmeiFqtFjRPvrkKNCaA5EE8UauxrpFA0EG73f6E4dLilWmUu5Tv4tm5S0QjQ0iQ15iCLOZr/OR6B4Qj9pYS8wPvHOCdVJarvQcc5A+2kSeQvyzH8voZBF2Y5WoVsIX81Of9TEQsHkU3NzeHskxtAkLJa7CnjDkxkPm1CKiKvIasC1FpX+ZVLqBq8kSn05HzpZut3EtUKqAO8oTFXqwFVBZG6yJPsMURgf7oNNcMfV9JD/iQl91eFi8vLz1Zt8wLFsAFB+R/yA+Y5BH6HtmCt7e3tAWRKgnUdyqSaS5PBQLExurfmLS1PP2MbsFDlH9kj3lWrzLBA51G/TmbESSAzpKkROUbY55pkP4uHnU53HxECDuxBN57qtRKoMKBSCauCXtycnIOcZ+VGL8cdtuGk8ULzfb29mKzXJAAfGC9qJhda8LibdgTF7ayNiMHjGwNFCQAhCKRfCoqb3ob7guyndkaDvJD1+4sdAh1BTmvvazpbV5fX3PmrAx5wksAJxxC4DN+Y/kc0WVNukycN9aMfVEHg0Ik8oZF++JCAWKRivA7kxMPrTUVRXvKE9LboBEifZ/1zk+1midDn019Z1umbYWVEwnj+QH5S+IQw7CYKg9wuKE3dTLXc9jAn+Fypjzh7AGXPZBlMH7lacHg6urKqxeM4VbqHMiEVYCvMUN8TzEEUp1GnPayCtLb4DtTFYANAZm3ScSjra4SAoZiMi9DYJEIw9s8qABsCJBLt/KwxMzj4ZN4RBHPrmMQ09ug1xIVgNx+4Pr6ug/CawFc/n39PCblAD0xlmsDz3nk+mBZpO4RaWIVgFwPGF2blNmMMORBwBeSYppzo4D8DN8bqUCYYbSvb4q8jQ2Z4JhRpoA8Mapit2bOgUjcF3obFzzIey1SPmjLD7lIvBV1kydkD8yMj795D5u9Xzt5Yi2ALS7NmSrhbUw0RZ7IzQGEwFTfZ96mNJokT5gCcmeQvt5Go2nyxMbBFn2/IOB9OLUL8oTNSgxF0svbcCOyC/KE9WgRloKnCKaXSbLFjUcbsyxK9bK5MjDKNkKecJ6NOkQUgVFs1BR5YuvhLv8aQoOm8kPDXhG8D611FfagDLxOp+k0cfmqVl5JLnCc5Ck2KJfc3KgdoPTxesi/BXzgPeIvmnKmx43NP2kAAAAASUVORK5CYII=" alt="chain link icon" />
138+
139+
> **✅ Checks complete**
140+
> Overmind didn't identify any potentially affected apps and items as a result of this pull request.
141+
142+
`, changeUrl)
143+
}
144+
}
145+
146+
return 0
147+
}
148+
149+
func init() {
150+
rootCmd.AddCommand(getChangeCmd)
151+
152+
getChangeCmd.PersistentFlags().String("change", "", "The frontend URL of the change to get")
153+
getChangeCmd.PersistentFlags().String("uuid", "", "The UUID of the change that should be displayed.")
154+
getChangeCmd.MarkFlagsMutuallyExclusive("change", "uuid")
155+
156+
getChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")
157+
getChangeCmd.PersistentFlags().String("format", "json", "How to render the change. Possible values: json, markdown")
158+
159+
getChangeCmd.PersistentFlags().String("timeout", "1m", "How long to wait for responses")
160+
}

0 commit comments

Comments
 (0)