diff --git a/Makefile b/Makefile index e327d5e..a42f71e 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ GOLANGCI_LINT_VERSION := v1.62.2 # renovate datasource=go depName=golang.org/x/vuln/cmd/govulncheck GOVULNCHECK_VERSION := v1.1.3 # renovate datasource=go depName=golang.org/x/tools/cmd/goimports -GOIMPORTS_VERSION := v0.27.0 +GOIMPORTS_VERSION := v0.28.0 # Check if the program is present in $PATH and install otherwise. # ${1} - oneOf{binary,yarn} diff --git a/cspell.yaml b/cspell.yaml index f864cac..d684e42 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -44,6 +44,7 @@ words: - slos - svcs - tpng + - unmarshalling - vuln - vulns - wrapf diff --git a/go.mod b/go.mod index dd8ee80..d9cc2d1 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/schollz/progressbar/v3 v3.17.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.10.0 ) require ( diff --git a/go.sum b/go.sum index 0a2af1d..012b7f3 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,8 @@ golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= diff --git a/internal/apply.go b/internal/apply.go index 9daf1b2..fecdc82 100644 --- a/internal/apply.go +++ b/internal/apply.go @@ -107,6 +107,7 @@ func (a ApplyCmd) runReplay(cmd *cobra.Command, objects []manifest.Object) error return nil } replayCmd := ReplayCmd{client: a.client} + replayCmd.arePlaylistEnabled(cmd.Context()) replays := make([]ReplayConfig, 0, len(slos)) for _, slo := range slos { replays = append(replays, ReplayConfig{ diff --git a/internal/replay.go b/internal/replay.go index 702edc4..f5dbf54 100644 --- a/internal/replay.go +++ b/internal/replay.go @@ -31,12 +31,13 @@ import ( ) type ReplayCmd struct { - client *sdk.Client - from flags.TimeValue - configPaths []string - sloName string - project string - deleteAll bool + client *sdk.Client + from flags.TimeValue + configPaths []string + sloName string + project string + deleteAll bool + playlistsAvailable bool } //go:embed replay_example.sh @@ -79,6 +80,7 @@ func (r *ReplayCmd) Run(cmd *cobra.Command) error { if r.client.Config.Project == "*" { return errProjectWildcardIsNotAllowed } + r.arePlaylistEnabled(cmd.Context()) replays, err := r.prepareConfigs() if err != nil { return err @@ -92,9 +94,7 @@ func (r *ReplayCmd) RunReplays(cmd *cobra.Command, replays []ReplayConfig) (fail return 0, err } - arePlaylistEnabled := r.arePlaylistEnabled(cmd.Context()) - - if arePlaylistEnabled { + if r.playlistsAvailable { cmd.Println(colorstring.Color("[yellow]- Your organization has access to Replay queues!")) cmd.Println(colorstring.Color("[yellow]- To learn more about Replay queues, follow this link: " + "https://docs.nobl9.com/replay-canary/ [reset]")) @@ -107,7 +107,7 @@ func (r *ReplayCmd) RunReplays(cmd *cobra.Command, replays []ReplayConfig) (fail i+1, len(replays), replay.SLO, replay.Project, replay.From.Format(flags.TimeLayout), time.Now().In(replay.From.Location()).Format(flags.TimeLayout)))) - if arePlaylistEnabled { + if r.playlistsAvailable { cmd.Println("Replay is added to the queue...") err = r.runReplay(cmd.Context(), replay) @@ -137,7 +137,12 @@ func (r *ReplayCmd) RunReplays(cmd *cobra.Command, replays []ReplayConfig) (fail return len(failedIndexes), nil } -func (r *ReplayCmd) arePlaylistEnabled(ctx context.Context) bool { +type PlaylistConfiguration struct { + EnabledPlaylists bool `json:"enabledPlaylists"` +} + +func (r *ReplayCmd) arePlaylistEnabled(ctx context.Context) { + r.playlistsAvailable = true data, _, err := r.doRequest( ctx, http.MethodGet, @@ -146,17 +151,13 @@ func (r *ReplayCmd) arePlaylistEnabled(ctx context.Context) bool { nil, nil) if err != nil { - return true + fmt.Printf("error checking playlist availability: %v\n", err) } var pc PlaylistConfiguration if err = json.Unmarshal(data, &pc); err != nil { - return true + fmt.Printf("error unmarshalling playlist configuration: %v\n", err) } - return pc.EnabledPlaylists -} - -type PlaylistConfiguration struct { - EnabledPlaylists bool `json:"enabledPlaylists"` + r.playlistsAvailable = pc.EnabledPlaylists } type ReplayConfig struct { @@ -325,16 +326,28 @@ outer: } // Check Replay availability. + if err := r.checkReplayAvailability(ctx, replays); err != nil { + return err + } + + return nil +} + +func (r *ReplayCmd) checkReplayAvailability(ctx context.Context, replays []ReplayConfig) error { notAvailable := make([]string, 0) mu := sync.Mutex{} eg, ctx := errgroup.WithContext(ctx) eg.SetLimit(10) + for i := range replays { eg.Go(func() error { c := replays[i] timeNow := time.Now() tt := c.ToReplay(timeNow) - offset := i * int(averageReplayDuration.Minutes()) + offset := 0 + if !r.playlistsAvailable { + offset = i * int(averageReplayDuration.Minutes()) + } expectedDuration := offset + tt.Duration.Value av, err := r.getReplayAvailability(ctx, c, tt.Duration.Unit, expectedDuration) if err != nil { @@ -343,6 +356,7 @@ outer: } if !av.Available { mu.Lock() + defer mu.Unlock() notAvailable = append(notAvailable, fmt.Sprintf("['%s' SLO in '%s' Project] %s", c.SLO, c.Project, r.replayUnavailabilityReasonExplanation( @@ -351,18 +365,20 @@ outer: time.Duration(expectedDuration)*time.Minute, time.Duration(offset)*time.Minute, timeNow))) - mu.Unlock() } return nil }) } - if err = eg.Wait(); err != nil { + + if err := eg.Wait(); err != nil { return err } + if len(notAvailable) > 0 { return errors.Errorf("The following SLOs are not available for Replay: \n - %s", strings.Join(notAvailable, "\n - ")) } + return nil } @@ -522,7 +538,7 @@ func (r *ReplayCmd) replayUnavailabilityReasonExplanation( " + %dm (start offset to ensure Replay covers the desired time window) %s."+ " Edit the Data Source and run Replay once again.", replay.metricSource.Name, replay.metricSource.Project, expectedDuration.String(), - timeNow.Format(flags.TimeLayout), r.from.Format(flags.TimeLayout), startOffsetMinutes, offsetNotice) + timeNow.Format(flags.TimeLayout), replay.From.Format(flags.TimeLayout), startOffsetMinutes, offsetNotice) case sdkModels.ReplayConcurrentReplayRunsLimitExhausted: return "You've exceeded the limit of concurrent Replay runs. Wait until the current Replay(s) are done." case sdkModels.ReplayUnknownAgentVersion: