Skip to content

Commit

Permalink
Add new resource sentry_release_deployment
Browse files Browse the repository at this point in the history
This resource creates a new deployment for a specific release and
environment in sentry.
  • Loading branch information
mvantellingen committed Jan 18, 2023
1 parent 7b8d789 commit b094fb2
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
1 change: 1 addition & 0 deletions sentry/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func NewProvider(version string) func() *schema.Provider {
"sentry_project": resourceSentryProject(),
"sentry_rule": resourceSentryRule(),
"sentry_team": resourceSentryTeam(),
"sentry_release_deployment": resourceSentryReleaseDeployment(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
171 changes: 171 additions & 0 deletions sentry/resource_sentry_release_deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package sentry

import (
"context"
"sort"

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

func resourceSentryReleaseDeployment() *schema.Resource {
return &schema.Resource{
CreateContext: resourceSentryReleaseDeploymentCreate,
ReadContext: resourceSentryReleaseDeploymentRead,
UpdateContext: resourceSentryReleaseDeploymentUpdate,
DeleteContext: resourceSentryReleaseDeploymentDelete,

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

Schema: map[string]*schema.Schema{
"organization": {
Description: "The slug of the organization the deploy belongs to.",
Type: schema.TypeString,
Required: true,
},
"version": {
Description: "The version identifier of the release.",
Type: schema.TypeString,
Required: true,
},
"environment": {
Type: schema.TypeString,
Required: true,
Description: "The environment this deployment is for.",
},
"url": {
Description: "The optional URL that points to the deploy.",
Type: schema.TypeString,
Optional: true,
},
"name": {
Description: "The optional name of the deploy.",
Type: schema.TypeString,
Optional: true,
},
"projects": {
Description: "The optional list of projects to deploy.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
},
}
}

func resourceSentryReleaseDeploymentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*sentry.Client)

org := d.Get("organization").(string)
version := d.Get("version").(string)
params := releaseDeploymentCreateParams(d)

deploy, _, err := client.ReleaseDeployments.Create(ctx, org, version, params)
if err != nil {
return diag.FromErr(err)
}

d.SetId(buildThreePartID(org, version, deploy.ID))
return resourceSentryReleaseDeploymentRead(ctx, d, meta)
}

func resourceSentryReleaseDeploymentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*sentry.Client)

org, release, deployID, err := splitSentryReleaseDeploymentID(d.Id())

tflog.Debug(ctx, "Reading release deployment", map[string]interface{}{
"org": org,
"release": release,
"deployID": deployID,
})
deploy, resp, err := client.ReleaseDeployments.Get(ctx, org, release, deployID)
if found, err := checkClientGet(resp, err, d); !found {
tflog.Info(ctx, "Removed deployment from state because it no longer exists in Sentry", map[string]interface{}{
"org": org,
"release": release,
"deployID": deployID,
})
return diag.FromErr(err)
}

sort.Strings(deploy.Projects)

d.SetId(buildThreePartID(org, release, deploy.ID))
retErr := multierror.Append(
d.Set("organization", org),
d.Set("version", release),
d.Set("environment", deploy.Environment),
d.Set("url", deploy.URL),
d.Set("name", deploy.Name),
d.Set("projects", deploy.Projects),
)
return diag.FromErr(retErr.ErrorOrNil())
}

func resourceSentryReleaseDeploymentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*sentry.Client)

// Since we cannot update or delete deployments we create a new deployment
// when an update is needed
org := d.Get("organization").(string)
version := d.Get("version").(string)
params := releaseDeploymentCreateParams(d)

deploy, _, err := client.ReleaseDeployments.Create(ctx, org, version, params)
if err != nil {
return diag.FromErr(err)
}

d.SetId(buildThreePartID(org, version, deploy.ID))
return resourceSentryReleaseDeploymentRead(ctx, d, meta)
}

func resourceSentryReleaseDeploymentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
org, release, deployID, err := splitSentryReleaseDeploymentID(d.Id())
if err != nil {
return diag.FromErr(err)
}

tflog.Debug(ctx, "Deleting deployment", map[string]interface{}{
"org": org,
"release": release,
"deployID": deployID,
})

// Sentry has no option to delete a deployment. So we skip this and just
// remove it from the terraform state
return nil
}

func splitSentryReleaseDeploymentID(id string) (org, release, deployID string, err error) {
org, release, deployID, err = splitThreePartID(id, "organization-id", "release", "deploy-id")
return
}

func releaseDeploymentCreateParams(d *schema.ResourceData) *sentry.ReleaseDeployment {
params := &sentry.ReleaseDeployment{
Environment: d.Get("environment").(string),
}
if v := d.Get("name").(string); v != "" {
params.Name = sentry.String(v)
}
if v := d.Get("url").(string); v != "" {
params.URL = sentry.String(v)
}
if v, ok := d.GetOk("projects"); ok {
projects := expandStringList(v.([]interface{}))
if len(projects) > 0 {
params.Projects = projects
}
}
return params

}
107 changes: 107 additions & 0 deletions sentry/resource_sentry_release_deployment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package sentry

import (
"context"
"errors"
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/jianyuan/go-sentry/v2/sentry"
)

func TestAccSentryReleaseDeployment_basic(t *testing.T) {
rn := "sentry_organization_member.john_doe"
environment := "test"

check := func(role string) resource.TestCheckFunc {
var deploy sentry.ReleaseDeployment
return resource.ComposeTestCheckFunc(
testAccCheckSentryReleaseDeploymentExists(rn, &deploy),
resource.TestCheckResourceAttr(rn, "organization", testOrganization),
resource.TestCheckResourceAttr(rn, "version", "0.1.0"),
)
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckSentryReleaseDeploymentDestroy,
Steps: []resource.TestStep{
{
Config: testAccSentryReleaseDeploymentConfig("0.1.0", environment),
Check: check("member"),
},
{
Config: testAccSentryReleaseDeploymentConfig("0.2.0", environment),
Check: check("manager"),
},
},
})
}

func testAccCheckSentryReleaseDeploymentDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*sentry.Client)

for _, rs := range s.RootModule().Resources {
if rs.Type != "sentry_release_deployment" {
continue
}

ctx := context.Background()
deploy, resp, err := client.ReleaseDeployments.Get(
ctx,
rs.Primary.Attributes["organization"],
rs.Primary.Attributes["version"],
rs.Primary.ID,
)
if err == nil {
if deploy != nil {
return errors.New("release deployment still exists")
}
}
if resp.StatusCode != 404 {
return err
}
return nil
}
return nil
}

func testAccCheckSentryReleaseDeploymentExists(n string, deploy *sentry.ReleaseDeployment) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("not found: %s", n)
}

if rs.Primary.ID == "" {
return errors.New("no deploy ID is set")
}

org, version, id, err := splitSentryReleaseDeploymentID(rs.Primary.ID)
if err != nil {
return err
}
client := testAccProvider.Meta().(*sentry.Client)
ctx := context.Background()
gotDeployment, _, err := client.ReleaseDeployments.Get(ctx, org, version, id)

if err != nil {
return err
}
*deploy = *gotDeployment
return nil
}
}

func testAccSentryReleaseDeploymentConfig(version, environment string) string {
return testAccSentryOrganizationDataSourceConfig + fmt.Sprintf(`
resource "sentry_release_deployment" "my-release" {
organization = data.sentry_organization.test.id
version = "%[1]s"
environment = "%[2]s"
}
`, version, environment)
}

0 comments on commit b094fb2

Please sign in to comment.