Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for notification configuration resource #86

Merged
merged 1 commit into from
Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 14 additions & 13 deletions tfe/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,20 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"tfe_oauth_client": resourceTFEOAuthClient(),
"tfe_organization": resourceTFEOrganization(),
"tfe_organization_token": resourceTFEOrganizationToken(),
"tfe_policy_set": resourceTFEPolicySet(),
"tfe_sentinel_policy": resourceTFESentinelPolicy(),
"tfe_ssh_key": resourceTFESSHKey(),
"tfe_team": resourceTFETeam(),
"tfe_team_access": resourceTFETeamAccess(),
"tfe_team_member": resourceTFETeamMember(),
"tfe_team_members": resourceTFETeamMembers(),
"tfe_team_token": resourceTFETeamToken(),
"tfe_workspace": resourceTFEWorkspace(),
"tfe_variable": resourceTFEVariable(),
"tfe_notification_configuration": resourceTFENotificationConfiguration(),
"tfe_oauth_client": resourceTFEOAuthClient(),
"tfe_organization": resourceTFEOrganization(),
"tfe_organization_token": resourceTFEOrganizationToken(),
"tfe_policy_set": resourceTFEPolicySet(),
"tfe_sentinel_policy": resourceTFESentinelPolicy(),
"tfe_ssh_key": resourceTFESSHKey(),
"tfe_team": resourceTFETeam(),
"tfe_team_access": resourceTFETeamAccess(),
"tfe_team_member": resourceTFETeamMember(),
"tfe_team_members": resourceTFETeamMembers(),
"tfe_team_token": resourceTFETeamToken(),
"tfe_workspace": resourceTFEWorkspace(),
"tfe_variable": resourceTFEVariable(),
},

ConfigureFunc: providerConfigure,
Expand Down
205 changes: 205 additions & 0 deletions tfe/resource_tfe_notification_configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package tfe

import (
"fmt"
"log"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)

func resourceTFENotificationConfiguration() *schema.Resource {
return &schema.Resource{
Create: resourceTFENotificationConfigurationCreate,
Read: resourceTFENotificationConfigurationRead,
Update: resourceTFENotificationConfigurationUpdate,
Delete: resourceTFENotificationConfigurationDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},

"destination_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(
[]string{
string(tfe.NotificationDestinationTypeGeneric),
string(tfe.NotificationDestinationTypeSlack),
},
false,
),
},

"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"token": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},

"triggers": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lafentres did this work in your testing? Depending on how hashicorp/terraform#22298 goes, this might need to be removed. Just FYI. Hopefully the primitive-element type case gets allowed though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being able to do this (in a supported way) would be nice though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vancluever, yep, this worked :) In addition to passing the tests I wrote, it also stopped me from putting in various invalid strings when I was creating notifications with Terraform locally.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird. I'm asking @radeksimko to weigh in here as I'm not too sure how this is working given the restrictions in place. Maybe the acceptance testing suite is not using InternalValidate after all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works just fine because you are validating each element, not the whole set. In other words the ValidateFunc is associated with TypeString, not TypeSet.

Copy link
Contributor

@vancluever vancluever Aug 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radeksimko what I'm wondering about is how it's passing InternalValidate with the current code in place: https://github.com/hashicorp/terraform/blob/v0.12.6/helper/schema/schema.go#L831-L836

Scratch that, I see that now. Good call and great application @lafentres!

[]string{
string(tfe.NotificationTriggerCreated),
string(tfe.NotificationTriggerPlanning),
string(tfe.NotificationTriggerNeedsAttention),
string(tfe.NotificationTriggerApplying),
string(tfe.NotificationTriggerCompleted),
string(tfe.NotificationTriggerErrored),
},
false,
),
},
},

"url": {
Type: schema.TypeString,
Required: true,
},

"workspace_external_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceTFENotificationConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get workspace
workspaceID := d.Get("workspace_external_id").(string)

// Get attributes
destinationType := tfe.NotificationDestinationType(d.Get("destination_type").(string))
enabled := d.Get("enabled").(bool)
name := d.Get("name").(string)
token := d.Get("token").(string)
url := d.Get("url").(string)

// Throw error if token is set with destinationType of slack
if token != "" && destinationType == tfe.NotificationDestinationTypeSlack {
return fmt.Errorf("Token cannot be set with destination_type of %s", destinationType)
}

// Create a new options struct
options := tfe.NotificationConfigurationCreateOptions{
DestinationType: tfe.NotificationDestination(destinationType),
Enabled: tfe.Bool(enabled),
Name: tfe.String(name),
Token: tfe.String(token),
URL: tfe.String(url),
}

// Add triggers set to the options struct
for _, trigger := range d.Get("triggers").(*schema.Set).List() {
options.Triggers = append(options.Triggers, trigger.(string))
}

log.Printf("[DEBUG] Create notification configuration: %s", name)
notificationConfiguration, err := tfeClient.NotificationConfigurations.Create(ctx, workspaceID, options)
if err != nil {
return fmt.Errorf("Error creating notification configuration %s: %v", name, err)
}

d.SetId(notificationConfiguration.ID)

return resourceTFENotificationConfigurationRead(d, meta)
}

func resourceTFENotificationConfigurationRead(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

log.Printf("[DEBUG] Read notification configuration: %s", d.Id())
notificationConfiguration, err := tfeClient.NotificationConfigurations.Read(ctx, d.Id())
if err != nil {
if err == tfe.ErrResourceNotFound {
log.Printf("[DEBUG] Notification configuration %s no longer exists", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error reading notification configuration %s: %v", d.Id(), err)
}

// Update config
d.Set("destination_type", notificationConfiguration.DestinationType)
d.Set("enabled", notificationConfiguration.Enabled)
d.Set("name", notificationConfiguration.Name)
// Don't set token here, as it is write only
// and setting it here would make it blank
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding this comment! I noticed that resources like OrganizationToken and TeamToken also don't set the actual token value, but with no comment as to why.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pieced this together after a lot of trial and error. It would be nice to get confirmation from someone who has done a lot of work with providers though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lafentres I think you're good here. 👍 If the value would be blank, then setting it here would for sure break the state and cause a diff.

I'm wondering why we even have a field for this value though? That's not your fault, just curious why it would even be in the SDK 🤔 Unless the notification configuration is shared with other parts of the CRUD, but it doesn't seem to be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why we even have a field for this value though?

Good point. In this resource the token field should probably be omitted in the response type.

In other resources (e.g. team token, organization token) its used to return a newly created token in the create response, so it adds value there. But here its a value that is provided by the caller, so its never used in any of the responses.

d.Set("triggers", notificationConfiguration.Triggers)
d.Set("url", notificationConfiguration.URL)

return nil
}

func resourceTFENotificationConfigurationUpdate(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

// Get attributes
destinationType := tfe.NotificationDestinationType(d.Get("destination_type").(string))
enabled := d.Get("enabled").(bool)
name := d.Get("name").(string)
token := d.Get("token").(string)
url := d.Get("url").(string)

// Throw error if token is set with destinationType of slack
if token != "" && destinationType == tfe.NotificationDestinationTypeSlack {
return fmt.Errorf("Token cannot be set with destination_type of %s", destinationType)
}

// Create a new options struct
options := tfe.NotificationConfigurationUpdateOptions{
Enabled: tfe.Bool(enabled),
Name: tfe.String(name),
Token: tfe.String(token),
URL: tfe.String(url),
}

// Add triggers set to the options struct
for _, trigger := range d.Get("triggers").(*schema.Set).List() {
options.Triggers = append(options.Triggers, trigger.(string))
}

log.Printf("[DEBUG] Update notification configuration: %s", d.Id())
_, err := tfeClient.NotificationConfigurations.Update(ctx, d.Id(), options)
if err != nil {
return fmt.Errorf("Error updating notification configuration %s: %v", d.Id(), err)
lafentres marked this conversation as resolved.
Show resolved Hide resolved
}

return resourceTFENotificationConfigurationRead(d, meta)
}

func resourceTFENotificationConfigurationDelete(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

log.Printf("[DEBUG] Delete notification configuration: %s", d.Id())
err := tfeClient.NotificationConfigurations.Delete(ctx, d.Id())
if err != nil {
if err == tfe.ErrResourceNotFound {
return nil
}
return fmt.Errorf("Error deleting notification configuration %s: %v", d.Id(), err)
}

return nil
}
Loading