diff --git a/azuread/resource_service_principal_password.go b/azuread/resource_service_principal_password.go index 54d9ea6c2..6b4d62bba 100644 --- a/azuread/resource_service_principal_password.go +++ b/azuread/resource_service_principal_password.go @@ -59,10 +59,20 @@ func resourceServicePrincipalPassword() *schema.Resource { }, "end_date": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.ValidateRFC3339TimeString, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"end_date_relative"}, + ValidateFunc: validation.ValidateRFC3339TimeString, + }, + + "end_date_relative": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"end_date"}, + ValidateFunc: validate.NoEmptyStrings, }, }, } @@ -75,7 +85,6 @@ func resourceServicePrincipalPasswordCreate(d *schema.ResourceData, meta interfa objectId := d.Get("service_principal_id").(string) value := d.Get("value").(string) // errors will be handled by the validation - endDate, _ := time.Parse(time.RFC3339, d.Get("end_date").(string)) var keyId string if v, ok := d.GetOk("key_id"); ok { @@ -89,6 +98,19 @@ func resourceServicePrincipalPasswordCreate(d *schema.ResourceData, meta interfa keyId = kid } + var endDate time.Time + if v := d.Get("end_date").(string); v != "" { + endDate, _ = time.Parse(time.RFC3339, v) + } else if v := d.Get("end_date_relative").(string); v != "" { + d, err := time.ParseDuration(v) + if err != nil { + return fmt.Errorf("unable to parse `end_date_relative` (%s) as a duration", v) + } + endDate = time.Now().Add(d) + } else { + return fmt.Errorf("one of `end_date` or `end_date_relative` must be specified") + } + credential := graphrbac.PasswordCredential{ KeyID: p.String(keyId), Value: p.String(value), diff --git a/azuread/resource_service_principal_password_test.go b/azuread/resource_service_principal_password_test.go index 3767ae367..e6131a4a2 100644 --- a/azuread/resource_service_principal_password_test.go +++ b/azuread/resource_service_principal_password_test.go @@ -111,7 +111,37 @@ func TestAccAzureADServicePrincipalPassword_customKeyId(t *testing.T) { }) } -func testCheckADServicePrincipalPasswordExists(name string) resource.TestCheckFunc { +func TestAccAzureADServicePrincipalPassword_relativeEndDate(t *testing.T) { + resourceName := "azuread_service_principal_password.test" + applicationId, err := uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + value, err := uuid.GenerateUUID() + if err != nil { + t.Fatal(err) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckADServicePrincipalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccADServicePrincipalPassword_relativeEndDate(applicationId, value), + Check: resource.ComposeTestCheckFunc( + // can't assert on Value since it's not returned + testCheckADServicePrincipalPasswordExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "start_date"), + resource.TestCheckResourceAttrSet(resourceName, "key_id"), + resource.TestCheckResourceAttrSet(resourceName, "end_date"), + ), + }, + }, + }) +} + +func testCheckADServicePrincipalPasswordExists(name string) resource.TestCheckFunc { //nolint unparam return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -152,7 +182,7 @@ func testCheckADServicePrincipalPasswordExists(name string) resource.TestCheckFu } } -func testAccADServicePrincipalPassword_basic(applicationId, value string) string { +func testAccADServicePrincipalPassword_template(applicationId string) string { return fmt.Sprintf(` resource "azuread_application" "test" { name = "acctestspa%s" @@ -161,13 +191,19 @@ resource "azuread_application" "test" { resource "azuread_service_principal" "test" { application_id = "${azuread_application.test.application_id}" } +`, applicationId) +} + +func testAccADServicePrincipalPassword_basic(applicationId, value string) string { + return fmt.Sprintf(` +%s resource "azuread_service_principal_password" "test" { service_principal_id = "${azuread_service_principal.test.id}" value = "%s" end_date = "2020-01-01T01:02:03Z" } -`, applicationId, value) +`, testAccADServicePrincipalPassword_template(applicationId), value) } func testAccADServicePrincipalPassword_requiresImport(applicationId, value string) string { @@ -186,13 +222,7 @@ resource "azuread_service_principal_password" "import" { func testAccADServicePrincipalPassword_customKeyId(applicationId, keyId, value string) string { return fmt.Sprintf(` -resource "azuread_application" "test" { - name = "acctestspa%s" -} - -resource "azuread_service_principal" "test" { - application_id = "${azuread_application.test.application_id}" -} +%s resource "azuread_service_principal_password" "test" { service_principal_id = "${azuread_service_principal.test.id}" @@ -200,5 +230,17 @@ resource "azuread_service_principal_password" "test" { value = "%s" end_date = "2020-01-01T01:02:03Z" } -`, applicationId, keyId, value) +`, testAccADServicePrincipalPassword_template(applicationId), keyId, value) +} + +func testAccADServicePrincipalPassword_relativeEndDate(applicationId, value string) string { + return fmt.Sprintf(` +%s + +resource "azuread_service_principal_password" "test" { + service_principal_id = "${azuread_service_principal.test.id}" + value = "%s" + end_date_relative = "8760h" +} +`, testAccADServicePrincipalPassword_template(applicationId), value) } diff --git a/website/docs/r/service_principal_password.html.markdown b/website/docs/r/service_principal_password.html.markdown index 8ad3a5a70..103386d95 100644 --- a/website/docs/r/service_principal_password.html.markdown +++ b/website/docs/r/service_principal_password.html.markdown @@ -44,7 +44,11 @@ The following arguments are supported: * `value` - (Required) The Password for this Service Principal. -* `end_date` - (Required) The End Date which the Password is valid until, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). Changing this field forces a new resource to be created. +* `end_date` - (Optional) The End Date which the Password is valid until, formatted as a RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). Changing this field forces a new resource to be created. + +* `end_date_relative` - (Optional) A relative duration for which the Password is valid until, for example `240h` (10 days) or `2400h30m`. Changing this field forces a new resource to be created. + +-> **NOTE:** One of `end_date` or `end_date_relative` must be set. * `key_id` - (Optional) A GUID used to uniquely identify this Key. If not specified a GUID will be created. Changing this field forces a new resource to be created.