Skip to content

Commit

Permalink
Merge pull request #657 from johncoleman83/master
Browse files Browse the repository at this point in the history
Adds license resource for user management
  • Loading branch information
imjaroiswebdev authored Apr 13, 2023
2 parents 310df82 + 3a0436d commit f815600
Show file tree
Hide file tree
Showing 11 changed files with 777 additions and 5 deletions.
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,27 @@ Make changes to the PagerDuty provider and post a pull request for review.
```
4. See `api_url_override` from Terraform docs for [PagerDuty Provider](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs#argument-reference) to set a custom proxy endpoint as PagerDuty client api url overriding service_region setup.

## Test a specific version of the go-pagerduty API client

Modify the `go.mod` file using a [Go module replacement](https://go.dev/doc/modules/managing-dependencies#external_fork) for `github.com/heimweh/go-pagerduty:
```
$ go mod edit -replace github.com/heimweh/go-pagerduty=/PATH/TO/LOCAL/github.com/<USERNAME>/<REPO>
```

Or update the file directly:
```
replace github.com/heimweh/go-pagerduty => /PATH/TO/LOCAL/go-pagerduty
```

Update vendored dependencies or configure compiler to prefer using downloaded modules based on `go.mod` file:
```
$ export GOFLAGS="-mod=mod"
```
Or:
```
$ go mod vendor
```

### Setup Local Logs

1. See [Debugging Terraform](https://www.terraform.io/internals/debugging). Either add this to your shell's profile
Expand Down Expand Up @@ -160,10 +181,12 @@ For example:
PAGERDUTY_ACC_INCIDENT_WORKFLOWS=1 make testacc TESTARGS="-run PagerDutyIncidentWorkflow"
PAGERDUTY_ACC_SERVICE_INTEGRATION_GENERIC_EMAIL_NO_FILTERS="user@<your_domain>.pagerduty.com" make testacc TESTARGS="-run PagerDutyServiceIntegration_GenericEmailNoFilters"
PAGERDUTY_ACC_CUSTOM_FIELDS=1 make testacc TESTARGS="-run PagerDutyCustomField"
PAGERDUTY_ACC_LICENSE_NAME="Full User" make testacc TESTARGS="-run DataSourcePagerDutyLicense_Basic"
```

| Variable Name | Feature Set |
| ----------------------------------------------------------- | ------------------- |
| `PAGERDUTY_ACC_INCIDENT_WORKFLOWS` | Incident Workflows |
| Variable Name | Feature Set |
| ------------------------------------------------------------ | ------------------- |
| `PAGERDUTY_ACC_INCIDENT_WORKFLOWS` | Incident Workflows |
| `PAGERDUTY_ACC_SERVICE_INTEGRATION_GENERIC_EMAIL_NO_FILTERS` | Service Integration |
| `PAGERDUTY_ACC_CUSTOM_FIELDS` | Custom Fields |
| `PAGERDUTY_ACC_LICENSE_NAME` | Licenses |
170 changes: 170 additions & 0 deletions pagerduty/data_source_pagerduty_license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package pagerduty

import (
"fmt"
"log"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/heimweh/go-pagerduty/pagerduty"
)

var licenseSchema = map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"summary": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"role_group": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"current_value": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"allocations_available": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"valid_roles": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"self": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"html_url": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
}

func dataSourcePagerDutyLicense() *schema.Resource {
return &schema.Resource{
Read: dataSourcePagerDutyLicenseRead,
Schema: licenseSchema,
}
}

func dataSourcePagerDutyLicenseRead(d *schema.ResourceData, meta interface{}) error {
client, err := meta.(*Config).Client()
if err != nil {
return err
}

log.Printf("[INFO] Fetching PagerDuty Licenses")

return resource.Retry(5*time.Minute, func() *resource.RetryError {
licenses, _, err := client.Licenses.List()
if err != nil {
// Delaying retry by 30s as recommended by PagerDuty
// https://developer.pagerduty.com/docs/rest-api-v2/rate-limiting/#what-are-possible-workarounds-to-the-events-api-rate-limit
time.Sleep(30 * time.Second)
return resource.RetryableError(err)
}

id, name, description := d.Get("id").(string), d.Get("name").(string), d.Get("description").(string)
found := findBestMatchLicense(licenses, id, name, description)

if found == nil {
ids := licensesToStringOfIds(licenses)
return resource.NonRetryableError(
fmt.Errorf("Unable to locate any license with ids in [%s] with the configured id: '%s', name: '%s' or description: '%s'", ids, id, name, description))
}

d.SetId(found.ID)
d.Set("name", found.Name)
d.Set("description", found.Description)
d.Set("type", found.Type)
d.Set("summary", found.Summary)
d.Set("role_group", found.RoleGroup)
d.Set("allocations_available", found.AllocationsAvailable)
d.Set("current_value", found.CurrentValue)
d.Set("valid_roles", found.ValidRoles)
d.Set("self", found.Self)
d.Set("html_url", found.HTMLURL)

return nil
})
}

func licensesToStringOfIds(licenses []*pagerduty.License) string {
ids := make([]string, len(licenses))
for i, v := range licenses {
ids[i] = v.ID
}
return strings.Join(ids, ", ")
}

func findBestMatchLicense(licenses []*pagerduty.License, id, name, description string) *pagerduty.License {
var found *pagerduty.License
for _, license := range licenses {
if licenseIsExactMatch(license, id, name, description) {
found = license
break
}
}

// If there is no exact match for a license, check for substring matches
// This allows customers to use a term such as "Full User", which is included
// in the names of all licenses that support creating full users. However,
// if id is set then it must match with licenseIsExactMatch
if id == "" && found == nil {
for _, license := range licenses {
if licenseContainsMatch(license, name, description) {
found = license
break
}
}
}

return found
}

func licenseIsExactMatch(license *pagerduty.License, id, name, description string) bool {
if id != "" {
return license.ID == id && matchesOrIsUnset(license.Name, name) && matchesOrIsUnset(license.Description, description)
}
return license.Name == name && license.Description == description
}

func matchesOrIsUnset(licenseAttr, config string) bool {
return config == "" || config == licenseAttr
}

func licenseContainsMatch(license *pagerduty.License, name, description string) bool {
return strings.Contains(license.Name, name) && strings.Contains(license.Description, description)
}
154 changes: 154 additions & 0 deletions pagerduty/data_source_pagerduty_license_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package pagerduty

import (
"fmt"
"os"
"regexp"
"testing"

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

func TestAccDataSourcePagerDutyLicense_Basic(t *testing.T) {
reference := "full_user"
description := ""
name := os.Getenv("PAGERDUTY_ACC_LICENSE_NAME")

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
testAccPreCheckLicenseNameTests(t, name)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourcePagerDutyLicenseConfig(reference, name, description),
Check: resource.ComposeTestCheckFunc(
testAccDataSourcePagerDutyLicense(fmt.Sprintf("data.pagerduty_license.%s", reference)),
),
},
},
})
}

func TestAccDataSourcePagerDutyLicense_Empty(t *testing.T) {
// Note that this test does not actually set any values for the name or
// description of the license. An accounts license data changes over time and
// per account. So, this test only verifies that a license can be found with
// an empty pagerduty_license datasource
reference := "full_user"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceEmptyPagerDutyLicenseConfig(reference),
Check: resource.ComposeTestCheckFunc(
testAccDataSourcePagerDutyLicense(fmt.Sprintf("data.pagerduty_license.%s", reference)),
),
},
},
})
}

func TestAccDataSourcePagerDutyLicense_Error(t *testing.T) {
reference := "testing_reference_missing_license"
expectedErrorString := "Unable to locate any license"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourcePagerDutyLicenseConfigError(reference),
ExpectError: regexp.MustCompile(expectedErrorString),
},
},
})
}

func TestAccDataSourcePagerDutyLicense_ErrorWithID(t *testing.T) {
reference := "testing_reference_missing_license"
expectedErrorString := "Unable to locate any license"
// Even with an expected name, if the configured ID is not found there
// should be an error
name := "Full User"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourcePagerDutyLicenseConfigErrorWithID(reference, name),
ExpectError: regexp.MustCompile(expectedErrorString),
},
},
})
}

func testAccPreCheckLicenseNameTests(t *testing.T, name string) {
if name == "" {
t.Skip("PAGERDUTY_ACC_LICENSE_NAME not set. Skipping tests requiring license names")
}
}

func testAccDataSourcePagerDutyLicense(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
r := s.RootModule().Resources[n]
a := r.Primary.Attributes

testAtts := []string{
"id",
"name",
"summary",
"description",
"role_group",
"current_value",
"allocations_available",
"html_url",
"self",
}

for _, att := range testAtts {
if _, ok := a[att]; !ok {
return fmt.Errorf("Expected the required attribute %s to exist", att)
}
}

return nil
}
}

func testAccDataSourcePagerDutyLicenseConfig(reference string, name string, description string) string {
return fmt.Sprintf(`
data "pagerduty_license" "%s" {
name = "%s"
description = "%s"
}
`, reference, name, description)
}

func testAccDataSourceEmptyPagerDutyLicenseConfig(reference string) string {
return fmt.Sprintf(`
data "pagerduty_license" "%s" {}
`, reference)
}

func testAccDataSourcePagerDutyLicenseConfigError(reference string) string {
return fmt.Sprintf(`
data "pagerduty_license" "%s" {
name = "%s"
}
`, reference, reference)
}

func testAccDataSourcePagerDutyLicenseConfigErrorWithID(reference, name string) string {
return fmt.Sprintf(`
data "pagerduty_license" "%s" {
id = "%s"
name = "%s"
}
`, reference, reference, name)
}
Loading

0 comments on commit f815600

Please sign in to comment.