Skip to content

Commit 0fafde5

Browse files
authored
Merge pull request #1976 from hashicorp/WarningWithExpiredAtNull
Add warning when expired_at attribute is null
2 parents 40feb54 + c591836 commit 0fafde5

11 files changed

+115
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
## Unreleased
2+
FEATURES:
3+
* Adds a warning message to `r/tfe_team_token`, `r/tfe_organization_token` and `r/tfe_audit_trail_token` resources to indicate that if no `expired_at` attribute is set, the token will expire in 2 years. By @sana-faraz [#1976](https://github.com/hashicorp/terraform-provider-tfe/pull/1976)
24

35
FEATURES:
46
* Adds `tfe_query_run` action, allowing users to invoke remote Terraform queries on HCP Terraform and Terraform Enterprise, by @sebasslash [#1982](https://github.com/hashicorp/terraform-provider-tfe/pull/1982)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright IBM Corp. 2018, 2025
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package planmodifiers
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
10+
)
11+
12+
type warnIfNullOnCreateModifier struct {
13+
message string
14+
}
15+
16+
func (m warnIfNullOnCreateModifier) Description(ctx context.Context) string {
17+
return "Warning that the attribute value is null during resource creation"
18+
}
19+
20+
func (m warnIfNullOnCreateModifier) MarkdownDescription(ctx context.Context) string {
21+
return m.Description(ctx)
22+
}
23+
24+
func (m warnIfNullOnCreateModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
25+
if req.State.Raw.IsNull() && req.ConfigValue.IsNull() {
26+
resp.Diagnostics.AddWarning(
27+
m.message,
28+
"",
29+
)
30+
}
31+
}
32+
33+
func WarnIfNullOnCreate(message string) planmodifier.String {
34+
return warnIfNullOnCreateModifier{
35+
message: message,
36+
}
37+
}

internal/provider/resource_tfe_audit_trail_token.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2222
"github.com/hashicorp/terraform-plugin-framework/types"
2323
"github.com/hashicorp/terraform-plugin-log/tflog"
24+
"github.com/hashicorp/terraform-provider-tfe/internal/provider/planmodifiers"
2425
)
2526

2627
type resourceAuditTrailToken struct {
@@ -99,8 +100,13 @@ func (r *resourceAuditTrailToken) Schema(ctx context.Context, req resource.Schem
99100
Description: "The time when the audit trail token will expire. This must be a valid ISO8601 timestamp.",
100101
CustomType: timetypes.RFC3339Type{},
101102
Optional: true,
103+
Computed: true,
102104
PlanModifiers: []planmodifier.String{
103105
stringplanmodifier.RequiresReplace(),
106+
stringplanmodifier.UseStateForUnknown(),
107+
planmodifiers.WarnIfNullOnCreate(
108+
"Audit Trail Token expiration null values defaults to 24 months",
109+
),
104110
},
105111
},
106112
"organization": schema.StringAttribute{

internal/provider/resource_tfe_audit_trail_token_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,37 @@ func TestAccTFEAuditTrailToken_withInvalidExpiry(t *testing.T) {
163163
})
164164
}
165165

166+
func TestAccTFEAuditTrailToken_withBlankExpiry(t *testing.T) {
167+
skipUnlessBeta(t)
168+
token := &tfe.OrganizationToken{}
169+
170+
tfeClient, err := getClientUsingEnv()
171+
if err != nil {
172+
t.Fatal(err)
173+
}
174+
org, orgCleanup := createBusinessOrganization(t, tfeClient)
175+
t.Cleanup(orgCleanup)
176+
177+
resource.Test(t, resource.TestCase{
178+
PreCheck: func() { testAccPreCheck(t) },
179+
ProtoV6ProviderFactories: testAccMuxedProviders,
180+
CheckDestroy: testAccCheckTFEAuditTrailTokenDestroy,
181+
Steps: []resource.TestStep{
182+
{
183+
Config: testAccTFEAuditTrailToken_withBlankExpiry(org.Name),
184+
Check: resource.ComposeTestCheckFunc(
185+
testAccCheckTFEAuditTrailTokenExists(
186+
"tfe_audit_trail_token.foobar", token),
187+
resource.TestCheckResourceAttr(
188+
"tfe_audit_trail_token.foobar", "organization", org.Name),
189+
resource.TestCheckResourceAttrSet(
190+
"tfe_audit_trail_token.foobar", "expired_at"),
191+
),
192+
},
193+
},
194+
})
195+
}
196+
166197
func TestAccTFEAuditTrailToken_import(t *testing.T) {
167198
tfeClient, err := getClientUsingEnv()
168199
if err != nil {
@@ -281,7 +312,6 @@ func testAccTFEAuditTrailToken_withBlankExpiry(orgName string) string {
281312
return fmt.Sprintf(`
282313
resource "tfe_audit_trail_token" "foobar" {
283314
organization = "%s"
284-
expired_at = ""
285315
}`, orgName)
286316
}
287317

internal/provider/resource_tfe_organization_token.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func resourceTFEOrganizationToken() *schema.Resource {
5353
"expired_at": {
5454
Type: schema.TypeString,
5555
Optional: true,
56+
Computed: true,
5657
ForceNew: true,
5758
},
5859
},
@@ -68,6 +69,11 @@ func resourceTFEOrganizationTokenCreate(d *schema.ResourceData, meta interface{}
6869
return err
6970
}
7071

72+
// Issue warning if expired_at is not provided
73+
if _, ok := d.GetOk("expired_at"); !ok {
74+
log.Printf("[WARN] The 'expired_at' attribute is not set for organization token. The token will default to an expiration of 24 months from creation.")
75+
}
76+
7177
log.Printf("[DEBUG] Check if a token already exists for organization: %s", organization)
7278
_, err = config.Client.OrganizationTokens.Read(ctx, organization)
7379
if err != nil && !errors.Is(err, tfe.ErrResourceNotFound) {
@@ -110,7 +116,9 @@ func resourceTFEOrganizationTokenCreate(d *schema.ResourceData, meta interface{}
110116
// We need to set this here in the create function as this value will
111117
// only be returned once during the creation of the token.
112118
d.Set("token", token.Token)
113-
119+
if !token.ExpiredAt.IsZero() {
120+
d.Set("expired_at", token.ExpiredAt.Format(time.RFC3339))
121+
}
114122
return resourceTFEOrganizationTokenRead(d, meta)
115123
}
116124

internal/provider/resource_tfe_organization_token_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ func TestAccTFEOrganizationToken_existsWithForce(t *testing.T) {
100100
}
101101

102102
func TestAccTFEOrganizationToken_withBlankExpiry(t *testing.T) {
103+
skipUnlessBeta(t)
103104
token := &tfe.OrganizationToken{}
104105
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
105-
expiredAt := ""
106106

107107
resource.Test(t, resource.TestCase{
108108
PreCheck: func() { testAccPreCheck(t) },
@@ -114,8 +114,9 @@ func TestAccTFEOrganizationToken_withBlankExpiry(t *testing.T) {
114114
Check: resource.ComposeTestCheckFunc(
115115
testAccCheckTFEOrganizationTokenExists(
116116
"tfe_organization_token.foobar", token),
117-
resource.TestCheckResourceAttr(
118-
"tfe_organization_token.foobar", "expired_at", expiredAt),
117+
// When expired_at is not provided, API sets default (24 months and its value is read from the API response
118+
resource.TestCheckResourceAttrSet(
119+
"tfe_organization_token.foobar", "expired_at"),
119120
),
120121
},
121122
},
@@ -288,7 +289,7 @@ resource "tfe_organization" "foobar" {
288289
289290
resource "tfe_organization_token" "foobar" {
290291
organization = tfe_organization.foobar.id
291-
expired_at = ""
292+
# expired_at not provided - API will set default to 24 months
292293
}`, rInt)
293294
}
294295

internal/provider/resource_tfe_team_token.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/hashicorp/terraform-plugin-framework/types"
2424
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
2525
"github.com/hashicorp/terraform-plugin-log/tflog"
26+
"github.com/hashicorp/terraform-provider-tfe/internal/provider/planmodifiers"
2627
)
2728

2829
var (
@@ -101,8 +102,13 @@ func (r *resourceTFETeamToken) Schema(_ context.Context, _ resource.SchemaReques
101102
"expired_at": schema.StringAttribute{
102103
Description: "The token's expiration date.",
103104
Optional: true,
105+
Computed: true,
104106
PlanModifiers: []planmodifier.String{
105107
stringplanmodifier.RequiresReplace(),
108+
stringplanmodifier.UseStateForUnknown(),
109+
planmodifiers.WarnIfNullOnCreate(
110+
"Team Token expiration null values defaults to 24 months",
111+
),
106112
},
107113
},
108114
"description": schema.StringAttribute{
@@ -182,7 +188,14 @@ func (r *resourceTFETeamToken) Create(ctx context.Context, req resource.CreateRe
182188
return
183189
}
184190

185-
result := modelFromTFEToken(plan.TeamID, types.StringValue(token.ID), types.StringValue(token.Token), plan.ForceRegenerate, plan.ExpiredAt, plan.Description)
191+
var expiredAtValue types.String
192+
if !token.ExpiredAt.IsZero() {
193+
expiredAtValue = types.StringValue(token.ExpiredAt.Format(time.RFC3339))
194+
} else {
195+
expiredAtValue = types.StringNull()
196+
}
197+
198+
result := modelFromTFEToken(plan.TeamID, types.StringValue(token.ID), types.StringValue(token.Token), plan.ForceRegenerate, expiredAtValue, plan.Description)
186199
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
187200
}
188201

internal/provider/resource_tfe_team_token_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ func TestAccTFETeamToken_invalidWithForceGenerateAndDescription(t *testing.T) {
131131
}
132132

133133
func TestAccTFETeamToken_withBlankExpiry(t *testing.T) {
134+
skipUnlessBeta(t)
134135
token := &tfe.TeamToken{}
135136
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
136-
expiredAt := ""
137137

138138
resource.Test(t, resource.TestCase{
139139
PreCheck: func() { testAccPreCheck(t) },
@@ -145,8 +145,10 @@ func TestAccTFETeamToken_withBlankExpiry(t *testing.T) {
145145
Check: resource.ComposeTestCheckFunc(
146146
testAccCheckTFETeamTokenExists(
147147
"tfe_team_token.foobar", token),
148-
resource.TestCheckResourceAttr(
149-
"tfe_team_token.foobar", "expired_at", expiredAt),
148+
// When expired_at is not provided, API sets default (24 months)
149+
// We now read this value from the API response
150+
resource.TestCheckResourceAttrSet(
151+
"tfe_team_token.foobar", "expired_at"),
150152
),
151153
},
152154
},
@@ -403,7 +405,7 @@ resource "tfe_team" "foobar" {
403405
404406
resource "tfe_team_token" "foobar" {
405407
team_id = tfe_team.foobar.id
406-
expired_at = ""
408+
# expired_at not provided - API will set default to 24 months
407409
}`, rInt)
408410
}
409411

website/docs/r/audit_trail_token.html.markdown

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ The following arguments are supported:
3030
generated even if a token already exists. This will invalidate the existing
3131
token!
3232
* `expired_at` - (Optional) The token's expiration date. The expiration date must be a date/time string in RFC3339
33-
format (e.g., "2024-12-31T23:59:59Z"). If no expiration date is supplied, the expiration date will default to null and
34-
never expire.
33+
format (e.g., "2024-12-31T23:59:59Z"). If no expiration date is supplied, the token will expire 24 months from creation and a warning during plan and apply phases will be displayed.
3534

3635
## Example Usage
3736

website/docs/r/organization_token.html.markdown

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ The following arguments are supported:
2828
* `force_regenerate` - (Optional) If set to `true`, a new token will be
2929
generated even if a token already exists. This will invalidate the existing
3030
token!
31-
* `expired_at` - (Optional) The token's expiration date. The expiration date must be a date/time string in RFC3339
32-
format (e.g., "2024-12-31T23:59:59Z"). If no expiration date is supplied, the expiration date will default to null and
33-
never expire.
31+
* `expired_at` - (Optional) The token's expiration date. The expiration date must be a date/time string in RFC3339
32+
format (e.g., "2024-12-31T23:59:59Z"). If no expiration date is supplied, the token will expire 24 months from creation.
3433

3534
## Example Usage
3635

0 commit comments

Comments
 (0)