Skip to content

Commit 34b34be

Browse files
committed
feature: [PC-13433]: add get Adjustment Events
1 parent 925d57d commit 34b34be

File tree

6 files changed

+295
-2
lines changed

6 files changed

+295
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Get Adjustment Events for 'sample-adjustment-name' from 2024-09-23T00:45:00 UTC to 2024-09-23T20:46:00 UTC.
2+
sloctl budgetadjustments get --adjustment-name=sample-adjustment-name --from=2024-09-23T00:45:00Z --to=2024-09-23T20:46:00Z
3+
4+
5+
# Get Adjustment Events for 'sample-adjustment-name' from 2024-09-23T00:45:00 UTC to 2024-09-23T20:46:00 UTC
6+
# only for one slo with sloName and project filters.
7+
sloctl budgetadjustments get \
8+
--adjustment-name=sample-adjustment-name \
9+
--from=2024-09-23T00:45:00Z \
10+
--to=2024-09-23T20:46:00Z \
11+
--project=sample-project-name \
12+
--slo-name=sample-slo-name

internal/budgetadjustments/flags.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package budgetadjustments
2+
3+
import (
4+
"time"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/nobl9/sloctl/internal/csv"
9+
)
10+
11+
const (
12+
flagAdjustment = "adjustment-name"
13+
flagFrom = "from"
14+
flagTo = "to"
15+
flagProject = "project"
16+
flagSloName = "slo-name"
17+
)
18+
19+
func registerOutputFormatFlags(
20+
cmd *cobra.Command,
21+
outputFormat, fieldSeparator, recordSeparator *string,
22+
) {
23+
cmd.PersistentFlags().StringVarP(outputFormat, "output", "o", "yaml",
24+
`Output format: one of yaml|json|csv.`)
25+
26+
cmd.PersistentFlags().StringVarP(fieldSeparator, csv.FieldSeparatorFlag, "",
27+
csv.DefaultFieldSeparator, "Field Separator for CSV.")
28+
29+
cmd.PersistentFlags().StringVarP(recordSeparator, csv.RecordSeparatorFlag, "",
30+
csv.DefaultRecordSeparator, "Record Separator for CSV.")
31+
32+
if err := cmd.PersistentFlags().MarkHidden(csv.RecordSeparatorFlag); err != nil {
33+
panic(err)
34+
}
35+
}
36+
37+
func registerAdjustmentFlag(cmd *cobra.Command, storeIn *string) {
38+
cmd.Flags().StringVar(storeIn, flagAdjustment, "", "Name of the Adjustment.")
39+
if err := cmd.MarkFlagRequired(flagAdjustment); err != nil {
40+
panic(err)
41+
}
42+
}
43+
44+
func registerProjectFlag(cmd *cobra.Command, storeIn *string) {
45+
cmd.Flags().StringVarP(storeIn, flagProject, "", "",
46+
"Name of the project. Required when sloName is defined.")
47+
}
48+
49+
func registerSloNameFlag(cmd *cobra.Command, storeIn *string) {
50+
cmd.Flags().StringVarP(storeIn, flagSloName, "", "",
51+
"Name of the SLO. Required when sloName is defined.")
52+
}
53+
54+
type TimeValue struct{ time.Time }
55+
56+
const (
57+
timeLayout = time.RFC3339
58+
timeLayoutString = "RFC3339"
59+
)
60+
61+
func (t *TimeValue) String() string {
62+
if t.IsZero() {
63+
return ""
64+
}
65+
return t.Format(timeLayout)
66+
}
67+
68+
func (t *TimeValue) Set(s string) (err error) {
69+
t.Time, err = time.Parse(timeLayout, s)
70+
return
71+
}
72+
73+
func (t *TimeValue) Type() string {
74+
return "time"
75+
}
76+
77+
func registerFromFlag(
78+
cmd *cobra.Command,
79+
storeIn *TimeValue,
80+
) {
81+
cmd.Flags().
82+
Var(storeIn, flagFrom, "Specifies the start date and time for the data range (in UTC).")
83+
if err := cmd.MarkFlagRequired(flagFrom); err != nil {
84+
panic(err)
85+
}
86+
}
87+
88+
func registerToFlag(
89+
cmd *cobra.Command,
90+
storeIn *TimeValue,
91+
) {
92+
cmd.Flags().Var(storeIn, flagTo, "Specifies the end date and time for the data range (in UTC).")
93+
if err := cmd.MarkFlagRequired(flagTo); err != nil {
94+
panic(err)
95+
}
96+
}

internal/budgetadjustments/get.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package budgetadjustments
2+
3+
import (
4+
_ "embed"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"time"
12+
13+
"github.com/nobl9/nobl9-go/sdk"
14+
"github.com/pkg/errors"
15+
"github.com/spf13/cobra"
16+
17+
"github.com/nobl9/sloctl/internal/printer"
18+
)
19+
20+
type GetCmd struct {
21+
client *sdk.Client
22+
filepath string
23+
dryRun bool
24+
outputFormat string
25+
fieldSeparator string
26+
recordSeparator string
27+
out io.Writer
28+
adjustment string
29+
from, to TimeValue
30+
project, sloName string
31+
}
32+
33+
type SLO struct {
34+
Project string `json:"project" validate:"required"`
35+
Name string `json:"name" validate:"required"`
36+
}
37+
38+
type Event struct {
39+
EventStart time.Time `json:"eventStart"`
40+
EventEnd time.Time `json:"eventEnd"`
41+
Slos []SLO `json:"slos"`
42+
}
43+
44+
//go:embed examples/get_example.sh
45+
var example string
46+
47+
func NewGetCmd(client *sdk.Client) *cobra.Command {
48+
get := &GetCmd{out: os.Stdout}
49+
50+
cmd := &cobra.Command{
51+
Use: "get",
52+
Short: "Return a list of events for given Adjustment with related SLOs.",
53+
Long: "This endpoint only return past and ongoing events (events that are already started)." +
54+
"Please see Editing budget adjustments." +
55+
"Maximum 500 events can be returned." +
56+
"Optional filtering for specific SLO (only one). If SLO is defined we will return only events for that SLO and the result will also include other SLOs that this events have. Sorted by eventStart.",
57+
Example: example,
58+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
59+
get.client = client
60+
project, _ := cmd.Flags().GetString(flagProject)
61+
sloName, _ := cmd.Flags().GetString(flagSloName)
62+
if project != "" {
63+
cmd.MarkFlagRequired(flagSloName)
64+
}
65+
if sloName != "" {
66+
cmd.MarkFlagRequired(flagProject)
67+
}
68+
},
69+
RunE: func(cmd *cobra.Command, args []string) error { return get.run(cmd) },
70+
}
71+
72+
registerOutputFormatFlags(cmd, &get.outputFormat, &get.fieldSeparator, &get.recordSeparator)
73+
registerAdjustmentFlag(cmd, &get.adjustment)
74+
registerProjectFlag(cmd, &get.project)
75+
registerSloNameFlag(cmd, &get.sloName)
76+
registerFromFlag(cmd, &get.from)
77+
registerToFlag(cmd, &get.to)
78+
79+
return cmd
80+
}
81+
82+
func (g *GetCmd) run(cmd *cobra.Command) error {
83+
if g.dryRun {
84+
g.client.WithDryRun()
85+
}
86+
values := url.Values{"from": {g.from.String()}, "to": {g.to.String()}}
87+
if g.sloName != "" {
88+
values.Add("sloName", g.sloName)
89+
}
90+
if g.project != "" {
91+
values.Add("project", g.project)
92+
}
93+
94+
resBody, err := doRequest(
95+
g.client,
96+
cmd.Context(),
97+
http.MethodGet,
98+
fmt.Sprintf("%s/%s/events", budgetAdjustmentAPI, g.adjustment),
99+
values,
100+
)
101+
if err != nil {
102+
return errors.Wrap(err, "failed to get")
103+
}
104+
105+
var events []Event
106+
if err := json.Unmarshal(resBody, &events); err != nil {
107+
return errors.Wrap(err, "failed parse response")
108+
}
109+
110+
g.printObjects(events)
111+
112+
return nil
113+
}
114+
115+
func (g *GetCmd) printObjects(objects interface{}) error {
116+
p, err := printer.New(
117+
g.out,
118+
printer.Format(g.outputFormat),
119+
g.fieldSeparator,
120+
g.recordSeparator,
121+
)
122+
if err != nil {
123+
return err
124+
}
125+
if err = p.Print(objects); err != nil {
126+
return err
127+
}
128+
return nil
129+
}

internal/budgetadjustments/request.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package budgetadjustments
2+
3+
import (
4+
"context"
5+
_ "embed"
6+
"io"
7+
"net/url"
8+
9+
"github.com/nobl9/nobl9-go/sdk"
10+
"github.com/pkg/errors"
11+
)
12+
13+
const budgetAdjustmentAPI = "/v1/budgetadjustments"
14+
15+
func doRequest(
16+
client *sdk.Client,
17+
ctx context.Context,
18+
method, endpoint string,
19+
values url.Values,
20+
) ([]byte, error) {
21+
req, err := client.CreateRequest(ctx, method, endpoint, nil, values, nil)
22+
if err != nil {
23+
return nil, err
24+
}
25+
resp, err := client.HTTP.Do(req)
26+
if err != nil {
27+
return nil, err
28+
}
29+
defer func() { _ = resp.Body.Close() }()
30+
if resp.StatusCode >= 300 {
31+
data, _ := io.ReadAll(resp.Body)
32+
return nil, errors.Errorf("bad response (status: %d): %s", resp.StatusCode, string(data))
33+
}
34+
return io.ReadAll(resp.Body)
35+
}

internal/budgetadjustments/root.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package budgetadjustments
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
7+
"github.com/nobl9/nobl9-go/sdk"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func NewRootCmd(client *sdk.Client) *cobra.Command {
12+
cmd := &cobra.Command{
13+
Use: "budgetadjustments",
14+
Short: "Manage budgetadjustment.",
15+
}
16+
cmd.PersistentFlags().BoolP("help", "h", false, fmt.Sprintf("Help for %s.", cmd.Name()))
17+
cmd.AddCommand(NewGetCmd(client))
18+
return cmd
19+
}

internal/root.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import (
66
"os"
77
"runtime"
88

9-
"github.com/spf13/cobra"
10-
119
v1alphaParser "github.com/nobl9/nobl9-go/manifest/v1alpha/parser"
1210
"github.com/nobl9/nobl9-go/sdk"
11+
"github.com/spf13/cobra"
12+
13+
"github.com/nobl9/sloctl/internal/budgetadjustments"
1314
)
1415

1516
const programName = "sloctl"
@@ -54,6 +55,7 @@ For every command more detailed help is available.`,
5455
rootCmd.AddCommand(root.NewConfigCmd())
5556
rootCmd.AddCommand(root.NewReplayCmd())
5657
rootCmd.AddCommand(root.NewAwsIamIds())
58+
rootCmd.AddCommand(budgetadjustments.NewRootCmd(root.GetClient()))
5759
return rootCmd
5860
}
5961

0 commit comments

Comments
 (0)