Skip to content

Commit 72cb51a

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

10 files changed

+768
-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: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package cockpit_test
2+
3+
import (
4+
"errors"
5+
"strings"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
9+
"github.com/hashicorp/terraform-plugin-testing/terraform"
10+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
11+
)
12+
13+
func TestAccActionCockpitResetGrafanaUserPassword_Basic(t *testing.T) {
14+
if acctest.IsRunningOpenTofu() {
15+
t.Skip("Skipping TestAccActionCockpitResetGrafanaUserPassword_Basic because actions are not yet supported on OpenTofu")
16+
}
17+
18+
tt := acctest.NewTestTools(t)
19+
defer tt.Cleanup()
20+
21+
resource.ParallelTest(t, resource.TestCase{
22+
ProtoV6ProviderFactories: tt.ProviderFactories,
23+
Steps: []resource.TestStep{
24+
{
25+
Config: `
26+
resource "scaleway_account_project" "project" {
27+
name = "tf_tests_cockpit_reset_password"
28+
}
29+
30+
resource "scaleway_cockpit_grafana_user" "main" {
31+
project_id = scaleway_account_project.project.id
32+
login = "test-user"
33+
role = "viewer"
34+
35+
lifecycle {
36+
action_trigger {
37+
events = [after_create]
38+
actions = [action.scaleway_cockpit_reset_grafana_user_password_action.main]
39+
}
40+
}
41+
}
42+
43+
action "scaleway_cockpit_reset_grafana_user_password_action" "main" {
44+
config {
45+
grafana_user_id = scaleway_cockpit_grafana_user.main.id
46+
project_id = scaleway_account_project.project.id
47+
}
48+
}
49+
`,
50+
},
51+
{
52+
Config: `
53+
resource "scaleway_account_project" "project" {
54+
name = "tf_tests_cockpit_reset_password"
55+
}
56+
57+
resource "scaleway_cockpit_grafana_user" "main" {
58+
project_id = scaleway_account_project.project.id
59+
login = "test-user"
60+
role = "viewer"
61+
62+
lifecycle {
63+
action_trigger {
64+
events = [after_create]
65+
actions = [action.scaleway_cockpit_reset_grafana_user_password_action.main]
66+
}
67+
}
68+
}
69+
70+
action "scaleway_cockpit_reset_grafana_user_password_action" "main" {
71+
config {
72+
grafana_user_id = scaleway_cockpit_grafana_user.main.id
73+
project_id = scaleway_account_project.project.id
74+
}
75+
}
76+
77+
data "scaleway_audit_trail_event" "cockpit" {
78+
project_id = scaleway_account_project.project.id
79+
method_name = "ResetGrafanaUserPassword"
80+
}
81+
`,
82+
Check: resource.ComposeTestCheckFunc(
83+
resource.TestCheckResourceAttrSet("data.scaleway_audit_trail_event.cockpit", "events.#"),
84+
func(state *terraform.State) error {
85+
rs, ok := state.RootModule().Resources["data.scaleway_audit_trail_event.cockpit"]
86+
if !ok {
87+
return errors.New("not found: data.scaleway_audit_trail_event.cockpit")
88+
}
89+
90+
for key, value := range rs.Primary.Attributes {
91+
if !strings.Contains(key, "method_name") {
92+
continue
93+
}
94+
95+
if value == "ResetGrafanaUserPassword" {
96+
return nil
97+
}
98+
}
99+
100+
return errors.New("did not find the ResetGrafanaUserPassword event")
101+
},
102+
),
103+
},
104+
},
105+
})
106+
}
107+
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+

0 commit comments

Comments
 (0)