Skip to content

Commit 1af2b1c

Browse files
cloud_connection terraform provider (#94)
Co-authored-by: Marcin Maciaszczyk <[email protected]>
1 parent ec48e60 commit 1af2b1c

File tree

7 files changed

+550
-2
lines changed

7 files changed

+550
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ example/**/.terraform/
1414
example/**/.terraform.lock.hcl
1515
example/**/terraform.tfstate*
1616
example/**/terraform.tfplan
17+
example/**/terraform.tfvars

example/cloudconnection/main.tf

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
terraform {
2+
required_providers {
3+
plural = {
4+
source = "pluralsh/plural"
5+
version = "0.2.25"
6+
}
7+
}
8+
}
9+
10+
provider "plural" {
11+
use_cli = true
12+
}
13+
14+
###############################################################################
15+
# (Optional) Input variables – move to variables.tf if you prefer
16+
###############################################################################
17+
18+
variable "aws_access_key_id" {
19+
type = string
20+
sensitive = true
21+
}
22+
23+
variable "aws_secret_access_key" {
24+
type = string
25+
sensitive = true
26+
}
27+
28+
variable "aws_region" {
29+
type = string
30+
default = "us-east-1"
31+
}
32+
33+
data "plural_user" "john" {
34+
35+
}
36+
37+
data "plural_user" "john_doe" {
38+
39+
}
40+
41+
###############################################################################
42+
# Resources
43+
###############################################################################
44+
45+
# Group that will get read access to the cloud connection
46+
resource "plural_group" "cloud_admins" {
47+
name = "cloud-admins"
48+
description = "Group with access to cloud connections"
49+
}
50+
51+
# Cloud-connection resource (AWS example)
52+
resource "plural_cloud_connection" "aws" {
53+
name = "your-connection-name"
54+
cloud_provider = "AWS"
55+
56+
configuration = {
57+
aws = {
58+
access_key_id = var.aws_access_key_id
59+
secret_access_key = var.aws_secret_access_key
60+
region = var.aws_region
61+
}
62+
}
63+
64+
read_bindings = [
65+
{
66+
id = "1122"
67+
user_id = data.plural_user.john.id
68+
},
69+
{
70+
id = "2233"
71+
group_id = plural_group.cloud_admins.id
72+
},
73+
{
74+
id = "4444"
75+
group_id = plural_group.cloud_admins.id
76+
}
77+
]
78+
}
79+
80+
###############################################################################
81+
# Outputs
82+
###############################################################################
83+
84+
output "cloud_connection_id" {
85+
value = plural_cloud_connection.aws.id
86+
description = "ID of the created cloud connection"
87+
}
88+
89+
output "cloud_connection_details" {
90+
value = plural_cloud_connection.aws
91+
description = "All attributes of the cloud connection"
92+
sensitive = true
93+
}
94+
95+
output "group_id" {
96+
value = plural_group.cloud_admins.id
97+
description = "ID of the created group"
98+
}

internal/common/bindings.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/hashicorp/terraform-plugin-framework/attr"
77
"github.com/hashicorp/terraform-plugin-framework/diag"
88
"github.com/hashicorp/terraform-plugin-framework/types"
9-
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
109
console "github.com/pluralsh/console/go/client"
1110
)
1211

@@ -86,7 +85,36 @@ func bindingsFrom(bindings []*console.PolicyBindingFragment, config types.Set, c
8685
d.Append(diags...)
8786
}
8887

89-
setValue, diags := types.SetValue(basetypes.ObjectType{AttrTypes: PolicyBindingAttrTypes}, values)
88+
setValue, diags := types.SetValue(types.ObjectType{AttrTypes: PolicyBindingAttrTypes}, values)
89+
d.Append(diags...)
90+
return setValue
91+
}
92+
93+
func BindingsFromReadOnly(bindings []*console.PolicyBindingFragment, ctx context.Context, d *diag.Diagnostics) types.Set {
94+
if len(bindings) == 0 {
95+
return types.SetNull(types.ObjectType{AttrTypes: PolicyBindingAttrTypes})
96+
}
97+
98+
values := make([]attr.Value, len(bindings))
99+
for i, binding := range bindings {
100+
value := PolicyBinding{
101+
ID: types.StringPointerValue(binding.ID),
102+
}
103+
104+
if binding.User != nil {
105+
value.UserID = types.StringValue(binding.User.ID)
106+
}
107+
108+
if binding.Group != nil {
109+
value.GroupID = types.StringValue(binding.Group.ID)
110+
}
111+
112+
objValue, diags := types.ObjectValueFrom(ctx, PolicyBindingAttrTypes, value)
113+
values[i] = objValue
114+
d.Append(diags...)
115+
}
116+
117+
setValue, diags := types.SetValue(types.ObjectType{AttrTypes: PolicyBindingAttrTypes}, values)
90118
d.Append(diags...)
91119
return setValue
92120
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package datasource
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"terraform-provider-plural/internal/client"
8+
"terraform-provider-plural/internal/common"
9+
"terraform-provider-plural/internal/model"
10+
11+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
12+
"github.com/hashicorp/terraform-plugin-framework/datasource"
13+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/path"
15+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
)
18+
19+
func NewCloudConnectionDataSource() datasource.DataSource {
20+
return &cloudConnectionDataSource{}
21+
}
22+
23+
type cloudConnectionDataSource struct {
24+
client *client.Client
25+
}
26+
27+
func (d *cloudConnectionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
28+
resp.TypeName = req.ProviderTypeName + "_cloud_connection"
29+
}
30+
31+
func (d *cloudConnectionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
32+
resp.Schema = schema.Schema{
33+
Attributes: map[string]schema.Attribute{
34+
"id": schema.StringAttribute{
35+
Description: "Internal identifier of this cloud connection",
36+
MarkdownDescription: "Internal identifier of this cloud connection",
37+
Optional: true,
38+
Computed: true,
39+
Validators: []validator.String{stringvalidator.ExactlyOneOf(path.MatchRoot("name"))},
40+
},
41+
"name": schema.StringAttribute{
42+
Description: "Human-readable name of this cloud connection.",
43+
MarkdownDescription: "Human-readable name of this cloud connection.",
44+
Optional: true,
45+
Computed: true,
46+
Validators: []validator.String{stringvalidator.ExactlyOneOf(path.MatchRoot("id"))},
47+
},
48+
"cloud_provider": schema.StringAttribute{
49+
Description: "The cloud provider of this cloud connection.",
50+
MarkdownDescription: "The cloud provider of this cloud connection.",
51+
Required: true,
52+
Validators: []validator.String{stringvalidator.OneOf("AWS", "GCP", "AZURE")},
53+
},
54+
"configuration": schema.SingleNestedAttribute{
55+
Description: "Cloud provider configuration",
56+
MarkdownDescription: "Cloud provider configuration",
57+
Required: true,
58+
},
59+
"read_bindings": schema.SetAttribute{
60+
Description: "The read bindings for this cloud connection.",
61+
MarkdownDescription: "The read bindings for this cloud connection.",
62+
Optional: true,
63+
ElementType: types.ObjectType{AttrTypes: common.PolicyBindingAttrTypes},
64+
},
65+
},
66+
}
67+
}
68+
69+
func (d *cloudConnectionDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
70+
if req.ProviderData == nil {
71+
return
72+
}
73+
74+
data, ok := req.ProviderData.(*common.ProviderData)
75+
if !ok {
76+
resp.Diagnostics.AddError(
77+
"Unexpected Cloud Connection Resource Configure Type",
78+
fmt.Sprintf("Expected *common.ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
79+
)
80+
return
81+
}
82+
83+
d.client = data.Client
84+
}
85+
86+
func (d *cloudConnectionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
87+
data := new(model.CloudConnection)
88+
resp.Diagnostics.Append(req.Config.Get(ctx, data)...)
89+
if resp.Diagnostics.HasError() {
90+
return
91+
}
92+
93+
if data.Id.IsNull() && data.Name.IsNull() {
94+
resp.Diagnostics.AddError(
95+
"Missing Cloud Connection ID and Name",
96+
"The provider could not read cloud connection data. ID or name needs to be specified.",
97+
)
98+
return
99+
}
100+
101+
response, err := d.client.GetCloudConnection(ctx, data.Id.ValueStringPointer(), data.Name.ValueStringPointer())
102+
if err != nil {
103+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read cloud connection, got error: %s", err))
104+
return
105+
}
106+
107+
data.From(response.CloudConnection, ctx, &resp.Diagnostics)
108+
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
109+
}

internal/model/cloud_connection.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package model
2+
3+
import (
4+
"context"
5+
6+
"terraform-provider-plural/internal/common"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
11+
console "github.com/pluralsh/console/go/client"
12+
)
13+
14+
type CloudConnection struct {
15+
Id types.String `tfsdk:"id"`
16+
Name types.String `tfsdk:"name"`
17+
CloudProvider types.String `tfsdk:"cloud_provider"`
18+
Configuration *CloudConnectionConfiguration `tfsdk:"configuration"`
19+
ReadBindings types.Set `tfsdk:"read_bindings"`
20+
}
21+
22+
type CloudConnectionConfiguration struct {
23+
AWS *AwsCloudConnectionAttributes `tfsdk:"aws"`
24+
GCP *GcpCloudConnectionAttributes `tfsdk:"gcp"`
25+
Azure *AzureCloudConnectionAttributes `tfsdk:"azure"`
26+
}
27+
28+
func (c *CloudConnectionConfiguration) Attributes() *console.CloudConnectionConfigurationAttributes {
29+
if c == nil {
30+
return nil
31+
}
32+
33+
if c.AWS != nil {
34+
return &console.CloudConnectionConfigurationAttributes{AWS: c.AWS.Attributes()}
35+
}
36+
37+
if c.Azure != nil {
38+
return &console.CloudConnectionConfigurationAttributes{Azure: c.Azure.Attributes()}
39+
}
40+
41+
if c.GCP != nil {
42+
return &console.CloudConnectionConfigurationAttributes{GCP: c.GCP.Attributes()}
43+
}
44+
45+
return nil
46+
}
47+
48+
type AwsCloudConnectionAttributes struct {
49+
AccessKeyID types.String `tfsdk:"access_key_id"`
50+
SecretAccessKey types.String `tfsdk:"secret_access_key"`
51+
Region types.String `tfsdk:"region"`
52+
}
53+
54+
func (c *AwsCloudConnectionAttributes) Attributes() *console.AWSCloudConnectionAttributes {
55+
return &console.AWSCloudConnectionAttributes{
56+
AccessKeyID: c.AccessKeyID.ValueString(),
57+
SecretAccessKey: c.SecretAccessKey.ValueString(),
58+
Region: c.Region.ValueString(),
59+
}
60+
}
61+
62+
type GcpCloudConnectionAttributes struct {
63+
ServiceAccountKey types.String `tfsdk:"service_account_key"`
64+
ProjectID types.String `tfsdk:"project_id"`
65+
}
66+
67+
func (c *GcpCloudConnectionAttributes) Attributes() *console.GCPCloudConnectionAttributes {
68+
return &console.GCPCloudConnectionAttributes{
69+
ServiceAccountKey: c.ServiceAccountKey.ValueString(),
70+
ProjectID: c.ProjectID.ValueString(),
71+
}
72+
}
73+
74+
type AzureCloudConnectionAttributes struct {
75+
SubscriptionID types.String `tfsdk:"subscription_id"`
76+
TenantID types.String `tfsdk:"tenant_id"`
77+
ClientID types.String `tfsdk:"client_id"`
78+
ClientSecret types.String `tfsdk:"client_secret"`
79+
}
80+
81+
func (c *AzureCloudConnectionAttributes) Attributes() *console.AzureCloudConnectionAttributes {
82+
return &console.AzureCloudConnectionAttributes{
83+
SubscriptionID: c.SubscriptionID.ValueString(),
84+
TenantID: c.TenantID.ValueString(),
85+
ClientID: c.ClientID.ValueString(),
86+
ClientSecret: c.ClientSecret.ValueString(),
87+
}
88+
}
89+
90+
func (c *CloudConnection) Attributes(ctx context.Context, d *diag.Diagnostics) console.CloudConnectionAttributes {
91+
return console.CloudConnectionAttributes{
92+
Name: c.Name.ValueString(),
93+
Provider: console.Provider(c.CloudProvider.ValueString()),
94+
Configuration: *c.Configuration.Attributes(),
95+
ReadBindings: common.SetToPolicyBindingAttributes(c.ReadBindings, ctx, d),
96+
}
97+
}
98+
99+
func (c *CloudConnection) From(cc *console.CloudConnectionFragment, ctx context.Context, d *diag.Diagnostics) {
100+
c.Id = types.StringValue(cc.ID)
101+
c.Name = types.StringValue(cc.Name)
102+
c.CloudProvider = types.StringValue(string(cc.Provider))
103+
c.ReadBindings = common.BindingsFromReadOnly(cc.ReadBindings, ctx, d)
104+
}

internal/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ func (p *PluralProvider) Resources(_ context.Context) []func() resource.Resource
211211
r.NewOIDCProviderResourceResource,
212212
r.NewSCMWebhookResource,
213213
r.NewObservabilityWebhookResource,
214+
r.NewCloudConnectionResource,
214215
}
215216
}
216217

@@ -226,6 +227,7 @@ func (p *PluralProvider) DataSources(_ context.Context) []func() datasource.Data
226227
ds.NewPRAutomationDataSource,
227228
ds.NewInfrastructureStackDataSource,
228229
ds.NewServiceContextDataSource,
230+
ds.NewCloudConnectionDataSource,
229231
}
230232
}
231233

0 commit comments

Comments
 (0)