Skip to content

Commit 826f51e

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

File tree

6 files changed

+291
-2
lines changed

6 files changed

+291
-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

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

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)