Skip to content

Commit 7232c31

Browse files
committed
feat(cockpit): add support for cockpit actions
1 parent a7843b1 commit 7232c31

10 files changed

+586
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package cockpit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/action"
10+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/scaleway/scaleway-sdk-go/api/cockpit/v1"
13+
"github.com/scaleway/scaleway-sdk-go/scw"
14+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
15+
)
16+
17+
var (
18+
_ action.Action = (*ResetGrafanaUserPasswordAction)(nil)
19+
_ action.ActionWithConfigure = (*ResetGrafanaUserPasswordAction)(nil)
20+
)
21+
22+
type ResetGrafanaUserPasswordAction struct {
23+
globalAPI *cockpit.GlobalAPI
24+
}
25+
26+
func (a *ResetGrafanaUserPasswordAction) Configure(ctx context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse) {
27+
if req.ProviderData == nil {
28+
return
29+
}
30+
31+
m, ok := req.ProviderData.(*meta.Meta)
32+
if !ok {
33+
resp.Diagnostics.AddError(
34+
"Unexpected Action Configure Type",
35+
fmt.Sprintf("Expected *meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData),
36+
)
37+
38+
return
39+
}
40+
41+
client := m.ScwClient()
42+
a.globalAPI = cockpit.NewGlobalAPI(client)
43+
}
44+
45+
func (a *ResetGrafanaUserPasswordAction) Metadata(ctx context.Context, req action.MetadataRequest, resp *action.MetadataResponse) {
46+
resp.TypeName = req.ProviderTypeName + "_cockpit_reset_grafana_user_password_action"
47+
}
48+
49+
type ResetGrafanaUserPasswordActionModel struct {
50+
GrafanaUserID types.String `tfsdk:"grafana_user_id"`
51+
ProjectID types.String `tfsdk:"project_id"`
52+
}
53+
54+
func NewResetGrafanaUserPasswordAction() action.Action {
55+
return &ResetGrafanaUserPasswordAction{}
56+
}
57+
58+
func (a *ResetGrafanaUserPasswordAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
59+
resp.Schema = schema.Schema{
60+
Attributes: map[string]schema.Attribute{
61+
"grafana_user_id": schema.StringAttribute{
62+
Required: true,
63+
Description: "ID of the Grafana user",
64+
},
65+
"project_id": schema.StringAttribute{
66+
Optional: true,
67+
Description: "ID of the Project. If not provided, will be extracted from grafana_user_id if it's in format 'project_id/user_id'",
68+
},
69+
},
70+
}
71+
}
72+
73+
func (a *ResetGrafanaUserPasswordAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
74+
var data ResetGrafanaUserPasswordActionModel
75+
76+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
77+
78+
if resp.Diagnostics.HasError() {
79+
return
80+
}
81+
82+
if a.globalAPI == nil {
83+
resp.Diagnostics.AddError(
84+
"Unconfigured globalAPI",
85+
"The action was not properly configured. The Scaleway client is missing. "+
86+
"This is usually a bug in the provider. Please report it to the maintainers.",
87+
)
88+
89+
return
90+
}
91+
92+
grafanaUserIDStr := data.GrafanaUserID.ValueString()
93+
if grafanaUserIDStr == "" {
94+
resp.Diagnostics.AddError(
95+
"Missing grafana_user_id",
96+
"The grafana_user_id attribute is required to reset Grafana user password.",
97+
)
98+
99+
return
100+
}
101+
102+
// Parse ID format: project_id/grafana_user_id or just grafana_user_id
103+
var grafanaUserID uint32
104+
projectID := data.ProjectID.ValueString()
105+
106+
if strings.Contains(grafanaUserIDStr, "/") {
107+
// ID format: project_id/grafana_user_id
108+
parsedProjectID, grafanaUserIDPart, err := parseCockpitID(grafanaUserIDStr)
109+
if err != nil {
110+
resp.Diagnostics.AddError(
111+
"Invalid grafana_user_id format",
112+
fmt.Sprintf("The grafana_user_id must be in format 'project_id/user_id' or just 'user_id': %s", err),
113+
)
114+
115+
return
116+
}
117+
118+
// Use parsed project_id if project_id was not explicitly provided
119+
if projectID == "" {
120+
projectID = parsedProjectID
121+
}
122+
123+
grafanaUserIDUint, err := strconv.ParseUint(grafanaUserIDPart, 10, 32)
124+
if err != nil {
125+
resp.Diagnostics.AddError(
126+
"Invalid grafana_user_id",
127+
fmt.Sprintf("The grafana_user_id must be a valid uint32: %s", err),
128+
)
129+
130+
return
131+
}
132+
133+
grafanaUserID = uint32(grafanaUserIDUint)
134+
} else {
135+
// Just grafana_user_id (uint32 as string)
136+
if projectID == "" {
137+
resp.Diagnostics.AddError(
138+
"Missing project_id",
139+
"The project_id attribute is required when grafana_user_id is not in format 'project_id/user_id'.",
140+
)
141+
142+
return
143+
}
144+
145+
grafanaUserIDUint, err := strconv.ParseUint(grafanaUserIDStr, 10, 32)
146+
if err != nil {
147+
resp.Diagnostics.AddError(
148+
"Invalid grafana_user_id",
149+
fmt.Sprintf("The grafana_user_id must be a valid uint32: %s", err),
150+
)
151+
152+
return
153+
}
154+
155+
grafanaUserID = uint32(grafanaUserIDUint)
156+
}
157+
158+
_, err := a.globalAPI.ResetGrafanaUserPassword(&cockpit.GlobalAPIResetGrafanaUserPasswordRequest{
159+
GrafanaUserID: grafanaUserID,
160+
ProjectID: projectID,
161+
}, scw.WithContext(ctx))
162+
163+
if err != nil {
164+
resp.Diagnostics.AddError(
165+
"Error executing Cockpit ResetGrafanaUserPassword action",
166+
fmt.Sprintf("Failed to reset password for Grafana user %s: %s", data.GrafanaUserID.ValueString(), err),
167+
)
168+
169+
return
170+
}
171+
}
172+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cockpit_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
8+
)
9+
10+
func TestAccActionCockpitResetGrafanaUserPassword_Basic(t *testing.T) {
11+
if acctest.IsRunningOpenTofu() {
12+
t.Skip("Skipping TestAccActionCockpitResetGrafanaUserPassword_Basic because actions are not yet supported on OpenTofu")
13+
}
14+
15+
tt := acctest.NewTestTools(t)
16+
defer tt.Cleanup()
17+
18+
resource.ParallelTest(t, resource.TestCase{
19+
ProtoV6ProviderFactories: tt.ProviderFactories,
20+
Steps: []resource.TestStep{
21+
{
22+
Config: `
23+
resource "scaleway_account_project" "project" {
24+
name = "tf_tests_cockpit_reset_password"
25+
}
26+
27+
resource "scaleway_cockpit_grafana_user" "main" {
28+
project_id = scaleway_account_project.project.id
29+
login = "test-user"
30+
role = "viewer"
31+
32+
lifecycle {
33+
action_trigger {
34+
events = [after_create]
35+
actions = [action.scaleway_cockpit_reset_grafana_user_password_action.main]
36+
}
37+
}
38+
}
39+
40+
action "scaleway_cockpit_reset_grafana_user_password_action" "main" {
41+
config {
42+
grafana_user_id = scaleway_cockpit_grafana_user.main.id
43+
project_id = scaleway_account_project.project.id
44+
}
45+
}
46+
`,
47+
},
48+
},
49+
})
50+
}
51+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package cockpit
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/action"
8+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
"github.com/scaleway/scaleway-sdk-go/api/cockpit/v1"
11+
"github.com/scaleway/scaleway-sdk-go/scw"
12+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
13+
)
14+
15+
var (
16+
_ action.Action = (*SyncGrafanaDataSourcesAction)(nil)
17+
_ action.ActionWithConfigure = (*SyncGrafanaDataSourcesAction)(nil)
18+
)
19+
20+
type SyncGrafanaDataSourcesAction struct {
21+
globalAPI *cockpit.GlobalAPI
22+
}
23+
24+
func (a *SyncGrafanaDataSourcesAction) Configure(ctx context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse) {
25+
if req.ProviderData == nil {
26+
return
27+
}
28+
29+
m, ok := req.ProviderData.(*meta.Meta)
30+
if !ok {
31+
resp.Diagnostics.AddError(
32+
"Unexpected Action Configure Type",
33+
fmt.Sprintf("Expected *meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData),
34+
)
35+
36+
return
37+
}
38+
39+
client := m.ScwClient()
40+
a.globalAPI = cockpit.NewGlobalAPI(client)
41+
}
42+
43+
func (a *SyncGrafanaDataSourcesAction) Metadata(ctx context.Context, req action.MetadataRequest, resp *action.MetadataResponse) {
44+
resp.TypeName = req.ProviderTypeName + "_cockpit_sync_grafana_data_sources_action"
45+
}
46+
47+
type SyncGrafanaDataSourcesActionModel struct {
48+
ProjectID types.String `tfsdk:"project_id"`
49+
}
50+
51+
func NewSyncGrafanaDataSourcesAction() action.Action {
52+
return &SyncGrafanaDataSourcesAction{}
53+
}
54+
55+
func (a *SyncGrafanaDataSourcesAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
56+
resp.Schema = schema.Schema{
57+
Attributes: map[string]schema.Attribute{
58+
"project_id": schema.StringAttribute{
59+
Required: true,
60+
Description: "ID of the Project",
61+
},
62+
},
63+
}
64+
}
65+
66+
func (a *SyncGrafanaDataSourcesAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
67+
var data SyncGrafanaDataSourcesActionModel
68+
69+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
70+
71+
if resp.Diagnostics.HasError() {
72+
return
73+
}
74+
75+
if a.globalAPI == nil {
76+
resp.Diagnostics.AddError(
77+
"Unconfigured globalAPI",
78+
"The action was not properly configured. The Scaleway client is missing. "+
79+
"This is usually a bug in the provider. Please report it to the maintainers.",
80+
)
81+
82+
return
83+
}
84+
85+
if data.ProjectID.IsNull() || data.ProjectID.ValueString() == "" {
86+
resp.Diagnostics.AddError(
87+
"Missing project_id",
88+
"The project_id attribute is required to sync Grafana data sources.",
89+
)
90+
91+
return
92+
}
93+
94+
err := a.globalAPI.SyncGrafanaDataSources(&cockpit.GlobalAPISyncGrafanaDataSourcesRequest{
95+
ProjectID: data.ProjectID.ValueString(),
96+
}, scw.WithContext(ctx))
97+
98+
if err != nil {
99+
resp.Diagnostics.AddError(
100+
"Error executing Cockpit SyncGrafanaDataSources action",
101+
fmt.Sprintf("Failed to sync Grafana data sources for project %s: %s", data.ProjectID.ValueString(), err),
102+
)
103+
104+
return
105+
}
106+
}
107+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package cockpit_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
8+
)
9+
10+
func TestAccActionCockpitSyncGrafanaDataSources_Basic(t *testing.T) {
11+
if acctest.IsRunningOpenTofu() {
12+
t.Skip("Skipping TestAccActionCockpitSyncGrafanaDataSources_Basic because actions are not yet supported on OpenTofu")
13+
}
14+
15+
tt := acctest.NewTestTools(t)
16+
defer tt.Cleanup()
17+
18+
resource.ParallelTest(t, resource.TestCase{
19+
ProtoV6ProviderFactories: tt.ProviderFactories,
20+
Steps: []resource.TestStep{
21+
{
22+
Config: `
23+
resource "scaleway_account_project" "project" {
24+
name = "tf_tests_cockpit_sync_data_sources"
25+
}
26+
27+
resource "scaleway_cockpit_source" "metrics" {
28+
project_id = scaleway_account_project.project.id
29+
name = "test-metrics-source"
30+
type = "metrics"
31+
retention_days = 31
32+
33+
lifecycle {
34+
action_trigger {
35+
events = [after_create]
36+
actions = [action.scaleway_cockpit_sync_grafana_data_sources_action.main]
37+
}
38+
}
39+
}
40+
41+
action "scaleway_cockpit_sync_grafana_data_sources_action" "main" {
42+
config {
43+
project_id = scaleway_account_project.project.id
44+
}
45+
}
46+
`,
47+
},
48+
},
49+
})
50+
}
51+

0 commit comments

Comments
 (0)