From 59dd850c341ffd96d219fd25cb0a085c90c1562a Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 21 Aug 2023 16:09:30 +0200 Subject: [PATCH 1/2] Implement manual-change subcommand; fix-up logging and output --- cmd/getbookmark.go | 16 +- cmd/getchange.go | 6 +- cmd/getsnapshot.go | 13 +- cmd/manualchange.go | 388 ++++++++++++++++++++++++++++++++++++++++++++ cmd/request.go | 30 ++-- cmd/root.go | 2 +- 6 files changed, 425 insertions(+), 30 deletions(-) create mode 100644 cmd/manualchange.go diff --git a/cmd/getbookmark.go b/cmd/getbookmark.go index d94bdba0..663f17b4 100644 --- a/cmd/getbookmark.go +++ b/cmd/getbookmark.go @@ -86,26 +86,16 @@ func GetBookmark(signals chan os.Signal, ready chan bool) int { } log.WithContext(ctx).WithFields(log.Fields{ "bookmark-uuid": uuid.UUID(response.Msg.Bookmark.Metadata.UUID), - "bookmark-created": response.Msg.Bookmark.Metadata.Created, + "bookmark-created": response.Msg.Bookmark.Metadata.Created.AsTime(), "bookmark-name": response.Msg.Bookmark.Properties.Name, "bookmark-description": response.Msg.Bookmark.Properties.Description, }).Info("found bookmark") - for _, q := range response.Msg.Bookmark.Properties.Queries { - log.WithContext(ctx).WithFields(log.Fields{ - "bookmark-query": q, - }).Info("found bookmark query") - } - for _, i := range response.Msg.Bookmark.Properties.ExcludedItems { - log.WithContext(ctx).WithFields(log.Fields{ - "bookmark-excluded-item": i, - }).Info("found bookmark excluded item") - } - b, err := json.MarshalIndent(response.Msg.Bookmark.Properties, "", " ") + b, err := json.MarshalIndent(response.Msg.Bookmark.ToMap(), "", " ") if err != nil { log.Infof("Error rendering bookmark: %v", err) } else { - log.Info(string(b)) + fmt.Println(string(b)) } return 0 diff --git a/cmd/getchange.go b/cmd/getchange.go index f954d1b3..cd8b4c6b 100644 --- a/cmd/getchange.go +++ b/cmd/getchange.go @@ -97,13 +97,13 @@ func GetChange(signals chan os.Signal, ready chan bool) int { switch viper.GetString("format") { case "json": - b, err := json.MarshalIndent(response.Msg.Change, "", " ") + b, err := json.MarshalIndent(response.Msg.Change.ToMap(), "", " ") if err != nil { log.Errorf("Error rendering bookmark: %v", err) return 1 - } else { - fmt.Println(string(b)) } + + 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 { diff --git a/cmd/getsnapshot.go b/cmd/getsnapshot.go index b48c395f..771c513f 100644 --- a/cmd/getsnapshot.go +++ b/cmd/getsnapshot.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "encoding/json" "fmt" "os" "os/signal" @@ -84,8 +85,8 @@ func GetSnapshot(signals chan os.Signal, ready chan bool) int { return 1 } log.WithContext(ctx).WithFields(log.Fields{ - "snapshot-uuid": response.Msg.Snapshot.Metadata.UUID, - "snapshot-created": response.Msg.Snapshot.Metadata.Created, + "snapshot-uuid": uuid.UUID(response.Msg.Snapshot.Metadata.UUID), + "snapshot-created": response.Msg.Snapshot.Metadata.Created.AsTime(), "snapshot-name": response.Msg.Snapshot.Properties.Name, "snapshot-description": response.Msg.Snapshot.Properties.Description, }).Info("found snapshot") @@ -104,6 +105,14 @@ func GetSnapshot(signals chan os.Signal, ready chan bool) int { "snapshot-item": i, }).Info("found snapshot item") } + + b, err := json.MarshalIndent(response.Msg.Snapshot.ToMap(), "", " ") + if err != nil { + log.Infof("Error rendering snapshot: %v", err) + } else { + fmt.Println(string(b)) + } + return 0 } diff --git a/cmd/manualchange.go b/cmd/manualchange.go new file mode 100644 index 00000000..7084d053 --- /dev/null +++ b/cmd/manualchange.go @@ -0,0 +1,388 @@ +package cmd + +import ( + "context" + "errors" + "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/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "google.golang.org/protobuf/encoding/protojson" + "nhooyr.io/websocket" + "nhooyr.io/websocket/wspb" +) + +// manualChangeCmd represents the submit-plan command +var manualChangeCmd = &cobra.Command{ + Use: "manual-change [--title TITLE] [--description DESCRIPTION] [--ticket-link URL] --query-scope SCOPE --query-type TYPE --query QUERY", + Short: "Creates a new Change from a given query", + 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 `manual-change` flags") + } + }, + Run: func(cmd *cobra.Command, args []string) { + sigs := make(chan os.Signal, 1) + + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + exitcode := ManualChange(sigs, nil) + tracing.ShutdownTracer() + os.Exit(exitcode) + }, +} + +func ManualChange(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 ManualChange", trace.WithAttributes( + attribute.String("om.config", fmt.Sprintf("%v", viper.AllSettings())), + )) + defer span.End() + + gatewayUrl := viper.GetString("gateway-url") + if gatewayUrl == "" { + gatewayUrl = fmt.Sprintf("%v/api/gateway", viper.GetString("url")) + viper.Set("gateway-url", gatewayUrl) + } + + lf := log.Fields{} + + ctx, err = ensureToken(ctx, []string{"changes:write"}, signals) + if err != nil { + log.WithContext(ctx).WithFields(lf).WithField("api-key-url", viper.GetString("api-key-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() + + client := AuthenticatedChangesClient(ctx) + changeUuid, err := getChangeUuid(ctx, sdp.ChangeStatus_CHANGE_STATUS_DEFINING, false) + if err != nil { + log.WithContext(ctx).WithError(err).WithFields(lf).Error("failed to searching for existing changes") + return 1 + } + + if changeUuid == uuid.Nil { + createResponse, err := client.CreateChange(ctx, &connect.Request[sdp.CreateChangeRequest]{ + Msg: &sdp.CreateChangeRequest{ + Properties: &sdp.ChangeProperties{ + Title: viper.GetString("title"), + Description: viper.GetString("description"), + TicketLink: viper.GetString("ticket-link"), + Owner: viper.GetString("owner"), + // CcEmails: viper.GetString("cc-emails"), + }, + }, + }) + if err != nil { + log.WithContext(ctx).WithError(err).WithFields(lf).Error("failed to create change") + return 1 + } + + maybeChangeUuid := createResponse.Msg.Change.Metadata.GetUUIDParsed() + if maybeChangeUuid == nil { + log.WithContext(ctx).WithError(err).WithFields(lf).Error("failed to read change id") + return 1 + } + + changeUuid = *maybeChangeUuid + lf["change"] = changeUuid + log.WithContext(ctx).WithFields(lf).Info("created a new change") + } else { + lf["change"] = changeUuid + log.WithContext(ctx).WithFields(lf).Info("re-using change") + } + + receivedItems := []*sdp.Reference{} + + method, err := methodFromString(viper.GetString("query-method")) + if err != nil { + log.WithContext(ctx).WithError(err).WithFields(lf).Error("can't parse --query-method") + return 1 + } + + u := uuid.New() + + queries := []*sdp.Query{ + { + UUID: u[:], + Method: method, + Scope: viper.GetString("query-scope"), + Type: viper.GetString("query-type"), + Query: viper.GetString("query"), + }, + } + options := &websocket.DialOptions{ + HTTPClient: NewAuthenticatedClient(ctx, otelhttp.DefaultClient), + } + + log.WithContext(ctx).WithFields(lf).WithField("item_count", len(queries)).Info("identifying items") + // nolint: bodyclose // nhooyr.io/websocket reads the body internally + c, _, err := websocket.Dial(ctx, viper.GetString("gateway-url"), options) + if err != nil { + log.WithContext(ctx).WithFields(lf).WithError(err).Error("Failed to connect to overmind API") + return 1 + } + defer c.Close(websocket.StatusGoingAway, "") + + // the default, 32kB is too small for cert bundles and rds-db-cluster-parameter-groups + c.SetReadLimit(2 * 1024 * 1024) + + queriesSentChan := make(chan struct{}) + go func() { + for _, q := range queries { + req := sdp.GatewayRequest{ + MinStatusInterval: minStatusInterval, + RequestType: &sdp.GatewayRequest_Query{ + Query: q, + }, + } + err = wspb.Write(ctx, c, &req) + + if err == nil { + log.WithContext(ctx).WithFields(log.Fields{ + "scope": q.Scope, + "type": q.Type, + "query": q.Query, + "method": q.Method.String(), + "uuid": q.ParseUuid().String(), + }).Trace("Started query") + } + if err != nil { + log.WithContext(ctx).WithFields(lf).WithError(err).WithField("req", &req).Error("Failed to send request") + continue + } + } + queriesSentChan <- struct{}{} + }() + + responses := make(chan *sdp.GatewayResponse) + + // Start a goroutine that reads responses + go func() { + for { + res := new(sdp.GatewayResponse) + + err = wspb.Read(ctx, c, res) + + if err != nil { + var e websocket.CloseError + if errors.As(err, &e) { + log.WithContext(ctx).WithFields(lf).WithFields(log.Fields{ + "code": e.Code.String(), + "reason": e.Reason, + }).Info("Websocket closing") + return + } + log.WithContext(ctx).WithFields(lf).WithError(err).Error("Failed to read response") + return + } + + responses <- res + } + }() + + activeQueries := make(map[uuid.UUID]bool) + queriesSent := false + + // Read the responses +responses: + for { + select { + case <-queriesSentChan: + queriesSent = true + + case <-signals: + log.WithContext(ctx).WithFields(lf).Info("Received interrupt, exiting") + return 1 + + case <-ctx.Done(): + log.WithContext(ctx).WithFields(lf).Info("Context cancelled, exiting") + return 1 + + case resp := <-responses: + switch resp.ResponseType.(type) { + + case *sdp.GatewayResponse_Status: + status := resp.GetStatus() + statusFields := log.Fields{ + "summary": status.Summary, + "responders": status.Summary.Responders, + "queriesSent": queriesSent, + "post_processing_complete": status.PostProcessingComplete, + } + + if status.Done() { + // fall through from all "final" query states, check if there's still queries in progress; + // only break from the loop if all queries have already been sent + // TODO: see above, still needs DefaultStartTimeout implemented to account for slow sources + allDone := allDone(ctx, activeQueries, lf) + statusFields["allDone"] = allDone + if allDone && queriesSent { + log.WithContext(ctx).WithFields(lf).WithFields(statusFields).Info("all responders and queries done") + break responses + } else { + log.WithContext(ctx).WithFields(lf).WithFields(statusFields).Info("all responders done, with unfinished queries") + } + } else { + log.WithContext(ctx).WithFields(lf).WithFields(statusFields).Info("still waiting for responders") + } + + case *sdp.GatewayResponse_QueryStatus: + status := resp.GetQueryStatus() + statusFields := log.Fields{ + "status": status.Status.String(), + } + queryUuid := status.GetUUIDParsed() + if queryUuid == nil { + log.WithContext(ctx).WithFields(lf).WithFields(statusFields).Debugf("Received QueryStatus with nil UUID") + continue responses + } + statusFields["query"] = queryUuid + + switch status.Status { + case sdp.QueryStatus_UNSPECIFIED: + statusFields["unexpected_status"] = true + case sdp.QueryStatus_STARTED: + activeQueries[*queryUuid] = true + case sdp.QueryStatus_FINISHED: + activeQueries[*queryUuid] = false + case sdp.QueryStatus_ERRORED: + activeQueries[*queryUuid] = false + case sdp.QueryStatus_CANCELLED: + activeQueries[*queryUuid] = false + default: + statusFields["unexpected_status"] = true + } + + log.WithContext(ctx).WithFields(lf).WithFields(statusFields).Debugf("query status update") + + case *sdp.GatewayResponse_NewItem: + item := resp.GetNewItem() + log.WithContext(ctx).WithFields(lf).WithField("item", item.GloballyUniqueName()).Infof("new item") + + receivedItems = append(receivedItems, item.Reference()) + + case *sdp.GatewayResponse_NewEdge: + log.WithContext(ctx).WithFields(lf).Debug("ignored edge") + + case *sdp.GatewayResponse_QueryError: + err := resp.GetQueryError() + log.WithContext(ctx).WithFields(lf).WithError(err).Errorf("Error from %v(%v)", err.ResponderName, err.SourceName) + + case *sdp.GatewayResponse_Error: + err := resp.GetError() + log.WithContext(ctx).WithFields(lf).WithField(log.ErrorKey, err).Errorf("generic error") + + default: + j := protojson.Format(resp) + log.WithContext(ctx).WithFields(lf).Infof("Unknown %T Response:\n%v", resp.ResponseType, j) + } + } + } + + if len(receivedItems) > 0 { + log.WithContext(ctx).WithFields(lf).WithField("received_items", len(receivedItems)).Info("updating changing items on the change record") + } else { + log.WithContext(ctx).WithFields(lf).WithField("received_items", len(receivedItems)).Info("updating change record with no items") + } + resultStream, err := client.UpdateChangingItems(ctx, &connect.Request[sdp.UpdateChangingItemsRequest]{ + Msg: &sdp.UpdateChangingItemsRequest{ + ChangeUUID: changeUuid[:], + ChangingItems: receivedItems, + }, + }) + if err != nil { + log.WithContext(ctx).WithFields(lf).WithError(err).Error("failed to update changing items") + return 1 + } + + last_log := time.Now() + first_log := true + for resultStream.Receive() { + msg := resultStream.Msg() + + // log the first message and at most every 250ms during discovery + // to avoid spanning the cli output + time_since_last_log := time.Since(last_log) + if first_log || msg.State != sdp.CalculateBlastRadiusResponse_STATE_DISCOVERING || time_since_last_log > 250*time.Millisecond { + log.WithContext(ctx).WithFields(lf).WithField("msg", msg).Info("status update") + last_log = time.Now() + first_log = false + } + } + if resultStream.Err() != nil { + log.WithContext(ctx).WithFields(lf).WithError(resultStream.Err()).Error("error streaming results") + return 1 + } + + changeUrl := fmt.Sprintf("%v/changes/%v", viper.GetString("frontend"), changeUuid) + log.WithContext(ctx).WithFields(lf).WithField("change-url", changeUrl).Info("change ready") + fmt.Println(changeUrl) + + fetchResponse, err := client.GetChange(ctx, &connect.Request[sdp.GetChangeRequest]{ + Msg: &sdp.GetChangeRequest{ + UUID: changeUuid[:], + }, + }) + if err != nil { + log.WithContext(ctx).WithFields(lf).WithError(err).Error("failed to get updated change") + return 1 + } + + for _, a := range fetchResponse.Msg.Change.Properties.AffectedAppsUUID { + appUuid, err := uuid.FromBytes(a) + if err != nil { + log.WithContext(ctx).WithFields(lf).WithError(err).WithField("app", a).Error("received invalid app uuid") + continue + } + log.WithContext(ctx).WithFields(lf).WithFields(log.Fields{ + "change-url": changeUrl, + "app": appUuid, + "app-url": fmt.Sprintf("%v/apps/%v", viper.GetString("frontend"), appUuid), + }).Info("affected app") + } + + return 0 +} + +func init() { + rootCmd.AddCommand(manualChangeCmd) + + manualChangeCmd.PersistentFlags().String("changes-url", "", "The changes service API endpoint (defaults to --url)") + manualChangeCmd.PersistentFlags().String("frontend", "https://app.overmind.tech", "The frontend base URL") + + manualChangeCmd.PersistentFlags().String("title", "", "Short title for this change.") + manualChangeCmd.PersistentFlags().String("description", "", "Quick description of the change.") + manualChangeCmd.PersistentFlags().String("ticket-link", "*", "Link to the ticket for this change.") + manualChangeCmd.PersistentFlags().String("owner", "", "The owner of this change.") + // manualChangeCmd.PersistentFlags().String("cc-emails", "", "A comma-separated list of emails to keep updated with the status of this change.") + + manualChangeCmd.PersistentFlags().String("query-method", "get", "The method to use (get, list, search)") + manualChangeCmd.PersistentFlags().String("query-scope", "*", "The scope to query") + manualChangeCmd.PersistentFlags().String("query-type", "*", "The type to query") + manualChangeCmd.PersistentFlags().String("query", "", "The actual query to send") + + manualChangeCmd.PersistentFlags().String("timeout", "3m", "How long to wait for responses") +} diff --git a/cmd/request.go b/cmd/request.go index ef30f64f..d770f5cc 100644 --- a/cmd/request.go +++ b/cmd/request.go @@ -291,6 +291,22 @@ responses: return 0 } +func methodFromString(method string) (sdp.QueryMethod, error) { + var result sdp.QueryMethod + + switch method { + case "get": + result = sdp.QueryMethod_GET + case "list": + result = sdp.QueryMethod_LIST + case "search": + result = sdp.QueryMethod_SEARCH + default: + return 0, fmt.Errorf("query method '%v' not supported", method) + } + return result, nil +} + func createInitialRequest() (*sdp.GatewayRequest, error) { req := &sdp.GatewayRequest{ MinStatusInterval: minStatusInterval, @@ -299,17 +315,9 @@ func createInitialRequest() (*sdp.GatewayRequest, error) { switch viper.GetString("request-type") { case "query": - var method sdp.QueryMethod - - switch viper.GetString("query-method") { - case "get": - method = sdp.QueryMethod_GET - case "list": - method = sdp.QueryMethod_LIST - case "search": - method = sdp.QueryMethod_SEARCH - default: - return nil, fmt.Errorf("query method %v not supported", viper.GetString("query-method")) + method, err := methodFromString(viper.GetString("query-method")) + if err != nil { + return nil, err } req.RequestType = &sdp.GatewayRequest_Query{ diff --git a/cmd/root.go b/cmd/root.go index 2ed9895f..85cc4976 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -259,7 +259,7 @@ func init() { rootCmd.PersistentFlags().String("api-key", "", "The API key to use for authentication, also read from OVM_API_KEY environment variable") err := viper.BindEnv("api-key", "OVM_API_KEY", "API_KEY") if err != nil { - log.WithError(err).Fatal("could not bind token") + log.WithError(err).Fatal("could not bind api key to env") } rootCmd.PersistentFlags().String("api-key-url", "", "The overmind API Keys endpoint (defaults to --url)") rootCmd.PersistentFlags().String("auth0-client-id", "j3LylZtIosVPZtouKI8WuVHmE6Lluva1", "OAuth Client ID to use when connecting with auth") From 38d9826aded51d9f6e646a1e8340b52d5157e555 Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 21 Aug 2023 16:13:51 +0200 Subject: [PATCH 2/2] Update sdp-go --- go.mod | 2 +- go.sum | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 37a0a435..5898a7ca 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bufbuild/connect-go v1.10.0 github.com/getsentry/sentry-go v0.23.0 github.com/google/uuid v1.3.0 - github.com/overmindtech/sdp-go v0.44.4 + github.com/overmindtech/sdp-go v0.44.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index bea860a2..e71369e8 100644 --- a/go.sum +++ b/go.sum @@ -231,10 +231,8 @@ github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/overmindtech/sdp-go v0.43.0 h1:1BVry/jSKABqEYKmLMIC8XCJIcWt0iWDDHU7AVROJwE= -github.com/overmindtech/sdp-go v0.43.0/go.mod h1:s/CnUoFH5WfugwQ6+v8M+0JXvCMlq1/bz2ha2G7B92E= -github.com/overmindtech/sdp-go v0.44.4 h1:2u8FoAzMQDIlvYFpx60kAfw4dXbInk9wyOtHMl5IXbA= -github.com/overmindtech/sdp-go v0.44.4/go.mod h1:9/yAXfMAAC0CQZ/pe3lxti/xL0cK27iOWpxYv11r5aw= +github.com/overmindtech/sdp-go v0.44.5 h1:NgjRwGmJWio8z40bOLo7OOd0Yeq9M9YIYpXrYOayyDs= +github.com/overmindtech/sdp-go v0.44.5/go.mod h1:9/yAXfMAAC0CQZ/pe3lxti/xL0cK27iOWpxYv11r5aw= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -332,8 +330,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -405,8 +401,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -419,8 +413,6 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -477,8 +469,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -494,8 +484,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=