-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add service user token resource
- Loading branch information
1 parent
21dff26
commit 461e1c3
Showing
7 changed files
with
346 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "humanitec_service_user_token Resource - terraform-provider-humanitec" | ||
subcategory: "" | ||
description: |- | ||
Tokens can be generated from service users and are used to interact with Humanitec’s API on the service user’s behalf. | ||
--- | ||
|
||
# humanitec_service_user_token (Resource) | ||
|
||
Tokens can be generated from service users and are used to interact with Humanitec’s API on the service user’s behalf. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "humanitec_user" "service_user" { | ||
name = "example-service-user" | ||
role = "administrator" | ||
type = "service" | ||
} | ||
resource "humanitec_service_user_token" "token" { | ||
id = "example-service-token" | ||
user_id = humanitec_user.service_user.id | ||
description = "example token description" | ||
expires_at = "2024-04-23T21:59:59.999Z" | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `id` (String) Identifier of the token. Must be unique for the user. | ||
- `user_id` (String) The service user ID. | ||
|
||
### Optional | ||
|
||
- `description` (String) A description of the token. | ||
- `expires_at` (String) The time the token expires. If not set, the token will not expire. | ||
|
||
### Read-Only | ||
|
||
- `token` (String, Sensitive) Unique token granting access to specific services within the platform. |
12 changes: 12 additions & 0 deletions
12
examples/resources/humanitec_service_user_token/resource.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
resource "humanitec_user" "service_user" { | ||
name = "example-service-user" | ||
role = "administrator" | ||
type = "service" | ||
} | ||
|
||
resource "humanitec_service_user_token" "token" { | ||
id = "example-service-token" | ||
user_id = humanitec_user.service_user.id | ||
description = "example token description" | ||
expires_at = "2024-04-23T21:59:59.999Z" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
|
||
"github.com/humanitec/humanitec-go-autogen" | ||
"github.com/humanitec/humanitec-go-autogen/client" | ||
) | ||
|
||
// Ensure provider defined types fully satisfy framework interfaces | ||
var _ resource.Resource = &ResourceServiceUserToken{} | ||
|
||
func NewResourceServiceUserToken() resource.Resource { | ||
return &ResourceServiceUserToken{} | ||
} | ||
|
||
// ResourceServiceUserToken defines the resource implementation. | ||
type ResourceServiceUserToken struct { | ||
client *humanitec.Client | ||
orgId string | ||
} | ||
|
||
// ServiceUserTokenModel describes the app data model. | ||
type ServiceUserTokenModel struct { | ||
ID types.String `tfsdk:"id"` | ||
UserID types.String `tfsdk:"user_id"` | ||
Description types.String `tfsdk:"description"` | ||
ExpiresAt types.String `tfsdk:"expires_at"` | ||
Token types.String `tfsdk:"token"` | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_service_user_token" | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
MarkdownDescription: "Tokens can be generated from service users and are used to interact with Humanitec’s API on the service user’s behalf.", | ||
|
||
Attributes: map[string]schema.Attribute{ | ||
"id": schema.StringAttribute{ | ||
MarkdownDescription: "Identifier of the token. Must be unique for the user.", | ||
Required: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
"user_id": schema.StringAttribute{ | ||
MarkdownDescription: "The service user ID.", | ||
Required: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
"description": schema.StringAttribute{ | ||
MarkdownDescription: "A description of the token.", | ||
Optional: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
"expires_at": schema.StringAttribute{ | ||
MarkdownDescription: "The time the token expires. If not set, the token will not expire.", | ||
Optional: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.RequiresReplace(), | ||
}, | ||
}, | ||
"token": schema.StringAttribute{ | ||
MarkdownDescription: "Unique token granting access to specific services within the platform.", | ||
Computed: true, | ||
Sensitive: true, | ||
PlanModifiers: []planmodifier.String{ | ||
stringplanmodifier.UseStateForUnknown(), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { | ||
// Prevent panic if the provider has not been configured. | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
resdata, ok := req.ProviderData.(*HumanitecData) | ||
|
||
if !ok { | ||
resp.Diagnostics.AddError( | ||
"Unexpected Resource Configure Type", | ||
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), | ||
) | ||
|
||
return | ||
} | ||
|
||
r.client = resdata.Client | ||
r.orgId = resdata.OrgID | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { | ||
var data *ServiceUserTokenModel | ||
|
||
// Read Terraform plan data into the model | ||
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) | ||
|
||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
id := data.ID.ValueString() | ||
userId := data.UserID.ValueString() | ||
description := data.Description.ValueStringPointer() | ||
expiresAt := data.ExpiresAt.ValueStringPointer() | ||
|
||
httpResp, err := r.client.CreateUserTokenWithResponse(ctx, userId, client.TokenDefinitionRequest{ | ||
Description: description, | ||
ExpiresAt: expiresAt, | ||
Id: id, | ||
Type: "static", | ||
}) | ||
if err != nil { | ||
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to create service user token, got error: %s", err)) | ||
return | ||
} | ||
if httpResp.StatusCode() != 200 { | ||
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to create service user token, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) | ||
return | ||
} | ||
|
||
data.Token = types.StringValue(httpResp.JSON200.Token) | ||
|
||
// Save data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { | ||
var data *ServiceUserTokenModel | ||
|
||
// Read Terraform prior state data into the model | ||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...) | ||
|
||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
id := data.ID.ValueString() | ||
userId := data.UserID.ValueString() | ||
|
||
httpResp, err := r.client.GetUserTokenWithResponse(ctx, userId, id) | ||
if err != nil { | ||
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to read service user token, got error: %s", err)) | ||
return | ||
} | ||
if httpResp.StatusCode() != 200 { | ||
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to read service user token, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) | ||
return | ||
} | ||
|
||
parseTokenInfoResponse(httpResp.JSON200, data) | ||
|
||
// Save updated data into Terraform state | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { | ||
resp.Diagnostics.AddError("UNSUPPORTED_OPERATION", "Updating a service user token is not supported") | ||
} | ||
|
||
func (r *ResourceServiceUserToken) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { | ||
var data *ServiceUserTokenModel | ||
|
||
// Read Terraform prior state data into the model | ||
resp.Diagnostics.Append(req.State.Get(ctx, &data)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
userId := data.UserID.ValueString() | ||
id := data.ID.ValueString() | ||
|
||
httpResp, err := r.client.DeleteUserTokenWithResponse(ctx, userId, id) | ||
if err != nil { | ||
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to delete service user token, got error: %s", err)) | ||
return | ||
} | ||
|
||
if httpResp.StatusCode() != 204 { | ||
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to delete service user token, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body)) | ||
return | ||
} | ||
} | ||
|
||
func parseTokenInfoResponse(res *client.TokenInfoResponse, data *ServiceUserTokenModel) { | ||
if res.Description != "" { | ||
data.Description = types.StringPointerValue(&res.Description) | ||
} | ||
if res.ExpiresAt != nil && *res.ExpiresAt != "0001-01-01T00:00:00Z" { | ||
data.ExpiresAt = types.StringPointerValue(res.ExpiresAt) | ||
} | ||
data.ID = types.StringValue(res.Id) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package provider | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
) | ||
|
||
func TestAccResourceServiceUserToken(t *testing.T) { | ||
const ( | ||
userName = "test user" | ||
tokenId = "test-token-id" | ||
description = "Test token description" | ||
) | ||
expiresAt := time.Now().Add(24 * time.Hour).Format("2006-01-02T15:04:05.999Z") | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, | ||
Steps: []resource.TestStep{ | ||
// Create and Read testing | ||
{ | ||
Config: testAccCreateResourceServiceUserToken(userName, tokenId, "", ""), | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr("humanitec_service_user_token.token", "id", tokenId), | ||
resource.TestCheckResourceAttrSet("humanitec_service_user_token.token", "token"), | ||
resource.TestCheckNoResourceAttr("humanitec_service_user_token.token", "description"), | ||
resource.TestCheckNoResourceAttr("humanitec_service_user_token.token", "expires_at"), | ||
), | ||
}, | ||
// Update and Read testing | ||
{ | ||
Config: testAccCreateResourceServiceUserToken(userName, tokenId, description, expiresAt), | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestCheckResourceAttr("humanitec_service_user_token.token", "id", tokenId), | ||
resource.TestCheckResourceAttrSet("humanitec_service_user_token.token", "token"), | ||
resource.TestCheckResourceAttr("humanitec_service_user_token.token", "description", description), | ||
resource.TestCheckResourceAttr("humanitec_service_user_token.token", "expires_at", expiresAt), | ||
), | ||
}, | ||
// Delete testing automatically occurs in TestCase | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCreateResourceServiceUserToken(userName, tokenId, tokenDescription, tokenExpiration string) string { | ||
tokenDescriptionEntry := "" | ||
if tokenDescription != "" { | ||
tokenDescriptionEntry = fmt.Sprintf(`description = "%s"`, tokenDescription) | ||
} | ||
tokenExpirationEntry := "" | ||
if tokenExpiration != "" { | ||
tokenExpirationEntry = fmt.Sprintf(`expires_at = "%s"`, tokenExpiration) | ||
} | ||
|
||
return fmt.Sprintf(` | ||
resource "humanitec_user" "service_user" { | ||
name = "%s" | ||
role = "administrator" | ||
type = "service" | ||
} | ||
resource "humanitec_service_user_token" "token" { | ||
id = "%s" | ||
user_id = humanitec_user.service_user.id | ||
%s | ||
%s | ||
}`, | ||
userName, tokenId, tokenDescriptionEntry, tokenExpirationEntry, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters