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