diff --git a/README.md b/README.md index b3bac983b..2e0c9a64b 100644 --- a/README.md +++ b/README.md @@ -210,3 +210,4 @@ PAGERDUTY_ACC_SCHEDULE_USED_BY_EP_W_1_LAYER=1 make testacc TESTARGS="-run PagerD | `PAGERDUTY_ACC_INCIDENT_CUSTOM_FIELDS` | Custom Fields | | `PAGERDUTY_ACC_LICENSE_NAME` | Licenses | | `PAGERDUTY_ACC_SCHEDULE_USED_BY_EP_W_1_LAYER` | Schedule | +| `PAGERDUTY_ACC_EXTERNAL_PROVIDER_VERSION` | Modifies the version used to compare plans between sdkv2 and framework implementations. Default `~> 3.6`. | diff --git a/main.go b/main.go index 3d0286da0..b8782dede 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ func Serve() { // terraform-plugin-framework providerserver.NewProtocol5(pagerdutyplugin.New()), // terraform-plugin-sdk - pagerduty.Provider().GRPCProvider, + pagerduty.Provider(pagerduty.IsMuxed).GRPCProvider, ) if err != nil { log.Fatal(err) diff --git a/pagerduty/provider.go b/pagerduty/provider.go index 02f89190b..ee30b93a7 100644 --- a/pagerduty/provider.go +++ b/pagerduty/provider.go @@ -14,8 +14,13 @@ import ( "github.com/heimweh/go-pagerduty/persistentconfig" ) +const ( + IsMuxed = true + IsNotMuxed = false +) + // Provider represents a resource provider in Terraform -func Provider() *schema.Provider { +func Provider(isMux bool) *schema.Provider { p := &schema.Provider{ Schema: map[string]*schema.Schema{ "skip_credentials_validation": { @@ -145,6 +150,10 @@ func Provider() *schema.Provider { }, } + if isMux { + delete(p.ResourcesMap, "pagerduty_business_service") + } + p.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { terraformVersion := p.TerraformVersion if terraformVersion == "" { diff --git a/pagerduty/provider_test.go b/pagerduty/provider_test.go index 93996ca71..5077b3f02 100644 --- a/pagerduty/provider_test.go +++ b/pagerduty/provider_test.go @@ -19,7 +19,7 @@ var testAccProvider *schema.Provider var testAccProviderFactories map[string]func() (*schema.Provider, error) func init() { - testAccProvider = Provider() + testAccProvider = Provider(IsNotMuxed) testAccProviders = map[string]*schema.Provider{ "pagerduty": testAccProvider, } @@ -31,13 +31,13 @@ func init() { } func TestProvider(t *testing.T) { - if err := Provider().InternalValidate(); err != nil { + if err := Provider(IsNotMuxed).InternalValidate(); err != nil { t.Fatalf("err: %s", err) } } func TestProviderImpl(t *testing.T) { - var _ *schema.Provider = Provider() + var _ *schema.Provider = Provider(IsNotMuxed) } func TestAccPagerDutyProviderAuthMethods_Basic(t *testing.T) { diff --git a/pagerduty/resource_pagerduty_business_service.go b/pagerduty/resource_pagerduty_business_service.go index 33f91700a..358403634 100644 --- a/pagerduty/resource_pagerduty_business_service.go +++ b/pagerduty/resource_pagerduty_business_service.go @@ -10,6 +10,7 @@ import ( "github.com/heimweh/go-pagerduty/pagerduty" ) +// Deprecated: Migrated to pagerdutyplugin.resourceBusinessService. Kept for testing purposes. func resourcePagerDutyBusinessService() *schema.Resource { return &schema.Resource{ Create: resourcePagerDutyBusinessServiceCreate, diff --git a/pagerduty/resource_pagerduty_business_service_test.go b/pagerduty/resource_pagerduty_business_service_test.go deleted file mode 100644 index 5e42b6149..000000000 --- a/pagerduty/resource_pagerduty_business_service_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package pagerduty - -import ( - "fmt" - "log" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/terraform" -) - -func init() { - resource.AddTestSweepers("pagerduty_business_service", &resource.Sweeper{ - Name: "pagerduty_business_service", - F: testSweepBusinessService, - }) -} - -func testSweepBusinessService(region string) error { - config, err := sharedConfigForRegion(region) - if err != nil { - return err - } - - client, err := config.Client() - if err != nil { - return err - } - - resp, _, err := client.BusinessServices.List() - if err != nil { - return err - } - - for _, businessService := range resp.BusinessServices { - if strings.HasPrefix(businessService.Name, "test") || strings.HasPrefix(businessService.Name, "tf-") { - log.Printf("Destroying business service %s (%s)", businessService.Name, businessService.ID) - if _, err := client.BusinessServices.Delete(businessService.ID); err != nil { - return err - } - } - } - - return nil -} - -func TestAccPagerDutyBusinessService_Basic(t *testing.T) { - name := fmt.Sprintf("tf-%s", acctest.RandString(5)) - description := fmt.Sprintf("tf-%s", acctest.RandString(5)) - pointOfContact := fmt.Sprintf("tf-%s", acctest.RandString(5)) - nameUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) - descriptionUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) - pointOfContactUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckPagerDutyBusinessServiceConfig(name, description, pointOfContact), - Check: resource.ComposeTestCheckFunc( - testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.foo"), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "name", name), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "description", description), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "point_of_contact", pointOfContact), - resource.TestCheckResourceAttrSet( - "pagerduty_business_service.foo", "self"), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "type", "business_service"), - ), - }, - { - Config: testAccCheckPagerDutyBusinessServiceConfigUpdated(nameUpdated, descriptionUpdated, pointOfContactUpdated), - Check: resource.ComposeTestCheckFunc( - testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.foo"), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "name", nameUpdated), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "description", descriptionUpdated), - resource.TestCheckResourceAttr( - "pagerduty_business_service.foo", "point_of_contact", pointOfContactUpdated), - resource.TestCheckResourceAttrSet( - "pagerduty_business_service.foo", "self"), - ), - }, - }, - }) -} - -func TestAccPagerDutyBusinessService_WithTeam(t *testing.T) { - businessService := fmt.Sprintf("tf-%s", acctest.RandString(5)) - teamName := fmt.Sprintf("tf-%s", acctest.RandString(5)) - description := fmt.Sprintf("tf-%s", acctest.RandString(5)) - pointOfContact := fmt.Sprintf("tf-%s", acctest.RandString(5)) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckPagerDutyBusinessServiceWithTeamConfig(businessService, teamName, description, pointOfContact), - Check: resource.ComposeTestCheckFunc( - testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.bar"), - resource.TestCheckResourceAttr( - "pagerduty_business_service.bar", "name", businessService), - resource.TestCheckResourceAttr( - "pagerduty_business_service.bar", "description", description), - resource.TestCheckResourceAttr( - "pagerduty_business_service.bar", "point_of_contact", pointOfContact), - resource.TestCheckResourceAttrSet( - "pagerduty_business_service.bar", "self"), - ), - }, - }, - }) -} - -func testAccCheckPagerDutyBusinessServiceExists(n string) 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 fmt.Errorf("No Business Service ID is set") - } - - client, _ := testAccProvider.Meta().(*Config).Client() - - found, _, err := client.BusinessServices.Get(rs.Primary.ID) - if err != nil { - return err - } - - if found.ID != rs.Primary.ID { - return fmt.Errorf("Business Service not found: %v - %v", rs.Primary.ID, found) - } - - return nil - } -} - -func testAccCheckPagerDutyBusinessServiceDestroy(s *terraform.State) error { - client, _ := testAccProvider.Meta().(*Config).Client() - for _, r := range s.RootModule().Resources { - if r.Type != "pagerduty_business_service" { - continue - } - - if _, _, err := client.BusinessServices.Get(r.Primary.ID); err == nil { - return fmt.Errorf("Business service still exists") - } - - } - return nil -} -func testAccCheckPagerDutyBusinessServiceConfig(name, description, poc string) string { - return fmt.Sprintf(` -resource "pagerduty_business_service" "foo" { - name = "%s" - description = "%s" - point_of_contact = "%s" -} -`, name, description, poc) -} - -func testAccCheckPagerDutyBusinessServiceConfigUpdated(name, description, poc string) string { - return fmt.Sprintf(` -resource "pagerduty_business_service" "foo" { - name = "%s" - description = "%s" - point_of_contact = "%s" -} -`, name, description, poc) -} - -func testAccCheckPagerDutyBusinessServiceWithTeamConfig(businessServiceName, teamName, description, poc string) string { - return fmt.Sprintf(` - -resource "pagerduty_team" "bar" { - name = "%s" -} - -resource "pagerduty_business_service" "bar" { - name = "%s" - description = "%s" - point_of_contact = "%s" - team = pagerduty_team.bar.id -} -`, teamName, businessServiceName, description, poc) -} diff --git a/pagerduty/import_pagerduty_business_service_test.go b/pagerdutyplugin/import_pagerduty_business_service_test.go similarity index 77% rename from pagerduty/import_pagerduty_business_service_test.go rename to pagerdutyplugin/import_pagerduty_business_service_test.go index b2070daf3..4a7c784eb 100644 --- a/pagerduty/import_pagerduty_business_service_test.go +++ b/pagerdutyplugin/import_pagerduty_business_service_test.go @@ -14,14 +14,13 @@ func TestAccPagerDutyBusinessService_import(t *testing.T) { poc := fmt.Sprintf("tf-%s", acctest.RandString(5)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, Steps: []resource.TestStep{ { Config: testAccCheckPagerDutyBusinessServiceConfig(name, desc, poc), }, - { ResourceName: "pagerduty_business_service.foo", ImportState: true, diff --git a/pagerdutyplugin/provider.go b/pagerdutyplugin/provider.go index 4f2a1e491..3fa80b6d4 100644 --- a/pagerdutyplugin/provider.go +++ b/pagerdutyplugin/provider.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/PagerDuty/go-pagerduty" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -14,7 +15,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type Provider struct{} +type Provider struct { + client *pagerduty.Client +} func (p *Provider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "pagerduty" @@ -53,10 +56,12 @@ func (p *Provider) DataSources(ctx context.Context) [](func() datasource.DataSou } func (p *Provider) Resources(ctx context.Context) [](func() resource.Resource) { - return [](func() resource.Resource){} + return [](func() resource.Resource){ + func() resource.Resource { return &resourceBusinessService{} }, + } } -func New() provider.Provider { +func New() *Provider { return &Provider{} } @@ -159,7 +164,9 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, if err != nil { resp.Diagnostics.AddError("Cannot obtain plugin client", err.Error()) } + p.client = client resp.DataSourceData = client + resp.ResourceData = client } type UseAppOauthScopedToken struct { diff --git a/pagerdutyplugin/provider_test.go b/pagerdutyplugin/provider_test.go index 91ed844d5..66e63338a 100644 --- a/pagerdutyplugin/provider_test.go +++ b/pagerdutyplugin/provider_test.go @@ -14,6 +14,8 @@ import ( pd "github.com/PagerDuty/terraform-provider-pagerduty/pagerduty" ) +var testAccProvider = New() + func testAccPreCheck(t *testing.T) { if v := os.Getenv("PAGERDUTY_PARALLEL"); v != "" { t.Parallel() @@ -36,13 +38,26 @@ func testAccCheckAttributes(n string, fn func(map[string]string) error) resource } } +func testAccExternalProviders() map[string]resource.ExternalProvider { + // Using the latest release before the introduction of + // Terraform plugin framework + version := "~> 3.6" + if v := os.Getenv("PAGERDUTY_ACC_EXTERNAL_PROVIDER_VERSION"); v != "" { + version = v + } + m := map[string]resource.ExternalProvider{ + "pagerduty": {Source: "pagerduty/pagerduty", VersionConstraint: version}, + } + return m +} + func testAccProtoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { return map[string]func() (tfprotov5.ProviderServer, error){ "pagerduty": func() (tfprotov5.ProviderServer, error) { ctx := context.Background() providers := []func() tfprotov5.ProviderServer{ - pd.Provider().GRPCProvider, - providerserver.NewProtocol5(New()), + pd.Provider(pd.IsMuxed).GRPCProvider, + providerserver.NewProtocol5(testAccProvider), } muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) diff --git a/pagerdutyplugin/resource_pagerduty_business_service.go b/pagerdutyplugin/resource_pagerduty_business_service.go new file mode 100644 index 000000000..a5482e511 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_business_service.go @@ -0,0 +1,234 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + helperResource "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +type resourceBusinessService struct { + client *pagerduty.Client +} + +var ( + _ resource.ResourceWithConfigure = (*resourceBusinessService)(nil) + _ resource.ResourceWithImportState = (*resourceBusinessService)(nil) +) + +func (r *resourceBusinessService) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "pagerduty_business_service" +} + +func (r *resourceBusinessService) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{Required: true}, + "id": schema.StringAttribute{Computed: true}, + "html_url": schema.StringAttribute{Computed: true}, + "self": schema.StringAttribute{Computed: true}, + "summary": schema.StringAttribute{Computed: true}, + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Managed by Terraform"), + }, + "type": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("business_service"), + DeprecationMessage: "This will become a computed attribute in the next major release.", + Validators: []validator.String{stringvalidator.OneOf("business_service")}, + }, + "point_of_contact": schema.StringAttribute{Optional: true}, + "team": schema.StringAttribute{Optional: true}, + }, + } +} + +func (r *resourceBusinessService) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan resourceBusinessServiceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + businessServicePlan := buildPagerdutyBusinessService(ctx, &plan) + log.Printf("[INFO] Creating PagerDuty business service %s", plan.Name) + + err := helperResource.RetryContext(ctx, 5*time.Minute, func() *helperResource.RetryError { + bs, err := r.client.CreateBusinessServiceWithContext(ctx, businessServicePlan) + if err != nil { + return helperResource.NonRetryableError(err) + } else if bs != nil { + businessServicePlan.ID = bs.ID + } + return nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating Business Service %s", plan.Name), + err.Error(), + ) + return + } + + plan = requestGetBusinessService(ctx, r.client, businessServicePlan.ID, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceBusinessService) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state resourceBusinessServiceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Reading PagerDuty business service %s", state.ID) + + state = requestGetBusinessService(ctx, r.client, state.ID.ValueString(), &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *resourceBusinessService) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan resourceBusinessServiceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + businessServicePlan := buildPagerdutyBusinessService(ctx, &plan) + if businessServicePlan.ID == "" { + var id string + req.State.GetAttribute(ctx, path.Root("id"), &id) + businessServicePlan.ID = id + } + log.Printf("[INFO] Updating PagerDuty business service %s", businessServicePlan.ID) + + businessService, err := r.client.UpdateBusinessServiceWithContext(ctx, businessServicePlan) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error updating Business Service %s", businessServicePlan.ID), + err.Error(), + ) + return + } + plan = flattenBusinessService(businessService) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceBusinessService) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var id types.String + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Deleting PagerDuty business service %s", id.String()) + + err := r.client.DeleteBusinessServiceWithContext(ctx, id.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting Business Service %s", id), + err.Error(), + ) + return + } + resp.State.RemoveResource(ctx) +} + +func (r *resourceBusinessService) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&r.client, req.ProviderData)...) +} + +func (r *resourceBusinessService) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceBusinessServiceModel struct { + ID types.String `tfsdk:"id"` + Description types.String `tfsdk:"description"` + HTMLUrl types.String `tfsdk:"html_url"` + Name types.String `tfsdk:"name"` + PointOfContact types.String `tfsdk:"point_of_contact"` + Self types.String `tfsdk:"self"` + Summary types.String `tfsdk:"summary"` + Team types.String `tfsdk:"team"` + Type types.String `tfsdk:"type"` +} + +func requestGetBusinessService(ctx context.Context, client *pagerduty.Client, id string, diags *diag.Diagnostics) resourceBusinessServiceModel { + var model resourceBusinessServiceModel + + err := helperResource.RetryContext(ctx, 5*time.Minute, func() *helperResource.RetryError { + businessService, err := client.GetBusinessServiceWithContext(ctx, id) + if err != nil { + return helperResource.RetryableError(err) + } + model = flattenBusinessService(businessService) + return nil + }) + if err != nil { + diags.AddError( + fmt.Sprintf("Error reading Business Service %s", id), + err.Error(), + ) + } + + return model +} + +func buildPagerdutyBusinessService(ctx context.Context, model *resourceBusinessServiceModel) *pagerduty.BusinessService { + businessService := pagerduty.BusinessService{ + ID: model.ID.ValueString(), + Description: model.Description.ValueString(), + HTMLUrl: model.HTMLUrl.ValueString(), + Name: model.Name.ValueString(), + PointOfContact: model.PointOfContact.ValueString(), + Self: model.Self.ValueString(), + Summary: model.Summary.ValueString(), + Team: &pagerduty.BusinessServiceTeam{ID: model.Team.ValueString()}, + Type: model.Type.ValueString(), + } + return &businessService +} + +func flattenBusinessService(src *pagerduty.BusinessService) resourceBusinessServiceModel { + model := resourceBusinessServiceModel{ + ID: types.StringValue(src.ID), + Description: types.StringValue(src.Description), + HTMLUrl: types.StringValue(src.HTMLUrl), + Name: types.StringValue(src.Name), + Self: types.StringValue(src.Self), + Summary: types.StringValue(src.Summary), + Type: types.StringValue(src.Type), + PointOfContact: types.StringNull(), + Team: types.StringNull(), + } + if src.PointOfContact != "" { + model.PointOfContact = types.StringValue(src.PointOfContact) + } + if src.Team != nil { + model.Team = types.StringValue(src.Team.ID) + } + return model +} diff --git a/pagerdutyplugin/resource_pagerduty_business_service_test.go b/pagerdutyplugin/resource_pagerduty_business_service_test.go new file mode 100644 index 000000000..6af157831 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_business_service_test.go @@ -0,0 +1,171 @@ +package pagerduty + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccPagerDutyBusinessService_Basic(t *testing.T) { + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + description := fmt.Sprintf("tf-%s", acctest.RandString(5)) + pointOfContact := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + nameUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + descriptionUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + pointOfContactUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyBusinessServiceConfig(name, description, pointOfContact), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.foo"), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "name", name), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "description", description), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "point_of_contact", pointOfContact), + resource.TestCheckResourceAttrSet("pagerduty_business_service.foo", "self"), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "type", "business_service"), + ), + }, + { + Config: testAccCheckPagerDutyBusinessServiceConfig(nameUpdated, descriptionUpdated, pointOfContactUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.foo"), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "name", nameUpdated), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "description", descriptionUpdated), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "point_of_contact", pointOfContactUpdated), + resource.TestCheckResourceAttrSet("pagerduty_business_service.foo", "self"), + ), + }, + }, + }) +} + +func TestAccPagerDutyBusinessService_WithTeam(t *testing.T) { + businessService := fmt.Sprintf("tf-%s", acctest.RandString(5)) + teamName := fmt.Sprintf("tf-%s", acctest.RandString(5)) + description := fmt.Sprintf("tf-%s", acctest.RandString(5)) + pointOfContact := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyBusinessServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyBusinessServiceWithTeamConfig(businessService, teamName, description, pointOfContact), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyBusinessServiceExists("pagerduty_business_service.bar"), + resource.TestCheckResourceAttr("pagerduty_business_service.bar", "name", businessService), + resource.TestCheckResourceAttr("pagerduty_business_service.bar", "description", description), + resource.TestCheckResourceAttr("pagerduty_business_service.bar", "point_of_contact", pointOfContact), + resource.TestCheckResourceAttrSet("pagerduty_business_service.bar", "self"), + ), + }, + }, + }) +} + +func TestAccPagerDutyBusinessService_SDKv2Compatibility(t *testing.T) { + name := fmt.Sprintf("tf-%s", acctest.RandString(5)) + description := fmt.Sprintf("tf-%s", acctest.RandString(5)) + pointOfContact := fmt.Sprintf("tf-%s", acctest.RandString(5)) + commonConfig := testAccCheckPagerDutyBusinessServiceConfig(name, description, pointOfContact) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + ExternalProviders: testAccExternalProviders(), + Config: commonConfig, + Check: resource.ComposeTestCheckFunc( + // Can't call `testAccCheckPagerDutyBusinessServiceExists` because the external + // provider doesn't call testAccProvider's Configure method, and its client is + // left empty. + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "name", name), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "description", description), + resource.TestCheckResourceAttr("pagerduty_business_service.foo", "point_of_contact", pointOfContact), + resource.TestCheckResourceAttrSet("pagerduty_business_service.foo", "self"), + ), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + Config: commonConfig, + ConfigPlanChecks: resource.ConfigPlanChecks{PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}}, + }, + }, + }) +} + +func testAccCheckPagerDutyBusinessServiceExists(n string) 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 fmt.Errorf("No Business Service ID is set") + } + + businessService, err := testAccProvider.client.GetBusinessServiceWithContext(context.Background(), rs.Primary.ID) + if err != nil { + return err + } + + if businessService.ID != rs.Primary.ID { + return fmt.Errorf("Business Service not found: %v - %v", rs.Primary.ID, businessService) + } + + return nil + } +} + +func testAccCheckPagerDutyBusinessServiceDestroy(s *terraform.State) error { + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_business_service" { + continue + } + ctx := context.Background() + _, err := testAccProvider.client.GetBusinessServiceWithContext(ctx, r.Primary.ID) + if err == nil { + return fmt.Errorf("Business service still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyBusinessServiceConfig(name, description, poc string) string { + return fmt.Sprintf(` +resource "pagerduty_business_service" "foo" { + name = "%s" + description = "%s" + point_of_contact = "%s" +} +`, name, description, poc) +} + +func testAccCheckPagerDutyBusinessServiceWithTeamConfig(businessServiceName, teamName, description, poc string) string { + return fmt.Sprintf(` +resource "pagerduty_team" "bar" { + name = "%s" +} + +resource "pagerduty_business_service" "bar" { + name = "%s" + description = "%s" + point_of_contact = "%s" + team = pagerduty_team.bar.id +} +`, teamName, businessServiceName, description, poc) +} diff --git a/util/http_util.go b/util/http_util.go new file mode 100644 index 000000000..1b480595d --- /dev/null +++ b/util/http_util.go @@ -0,0 +1,37 @@ +package util + +import ( + "errors" + "net/http" + "regexp" + + "github.com/PagerDuty/go-pagerduty" +) + +func IsBadRequestError(err error) bool { + var apiErr pagerduty.APIError + if errors.As(err, &apiErr) { + return apiErr.StatusCode == http.StatusBadRequest + } + return false +} + +var notFoundErrorRegexp = regexp.MustCompile(".*: 404 Not Found$") + +func IsNotFoundError(err error) bool { + if err == nil { + return false + } + + var apiErr pagerduty.APIError + if errors.As(err, &apiErr) { + if apiErr.StatusCode == http.StatusNotFound { + return true + } + } + + // There are some errors that doesn't stick to expected error interface + // and fallback to a simple text error message that can be capture by + // this regexp. + return notFoundErrorRegexp.MatchString(err.Error()) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/doc.go new file mode 100644 index 000000000..c1ca3989b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package stringdefault provides default values for types.String attributes. +package stringdefault diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/static_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/static_value.go new file mode 100644 index 000000000..deb2965bd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault/static_value.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringdefault + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// StaticString returns a static string value default handler. +// +// Use StaticString if a static default value for a string should be set. +func StaticString(defaultVal string) defaults.String { + return staticStringDefault{ + defaultVal: defaultVal, + } +} + +// staticStringDefault is static value default handler that +// sets a value on a string attribute. +type staticStringDefault struct { + defaultVal string +} + +// Description returns a human-readable description of the default value handler. +func (d staticStringDefault) Description(_ context.Context) string { + return fmt.Sprintf("value defaults to %s", d.defaultVal) +} + +// MarkdownDescription returns a markdown description of the default value handler. +func (d staticStringDefault) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value defaults to `%s`", d.defaultVal) +} + +// DefaultString implements the static default value logic. +func (d staticStringDefault) DefaultString(_ context.Context, req defaults.StringRequest, resp *defaults.StringResponse) { + resp.PlanValue = types.StringValue(d.defaultVal) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 76ec5f675..4c8122f2b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -166,6 +166,7 @@ github.com/hashicorp/terraform-plugin-framework/resource github.com/hashicorp/terraform-plugin-framework/resource/schema github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier +github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault github.com/hashicorp/terraform-plugin-framework/schema/validator github.com/hashicorp/terraform-plugin-framework/tfsdk github.com/hashicorp/terraform-plugin-framework/types