Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/acceptance-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,4 @@ jobs:
SCW_SECRET_KEY: "11111111-1111-1111-1111-111111111111"
SCW_ENABLE_BETA: true
TF_ACC_LOG: trace
TF_ACC_OPENTOFU: true
3 changes: 2 additions & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ jobs:
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: hashicorp/setup-terraform@v3
- name: Install Terraform
uses: hashicorp/setup-terraform@v3
- run: go tool tfplugindocs validate
- run: rm -fr ./docs
- run: go tool tfplugindocs generate
Expand Down
21 changes: 21 additions & 0 deletions docs/actions/instance_server_action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
subcategory: "Instances"
page_title: "Scaleway: scaleway_instance_server_action"
---

# scaleway_instance_server_action (Action)

<!-- action schema generated by tfplugindocs -->
## Schema

### Required

- `action` (String) Type of action to perform
- `server_id` (String) Server id to send the action to

### Optional

- `wait` (Boolean) Wait for server to finish action
- `zone` (String) Zone of server to send the action to


1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/hashicorp/terraform-plugin-framework v1.16.0
github.com/hashicorp/terraform-plugin-framework-validators v0.18.1-0.20250909114857-8e55d8ccabdb
github.com/hashicorp/terraform-plugin-go v0.29.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.21.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ github.com/hashicorp/terraform-plugin-docs v0.24.0 h1:YNZYd+8cpYclQyXbl1EEngbld8
github.com/hashicorp/terraform-plugin-docs v0.24.0/go.mod h1:YLg+7LEwVmRuJc0EuCw0SPLxuQXw5mW8iJ5ml/kvi+o=
github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE=
github.com/hashicorp/terraform-plugin-framework v1.16.0/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.1-0.20250909114857-8e55d8ccabdb h1:wRiOv+xaGRrBuc8r774OtrELwQCiSLLQNrkH00ZLO90=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.1-0.20250909114857-8e55d8ccabdb/go.mod h1:vU2y54LtDNHGLjDD7LH/if+4KBKZ5ljTrgDdrM6y8Pc=
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
Expand Down
11 changes: 11 additions & 0 deletions internal/acctest/opentofu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package acctest

import (
"os"

"github.com/scaleway/terraform-provider-scaleway/v2/internal/env"
)

func IsRunningOpenTofu() bool {
return os.Getenv(env.AccRunningOpenTofu) == "true"
}
3 changes: 3 additions & 0 deletions internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ const (
AppendUserAgent = "TF_APPEND_USER_AGENT"
// AccDomainRegistration if set to "true" will trigger acceptance test for domain registration
AccDomainRegistration = "TF_ACC_DOMAIN_REGISTRATION"
// AccRunningOpenTofu is set to "true" in the CI to document that we are using OpenTofu. It can be helpful to skip
// tests that are not yet compatible with OpenTofu
AccRunningOpenTofu = "TF_ACC_OPENTOFU"
)
145 changes: 145 additions & 0 deletions internal/services/instance/action_server_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package instance

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/action"
"github.com/hashicorp/terraform-plugin-framework/action/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
)

var (
_ action.Action = (*ServerAction)(nil)
_ action.ActionWithConfigure = (*ServerAction)(nil)
)

type ServerAction struct {
instanceAPI *instance.API
}

func (a *ServerAction) Configure(ctx context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse) {
if req.ProviderData == nil {
return
}

m, ok := req.ProviderData.(*meta.Meta)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Action Configure Type",
fmt.Sprintf("Expected *scw.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

client := m.ScwClient()
a.instanceAPI = instance.NewAPI(client)
}

func (a *ServerAction) Metadata(ctx context.Context, req action.MetadataRequest, resp *action.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_instance_server_action"
}

type ServerActionModel struct {
ServerID types.String `tfsdk:"server_id"`
Zone types.String `tfsdk:"zone"`
Action types.String `tfsdk:"action"`
Wait types.Bool `tfsdk:"wait"`
}

func NewServerAction() action.Action {
return &ServerAction{}
}

func (a *ServerAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
actionsValues := instance.ServerAction("").Values()

actionStringValues := make([]string, 0, len(actionsValues))
for _, actionValue := range actionsValues {
actionStringValues = append(actionStringValues, actionValue.String())
}

resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"action": schema.StringAttribute{
Required: true,
Description: "Type of action to perform",
Validators: []validator.String{
stringvalidator.OneOfCaseInsensitive(actionStringValues...),
},
},
"server_id": schema.StringAttribute{
Required: true,
Description: "Server id to send the action to",
},
"zone": schema.StringAttribute{
Optional: true,
Description: "Zone of server to send the action to",
},
"wait": schema.BoolAttribute{
Optional: true,
Description: "Wait for server to finish action",
},
},
}
}

func (a *ServerAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var data ServerActionModel
// Read action config data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

if a.instanceAPI == nil {
resp.Diagnostics.AddError(
"Unconfigured instanceAPI",
"The action was not properly configured. The Scaleway client is missing. "+
"This is usually a bug in the provider. Please report it to the maintainers.",
)

return
}

actionReq := &instance.ServerActionRequest{
ServerID: locality.ExpandID(data.ServerID.ValueString()),
Action: instance.ServerAction(data.Action.ValueString()),
}
if !data.Zone.IsNull() {
actionReq.Zone = scw.Zone(data.Zone.String())
}

_, err := a.instanceAPI.ServerAction(actionReq)
if err != nil {
resp.Diagnostics.AddError(
"error in server action",
fmt.Sprintf("%s", err))
}

if data.Wait.ValueBool() {
waitReq := &instance.WaitForServerRequest{
ServerID: locality.ExpandID(data.ServerID.ValueString()),
Zone: scw.Zone(data.Zone.String()),
}

if !data.Zone.IsNull() {
waitReq.Zone = scw.Zone(data.Zone.String())
}

_, errWait := a.instanceAPI.WaitForServer(waitReq)
if errWait != nil {
resp.Diagnostics.AddError(
"error in wait server",
fmt.Sprintf("%s", err))
}
}
}
126 changes: 126 additions & 0 deletions internal/services/instance/action_server_action_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package instance_test

import (
"errors"
"fmt"
"regexp"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
)

func TestAccActionServer_Basic(t *testing.T) {
if acctest.IsRunningOpenTofu() {
t.Skip("Skipping TestAccActionServer_Basic because action are not yet supported on OpenTofu")
}

tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
Config: `
resource "scaleway_instance_server" "main" {
name = "test-terraform-datasource-private-nic"
type = "DEV1-S"
image = "ubuntu_jammy"

lifecycle {
action_trigger {
events = [after_create]
actions = [action.scaleway_instance_server_action.main]
}
}
}

action "scaleway_instance_server_action" "main" {
config {
action = "reboot"
server_id = scaleway_instance_server.main.id
}
}`,
},
{
Config: `
resource "scaleway_instance_server" "main" {
name = "test-terraform-datasource-private-nic"
type = "DEV1-S"
image = "ubuntu_jammy"

lifecycle {
action_trigger {
events = [after_create]
actions = [action.scaleway_instance_server_action.main]
}
}
}

action "scaleway_instance_server_action" "main" {
config {
action = "reboot"
server_id = scaleway_instance_server.main.id
}
}

data "scaleway_audit_trail_event" "instance" {
resource_type = "instance_server"
resource_id = scaleway_instance_server.main.id
method_name = "ServerAction"
}`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.scaleway_audit_trail_event.instance", "events.#"),
func(state *terraform.State) error {
rs, ok := state.RootModule().Resources["data.scaleway_audit_trail_event.instance"]
if !ok {
return errors.New("not found: data.scaleway_audit_trail_event.instance")
}

countStr := rs.Primary.Attributes["events.#"]

count, err := strconv.Atoi(countStr)
if err != nil {
return fmt.Errorf("could not parse events.# as integer: %w", err)
}

if count < 1 {
return fmt.Errorf("expected events count > 1, got %d", count)
}

return nil
},
),
},
},
})
}

func TestAccActionServer_UnknownVerb(t *testing.T) {
if acctest.IsRunningOpenTofu() {
t.Skip("Skipping TestAccActionServer_Basic because action are not yet supported on OpenTofu")
}

tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
Config: `
action "scaleway_instance_server_action" "main" {
config {
action = "unknownVerb"
server_id = "11111111-1111-1111-1111-111111111111"
}
}
`,
ExpectError: regexp.MustCompile("Invalid Attribute Value Match"),
},
},
})
}
1,661 changes: 1,661 additions & 0 deletions internal/services/instance/testdata/action-server-basic.cassette.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
version: 2
interactions: []
Loading
Loading