Skip to content

Commit c159860

Browse files
jremy42remyleone
andauthored
feat(cockpit): add config get command to generate Prometheus remote_write snippet (#5529)
Co-authored-by: Rémy Léone <rleone@scaleway.com>
1 parent a99978b commit c159860

7 files changed

Lines changed: 384 additions & 0 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3+
Generate a ready-to-use configuration snippet for a Cockpit data source.
4+
5+
Supported tools:
6+
- prometheus: generates a remote_write block for prometheus.yml (metrics data sources only).
7+
8+
Use generate-token=true to create a new Cockpit token and inject it directly in the snippet.
9+
The token is created with the minimum required write scope for the data source type.
10+
11+
USAGE:
12+
scw cockpit config get <data-source-id ...> [arg=value ...]
13+
14+
EXAMPLES:
15+
Generate a Prometheus remote_write snippet
16+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus
17+
18+
Generate a Prometheus remote_write snippet with a new token
19+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus generate-token=true
20+
21+
Generate a Prometheus remote_write snippet with a named token
22+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus generate-token=true token-name=my-prometheus
23+
24+
ARGS:
25+
data-source-id ID of the data source to generate the configuration for
26+
type Configuration template type (prometheus)
27+
[generate-token] Create a new Cockpit token and inject it in the generated snippet
28+
[token-name=prometheus-push] Name of the token to create when generate-token=true
29+
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)
30+
31+
FLAGS:
32+
-h, --help help for get
33+
--list-sub-commands List all subcommands
34+
35+
GLOBAL FLAGS:
36+
-c, --config string The path to the config file
37+
-D, --debug Enable debug mode
38+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
39+
-p, --profile string The config profile to use
40+
41+
SEE ALSO:
42+
# Get a data source
43+
scw cockpit data-source get
44+
45+
# Create a Cockpit token
46+
scw cockpit token create
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3+
Config management commands.
4+
5+
USAGE:
6+
scw cockpit config <command>
7+
8+
AVAILABLE COMMANDS:
9+
get Generate a data source configuration snippet
10+
11+
FLAGS:
12+
-h, --help help for config
13+
--list-sub-commands List all subcommands
14+
15+
GLOBAL FLAGS:
16+
-c, --config string The path to the config file
17+
-D, --debug Enable debug mode
18+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
19+
-p, --profile string The config profile to use
20+
21+
Use "scw cockpit config [command] --help" for more information about a command.

cmd/scw/testdata/test-all-usage-cockpit-usage.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ USAGE:
77

88
AVAILABLE COMMANDS:
99
alert-manager Alerting management commands
10+
config Config management commands
1011
contact-point Contact point management commands
1112
data-source Datasource management commands
1213
grafana Grafana user management commands

docs/commands/cockpit.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This API allows you to manage your Scaleway Cockpit, for storing and visualizing
66
- [Disable the Alert manager](#disable-the-alert-manager)
77
- [Enable the Alert manager](#enable-the-alert-manager)
88
- [Get the Alert manager](#get-the-alert-manager)
9+
- [Config management commands](#config-management-commands)
10+
- [Generate a data source configuration snippet](#generate-a-data-source-configuration-snippet)
911
- [Contact point management commands](#contact-point-management-commands)
1012
- [Create a contact point](#create-a-contact-point)
1113
- [Delete a contact point](#delete-a-contact-point)
@@ -109,6 +111,60 @@ scw cockpit alert-manager get [arg=value ...]
109111

110112

111113

114+
## Config management commands
115+
116+
Config management commands.
117+
118+
119+
### Generate a data source configuration snippet
120+
121+
Generate a ready-to-use configuration snippet for a Cockpit data source.
122+
123+
Supported tools:
124+
- prometheus: generates a remote_write block for prometheus.yml (metrics data sources only).
125+
126+
Use generate-token=true to create a new Cockpit token and inject it directly in the snippet.
127+
The token is created with the minimum required write scope for the data source type.
128+
129+
**Usage:**
130+
131+
```
132+
scw cockpit config get <data-source-id ...> [arg=value ...]
133+
```
134+
135+
136+
**Args:**
137+
138+
| Name | | Description |
139+
|------|---|-------------|
140+
| data-source-id | Required | ID of the data source to generate the configuration for |
141+
| type | Required<br />One of: `prometheus` | Configuration template type |
142+
| generate-token | | Create a new Cockpit token and inject it in the generated snippet |
143+
| token-name | Default: `prometheus-push` | Name of the token to create when generate-token=true |
144+
| region | Default: `fr-par`<br />One of: `fr-par`, `nl-ams`, `pl-waw` | Region to target. If none is passed will use default region from the config |
145+
146+
147+
**Examples:**
148+
149+
150+
Generate a Prometheus remote_write snippet
151+
```
152+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus
153+
```
154+
155+
Generate a Prometheus remote_write snippet with a new token
156+
```
157+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus generate-token=true
158+
```
159+
160+
Generate a Prometheus remote_write snippet with a named token
161+
```
162+
scw cockpit config get 11111111-1111-1111-1111-111111111111 type=prometheus generate-token=true token-name=my-prometheus
163+
```
164+
165+
166+
167+
112168
## Contact point management commands
113169

114170
Contact point management commands.

internal/namespaces/cockpit/v1/custom.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ func GetCommands() *core.Commands {
1010
cmds.MustFind("cockpit").Groups = []string{"monitoring"}
1111

1212
cmds.MustFind("cockpit", "token", "get").Override(cockpitTokenGetBuilder)
13+
cmds.Add(cockpitConfigRoot())
14+
cmds.Add(cockpitConfigGetCommand())
1315

1416
return cmds
1517
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package cockpit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
"strings"
8+
9+
"github.com/scaleway/scaleway-cli/v2/core"
10+
cockpit "github.com/scaleway/scaleway-sdk-go/api/cockpit/v1"
11+
"github.com/scaleway/scaleway-sdk-go/scw"
12+
)
13+
14+
type cockpitConfigType string
15+
16+
const (
17+
cockpitConfigTypePrometheus cockpitConfigType = "prometheus"
18+
)
19+
20+
// tokenScopeForDataSourceType returns the write scope matching a data source type.
21+
var tokenScopeForDataSourceType = map[cockpit.DataSourceType]cockpit.TokenScope{
22+
cockpit.DataSourceTypeMetrics: cockpit.TokenScopeWriteOnlyMetrics,
23+
cockpit.DataSourceTypeLogs: cockpit.TokenScopeWriteOnlyLogs,
24+
cockpit.DataSourceTypeTraces: cockpit.TokenScopeWriteOnlyTraces,
25+
}
26+
27+
type cockpitConfigGetRequest struct {
28+
DataSourceID string
29+
Type cockpitConfigType
30+
GenerateToken bool
31+
TokenName string
32+
Region scw.Region
33+
}
34+
35+
func cockpitConfigRoot() *core.Command {
36+
return &core.Command{
37+
Short: "Config management commands",
38+
Long: "Config management commands.",
39+
Namespace: "cockpit",
40+
Resource: "config",
41+
}
42+
}
43+
44+
func cockpitConfigGetCommand() *core.Command {
45+
return &core.Command{
46+
Namespace: "cockpit",
47+
Resource: "config",
48+
Verb: "get",
49+
Short: "Generate a data source configuration snippet",
50+
Long: `Generate a ready-to-use configuration snippet for a Cockpit data source.
51+
52+
Supported tools:
53+
- prometheus: generates a remote_write block for prometheus.yml (metrics data sources only).
54+
55+
Use generate-token=true to create a new Cockpit token and inject it directly in the snippet.
56+
The token is created with the minimum required write scope for the data source type.`,
57+
ArgsType: reflect.TypeOf(cockpitConfigGetRequest{}),
58+
ArgSpecs: core.ArgSpecs{
59+
{
60+
Name: "data-source-id",
61+
Short: "ID of the data source to generate the configuration for",
62+
Required: true,
63+
Positional: true,
64+
},
65+
{
66+
Name: "type",
67+
Short: "Configuration template type",
68+
Required: true,
69+
EnumValues: []string{string(cockpitConfigTypePrometheus)},
70+
},
71+
{
72+
Name: "generate-token",
73+
Short: "Create a new Cockpit token and inject it in the generated snippet",
74+
},
75+
{
76+
Name: "token-name",
77+
Short: "Name of the token to create when generate-token=true",
78+
Default: core.DefaultValueSetter("prometheus-push"),
79+
},
80+
core.RegionArgSpec(
81+
scw.RegionFrPar,
82+
scw.RegionNlAms,
83+
scw.RegionPlWaw,
84+
),
85+
},
86+
Examples: []*core.Example{
87+
{
88+
Short: "Generate a Prometheus remote_write snippet",
89+
ArgsJSON: `{"data_source_id":"11111111-1111-1111-1111-111111111111","type":"prometheus"}`,
90+
},
91+
{
92+
Short: "Generate a Prometheus remote_write snippet with a new token",
93+
ArgsJSON: `{"data_source_id":"11111111-1111-1111-1111-111111111111","type":"prometheus","generate_token":true}`,
94+
},
95+
{
96+
Short: "Generate a Prometheus remote_write snippet with a named token",
97+
ArgsJSON: `{"data_source_id":"11111111-1111-1111-1111-111111111111","type":"prometheus","generate_token":true,"token_name":"my-prometheus"}`,
98+
},
99+
},
100+
SeeAlsos: []*core.SeeAlso{
101+
{
102+
Command: "scw cockpit data-source get",
103+
Short: "Get a data source",
104+
},
105+
{
106+
Command: "scw cockpit token create",
107+
Short: "Create a Cockpit token",
108+
},
109+
},
110+
Run: cockpitConfigGetRun,
111+
}
112+
}
113+
114+
func cockpitConfigGetRun(ctx context.Context, argsI any) (any, error) {
115+
args := argsI.(*cockpitConfigGetRequest)
116+
117+
client := core.ExtractClient(ctx)
118+
api := cockpit.NewRegionalAPI(client)
119+
120+
dataSource, err := api.GetDataSource(&cockpit.RegionalAPIGetDataSourceRequest{
121+
Region: args.Region,
122+
DataSourceID: args.DataSourceID,
123+
})
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
if args.Type == cockpitConfigTypePrometheus &&
129+
dataSource.Type != cockpit.DataSourceTypeMetrics {
130+
return nil, &core.CliError{
131+
Err: fmt.Errorf(
132+
"config type %q requires a metrics data source, got %q",
133+
args.Type,
134+
dataSource.Type,
135+
),
136+
Hint: "Use `scw cockpit data-source list types.0=metrics` " +
137+
"to find a compatible data source.",
138+
}
139+
}
140+
141+
var tokenSecretKey *string
142+
if args.GenerateToken {
143+
scope, ok := tokenScopeForDataSourceType[dataSource.Type]
144+
if !ok {
145+
return nil, fmt.Errorf(
146+
"unsupported data source type %q for token creation",
147+
dataSource.Type,
148+
)
149+
}
150+
151+
token, err := api.CreateToken(&cockpit.RegionalAPICreateTokenRequest{
152+
Region: args.Region,
153+
ProjectID: dataSource.ProjectID,
154+
Name: args.TokenName,
155+
TokenScopes: []cockpit.TokenScope{scope},
156+
})
157+
if err != nil {
158+
return nil, err
159+
}
160+
if token.SecretKey == nil || *token.SecretKey == "" {
161+
return nil, fmt.Errorf("created token %q has no secret key", token.ID)
162+
}
163+
164+
tokenSecretKey = token.SecretKey
165+
}
166+
167+
return RenderPrometheusRemoteWriteConfig(dataSource.URL, tokenSecretKey), nil
168+
}
169+
170+
// RenderPrometheusRemoteWriteConfig renders a Prometheus remote_write YAML snippet for stdout.
171+
func RenderPrometheusRemoteWriteConfig(
172+
dataSourceURL string,
173+
tokenSecretKey *string,
174+
) core.RawResult {
175+
remoteWriteURL := BuildPrometheusRemoteWriteURL(dataSourceURL)
176+
177+
lines := []string{
178+
"# Snippet of Prometheus configuration to add to prometheus.yml",
179+
"remote_write:",
180+
` - url: "` + remoteWriteURL + `"`,
181+
}
182+
183+
if tokenSecretKey != nil {
184+
lines = append(lines,
185+
" headers:",
186+
" X-TOKEN: "+*tokenSecretKey,
187+
)
188+
}
189+
190+
lines = append(lines, "")
191+
192+
return core.RawResult(strings.Join(lines, "\n"))
193+
}
194+
195+
// BuildPrometheusRemoteWriteURL returns the remote_write push URL for a Cockpit metrics data source base URL.
196+
func BuildPrometheusRemoteWriteURL(dataSourceURL string) string {
197+
baseURL := strings.TrimRight(dataSourceURL, "/")
198+
if strings.HasSuffix(baseURL, "/api/v1/push") {
199+
return baseURL
200+
}
201+
202+
return baseURL + "/api/v1/push"
203+
}

0 commit comments

Comments
 (0)