diff --git a/README.md b/README.md index 2e0c9a64b..071d26f70 100644 --- a/README.md +++ b/README.md @@ -210,4 +210,5 @@ 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_JIRA_ACCOUNT_MAPPING_ID` | Set Jira account-mapping ID to use during acceptance tests | | `PAGERDUTY_ACC_EXTERNAL_PROVIDER_VERSION` | Modifies the version used to compare plans between sdkv2 and framework implementations. Default `~> 3.6`. | diff --git a/go.mod b/go.mod index 8809fb6c5..a1c072521 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/PagerDuty/terraform-provider-pagerduty go 1.20 require ( - github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 + github.com/PagerDuty/go-pagerduty v1.8.1-0.20241104145658-2a0050d437ac github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.2 diff --git a/go.sum b/go.sum index 9fca179c9..a710df66d 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,10 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 h1:y/icahuphX4xGMW4nLN+Bl4MbFUU4rEA9spwgcPIDJk= github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88/go.mod h1:ilimTqwHSBjmvKeYA/yayDBZvzf/CX4Pwa9Qbhekzok= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241025123418-0c3fc7303be4 h1:egkL94FTYFe3GfnJMpY2aAj6tA2wXXYRfGPJPfC90BQ= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241025123418-0c3fc7303be4/go.mod h1:ilimTqwHSBjmvKeYA/yayDBZvzf/CX4Pwa9Qbhekzok= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241104145658-2a0050d437ac h1:GtkaaoQH3DSWt9hxC71pAp31pyhKFxU1nBfa5iZWecE= +github.com/PagerDuty/go-pagerduty v1.8.1-0.20241104145658-2a0050d437ac/go.mod h1:ilimTqwHSBjmvKeYA/yayDBZvzf/CX4Pwa9Qbhekzok= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping.go b/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping.go new file mode 100644 index 000000000..bc13e03e3 --- /dev/null +++ b/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping.go @@ -0,0 +1,95 @@ +package pagerduty + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +type dataSourceJiraCloudAccountMapping struct{ client *pagerduty.Client } + +var _ datasource.DataSourceWithConfigure = (*dataSourceJiraCloudAccountMapping)(nil) + +func (*dataSourceJiraCloudAccountMapping) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "pagerduty_jira_cloud_account_mapping" +} + +func (*dataSourceJiraCloudAccountMapping) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{Computed: true}, + "base_url": schema.StringAttribute{Computed: true}, + "subdomain": schema.StringAttribute{Required: true}, + }, + } +} + +func (d *dataSourceJiraCloudAccountMapping) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&d.client, req.ProviderData)...) +} + +func (d *dataSourceJiraCloudAccountMapping) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + log.Println("[INFO] Reading PagerDuty jira cloud account mapping") + + var searchSubdomain types.String + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("subdomain"), &searchSubdomain)...) + if resp.Diagnostics.HasError() { + return + } + + var found *pagerduty.JiraCloudAccountsMapping + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + response, err := d.client.ListJiraCloudAccountsMappings(ctx, pagerduty.ListJiraCloudAccountsMappingsOptions{}) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + + for _, m := range response.AccountsMappings { + if m.PagerDutyAccount.Subdomain == searchSubdomain.ValueString() { + found = &m + break + } + } + return nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty jira cloud account mapping with subdomain %s", searchSubdomain), + err.Error(), + ) + return + } + + if found == nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Unable to locate any jira cloud account mapping with the subdomain %s", searchSubdomain), + "", + ) + return + } + + model := dataSourceJiraCloudAccountMappingModel{ + ID: types.StringValue(found.ID), + BaseURL: types.StringValue(found.JiraCloudAccount.BaseURL), + Subdomain: types.StringValue(found.PagerDutyAccount.Subdomain), + } + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +type dataSourceJiraCloudAccountMappingModel struct { + ID types.String `tfsdk:"id"` + BaseURL types.String `tfsdk:"base_url"` + Subdomain types.String `tfsdk:"subdomain"` +} diff --git a/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping_test.go b/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping_test.go new file mode 100644 index 000000000..93783f018 --- /dev/null +++ b/pagerdutyplugin/data_source_pagerduty_jira_cloud_account_mapping_test.go @@ -0,0 +1,40 @@ +package pagerduty + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataSourcePagerDutyJiraCloudAccountMapping_Basic(t *testing.T) { + subdomain := os.Getenv("PAGERDUTY_SUBDOMAIN") + if subdomain == "" { + t.Skip("Missing env variable PAGERDUTY_SUBDOMAIN") + return + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePagerDutyJiraCloudAccountMappingConfig(subdomain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.pagerduty_jira_cloud_account_mapping.by_subdomain", "subdomain", subdomain), + resource.TestCheckResourceAttrSet("data.pagerduty_jira_cloud_account_mapping.by_subdomain", "id"), + resource.TestCheckResourceAttrSet("data.pagerduty_jira_cloud_account_mapping.by_subdomain", "base_url"), + ), + }, + }, + }) +} + +func testAccDataSourcePagerDutyJiraCloudAccountMappingConfig(subdomain string) string { + return fmt.Sprintf(` +data "pagerduty_jira_cloud_account_mapping" "by_subdomain" { + subdomain = "%s" +} +`, subdomain) +} diff --git a/pagerdutyplugin/provider.go b/pagerdutyplugin/provider.go index 1e1050af9..7eac9937d 100644 --- a/pagerdutyplugin/provider.go +++ b/pagerdutyplugin/provider.go @@ -18,7 +18,8 @@ import ( ) type Provider struct { - client *pagerduty.Client + client *pagerduty.Client + apiURLOverride string } func (p *Provider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { @@ -56,8 +57,9 @@ func (p *Provider) DataSources(_ context.Context) [](func() datasource.DataSourc func() datasource.DataSource { return &dataSourceBusinessService{} }, func() datasource.DataSource { return &dataSourceExtensionSchema{} }, func() datasource.DataSource { return &dataSourceIntegration{} }, - func() datasource.DataSource { return &dataSourceLicense{} }, + func() datasource.DataSource { return &dataSourceJiraCloudAccountMapping{} }, func() datasource.DataSource { return &dataSourceLicenses{} }, + func() datasource.DataSource { return &dataSourceLicense{} }, func() datasource.DataSource { return &dataSourcePriority{} }, func() datasource.DataSource { return &dataSourceService{} }, func() datasource.DataSource { return &dataSourceStandardsResourceScores{} }, @@ -74,6 +76,7 @@ func (p *Provider) Resources(_ context.Context) [](func() resource.Resource) { func() resource.Resource { return &resourceBusinessService{} }, func() resource.Resource { return &resourceExtensionServiceNow{} }, func() resource.Resource { return &resourceExtension{} }, + func() resource.Resource { return &resourceJiraCloudAccountMappingRule{} }, func() resource.Resource { return &resourceServiceDependency{} }, func() resource.Resource { return &resourceTagAssignment{} }, func() resource.Resource { return &resourceTag{} }, @@ -122,6 +125,10 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, InsecureTls: insecureTls, } + if config.APIURLOverride == "" && p.apiURLOverride != "" { + config.APIURLOverride = p.apiURLOverride + } + if !args.UseAppOauthScopedToken.IsNull() { blockList := []UseAppOauthScopedToken{} resp.Diagnostics.Append(args.UseAppOauthScopedToken.ElementsAs(ctx, &blockList, false)...) diff --git a/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule.go b/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule.go new file mode 100644 index 000000000..b99381d9b --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule.go @@ -0,0 +1,742 @@ +package pagerduty + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +type resourceJiraCloudAccountMappingRule struct{ client *pagerduty.Client } + +var ( + _ resource.ResourceWithConfigure = (*resourceJiraCloudAccountMappingRule)(nil) + _ resource.ResourceWithImportState = (*resourceJiraCloudAccountMappingRule)(nil) +) + +func (r *resourceJiraCloudAccountMappingRule) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "pagerduty_jira_cloud_account_mapping_rule" +} + +func (r *resourceJiraCloudAccountMappingRule) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Validators: []validator.String{stringvalidator.LengthAtMost(100)}, + }, + "account_mapping": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "autocreate_jql_disabled_reason": schema.StringAttribute{ + Computed: true, + }, + "autocreate_jql_disabled_until": schema.StringAttribute{ + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "config": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "service": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "jira": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "autocreate_jql": schema.StringAttribute{Optional: true}, + "create_issue_on_incident_trigger": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "sync_notes_user": schema.StringAttribute{Optional: true}, + }, + Blocks: map[string]schema.Block{ + "custom_fields": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "source_incident_field": schema.StringAttribute{Optional: true}, + "target_issue_field": schema.StringAttribute{Required: true}, + "target_issue_field_name": schema.StringAttribute{Required: true}, + "type": schema.StringAttribute{Required: true}, + "value": schema.StringAttribute{Optional: true}, + }, + }, + }, + "issue_type": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{Required: true}, + "name": schema.StringAttribute{Required: true}, + }, + }, + "priorities": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "jira_id": schema.StringAttribute{Required: true}, + "pagerduty_id": schema.StringAttribute{Required: true}, + }, + }, + }, + "project": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "key": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + "status_mapping": schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "acknowledged": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("name")), + }, + }, + "name": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("id")), + }, + }, + }, + }, + "resolved": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("name")), + }, + }, + "name": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("id")), + }, + }, + }, + }, + "triggered": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{Required: true}, + "name": schema.StringAttribute{Required: true}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r *resourceJiraCloudAccountMappingRule) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model resourceJiraCloudAccountMappingRuleModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + accountMappingID, plan := buildPagerdutyJiraCloudAccountsMappingRule(ctx, &model, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + log.Printf("[INFO] Creating PagerDuty jira cloud account mapping rule %s for account mapping %s", plan.ID, accountMappingID) + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + response, err := r.client.CreateJiraCloudAccountsMappingRule(ctx, accountMappingID, plan) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + plan.ID = response.ID + return nil + }) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error creating PagerDuty jira cloud account mapping rule %s", plan.Name), + err.Error(), + ) + return + } + + model, err = requestGetJiraCloudAccountsMappingRule(ctx, r.client, accountMappingID, plan.ID, true) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty jira cloud account mapping rule %s", plan.ID), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceJiraCloudAccountMappingRule) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var id types.String + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &id)...) + if resp.Diagnostics.HasError() { + return + } + + accountMappingID, ruleID, err := util.ResourcePagerDutyParseColonCompoundID(id.ValueString()) + if err != nil { + resp.Diagnostics.AddAttributeError(path.Root("id"), "Invalid Value", err.Error()) + return + } + log.Printf("[INFO] Reading PagerDuty jira cloud account mapping rule %s for account mapping %s", ruleID, accountMappingID) + + state, err := requestGetJiraCloudAccountsMappingRule(ctx, r.client, accountMappingID, ruleID, false) + if err != nil { + if util.IsNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + fmt.Sprintf("Error reading PagerDuty jira cloud account mapping rule %s", id), + err.Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *resourceJiraCloudAccountMappingRule) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model resourceJiraCloudAccountMappingRuleModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + _, plan := buildPagerdutyJiraCloudAccountsMappingRule(ctx, &model, &resp.Diagnostics) + accountMappingID, ruleID, err := util.ResourcePagerDutyParseColonCompoundID(plan.ID) + if err != nil { + resp.Diagnostics.AddAttributeError(path.Root("id"), "Invalid Value", err.Error()) + return + } + plan.ID = ruleID + log.Printf("[INFO] Updating PagerDuty jira cloud account mapping rule %s for account mapping %s", ruleID, accountMappingID) + + jiraCloudAccountsMappingRule, err := r.client.UpdateJiraCloudAccountsMappingRule(ctx, accountMappingID, plan) + if err != nil { + if util.IsNotFoundError(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + fmt.Sprintf("Error updating PagerDuty jira cloud account mapping rule %s for account mapping %s", ruleID, accountMappingID), + err.Error(), + ) + return + } + model = flattenJiraCloudAccountsMappingRule(jiraCloudAccountsMappingRule) + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (r *resourceJiraCloudAccountMappingRule) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var idValue types.String + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("id"), &idValue)...) + if resp.Diagnostics.HasError() { + return + } + + accountMappingID, ruleID, err := util.ResourcePagerDutyParseColonCompoundID(idValue.ValueString()) + if err != nil { + resp.Diagnostics.AddAttributeError(path.Root("id"), "Invalid Value", err.Error()) + return + } + log.Printf("[INFO] Deleting PagerDuty jira cloud account mapping rule %s for account mapping %s", ruleID, accountMappingID) + + err = r.client.DeleteJiraCloudAccountsMappingRule(ctx, accountMappingID, ruleID) + if err != nil && !util.IsNotFoundError(err) { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting PagerDuty jira cloud account mapping rule %s for account mapping %s", ruleID, accountMappingID), + err.Error(), + ) + return + } + resp.State.RemoveResource(ctx) +} + +func (r *resourceJiraCloudAccountMappingRule) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + resp.Diagnostics.Append(ConfigurePagerdutyClient(&r.client, req.ProviderData)...) +} + +func (r *resourceJiraCloudAccountMappingRule) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + if _, _, err := util.ResourcePagerDutyParseColonCompoundID(req.ID); err != nil { + resp.Diagnostics.AddAttributeError(path.Root("id"), "Invalid Value", err.Error()) + return + } + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceJiraCloudAccountMappingRuleModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + AccountsMapping types.String `tfsdk:"account_mapping"` + Config types.Object `tfsdk:"config"` + AutocreateJQLDisabledReason types.String `tfsdk:"autocreate_jql_disabled_reason"` + AutocreateJQLDisabledUntil types.String `tfsdk:"autocreate_jql_disabled_until"` +} + +func requestGetJiraCloudAccountsMappingRule(ctx context.Context, client *pagerduty.Client, accountMappingID, ruleID string, retryNotFound bool) (resourceJiraCloudAccountMappingRuleModel, error) { + var model resourceJiraCloudAccountMappingRuleModel + + err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + jiraCloudAccountsMappingRule, err := client.GetJiraCloudAccountsMappingRule(ctx, accountMappingID, ruleID) + if err != nil { + if util.IsBadRequestError(err) { + return retry.NonRetryableError(err) + } + if !retryNotFound && util.IsNotFoundError(err) { + return retry.NonRetryableError(err) + } + return retry.RetryableError(err) + } + model = flattenJiraCloudAccountsMappingRule(jiraCloudAccountsMappingRule) + return nil + }) + + return model, err +} + +func buildPagerdutyJiraCloudAccountsMappingRule(ctx context.Context, model *resourceJiraCloudAccountMappingRuleModel, diags *diag.Diagnostics) (string, pagerduty.JiraCloudAccountsMappingRule) { + return model.AccountsMapping.ValueString(), pagerduty.JiraCloudAccountsMappingRule{ + ID: model.ID.ValueString(), + Name: model.Name.ValueString(), + Config: buildPagerdutyJiraCloudAccountsMappingRuleConfig(ctx, model, diags), + } +} + +func buildPagerdutyJiraCloudAccountsMappingRuleConfig(ctx context.Context, model *resourceJiraCloudAccountMappingRuleModel, diags *diag.Diagnostics) pagerduty.JiraCloudAccountsMappingRuleConfig { + var target struct { + Jira types.Object `tfsdk:"jira"` + Service types.String `tfsdk:"service"` + } + + d := model.Config.As(ctx, &target, basetypes.ObjectAsOptions{}) + diags.Append(d...) + if d.HasError() { + return pagerduty.JiraCloudAccountsMappingRuleConfig{} + } + + return pagerduty.JiraCloudAccountsMappingRuleConfig{ + Jira: buildJiraCloudSettings(ctx, target.Jira, diags), + Service: pagerduty.APIObject{ + ID: target.Service.ValueString(), + Type: "service_reference", + }, + } +} + +func buildJiraCloudSettings(ctx context.Context, obj types.Object, diags *diag.Diagnostics) pagerduty.JiraCloudSettings { + var target struct { + AutocreateJQL types.String `tfsdk:"autocreate_jql"` + CreateIssueOnIncidentTrigger types.Bool `tfsdk:"create_issue_on_incident_trigger"` + CustomFields types.List `tfsdk:"custom_fields"` + IssueType types.Object `tfsdk:"issue_type"` + Priorities types.List `tfsdk:"priorities"` + Project types.Object `tfsdk:"project"` + StatusMapping types.Object `tfsdk:"status_mapping"` + SyncNotesUser types.String `tfsdk:"sync_notes_user"` + } + d := obj.As(ctx, &target, basetypes.ObjectAsOptions{}) + diags.Append(d...) + if d.HasError() { + return pagerduty.JiraCloudSettings{} + } + + jira := pagerduty.JiraCloudSettings{ + AutocreateJQL: target.AutocreateJQL.ValueStringPointer(), + CreateIssueOnIncidentTrigger: target.CreateIssueOnIncidentTrigger.ValueBool(), + CustomFields: buildPagerdutyJiraCloudCustomFields(ctx, target.CustomFields, diags), + IssueType: buildJiraCloudRef(ctx, target.IssueType, diags), + Priorities: buildJiraCloudPriorities(ctx, target.Priorities, diags), + Project: buildJiraCloudProject(ctx, target.Project, diags), + StatusMapping: buildJiraCloudStatusMapping(ctx, target.StatusMapping, diags), + SyncNotesUser: nil, + } + if !target.SyncNotesUser.IsNull() && !target.SyncNotesUser.IsUnknown() { + jira.SyncNotesUser = &pagerduty.UserJiraCloud{ + APIObject: pagerduty.APIObject{ + ID: target.SyncNotesUser.ValueString(), + Type: "user_reference", + }, + } + } + + return jira +} + +func buildPagerdutyJiraCloudCustomFields(ctx context.Context, list types.List, diags *diag.Diagnostics) []pagerduty.JiraCloudCustomField { + var target []struct { + SourceIncidentField types.String `tfsdk:"source_incident_field"` + TargetIssueField types.String `tfsdk:"target_issue_field"` + TargetIssueFieldName types.String `tfsdk:"target_issue_field_name"` + Type types.String `tfsdk:"type"` + Value types.String `tfsdk:"value"` + } + + d := list.ElementsAs(ctx, &target, true) + diags.Append(d...) + if d.HasError() { + return nil + } + + var customFields []pagerduty.JiraCloudCustomField + for _, cf := range target { + field := pagerduty.JiraCloudCustomField{ + SourceIncidentField: nil, + TargetIssueField: cf.TargetIssueField.ValueString(), + TargetIssueFieldName: cf.TargetIssueFieldName.ValueString(), + Type: cf.Type.ValueString(), + Value: cf.Value.ValueString(), + } + if !cf.SourceIncidentField.IsNull() && !cf.SourceIncidentField.IsUnknown() { + field.SourceIncidentField = cf.SourceIncidentField.ValueStringPointer() + } + customFields = append(customFields, field) + } + + return customFields +} + +func buildJiraCloudRef(ctx context.Context, obj types.Object, diags *diag.Diagnostics) pagerduty.JiraCloudReference { + var target struct { + ID string `tfsdk:"id"` + Name string `tfsdk:"name"` + } + + d := obj.As(ctx, &target, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}) + if diags.Append(d...); d.HasError() { + return pagerduty.JiraCloudReference{} + } + + return pagerduty.JiraCloudReference{ID: target.ID, Name: target.Name} +} + +func buildJiraCloudPriorities(ctx context.Context, list types.List, diags *diag.Diagnostics) []pagerduty.JiraCloudPriority { + var target []struct { + JiraID types.String `tfsdk:"jira_id"` + PagerDutyID types.String `tfsdk:"pagerduty_id"` + } + + d := list.ElementsAs(ctx, &target, true) + diags.Append(d...) + if d.HasError() { + return nil + } + + priorities := make([]pagerduty.JiraCloudPriority, 0, len(list.Elements())) + for _, p := range target { + priorities = append(priorities, pagerduty.JiraCloudPriority{ + JiraID: p.JiraID.ValueString(), + PagerDutyID: p.PagerDutyID.ValueString(), + }) + } + + return priorities +} + +func buildJiraCloudProject(ctx context.Context, obj types.Object, diags *diag.Diagnostics) pagerduty.JiraCloudReference { + var target struct { + ID string `tfsdk:"id"` + Name string `tfsdk:"name"` + Key string `tfsdk:"key"` + } + + d := obj.As(ctx, &target, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}) + if diags.Append(d...); d.HasError() { + return pagerduty.JiraCloudReference{} + } + + return pagerduty.JiraCloudReference{ID: target.ID, Name: target.Name, Key: target.Key} +} + +func buildJiraCloudStatusMapping(ctx context.Context, obj types.Object, diags *diag.Diagnostics) pagerduty.JiraCloudStatusMapping { + var target struct { + Acknowledged types.Object `tfsdk:"acknowledged"` + Resolved types.Object `tfsdk:"resolved"` + Triggered types.Object `tfsdk:"triggered"` + } + + d := obj.As(ctx, &target, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, UnhandledUnknownAsEmpty: true}) + if diags.Append(d...); d.HasError() { + return pagerduty.JiraCloudStatusMapping{} + } + + var acknowledged *pagerduty.JiraCloudReference + if v := buildJiraCloudRef(ctx, target.Acknowledged, diags); v.ID != "" { + acknowledged = &v + } + + var resolved *pagerduty.JiraCloudReference + if v := buildJiraCloudRef(ctx, target.Resolved, diags); v.ID != "" { + resolved = &v + } + + var triggered *pagerduty.JiraCloudReference + if v := buildJiraCloudRef(ctx, target.Triggered, diags); v.ID != "" { + triggered = &v + } + + return pagerduty.JiraCloudStatusMapping{ + Acknowledged: acknowledged, + Resolved: resolved, + Triggered: triggered, + } +} + +func flattenJiraCloudAccountsMappingRule(response *pagerduty.JiraCloudAccountsMappingRule) resourceJiraCloudAccountMappingRuleModel { + id := types.StringNull() + if response.AccountsMapping != nil { + id = types.StringValue(response.AccountsMapping.ID + ":" + response.ID) + } + + autocreateJQLDisabledReason := types.StringNull() + if response.AutocreateJqlDisabledReason != "" { + autocreateJQLDisabledReason = types.StringValue(response.AutocreateJqlDisabledReason) + } + + autocreateJQLDisabledUntil := types.StringNull() + if response.AutocreateJqlDisabledUntil != "" { + autocreateJQLDisabledUntil = types.StringValue(response.AutocreateJqlDisabledUntil) + } + + model := resourceJiraCloudAccountMappingRuleModel{ + ID: id, + Config: flattenJiraCloudAccountsMappingRuleConfig(response), + Name: types.StringValue(response.Name), + AccountsMapping: types.StringValue(response.AccountsMapping.ID), + AutocreateJQLDisabledReason: autocreateJQLDisabledReason, + AutocreateJQLDisabledUntil: autocreateJQLDisabledUntil, + } + + return model +} + +func flattenJiraCloudAccountsMappingRuleConfig(response *pagerduty.JiraCloudAccountsMappingRule) types.Object { + var configJiraObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "autocreate_jql": types.StringType, + "create_issue_on_incident_trigger": types.BoolType, + "custom_fields": types.ListType{ElemType: jiraCloudCustomFieldObjectType}, + "issue_type": jiraCloudRefObjectType, + "priorities": types.ListType{ElemType: jiraCloudPriorityObjectType}, + "project": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + "key": types.StringType, + }, + }, + "status_mapping": jiraCloudStatusMappingObjectType, + "sync_notes_user": types.StringType, + }, + } + + var configObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "service": types.StringType, + "jira": configJiraObjectType, + }, + } + + autocreateJQL := types.StringNull() + if response.Config.Jira.AutocreateJQL != nil { + autocreateJQL = types.StringValue(*response.Config.Jira.AutocreateJQL) + } + + syncNotesUser := types.StringNull() + if response.Config.Jira.SyncNotesUser != nil { + syncNotesUser = types.StringValue(response.Config.Jira.SyncNotesUser.ID) + } + + return types.ObjectValueMust(configObjectType.AttrTypes, map[string]attr.Value{ + "service": types.StringValue(response.Config.Service.ID), + "jira": types.ObjectValueMust(configJiraObjectType.AttrTypes, map[string]attr.Value{ + "autocreate_jql": autocreateJQL, + "create_issue_on_incident_trigger": types.BoolValue(response.Config.Jira.CreateIssueOnIncidentTrigger), + "sync_notes_user": syncNotesUser, + "custom_fields": flattenJiraCloudCustomFields(response), + "issue_type": flattenJiraCloudRef(response.Config.Jira.IssueType), + "priorities": flattenJiraCloudPriorities(response), + "project": flattenJiraCloudRefKeyed(response.Config.Jira.Project), + "status_mapping": flattenJiraCloudStatusMapping(response), + }), + }) +} + +func flattenJiraCloudCustomFields(response *pagerduty.JiraCloudAccountsMappingRule) types.List { + elements := make([]attr.Value, 0) + + for _, cf := range response.Config.Jira.CustomFields { + values := map[string]attr.Value{ + "source_incident_field": types.StringNull(), + "target_issue_field": types.StringValue(cf.TargetIssueField), + "target_issue_field_name": types.StringValue(cf.TargetIssueFieldName), + "type": types.StringValue(cf.Type), + "value": types.StringNull(), + } + if cf.SourceIncidentField != nil { + values["source_incident_field"] = types.StringValue(*cf.SourceIncidentField) + } + if !util.IsNilFunc(cf.Value) { + s, ok := cf.Value.(string) + if !ok { + buf, _ := json.Marshal(cf.Value) + s = string(buf) + } + values["value"] = types.StringValue(s) + } + elements = append(elements, types.ObjectValueMust(jiraCloudCustomFieldObjectType.AttrTypes, values)) + } + + return types.ListValueMust(jiraCloudCustomFieldObjectType, elements) +} + +func flattenJiraCloudRef(v pagerduty.JiraCloudReference) types.Object { + return types.ObjectValueMust(jiraCloudRefObjectType.AttrTypes, map[string]attr.Value{ + "id": types.StringValue(v.ID), + "name": types.StringValue(v.Name), + }) +} + +func flattenJiraCloudRefKeyed(v pagerduty.JiraCloudReference) types.Object { + return types.ObjectValueMust(jiraCloudRefKeyedObjectType.AttrTypes, map[string]attr.Value{ + "id": types.StringValue(v.ID), + "name": types.StringValue(v.Name), + "key": types.StringValue(v.Key), + }) +} + +func flattenJiraCloudPriorities(response *pagerduty.JiraCloudAccountsMappingRule) types.List { + elements := make([]attr.Value, 0, len(response.Config.Jira.Priorities)) + for _, p := range response.Config.Jira.Priorities { + elements = append(elements, types.ObjectValueMust(jiraCloudPriorityObjectType.AttrTypes, map[string]attr.Value{ + "jira_id": types.StringValue(p.JiraID), + "pagerduty_id": types.StringValue(p.PagerDutyID), + })) + } + return types.ListValueMust(jiraCloudPriorityObjectType, elements) +} + +func flattenJiraCloudStatusMapping(response *pagerduty.JiraCloudAccountsMappingRule) types.Object { + acknowledged := types.ObjectNull(jiraCloudRefObjectType.AttrTypes) + if response.Config.Jira.StatusMapping.Acknowledged != nil { + acknowledged = flattenJiraCloudRef(*response.Config.Jira.StatusMapping.Acknowledged) + } + + resolved := types.ObjectNull(jiraCloudRefObjectType.AttrTypes) + if response.Config.Jira.StatusMapping.Resolved != nil { + resolved = flattenJiraCloudRef(*response.Config.Jira.StatusMapping.Resolved) + } + + triggered := types.ObjectNull(jiraCloudRefObjectType.AttrTypes) + if response.Config.Jira.StatusMapping.Triggered != nil { + triggered = flattenJiraCloudRef(*response.Config.Jira.StatusMapping.Triggered) + } + + return types.ObjectValueMust(jiraCloudStatusMappingObjectType.AttrTypes, map[string]attr.Value{ + "acknowledged": acknowledged, + "resolved": resolved, + "triggered": triggered, + }) +} + +var jiraCloudCustomFieldObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "source_incident_field": types.StringType, + "target_issue_field": types.StringType, + "target_issue_field_name": types.StringType, + "type": types.StringType, + "value": types.StringType, + }, +} + +var jiraCloudRefObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + }, +} + +var jiraCloudRefKeyedObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "id": types.StringType, + "name": types.StringType, + "key": types.StringType, + }, +} + +var jiraCloudPriorityObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "jira_id": types.StringType, + "pagerduty_id": types.StringType, + }, +} + +var jiraCloudStatusMappingObjectType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "acknowledged": jiraCloudRefObjectType, + "resolved": jiraCloudRefObjectType, + "triggered": jiraCloudRefObjectType, + }, +} diff --git a/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule_test.go b/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule_test.go new file mode 100644 index 000000000..e05b1a261 --- /dev/null +++ b/pagerdutyplugin/resource_pagerduty_jira_cloud_account_mapping_rule_test.go @@ -0,0 +1,325 @@ +package pagerduty + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/PagerDuty/terraform-provider-pagerduty/util" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccPagerDutyJiraCloudAccountsMappingRule_Basic(t *testing.T) { + accountMappingID := os.Getenv("PAGERDUTY_ACC_JIRA_ACCOUNT_MAPPING_ID") + if accountMappingID == "" { + t.Skip("Missing env variable PAGERDUTY_ACC_JIRA_ACCOUNT_MAPPING_ID") + return + } + + rule := fmt.Sprintf("tf-%s", acctest.RandString(5)) + ruleUpdated := fmt.Sprintf("tf-%s", acctest.RandString(5)) + service := fmt.Sprintf("tf-%s", acctest.RandString(5)) + user := fmt.Sprintf("tf-%s", acctest.RandString(5)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories(), + CheckDestroy: testAccCheckPagerDutyJiraCloudAccountsMappingRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckPagerDutyJiraCloudAccountsMappingRuleConfig(service, user, rule, accountMappingID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyJiraCloudAccountsMappingRuleExists("pagerduty_jira_cloud_account_mapping_rule.foo"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "name", rule), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "account_mapping", accountMappingID), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "autocreate_jql_disabled_reason"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "autocreate_jql_disabled_until"), + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.service"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.autocreate_jql", "priority = Highest"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.create_issue_on_incident_trigger", "true"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.0.source_incident_field", "incident_description"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.0.target_issue_field", "description"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.0.target_issue_field_name", "Description"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.0.type", "attribute"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.1.source_incident_field", "incident_number"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.1.target_issue_field", "customfield_10001"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.1.target_issue_field_name", "PagerDuty Incident Number"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.1.type", "attribute"), + + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.2.source_incident_field"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.2.target_issue_field", "security"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.2.target_issue_field_name", "Security Level"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.2.type", "jira_value"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.2.value", `{"displayName":"Sec Level 1","id":"10000"}`), + + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.3.source_incident_field"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.3.target_issue_field", "labels"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.3.target_issue_field_name", "Labels"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.3.type", "const"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.3.value", "pagerduty, incident"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.issue_type.id", "10001"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.issue_type.name", "Incident"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.0.jira_id", "1"), + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.0.pagerduty_id"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.1.jira_id", "2"), + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.1.pagerduty_id"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.2.jira_id", "3"), + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.2.pagerduty_id"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.id", "10100"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.name", "IT Support"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.key", "ITS"), + + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.acknowledged.id", "2"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.acknowledged.name", "In Progress"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.resolved.id", "3"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.resolved.name", "Resolved"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.triggered.id", "1"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.triggered.name", "Open"), + + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.sync_notes_user"), + ), + }, + + { + Config: testAccCheckPagerDutyJiraCloudAccountsMappingRuleConfigUpdated(service, ruleUpdated, accountMappingID), + Check: resource.ComposeTestCheckFunc( + testAccCheckPagerDutyJiraCloudAccountsMappingRuleExists("pagerduty_jira_cloud_account_mapping_rule.foo"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "name", ruleUpdated), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "account_mapping", accountMappingID), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "autocreate_jql_disabled_reason"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "autocreate_jql_disabled_until"), + resource.TestCheckResourceAttrSet("pagerduty_jira_cloud_account_mapping_rule.foo", "config.service"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.autocreate_jql"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.create_issue_on_incident_trigger", "false"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.custom_fields.#", "0"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.issue_type.id", "10001"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.issue_type.name", "Incident"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.priorities.#", "0"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.id", "10100"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.name", "IT Support"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.project.key", "ITS"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.resolved"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.acknowledged"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.triggered.id", "1"), + resource.TestCheckResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.status_mapping.triggered.name", "Open"), + resource.TestCheckNoResourceAttr("pagerduty_jira_cloud_account_mapping_rule.foo", "config.jira.sync_notes_user"), + ), + }, + }, + }) +} + +func testAccCheckPagerDutyJiraCloudAccountsMappingRuleDestroy(s *terraform.State) error { + for _, r := range s.RootModule().Resources { + if r.Type != "pagerduty_jira_cloud_account_mapping_rule" { + continue + } + + ctx := context.Background() + + accountMappingID, ruleID, err := util.ResourcePagerDutyParseColonCompoundID(r.Primary.ID) + if err != nil { + return err + } + + if _, err := testAccProvider.client.GetJiraCloudAccountsMappingRule(ctx, accountMappingID, ruleID); err == nil { + return fmt.Errorf("Rule still exists") + } + + } + return nil +} + +func testAccCheckPagerDutyJiraCloudAccountsMappingRuleExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := context.Background() + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No rule ID is set") + } + + accountMappingID, ruleID, err := util.ResourcePagerDutyParseColonCompoundID(rs.Primary.ID) + if err != nil { + return err + } + + found, err := testAccProvider.client.GetJiraCloudAccountsMappingRule(ctx, accountMappingID, ruleID) + if err != nil { + return err + } + + if found.ID != ruleID { + return fmt.Errorf("Rule not found: %v - %v", rs.Primary.ID, found) + } + + return nil + } +} + +func testAccCheckPagerDutyJiraCloudAccountsMappingRuleConfig(service, user, rule, accountMappingID string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +data "pagerduty_priority" "p1" { + name = "P1" +} + +data "pagerduty_priority" "p2" { + name = "P2" +} + +data "pagerduty_priority" "p3" { + name = "P3" +} + +resource "pagerduty_service" "foo" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_user" "foo" { + name = "%s" + email = "%[2]s@foo.test" +} + +resource "pagerduty_jira_cloud_account_mapping_rule" "foo" { + name = "%s" + account_mapping = "%s" + config { + service = pagerduty_service.foo.id + jira { + autocreate_jql = "priority = Highest" + create_issue_on_incident_trigger = true + custom_fields { + source_incident_field = "incident_description" + target_issue_field = "description" + target_issue_field_name = "Description" + type = "attribute" + } + custom_fields { + source_incident_field = "incident_number" + target_issue_field = "customfield_10001" + target_issue_field_name = "PagerDuty Incident Number" + type = "attribute" + } + custom_fields { + target_issue_field = "security" + target_issue_field_name = "Security Level" + type = "jira_value" + value = jsonencode({ + displayName = "Sec Level 1" + id = "10000" + }) + } + custom_fields { + target_issue_field = "labels" + target_issue_field_name = "Labels" + type = "const" + value = "pagerduty, incident" + } + issue_type { + id = "10001" + name = "Incident" + } + priorities { + jira_id = "1" + pagerduty_id = data.pagerduty_priority.p1.id + } + priorities { + jira_id = "2" + pagerduty_id = data.pagerduty_priority.p2.id + } + priorities { + jira_id = "3" + pagerduty_id = data.pagerduty_priority.p3.id + } + project { + id = "10100" + key = "ITS" + name = "IT Support" + } + status_mapping { + acknowledged { + id = "2" + name = "In Progress" + } + resolved { + id = "3" + name = "Resolved" + } + triggered { + id = "1" + name = "Open" + } + } + sync_notes_user = pagerduty_user.foo.id + } + } +}`, service, user, rule, accountMappingID) +} + +func testAccCheckPagerDutyJiraCloudAccountsMappingRuleConfigUpdated(service, rule, accountMappingID string) string { + return fmt.Sprintf(` +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +data "pagerduty_priority" "p1" { + name = "P1" +} + +data "pagerduty_priority" "p2" { + name = "P2" +} + +data "pagerduty_priority" "p3" { + name = "P3" +} + +resource "pagerduty_service" "foo" { + name = "%s" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_jira_cloud_account_mapping_rule" "foo" { + name = "%s" + account_mapping = "%s" + config { + service = pagerduty_service.foo.id + jira { + issue_type { + id = "10001" + name = "Incident" + } + project { + id = "10100" + key = "ITS" + name = "IT Support" + } + status_mapping { + triggered { + id = "1" + name = "Open" + } + } + } + } +}`, service, rule, accountMappingID) +} diff --git a/vendor/github.com/PagerDuty/go-pagerduty/jira_cloud_accounts_mapping.go b/vendor/github.com/PagerDuty/go-pagerduty/jira_cloud_accounts_mapping.go new file mode 100644 index 000000000..d34f6fa6d --- /dev/null +++ b/vendor/github.com/PagerDuty/go-pagerduty/jira_cloud_accounts_mapping.go @@ -0,0 +1,301 @@ +package pagerduty + +import ( + "context" + + "github.com/google/go-querystring/query" +) + +// JiraCloudAccountsMapping establishes a connection between a PagerDuty account +// and a Jira Cloud instance, enabling integration and synchronization between +// the two platforms +type JiraCloudAccountsMapping struct { + UpdatedAt string `json:"updated_at,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + ID string `json:"id,omitempty"` + JiraCloudAccount JiraCloudAccount `json:"jira_cloud_account"` + PagerDutyAccount PagerDutyAccount `json:"pagerduty_account"` +} + +// JiraCloudAccount describes an account from Jira +type JiraCloudAccount struct { + // The base URL of the Jira Cloud instance, used for API calls and + // constructing links + BaseURL string `json:"base_url"` +} + +// PagerDutyAccount describes an account from PagerDuty +type PagerDutyAccount struct { + // The unique subdomain of the PagerDuty account, used to identify and + // access the account (e.g., acme in https://acme.pagerduty.com) + Subdomain string `json:"subdomain"` +} + +// JiraCloudAccountsMappingRule configures the bidirectional synchronization +// between Jira issues and PagerDuty incidents +type JiraCloudAccountsMappingRule struct { + AccountsMapping *APIObject `json:"account_mapping,omitempty"` + AutocreateJqlDisabledReason string `json:"autocreate_jql_disabled_reason,omitempty"` + AutocreateJqlDisabledUntil string `json:"autocreate_jql_disabled_until,omitempty"` + Config JiraCloudAccountsMappingRuleConfig `json:"config"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// JiraCloudAccountsMappingRuleConfig is the configuration for bidirectional +// synchronization between Jira issues and PagerDuty incidents +type JiraCloudAccountsMappingRuleConfig struct { + Jira JiraCloudSettings `json:"jira"` + Service APIObject `json:"service"` +} + +// JiraCloudSettings are settings for the Jira aspect of the synchronization +type JiraCloudSettings struct { + AutocreateJQL *string `json:"autocreate_jql"` + CreateIssueOnIncidentTrigger bool `json:"create_issue_on_incident_trigger"` + CustomFields []JiraCloudCustomField `json:"custom_fields"` + IssueType JiraCloudReference `json:"issue_type"` + Priorities []JiraCloudPriority `json:"priorities"` + Project JiraCloudReference `json:"project"` + StatusMapping JiraCloudStatusMapping `json:"status_mapping"` + SyncNotesUser *UserJiraCloud `json:"sync_notes_user"` +} + +// JiraCloudCustomField defines how Jira fields are populated when a Jira Issue +// is created from a PagerDuty Incident +type JiraCloudCustomField struct { + // The PagerDuty incident field from which the value will be extracted + // (only applicable if type is attribute) + // + // Allowed values: + // incident_number, incident_title, incident_description, + // incident_status, incident_created_at, incident_service, + // incident_escalation_policy, incident_impacted_services, + // incident_html_url, incident_assignees, incident_acknowledgers, + // incident_last_status_change_at, incident_last_status_change_by, + // incident_urgency, incident_priority, null + SourceIncidentField *string `json:"source_incident_field"` + + // The unique identifier key of the Jira field that will be set + TargetIssueField string `json:"target_issue_field"` + + // The human-readable name of the Jira field + TargetIssueFieldName string `json:"target_issue_field_name"` + + // The type of the value that will be set + // + // Allowed values: + // attribute, const, jira_value + Type string `json:"type"` + + // The value to be set for the Jira field (only applicable if type is + // const or jira_value) + Value interface{} `json:"value,omitempty"` +} + +// JiraCloudReference is a reference pointing to a Jira Cloud object +type JiraCloudReference struct { + ID string `json:"id"` + Name string `json:"name"` + Key string `json:"key,omitempty"` +} + +// JiraCloudPriority is an association between a PagerDuty incident priority +// and a Jira issue priority +type JiraCloudPriority struct { + JiraID string `json:"jira_id"` + PagerDutyID string `json:"pagerduty_id"` +} + +// JiraCloudStatusMapping is an association between PagerDuty incident statuses +// to their corresponding Jira issue statuses +type JiraCloudStatusMapping struct { + Acknowledged *JiraCloudReference `json:"acknowledged"` + Resolved *JiraCloudReference `json:"resolved"` + Triggered *JiraCloudReference `json:"triggered"` +} + +// UserJiraCloud is a PagerDuty user for syncing notes and comments between Jira +// issues and PagerDuty incidents. If not provided, note synchronization is +// disabled +type UserJiraCloud struct { + APIObject + Email string `json:"email,omitempty"` +} + +// ListJiraCloudAccountsMappingsOptions are the options available when calling the ListJiraCloudAccountsMappings API endpoint +type ListJiraCloudAccountsMappingsOptions struct { + Limit uint `url:"limit,omitempty"` + Offset uint `url:"offset,omitempty"` + Total bool `url:"total,omitempty"` +} + +// ListJiraCloudAccountsMappingsResponse is the response when calling the ListJiraCloudAccountsMappings API endpoint +type ListJiraCloudAccountsMappingsResponse struct { + APIListObject + AccountsMappings []JiraCloudAccountsMapping `json:"accounts_mappings"` +} + +// ListJiraCloudAccountsMappings lists existing account mappings +func (c *Client) ListJiraCloudAccountsMappings(ctx context.Context, o ListJiraCloudAccountsMappingsOptions) (*ListJiraCloudAccountsMappingsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/integration-jira-cloud/accounts_mappings?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListJiraCloudAccountsMappingsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// GetJiraCloudAccountsMapping lists existing account mappings +func (c *Client) GetJiraCloudAccountsMapping(ctx context.Context, id string) (*JiraCloudAccountsMapping, error) { + resp, err := c.get(ctx, "/integration-jira-cloud/accounts_mappings/"+id, nil) + if err != nil { + return nil, err + } + + var result JiraCloudAccountsMapping + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// ListJiraCloudAccountsMappingRulesOptions are the options available when +// calling the ListJiraCloudAccountsMappingRules API endpoint +type ListJiraCloudAccountsMappingRulesOptions struct { + Limit uint `url:"limit,omitempty"` + Offset uint `url:"offset,omitempty"` + Total bool `url:"total,omitempty"` +} + +// ListJiraCloudAccountsMappingRulesResponse is the response when calling the +// ListJiraCloudAccountsMappingRules API endpoint +type ListJiraCloudAccountsMappingRulesResponse struct { + APIListObject + Rules []JiraCloudAccountsMappingRule `json:"rules"` +} + +// ListJiraCloudAccountsMappingRules lists existing rules for a specific account +// mapping +func (c *Client) ListJiraCloudAccountsMappingRules(ctx context.Context, id string, o ListJiraCloudAccountsMappingRulesOptions) (*ListJiraCloudAccountsMappingsResponse, error) { + v, err := query.Values(o) + if err != nil { + return nil, err + } + + resp, err := c.get(ctx, "/integration-jira-cloud/accounts_mappings/"+id+"/rules?"+v.Encode(), nil) + if err != nil { + return nil, err + } + + var result ListJiraCloudAccountsMappingsResponse + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// CreateJiraCloudAccountsMappingRule creates a new rule in Jira Cloud's integration +func (c *Client) CreateJiraCloudAccountsMappingRule(ctx context.Context, id string, rule JiraCloudAccountsMappingRule) (*JiraCloudAccountsMappingRule, error) { + resp, err := c.post(ctx, "/integration-jira-cloud/accounts_mappings/"+id+"/rules", rule, nil) + if err != nil { + return nil, err + } + + var result JiraCloudAccountsMappingRule + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// GetJiraCloudAccountsMappingRule gets detailed information about an existing rule +func (c *Client) GetJiraCloudAccountsMappingRule(ctx context.Context, id, ruleID string) (*JiraCloudAccountsMappingRule, error) { + resp, err := c.get(ctx, "/integration-jira-cloud/accounts_mappings/"+id+"/rules/"+ruleID, nil) + if err != nil { + return nil, err + } + + var result JiraCloudAccountsMappingRule + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} + +// DeleteJiraCloudAccountsMappingRule deletes an existing rule in Jira Cloud's integration +func (c *Client) DeleteJiraCloudAccountsMappingRule(ctx context.Context, id, ruleID string) error { + _, err := c.delete(ctx, "/integration-jira-cloud/accounts_mappings/"+id+"/rules/"+ruleID) + return err +} + +// updateJiraCloudAccountsMappingRuleBody is the body for the call to the +// UpdateJiraCloudAccountsMappingRule API endpoint +type updateJiraCloudAccountsMappingRuleBody struct { + Config updateJiraCloudAccountsMappingRuleConfig `json:"config"` + Name string `json:"name"` +} + +// updateJiraCloudAccountsMappingRuleOptionsConfig is an special representation +// of a configuration used for updating a rule. +type updateJiraCloudAccountsMappingRuleConfig struct { + Jira updateJiraCloudSettings `json:"jira"` +} + +// updateJiraCloudSettings are settings to update the Jira aspect of the +// synchronization +type updateJiraCloudSettings struct { + AutocreateJQL *string `json:"autocreate_jql"` + CreateIssueOnIncidentTrigger bool `json:"create_issue_on_incident_trigger"` + CustomFields []JiraCloudCustomField `json:"custom_fields"` + IssueType JiraCloudReference `json:"issue_type"` + Priorities []JiraCloudPriority `json:"priorities"` + StatusMapping JiraCloudStatusMapping `json:"status_mapping"` + SyncNotesUser *UserJiraCloud `json:"sync_notes_user"` +} + +// UpdateJiraCloudAccountsMappingRule updates an existing rule in Jira Cloud's integration +func (c *Client) UpdateJiraCloudAccountsMappingRule(ctx context.Context, accountMappingID string, rule JiraCloudAccountsMappingRule) (*JiraCloudAccountsMappingRule, error) { + o := updateJiraCloudAccountsMappingRuleBody{ + Config: updateJiraCloudAccountsMappingRuleConfig{ + Jira: updateJiraCloudSettings{ + AutocreateJQL: rule.Config.Jira.AutocreateJQL, + CreateIssueOnIncidentTrigger: rule.Config.Jira.CreateIssueOnIncidentTrigger, + CustomFields: rule.Config.Jira.CustomFields, + IssueType: rule.Config.Jira.IssueType, + Priorities: rule.Config.Jira.Priorities, + StatusMapping: rule.Config.Jira.StatusMapping, + SyncNotesUser: rule.Config.Jira.SyncNotesUser, + }, + }, + Name: rule.Name, + } + + resp, err := c.put(ctx, "/integration-jira-cloud/accounts_mappings/"+accountMappingID+"/rules/"+rule.ID, o, nil) + if err != nil { + return nil, err + } + + var result JiraCloudAccountsMappingRule + if err = c.decodeJSON(resp, &result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/doc.go new file mode 100644 index 000000000..fe6b0f76d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package booldefault provides default values for types.Bool attributes. +package booldefault diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/static_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/static_value.go new file mode 100644 index 000000000..797f81d09 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault/static_value.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package booldefault + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// StaticBool returns a static boolean value default handler. +// +// Use StaticBool if a static default value for a boolean should be set. +func StaticBool(defaultVal bool) defaults.Bool { + return staticBoolDefault{ + defaultVal: defaultVal, + } +} + +// staticBoolDefault is static value default handler that +// sets a value on a boolean attribute. +type staticBoolDefault struct { + defaultVal bool +} + +// Description returns a human-readable description of the default value handler. +func (d staticBoolDefault) Description(_ context.Context) string { + return fmt.Sprintf("value defaults to %t", d.defaultVal) +} + +// MarkdownDescription returns a markdown description of the default value handler. +func (d staticBoolDefault) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value defaults to `%t`", d.defaultVal) +} + +// DefaultBool implements the static default value logic. +func (d staticBoolDefault) DefaultBool(_ context.Context, req defaults.BoolRequest, resp *defaults.BoolResponse) { + resp.PlanValue = types.BoolValue(d.defaultVal) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 81aff8d93..bc372a9cb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/PagerDuty/go-pagerduty v1.8.1-0.20241002154647-8ceedfd04d88 +# github.com/PagerDuty/go-pagerduty v1.8.1-0.20241104145658-2a0050d437ac ## explicit; go 1.19 github.com/PagerDuty/go-pagerduty # github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c @@ -164,6 +164,7 @@ github.com/hashicorp/terraform-plugin-framework/provider/schema github.com/hashicorp/terraform-plugin-framework/providerserver github.com/hashicorp/terraform-plugin-framework/resource github.com/hashicorp/terraform-plugin-framework/resource/schema +github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier diff --git a/website/docs/d/jira_cloud_account_mapping.html.markdown b/website/docs/d/jira_cloud_account_mapping.html.markdown new file mode 100644 index 000000000..d7faaaa43 --- /dev/null +++ b/website/docs/d/jira_cloud_account_mapping.html.markdown @@ -0,0 +1,32 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_jira_cloud_account_mapping" +sidebar_current: "docs-pagerduty-datasource-jira-cloud-account-mapping" +description: |- + An Account Mapping establishes a connection between a PagerDuty account and a Jira Cloud instance, enabling integration and synchronization between the two platforms. +--- + +# pagerduty\_jira\_cloud\_account\_mapping + +Use this data source to get information about a specific [account mapping][1]. + +## Example Usage + +```hcl +data "pagerduty_jira_cloud_account_mapping" "circular" { + name = "pdt-circular" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `subdomain` - (Required) The service name to use to find a service in the PagerDuty API. + +## Attributes Reference + +* `id` - The ID of the found account mapping. +* `base_url` - The base URL of the Jira Cloud instance, used for API calls and constructing links. + +[1]: https://developer.pagerduty.com/api-reference/8d707b61562b7-get-an-account-mapping diff --git a/website/docs/r/jira_cloud_account_mapping_rule.html.markdown b/website/docs/r/jira_cloud_account_mapping_rule.html.markdown new file mode 100644 index 000000000..72e3f661f --- /dev/null +++ b/website/docs/r/jira_cloud_account_mapping_rule.html.markdown @@ -0,0 +1,182 @@ +--- +layout: "pagerduty" +page_title: "PagerDuty: pagerduty_jira_cloud_account_mapping_rule" +sidebar_current: "docs-pagerduty-resource-jira-cloud-account-mapping-rule" +description: |- + Creates and manages a Jira Cloud account mapping Rule to integrate with PagerDuty. +--- + +# pagerduty\_jira\_cloud\_account\_mapping\_rule + +An Jira Cloud's account mapping [rule](https://developer.pagerduty.com/api-reference/85dc30ba966a6-create-a-rule) +configures the bidirectional synchronization between Jira issues and PagerDuty +incidents. + +## Example Usage + +```hcl +data "pagerduty_escalation_policy" "default" { + name = "Default" +} + +data "pagerduty_priority" "p1" { + name = "P1" +} + +data "pagerduty_priority" "p2" { + name = "P2" +} + +data "pagerduty_priority" "p3" { + name = "P3" +} + +resource "pagerduty_service" "foo" { + name = "My Web App" + escalation_policy = data.pagerduty_escalation_policy.default.id +} + +resource "pagerduty_user" "foo" { + name = "Earline Greenholt" + email = "125.greenholt.earline@graham.name" +} + +resource "pagerduty_jira_cloud_account_mapping_rule" "foo" { + name = "Integration with My Web App" + account_mapping = "PLBP09X" + config { + service = pagerduty_service.foo.id + jira { + autocreate_jql = "priority = Highest" + create_issue_on_incident_trigger = true + custom_fields { + source_incident_field = "incident_description" + target_issue_field = "description" + target_issue_field_name = "Description" + type = "attribute" + } + custom_fields { + target_issue_field = "security" + target_issue_field_name = "Security Level" + type = "jira_value" + value = jsonencode({ + displayName = "Sec Level 1" + id = "10000" + }) + } + issue_type { + id = "10001" + name = "Incident" + } + priorities { + jira_id = "1" + pagerduty_id = data.pagerduty_priority.p1.id + } + priorities { + jira_id = "2" + pagerduty_id = data.pagerduty_priority.p2.id + } + priorities { + jira_id = "3" + pagerduty_id = data.pagerduty_priority.p3.id + } + project { + id = "10100" + key = "ITS" + name = "IT Support" + } + status_mapping { + acknowledged { + id = "2" + name = "In Progress" + } + resolved { + id = "3" + name = "Resolved" + } + triggered { + id = "1" + name = "Open" + } + } + sync_notes_user = pagerduty_user.foo.id + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the rule. +* `account_mapping` - (Required) [Updating can cause a resource replacement] The account mapping this rule belongs to. +* `config` - (Required) Configuration for bidirectional synchronization between Jira issues and PagerDuty incidents. + +The `config` block contains the following arguments: + +* `service` - (Required) [Updating can cause a resource replacement] The ID of the linked PagerDuty service. +* `jira` - (Required) Synchronization settings. + +The `jira` block contains the following arguments: + +* `autocreate_jql` - JQL query to automatically create PagerDuty incidents when matching Jira issues are created. Leave empty to disable this feature. +* `create_issue_on_incident_trigger` - When enabled, automatically creates a Jira issue whenever a PagerDuty incident is triggered. +* `custom_fields` - Defines how Jira fields are populated when a Jira Issue is created from a PagerDuty Incident. +* `issue_type` - (Required) Specifies the Jira issue type to be created or synchronized with PagerDuty incidents. +* `priorities` - Maps PagerDuty incident priorities to Jira issue priorities for synchronization. +* `project` - (Required) [Updating can cause a resource replacement] Defines the Jira project where issues will be created or synchronized. +* `status_mapping` - (Required) Maps PagerDuty incident statuses to corresponding Jira issue statuses for synchronization. +* `sync_notes_user` - ID of the PagerDuty user for syncing notes and comments between Jira issues and PagerDuty incidents. If not provided, note synchronization is disabled. + +A `custom_fields` block contains the following arguments: + +* `type` - (Required) The type of the value that will be set; one of `attribute`, `const` or `jira_value`. +* `source_incident_field` - The PagerDuty incident field from which the value will be extracted (only applicable if `type` is `attribute`); one of `incident_number`, `incident_title`, `incident_description`, `incident_status`, `incident_created_at`, `incident_service`, `incident_escalation_policy`, `incident_impacted_services`, `incident_html_url`, `incident_assignees`, `incident_acknowledgers`, `incident_last_status_change_at`, `incident_last_status_change_by`, `incident_urgency` or `incident_priority`. +* `target_issue_field` - (Required) The unique identifier key of the Jira field that will be set. +* `target_issue_field_name` - (Required) The human-readable name of the Jira field. +* `value` - The value to be set for the Jira field (only applicable if `type` is `const` or `jira_value`). It must be set as a JSON string. + +The `issue_type` block contains the following arguments: + +* `id` - (Required) Unique identifier for the Jira issue type. +* `name` - (Required) The name of the Jira issue type. + +A `priorities` block contains the following arguments: + +* `jira_id` - (Required) The ID of the Jira priority. +* `pagerduty_id` - (Required) The ID of the PagerDuty priority. + +The `project` block contains the following arguments: + +* `id` - (Required) Unique identifier for the Jira project. +* `key` - (Required) The short key name of the Jira project. +* `name` - (Required) The name of the Jira project. + +The `status_mapping` block contains the following arguments: + +* `acknowledged` - Jira status that maps to the PagerDuty `acknowledged` status. +* `resolved` - Jira status that maps to the PagerDuty `resolved` status. +* `triggered` - (Required) Jira status that maps to the PagerDuty `triggered` status. + +The `acknowledged`, `resolved` and `triggered` blocks contains the following arguments: + +* `id` - Unique identifier for the Jira status. +* `name` - Name of the Jira status. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the service. +* `autocreate_jql_disabled_reason` - If auto-creation using JQL is disabled, this field provides the reason for the disablement. +* `autocreate_jql_disabled_until` - The timestamp until which the auto-creation using JQL feature is disabled. + +## Import + +Jira Cloud account mapping rules can be imported using the `account_mapping_id` and `rule_id`, e.g. + +``` +$ terraform import pagerduty_jira_cloud_account_mapping_rule.main PLBP09X:PLB09Z +```