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
30 changes: 15 additions & 15 deletions cmd/endchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"time"

"github.com/bufbuild/connect-go"
"github.com/google/uuid"
"github.com/overmindtech/ovm-cli/tracing"
"github.com/overmindtech/sdp-go"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -47,37 +46,38 @@ func EndChange(signals chan os.Signal, ready chan bool) int {
return 1
}

snapshotUuid, err := uuid.Parse(viper.GetString("uuid"))
if err != nil {
log.Errorf("invalid --uuid value '%v', error: %v", viper.GetString("uuid"), err)
return 1
}

ctx := context.Background()
ctx, span := tracing.Tracer().Start(ctx, "CLI EndChange", trace.WithAttributes(
attribute.String("om.config", fmt.Sprintf("%v", viper.AllSettings())),
))
defer span.End()

lf := log.Fields{
"uuid": snapshotUuid.String(),
}

ctx, err = ensureToken(ctx, signals)
if err != nil {
log.WithContext(ctx).WithFields(lf).WithError(err).Error("failed to authenticate")
log.WithContext(ctx).WithFields(log.Fields{

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to apiKey](1) flows to a logging call.
"url": viper.GetString("url"),
}).WithError(err).Error("failed to authenticate")
return 1
}

// apply a timeout to the main body of processing
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

lf := log.Fields{}
changeUuid, err := getChangeUuid(ctx, sdp.ChangeStatus_CHANGE_STATUS_HAPPENING)
if err != nil {
log.WithError(err).WithFields(lf).Error("failed to identify change")
return 1
}

lf["uuid"] = changeUuid.String()

// snapClient := AuthenticatedSnapshotsClient(ctx)
client := AuthenticatedChangesClient(ctx)
stream, err := client.EndChange(ctx, &connect.Request[sdp.EndChangeRequest]{
Msg: &sdp.EndChangeRequest{
ChangeUUID: snapshotUuid[:],
ChangeUUID: changeUuid[:],
},
})
if err != nil {
Expand All @@ -100,9 +100,9 @@ func EndChange(signals chan os.Signal, ready chan bool) int {
func init() {
rootCmd.AddCommand(endChangeCmd)

endChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")
withChangeUuidFlags(endChangeCmd)

endChangeCmd.PersistentFlags().String("uuid", "", "The UUID of the snapshot that should be displayed.")
endChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")

endChangeCmd.PersistentFlags().String("timeout", "1m", "How long to wait for responses")
}
139 changes: 139 additions & 0 deletions cmd/getchange.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"os"
"os/signal"
"syscall"
"time"

"github.com/bufbuild/connect-go"
"github.com/google/uuid"
"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"
)

// 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}",
Short: "Displays the contents of a change.",
PreRun: func(cmd *cobra.Command, args []string) {
// Bind these to viper
err := viper.BindPFlags(cmd.Flags())
if err != nil {
log.WithError(err).Fatal("could not bind `get-change` flags")
}
},
Run: func(cmd *cobra.Command, args []string) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

exitcode := GetChange(sigs, nil)
tracing.ShutdownTracer()
os.Exit(exitcode)
},
}

func GetChange(signals chan os.Signal, ready chan bool) int {
timeout, err := time.ParseDuration(viper.GetString("timeout"))
if err != nil {
log.Errorf("invalid --timeout value '%v', error: %v", viper.GetString("timeout"), err)
return 1
}

ctx := context.Background()
ctx, span := tracing.Tracer().Start(ctx, "CLI GetChange", trace.WithAttributes(
attribute.String("om.config", fmt.Sprintf("%v", viper.AllSettings())),
))
defer span.End()

ctx, err = ensureToken(ctx, signals)
if err != nil {
log.WithContext(ctx).WithFields(log.Fields{

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to apiKey](1) flows to a logging call.
"url": viper.GetString("url"),
}).WithError(err).Error("failed to authenticate")
return 1
}

// apply a timeout to the main body of processing
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

lf := log.Fields{}
changeUuid, err := getChangeUuid(ctx, sdp.ChangeStatus(sdp.ChangeStatus_value[viper.GetString("status")]))
if err != nil {
log.WithError(err).WithFields(lf).Error("failed to identify change")
return 1
}

lf["uuid"] = changeUuid.String()

client := AuthenticatedChangesClient(ctx)
response, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{
Msg: &sdp.GetChangeRequest{
UUID: changeUuid[:],
},
})
if err != nil {
log.WithContext(ctx).WithError(err).WithFields(log.Fields{

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to apiKey](1) flows to a logging call.
"change-url": viper.GetString("change-url"),
}).Error("failed to get change")
return 1
}
log.WithContext(ctx).WithFields(log.Fields{

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to apiKey](1) flows to a logging call.
"change-uuid": uuid.UUID(response.Msg.Change.Metadata.UUID),
"change-created": response.Msg.Change.Metadata.CreatedAt.AsTime(),
"change-name": response.Msg.Change.Properties.Title,
"change-description": response.Msg.Change.Properties.Description,
}).Info("found change")

switch viper.GetString("format") {
case "json":
b, _ := json.MarshalIndent(response.Msg.Change, "", " ")
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 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" />
> **Warning**
> Overmind identified potentially affected apps and items as a result of this pull request.
<br>
| <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 |
| ------------- |
| [%v items](%v) |
`, changeUrl, response.Msg.Change.Metadata.NumAffectedItems, changeUrl)
} else {
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" />
> **✅ Checks complete**
> Overmind didn't identify any potentially affected apps and items as a result of this pull request.
`, changeUrl)
}
}

return 0
}

func init() {
rootCmd.AddCommand(getChangeCmd)

withChangeUuidFlags(getChangeCmd)
getChangeCmd.PersistentFlags().String("status", "", "The expected status of the change. Use this with --ticket-link. Allowed values: CHANGE_STATUS_UNSPECIFIED, CHANGE_STATUS_DEFINING, CHANGE_STATUS_HAPPENING, CHANGE_STATUS_PROCESSING, CHANGE_STATUS_DONE")

getChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")
getChangeCmd.PersistentFlags().String("format", "json", "How to render the change. Possible values: json, markdown")

getChangeCmd.PersistentFlags().String("timeout", "1m", "How long to wait for responses")
}
62 changes: 62 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"os"
"path"
"strings"
"time"

Expand Down Expand Up @@ -183,6 +184,67 @@ func ensureToken(ctx context.Context, signals chan os.Signal) (context.Context,
return ctx, fmt.Errorf("no --api-key configured and target URL (%v) is insecure", parsed)
}

// getChangeUuid returns the UUID of a change, as selected by --uuid or --change, or a state with the specified status and having --ticket-link
func getChangeUuid(ctx context.Context, expectedStatus sdp.ChangeStatus) (uuid.UUID, error) {
var changeUuid uuid.UUID
var err error

if viper.GetString("uuid") != "" {
changeUuid, err = uuid.Parse(viper.GetString("uuid"))
if err != nil {
return uuid.Nil, fmt.Errorf("invalid --uuid value '%v', error: %v", viper.GetString("uuid"), err)
}
}

if viper.GetString("change") != "" {
changeUrl, err := url.ParseRequestURI(viper.GetString("change"))
if err != nil {
return uuid.Nil, fmt.Errorf("invalid --change value '%v', error: %v", viper.GetString("change"), err)
}
changeUuid, err = uuid.Parse(path.Base(changeUrl.Path))
if err != nil {
return uuid.Nil, fmt.Errorf("invalid --change value '%v', couldn't parse: %v", viper.GetString("change"), err)
}
}

if viper.GetString("ticket-link") != "" {
client := AuthenticatedChangesClient(ctx)

var maybeChangeUuid *uuid.UUID
changesList, err := client.ListChangesByStatus(ctx, &connect.Request[sdp.ListChangesByStatusRequest]{
Msg: &sdp.ListChangesByStatusRequest{
Status: expectedStatus,
},
})
if err != nil {
return uuid.Nil, errors.New("failed to searching for existing changes")
}

for _, c := range changesList.Msg.Changes {
if c.Properties.TicketLink == viper.GetString("ticket-link") {
maybeChangeUuid = c.Metadata.GetUUIDParsed()
if maybeChangeUuid != nil {
changeUuid = *maybeChangeUuid
break
}
}
}
}

// if changeUuid == uuid.Nil {
// return uuid.Nil, errors.New("no change specified; use one of --change, --ticket-link or --uuid")
// }

return changeUuid, nil
}

func withChangeUuidFlags(cmd *cobra.Command) {
cmd.PersistentFlags().String("change", "", "The frontend URL of the change to get")
cmd.PersistentFlags().String("ticket-link", "", "Link to the ticket for this change.")
cmd.PersistentFlags().String("uuid", "", "The UUID of the change that should be displayed.")
cmd.MarkFlagsMutuallyExclusive("change", "ticket-link", "uuid")
}

func init() {
cobra.OnInitialize(initConfig)

Expand Down
30 changes: 15 additions & 15 deletions cmd/startchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"time"

"github.com/bufbuild/connect-go"
"github.com/google/uuid"
"github.com/overmindtech/ovm-cli/tracing"
"github.com/overmindtech/sdp-go"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -47,37 +46,38 @@ func StartChange(signals chan os.Signal, ready chan bool) int {
return 1
}

snapshotUuid, err := uuid.Parse(viper.GetString("uuid"))
if err != nil {
log.Errorf("invalid --uuid value '%v', error: %v", viper.GetString("uuid"), err)
return 1
}

ctx := context.Background()
ctx, span := tracing.Tracer().Start(ctx, "CLI StartChange", trace.WithAttributes(
attribute.String("om.config", fmt.Sprintf("%v", viper.AllSettings())),
))
defer span.End()

lf := log.Fields{
"uuid": snapshotUuid.String(),
}

ctx, err = ensureToken(ctx, signals)
if err != nil {
log.WithContext(ctx).WithFields(lf).WithError(err).Error("failed to authenticate")
log.WithContext(ctx).WithFields(log.Fields{

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information

[Sensitive data returned by an access to apiKey](1) flows to a logging call.
"url": viper.GetString("url"),
}).WithError(err).Error("failed to authenticate")
return 1
}

// apply a timeout to the main body of processing
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

lf := log.Fields{}
changeUuid, err := getChangeUuid(ctx, sdp.ChangeStatus_CHANGE_STATUS_DEFINING)
if err != nil {
log.WithError(err).WithFields(lf).Error("failed to identify change")
return 1
}

lf["uuid"] = changeUuid.String()

// snapClient := AuthenticatedSnapshotsClient(ctx)
client := AuthenticatedChangesClient(ctx)
stream, err := client.StartChange(ctx, &connect.Request[sdp.StartChangeRequest]{
Msg: &sdp.StartChangeRequest{
ChangeUUID: snapshotUuid[:],
ChangeUUID: changeUuid[:],
},
})
if err != nil {
Expand All @@ -100,9 +100,9 @@ func StartChange(signals chan os.Signal, ready chan bool) int {
func init() {
rootCmd.AddCommand(startChangeCmd)

startChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")
withChangeUuidFlags(startChangeCmd)

startChangeCmd.PersistentFlags().String("uuid", "", "The UUID of the snapshot that should be displayed.")
startChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech/", "The frontend base URL")

startChangeCmd.PersistentFlags().String("timeout", "1m", "How long to wait for responses")
}
Loading