Skip to content

Commit

Permalink
provider/stack: add new stack resource for Stacks API integration
Browse files Browse the repository at this point in the history
This PR adds a "scylladbcloud_stack" resource which is used for
accounting managed resources with the Stacks API.

It uses new client, as the payloads for Stacks API are HMAC
signed.
  • Loading branch information
rjeczalik committed Jun 26, 2024
1 parent 54c249d commit 945ef82
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 51 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/internal/provider/scylla/ @rjeczalik
/internal/provider/stack/ @rjeczalik
/internal/provider/allowlistrule/ @rjeczalik
/internal/provider/cluster/ @rjeczalik @charconstpointer @ksinica
/internal/provider/cqlauth/ @rjeczalik
Expand Down
25 changes: 20 additions & 5 deletions internal/provider/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
nodeDiskSize, nodeDiskSizeOK = d.GetOk("node_disk_size")
)

m, err := c.Meta()
if err != nil {
return diag.Errorf("error reading metadata: %s", err)
}

if !enableVpcPeering {
r.BroadcastType = "PUBLIC"
}
Expand All @@ -221,7 +226,7 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
_ = d.Set("cidr_block", cidr)
}

p := c.Meta.ProviderByName(cloud)
p := m.ProviderByName(cloud)
if p == nil {
return diag.Errorf(`unrecognized value %q for "cloud" attribute`, cloud)
}
Expand Down Expand Up @@ -254,8 +259,8 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
r.InstanceID = mi.ID

if !versionOK {
r.ScyllaVersionID = c.Meta.ScyllaVersions.DefaultScyllaVersionID
} else if mv := c.Meta.VersionByName(version.(string)); mv != nil {
r.ScyllaVersionID = m.ScyllaVersions.DefaultScyllaVersionID
} else if mv := m.VersionByName(version.(string)); mv != nil {
r.ScyllaVersionID = mv.VersionID
} else {
return diag.Errorf(`unrecognized value %q for "scylla_version" attribute`, version)
Expand Down Expand Up @@ -294,6 +299,11 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*scylla.Client)

m, err := c.Meta()
if err != nil {
return diag.Errorf("error reading metadata: %s", err)
}

clusterID, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return diag.Errorf("error reading id=%q: %s", d.Id(), err)
Expand Down Expand Up @@ -326,7 +336,7 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter
return diag.Errorf("error reading cluster: %s", err)
}

p := c.Meta.ProviderByID(cluster.CloudProviderID)
p := m.ProviderByID(cluster.CloudProviderID)
if p == nil {
return diag.Errorf("unexpected cloud provider ID: %d", cluster.CloudProviderID)
}
Expand Down Expand Up @@ -448,6 +458,11 @@ func resourceClusterUpgradeV0(ctx context.Context, rawState map[string]any, meta
nodeType, nodeTypeOK = rawState["node_type"].(string)
)

m, err := c.Meta()
if err != nil {
return nil, fmt.Errorf("error reading metadata: %w", err)
}

if !cloudOK {
return nil, fmt.Errorf(`"cloud" is undefined`)
}
Expand All @@ -456,7 +471,7 @@ func resourceClusterUpgradeV0(ctx context.Context, rawState map[string]any, meta
return nil, fmt.Errorf(`"node_type" is undefined`)
}

p := c.Meta.ProviderByName(cloud)
p := m.ProviderByName(cloud)
if p == nil {
return nil, fmt.Errorf(`unrecognized value %q for "cloud"`, cloud)
}
Expand Down
7 changes: 6 additions & 1 deletion internal/provider/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ func resourceClusterConnectionCreate(ctx context.Context, d *schema.ResourceData
p *scylla.CloudProvider
)

m, err := c.Meta()
if err != nil {
return diag.Errorf("error reading metadata: %s", err)
}

dcs, err := c.ListDataCenters(ctx, int64(clusterID))
if err != nil {
return diag.Errorf("error reading clusters: %s", err)
Expand All @@ -158,7 +163,7 @@ func resourceClusterConnectionCreate(ctx context.Context, d *schema.ResourceData
for _, dc := range dcs {
if strings.EqualFold(dc.Name, dcName) {
r.ClusterDCID = dc.ID
p = c.Meta.ProviderByID(dc.CloudProviderID)
p = m.ProviderByID(dc.CloudProviderID)
if p == nil {
return diag.Errorf("unable to find cloud provider with id=%d", dc.CloudProviderID)
}
Expand Down
22 changes: 3 additions & 19 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
"github.com/scylladb/terraform-provider-scylladbcloud/internal/provider/connection"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/provider/cqlauth"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/provider/serverless"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/provider/stack"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/provider/vpcpeering"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/tfcontext"

"github.com/scylladb/terraform-provider-scylladbcloud/internal/scylla"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -52,6 +51,7 @@ func New(_ context.Context) (*schema.Provider, error) {
"scylladbcloud_vpc_peering": vpcpeering.ResourceVPCPeering(),
"scylladbcloud_serverless_cluster": serverless.ResourceServerlessCluster(),
"scylladbcloud_cluster_connection": connection.ResourceClusterConnection(),
"scylladbcloud_stack": stack.ResourceStack(),
},
}

Expand All @@ -68,27 +68,11 @@ func configure(ctx context.Context, p *schema.Provider, d *schema.ResourceData)
token = d.Get("token").(string)
)

c, err := scylla.NewClient()
c, err := scylla.NewClient(endpoint, token, userAgent(p.TerraformVersion))
if err != nil {
return nil, diag.Errorf("could not create new Scylla client: %s", err)
}

ctx = tfcontext.AddProviderInfo(ctx, endpoint)
if c.Endpoint, err = url.Parse(endpoint); err != nil {
return nil, diag.FromErr(err)
}

if c.Meta, err = scylla.BuildCloudmeta(ctx, c); err != nil {
return nil, diag.Errorf("could not build Cloudmeta: %s", err)
}

c.Headers.Set("Accept", "application/json; charset=utf-8")
c.Headers.Set("User-Agent", userAgent(p.TerraformVersion))

if err := c.Auth(ctx, token); err != nil {
return nil, diag.FromErr(err)
}

return c, nil
}

Expand Down
135 changes: 135 additions & 0 deletions internal/provider/stack/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package stack

import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/scylladb/terraform-provider-scylladbcloud/internal/scylla"
"github.com/scylladb/terraform-provider-scylladbcloud/internal/scylla/model"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
stackTimeout = 60 * time.Second
)

func ResourceStack() *schema.Resource {
return &schema.Resource{
CreateContext: resourceStackCreate,
ReadContext: resourceStackRead,
UpdateContext: resourceStackUpdate,
DeleteContext: resourceStackDelete,

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(stackTimeout),
Update: schema.DefaultTimeout(stackTimeout),
Delete: schema.DefaultTimeout(stackTimeout),
},

Schema: map[string]*schema.Schema{
"attributes": {
Description: "List of managed resources",
Required: true,
Type: schema.TypeMap,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func resourceStackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var (
c = meta.(*scylla.Client)
s = &model.Stack{
RequestType: "Create",
ResourceProperties: d.Get("attributes").(map[string]interface{}),
}
)

id, err := sendStack(ctx, c, s)
if err != nil {
return diag.Errorf("failed to create stack: %s", err)
}

d.SetId(id)

return nil
}

func resourceStackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*scylla.Client)

_ = c

tflog.Trace(ctx, "\n\nXDDD\n\nstack read")

return nil
}

func resourceStackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var (
c = meta.(*scylla.Client)
s = &model.Stack{
RequestType: "Update",
ResourceProperties: d.Get("attributes").(map[string]interface{}),
}
)

_, err := sendStack(ctx, c, s)
if err != nil {
return diag.Errorf("failed to create stack: %s", err)
}

return nil
}

func resourceStackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var (
c = meta.(*scylla.Client)
s = &model.Stack{
RequestType: "Delete",
ResourceProperties: d.Get("attributes").(map[string]interface{}),
}
)

_, err := sendStack(ctx, c, s)
if err != nil {
return diag.Errorf("failed to create stack: %s", err)
}

return nil
}

func sendStack(ctx context.Context, c *scylla.Client, s *model.Stack) (string, error) {
auth := strings.Split(c.Token, ":")

if len(auth) != 2 {
return "", errors.New("invalid token format")
}

req := c.V2.Request(ctx, "POST", s, "/")

req.Header.Set("X-Scylla-Cloud-Stack-Flavor", "tf")

if err := c.V2.BasicSign(req, auth[0], []byte(auth[1])); err != nil {
return "", fmt.Errorf("failed to sign request: %w", err)
}

if _, err := c.V2.Do(req, s); err != nil {
return "", fmt.Errorf("failed to create stack: %w", err)
}

return auth[0], nil
}
18 changes: 14 additions & 4 deletions internal/provider/vpcpeering/vpc_peering.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ func resourceVPCPeeringCreate(ctx context.Context, d *schema.ResourceData, meta
dc *model.Datacenter
)

m, err := c.Meta()
if err != nil {
return diag.Errorf("error reading metadata: %s", err)
}

dcs, err := c.ListDataCenters(ctx, int64(clusterID))
if err != nil {
return diag.Errorf("error reading clusters: %s", err)
Expand All @@ -143,7 +148,7 @@ func resourceVPCPeeringCreate(ctx context.Context, d *schema.ResourceData, meta

if strings.EqualFold(dc.Name, dcName) {
r.DatacenterID = dc.ID
p = c.Meta.ProviderByID(dc.CloudProviderID)
p = m.ProviderByID(dc.CloudProviderID)
break
}
}
Expand All @@ -167,14 +172,14 @@ func resourceVPCPeeringCreate(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf(`"peer_cidr_blocks" is required for %q cloud`, p.CloudProvider.Name)
}

cidr, ok := c.Meta.GCPBlocks[pr]
cidr, ok := m.GCPBlocks[pr]
if !ok {
return diag.Errorf("no default peer CIDR block found for %q region", pr)
}

cidrBlocks = []any{cidr}
} else if strings.EqualFold(p.CloudProvider.Name, "GCP") {
cidr, ok := c.Meta.GCPBlocks[pr]
cidr, ok := m.GCPBlocks[pr]
if !ok {
return diag.Errorf("no default peer CIDR block found for %q region", pr)
}
Expand Down Expand Up @@ -226,6 +231,11 @@ func resourceVPCPeeringRead(ctx context.Context, d *schema.ResourceData, meta in
p *scylla.CloudProvider
)

m, err := c.Meta()
if err != nil {
return diag.Errorf("error reading metadata: %s", err)
}

clusters, err := c.ListClusters(ctx)
if err != nil {
return diag.Errorf("error reading cluster list: %s", err)
Expand Down Expand Up @@ -261,7 +271,7 @@ lookup:
return nil
}

if p = c.Meta.ProviderByID(cluster.CloudProviderID); p == nil {
if p = m.ProviderByID(cluster.CloudProviderID); p == nil {
return diag.Errorf("unable to find cloud provider with id=%d", cluster.CloudProviderID)
}

Expand Down
Loading

0 comments on commit 945ef82

Please sign in to comment.