Skip to content

Commit 6dcd5d1

Browse files
authored
Teams approval channel (#115)
* teams approval channel * docs
1 parent b04c1cd commit 6dcd5d1

File tree

5 files changed

+381
-0
lines changed

5 files changed

+381
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "torque_teams_approval_channel Resource - terraform-provider-torque"
4+
subcategory: ""
5+
description: |-
6+
Creates a new MS Teams approval channel.
7+
---
8+
9+
# torque_teams_approval_channel (Resource)
10+
11+
Creates a new MS Teams approval channel.
12+
13+
## Example Usage
14+
15+
```terraform
16+
terraform {
17+
required_providers {
18+
torque = {
19+
source = "qualitorque/torque"
20+
}
21+
}
22+
}
23+
24+
provider "torque" {
25+
host = "https://portal.qtorque.io/"
26+
space = "space"
27+
token = "111111111111"
28+
}
29+
30+
resource "torque_teams_approval_channel" "channel" {
31+
name = "channel"
32+
description = "description"
33+
webhook_address = "webhook"
34+
approvers = ["approver@company.com"]
35+
}
36+
```
37+
38+
<!-- schema generated by tfplugindocs -->
39+
## Schema
40+
41+
### Required
42+
43+
- `approvers` (List of String) List of existing emails of users that will be the approvers of this approval channel
44+
- `name` (String) Name of the approval channel.
45+
- `webhook_address` (String) MS Teams Webhook Address
46+
47+
### Optional
48+
49+
- `description` (String) Description of the approval channel
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
terraform {
2+
required_providers {
3+
torque = {
4+
source = "qualitorque/torque"
5+
}
6+
}
7+
}
8+
9+
provider "torque" {
10+
host = "https://portal.qtorque.io/"
11+
space = "space"
12+
token = "111111111111"
13+
}
14+
15+
resource "torque_teams_approval_channel" "channel" {
16+
name = "channel"
17+
description = "description"
18+
webhook_address = "webhook"
19+
approvers = ["approver@company.com"]
20+
}

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ func (p *torqueProvider) Resources(ctx context.Context) []func() resource.Resour
238238
resources.NewTorqueAzureBlobObjectContentInputSourceResource,
239239
resources.NewTorqueDeploymentEngineResource,
240240
resources.NewTorqueEmailApprovalChannelResource,
241+
resources.NewTorqueTeamsApprovalChannelResource,
241242
}
242243
}
243244

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
"github.com/qualitorque/terraform-provider-torque/client"
18+
)
19+
20+
// Ensure provider defined types fully satisfy framework interfaces.
21+
var _ resource.Resource = &TorqueTeamsApprovalChannelResource{}
22+
var _ resource.ResourceWithImportState = &TorqueTeamsApprovalChannelResource{}
23+
24+
func NewTorqueTeamsApprovalChannelResource() resource.Resource {
25+
return &TorqueTeamsApprovalChannelResource{}
26+
}
27+
28+
// TorqueTeamsApprovalChannelResource defines the resource implementation.
29+
type TorqueTeamsApprovalChannelResource struct {
30+
client *client.Client
31+
}
32+
33+
// TorqueTeamsApprovalChannelResourceModel describes the resource data model.
34+
type TorqueTeamsApprovalChannelResourceModel struct {
35+
Name types.String `tfsdk:"name"`
36+
Description types.String `tfsdk:"description"`
37+
Approvers types.List `tfsdk:"approvers"`
38+
WebhookAddress types.String `tfsdk:"webhook_address"`
39+
}
40+
41+
const (
42+
teams_approval_channel_type = "Teams"
43+
)
44+
45+
func (r *TorqueTeamsApprovalChannelResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
46+
resp.TypeName = "torque_teams_approval_channel"
47+
}
48+
49+
func (r *TorqueTeamsApprovalChannelResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
50+
resp.Schema = schema.Schema{
51+
// This description is used by the documentation generator and the language server.
52+
MarkdownDescription: "Creates a new MS Teams approval channel.",
53+
Attributes: map[string]schema.Attribute{
54+
"name": schema.StringAttribute{
55+
MarkdownDescription: "Name of the approval channel.",
56+
Optional: false,
57+
Computed: false,
58+
Required: true,
59+
PlanModifiers: []planmodifier.String{
60+
stringplanmodifier.RequiresReplace(),
61+
},
62+
},
63+
"description": schema.StringAttribute{
64+
MarkdownDescription: "Description of the approval channel",
65+
Optional: true,
66+
Computed: true,
67+
Required: false,
68+
Default: stringdefault.StaticString(""),
69+
},
70+
"webhook_address": schema.StringAttribute{
71+
MarkdownDescription: "MS Teams Webhook Address",
72+
Optional: false,
73+
Computed: false,
74+
Required: true,
75+
},
76+
"approvers": schema.ListAttribute{
77+
Description: "List of existing emails of users that will be the approvers of this approval channel",
78+
Required: true,
79+
Optional: false,
80+
ElementType: types.StringType,
81+
Validators: []validator.List{
82+
listvalidator.SizeAtLeast(1), // Ensure the list has at least one entry if required
83+
},
84+
},
85+
},
86+
}
87+
}
88+
func (r *TorqueTeamsApprovalChannelResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
89+
// Prevent panic if the provider has not been configured.
90+
if req.ProviderData == nil {
91+
return
92+
}
93+
94+
client, ok := req.ProviderData.(*client.Client)
95+
96+
if !ok {
97+
resp.Diagnostics.AddError(
98+
"Unexpected Resource Configure Type",
99+
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
100+
)
101+
102+
return
103+
}
104+
105+
r.client = client
106+
}
107+
108+
func (r *TorqueTeamsApprovalChannelResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
109+
var data TorqueTeamsApprovalChannelResourceModel
110+
var details client.ApprovalChannelDetails
111+
var approvers []client.Approver
112+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
113+
114+
if resp.Diagnostics.HasError() {
115+
return
116+
}
117+
118+
for _, approver := range data.Approvers.Elements() {
119+
approvers = append(approvers, client.Approver{
120+
UserEmail: strings.Replace(approver.String(), "\"", "", -1),
121+
})
122+
}
123+
details.Approvers = approvers
124+
details.Type = teams_approval_channel_type
125+
details.WebhookAddress = data.WebhookAddress.ValueStringPointer()
126+
err := r.client.CreateApprovalChannel(data.Name.ValueString(), data.Description.ValueString(), details)
127+
if err != nil {
128+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Approval Channel, got error: %s", err))
129+
return
130+
}
131+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
132+
}
133+
134+
func (r *TorqueTeamsApprovalChannelResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
135+
var data TorqueTeamsApprovalChannelResourceModel
136+
approvers := []string{}
137+
diags := req.State.Get(ctx, &data)
138+
resp.Diagnostics.Append(diags...)
139+
if resp.Diagnostics.HasError() {
140+
return
141+
}
142+
approval_channel, err := r.client.GetApprovalChannel(data.Name.ValueString())
143+
if err != nil {
144+
resp.Diagnostics.AddError(
145+
"Error Reading Approval Channel details",
146+
"Could not read Approval Channel "+data.Name.ValueString()+": "+err.Error(),
147+
)
148+
return
149+
}
150+
data.Name = types.StringValue(approval_channel.Name)
151+
data.Description = types.StringValue(approval_channel.Description)
152+
data.WebhookAddress = types.StringPointerValue(approval_channel.Details.WebhookAddress)
153+
for _, approver := range approval_channel.Details.Approvers {
154+
approvers = append(approvers, approver.UserEmail)
155+
}
156+
data.Approvers, _ = types.ListValueFrom(ctx, types.StringType, approvers)
157+
158+
diags = resp.State.Set(ctx, &data)
159+
resp.Diagnostics.Append(diags...)
160+
if resp.Diagnostics.HasError() {
161+
return
162+
}
163+
}
164+
165+
func (r *TorqueTeamsApprovalChannelResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
166+
var data TorqueTeamsApprovalChannelResourceModel
167+
var details client.ApprovalChannelDetails
168+
var approvers []client.Approver
169+
170+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
171+
172+
for _, approver := range data.Approvers.Elements() {
173+
approvers = append(approvers, client.Approver{
174+
UserEmail: strings.Replace(approver.String(), "\"", "", -1),
175+
})
176+
}
177+
details.Approvers = approvers
178+
details.Type = teams_approval_channel_type
179+
details.WebhookAddress = data.WebhookAddress.ValueStringPointer()
180+
err := r.client.UpdateApprovalChannel(data.Name.ValueString(), data.Description.ValueString(), details)
181+
if err != nil {
182+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Input Source, got error: %s", err))
183+
return
184+
}
185+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
186+
}
187+
188+
func (r *TorqueTeamsApprovalChannelResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
189+
var data TorqueTeamsApprovalChannelResourceModel
190+
191+
// Read Terraform prior state data into the model.
192+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
193+
194+
if resp.Diagnostics.HasError() {
195+
return
196+
}
197+
198+
err := r.client.DeleteApprovalChannel(data.Name.ValueString())
199+
if err != nil {
200+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Approval Channel, got error: %s", err))
201+
return
202+
}
203+
204+
}
205+
206+
func (r *TorqueTeamsApprovalChannelResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
207+
resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp)
208+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tests
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
12+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
13+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
14+
)
15+
16+
func TestTorqueTeamsApprovalChannel(t *testing.T) {
17+
const (
18+
approval_channel = "approval_channel"
19+
description = "description"
20+
new_description = "new_description"
21+
approver = "terraformtester@quali.com"
22+
approver2 = "terraformtester2@quali.com"
23+
webhook_address = "webhook"
24+
new_webhook_address = "new_webhook"
25+
)
26+
27+
var unique_name = approval_channel + "_" + index
28+
resource.Test(t, resource.TestCase{
29+
PreCheck: func() { testAccPreCheck(t) },
30+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
31+
Steps: []resource.TestStep{
32+
{
33+
Config: providerConfig + fmt.Sprintf(`
34+
resource "torque_teams_approval_channel" "channel" {
35+
name = "%s"
36+
description = "%s"
37+
webhook_address = "%s"
38+
approvers = ["%s"]
39+
}
40+
`, unique_name, description, webhook_address, approver),
41+
ConfigStateChecks: []statecheck.StateCheck{
42+
statecheck.ExpectKnownValue(
43+
"torque_teams_approval_channel.channel",
44+
tfjsonpath.New("name"),
45+
knownvalue.StringExact(unique_name),
46+
),
47+
statecheck.ExpectKnownValue(
48+
"torque_teams_approval_channel.channel",
49+
tfjsonpath.New("description"),
50+
knownvalue.StringExact(description),
51+
),
52+
statecheck.ExpectKnownValue(
53+
"torque_teams_approval_channel.channel",
54+
tfjsonpath.New("webhook_address"),
55+
knownvalue.StringExact(webhook_address),
56+
),
57+
statecheck.ExpectKnownValue(
58+
"torque_teams_approval_channel.channel",
59+
tfjsonpath.New("approvers").AtSliceIndex(0),
60+
knownvalue.StringExact(approver),
61+
),
62+
},
63+
},
64+
{
65+
Config: providerConfig + fmt.Sprintf(`
66+
resource "torque_teams_approval_channel" "channel" {
67+
name = "%s"
68+
description = "%s"
69+
webhook_address = "%s"
70+
approvers = ["%s","%s"]
71+
}
72+
`, unique_name, new_description, new_webhook_address, approver, approver2),
73+
ConfigStateChecks: []statecheck.StateCheck{
74+
statecheck.ExpectKnownValue(
75+
"torque_teams_approval_channel.channel",
76+
tfjsonpath.New("name"),
77+
knownvalue.StringExact(unique_name),
78+
),
79+
statecheck.ExpectKnownValue(
80+
"torque_teams_approval_channel.channel",
81+
tfjsonpath.New("description"),
82+
knownvalue.StringExact(new_description),
83+
),
84+
statecheck.ExpectKnownValue(
85+
"torque_teams_approval_channel.channel",
86+
tfjsonpath.New("webhook_address"),
87+
knownvalue.StringExact(new_webhook_address),
88+
),
89+
statecheck.ExpectKnownValue(
90+
"torque_teams_approval_channel.channel",
91+
tfjsonpath.New("approvers").AtSliceIndex(0),
92+
knownvalue.StringExact(approver),
93+
),
94+
statecheck.ExpectKnownValue(
95+
"torque_teams_approval_channel.channel",
96+
tfjsonpath.New("approvers").AtSliceIndex(1),
97+
knownvalue.StringExact(approver2),
98+
),
99+
},
100+
},
101+
},
102+
})
103+
}

0 commit comments

Comments
 (0)