Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'platform_aws_iam_role' resource #125

Merged
merged 9 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/acceptance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.22
- name: Install Helm
uses: azure/[email protected]
- name: Install Terraform CLI
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
go-version: 1.22
-
name: Import GPG key
id: import_gpg
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.12.0 (September 13, 2024). Tested on Artifactory 7.90.10 with Terraform 1.9.5 and OpenTofu 1.8.2

FEATURES:

**New Resource:**
* `platform_aws_iam_role` - Resource to manage AWS IAM role. PR: [#125](https://github.com/jfrog/terraform-provider-platform/pull/125)

## 1.11.1 (September 9, 2024). Tested on Artifactory 7.90.9 with Terraform 1.9.5 and OpenTofu 1.8.2

IMPROVEMENTS:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ During the provider start up, if it finds env var `TFC_WORKLOAD_IDENTITY_TOKEN`

Follow [confgure an OIDC integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration). Enter a name for the provider, e.g. `terraform-cloud`. Use `https://app.terraform.io` for "Provider URL". Choose your own value for "Audience", e.g. `jfrog-terraform-cloud`.

Then [configure an identity mapping](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-identity-mappings) with an empty "Claims JSON" (`{}`), and select the "Token scope", "User", and "Service" as desired.
Then [configure an identity mapping](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-identity-mappings) with appropriate "Claims JSON" (e.g. `aud`, `sub` at minimum. See [Terraform Workload Identity - Configuring Trust with your Cloud Platform](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/workload-identity-tokens#configuring-trust-with-your-cloud-platform)), and select the "Token scope", "User", and "Service" as desired.

#### Set environment variable in your Terraform Workspace

Expand Down
39 changes: 39 additions & 0 deletions docs/resources/aws_iam_role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "platform_aws_iam_role Resource - terraform-provider-platform"
subcategory: ""
description: |-
Provides a resource to manage AWS IAM roles for JFrog platform users. You can use the AWS IAM roles for passwordless access to Amazon EKS. For more information, see Passwordless Access for Amazon EKS https://jfrog.com/help/r/jfrog-installation-setup-documentation/passwordless-access-for-amazon-eks.
->Only available for Artifactory 7.90.10 or later.
---

# platform_aws_iam_role (Resource)

Provides a resource to manage AWS IAM roles for JFrog platform users. You can use the AWS IAM roles for passwordless access to Amazon EKS. For more information, see [Passwordless Access for Amazon EKS](https://jfrog.com/help/r/jfrog-installation-setup-documentation/passwordless-access-for-amazon-eks).

->Only available for Artifactory 7.90.10 or later.

## Example Usage

```terraform
resource "platform_aws_iam_role" "myuser-aws-iam-role" {
username = "myuser"
iam_role = "arn:aws:iam::000000000000:role/example"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `iam_role` (String) The AWS IAM role. Must follow the regex, "^arn:aws:iam::\d{12}:role/[\w+=,.@:-]+$"
- `username` (String) The JFrog Platform user name.

## Import

Import is supported using the following syntax:

```shell
terraform import platform_aws_iam_role.myuser-aws-iam-role myuser
```
1 change: 1 addition & 0 deletions examples/resources/platform_aws_iam_role/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import platform_aws_iam_role.myuser-aws-iam-role myuser
4 changes: 4 additions & 0 deletions examples/resources/platform_aws_iam_role/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "platform_aws_iam_role" "myuser-aws-iam-role" {
username = "myuser"
iam_role = "arn:aws:iam::000000000000:role/example"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/jfrog/terraform-provider-platform
// if you need to do local dev, literally just uncomment the line below
// replace github.com/jfrog/terraform-provider-shared => ../terraform-provider-shared

go 1.21.5
go 1.22.5

require (
github.com/go-resty/resty/v2 v2.14.0
Expand Down
1 change: 1 addition & 0 deletions pkg/platform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func (p *PlatformProvider) DataSources(ctx context.Context) []func() datasource.

func (p *PlatformProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewAWSIAMRoleResource,
NewLicenseResource,
NewGlobalRoleResource,
NewOIDCConfigurationResource,
Expand Down
243 changes: 243 additions & 0 deletions pkg/platform/resource_aws_iam_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package platform

import (
"context"
"fmt"
"net/http"
"regexp"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/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/jfrog/terraform-provider-shared/util"
utilfw "github.com/jfrog/terraform-provider-shared/util/fw"
)

const (
AWSIAMRolesEndpoint = "access/api/v1/aws/iam_role"
AWSIAMRoleEndpoint = "access/api/v1/aws/iam_role/{username}"
)

func NewAWSIAMRoleResource() resource.Resource {
return &AWSIAMRoleResource{
TypeName: "platform_aws_iam_role",
}
}

type AWSIAMRoleResource struct {
ProviderData PlatformProviderMetadata
TypeName string
}

type AWSIAMRoleResourceModel struct {
Username types.String `tfsdk:"username"`
IAMRole types.String `tfsdk:"iam_role"`
}

type AWSIAMRoleAPIModel struct {
Username string `json:"username"`
IAMRole string `json:"iam_role"`
}

func (r *AWSIAMRoleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = r.TypeName
}

func (r *AWSIAMRoleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"username": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Description: "The JFrog Platform user name.",
},
"iam_role": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(`^arn:aws:iam::\d{12}:role/[\w+=,.@:-]+$`), "Must follow the regex, \"^arn:aws:iam::\\d{12}:role/[\\w+=,.@:-]+$\""),
},
MarkdownDescription: "The AWS IAM role. Must follow the regex, \"^arn:aws:iam::\\d{12}:role/[\\w+=,.@:-]+$\"",
},
},
MarkdownDescription: "Provides a resource to manage AWS IAM roles for JFrog platform users. You can use the AWS IAM roles for passwordless access to Amazon EKS. For more information, see [Passwordless Access for Amazon EKS](https://jfrog.com/help/r/jfrog-installation-setup-documentation/passwordless-access-for-amazon-eks).\n\n" +
"->Only available for Artifactory 7.90.10 or later.",
}
}

func (r *AWSIAMRoleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}
r.ProviderData = req.ProviderData.(PlatformProviderMetadata)

supported, err := util.CheckVersion(r.ProviderData.ArtifactoryVersion, "7.90.10")
if err != nil {
resp.Diagnostics.AddError(
"Failed to check Artifactory version",
err.Error(),
)
return
}

if !supported {
resp.Diagnostics.AddError(
"Unsupported Artifactory version",
fmt.Sprintf("This resource is supported by Artifactory version 7.90.10 or later. Current version: %s", r.ProviderData.ArtifactoryVersion),
)
return
}
}

func (r *AWSIAMRoleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)

var plan AWSIAMRoleResourceModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

role := AWSIAMRoleAPIModel{
Username: plan.Username.ValueString(),
IAMRole: plan.IAMRole.ValueString(),
}

response, err := r.ProviderData.Client.R().
SetBody(role).
Put(AWSIAMRolesEndpoint)

if err != nil {
utilfw.UnableToCreateResourceError(resp, err.Error())
return
}

if response.IsError() {
utilfw.UnableToCreateResourceError(resp, response.String())
return
}

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *AWSIAMRoleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)

var state AWSIAMRoleResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

var role AWSIAMRoleAPIModel

response, err := r.ProviderData.Client.R().
SetPathParam("username", state.Username.ValueString()).
SetResult(&role).
Get(AWSIAMRoleEndpoint)

if err != nil {
utilfw.UnableToRefreshResourceError(resp, err.Error())
return
}

if response.IsError() {
utilfw.UnableToRefreshResourceError(resp, response.String())
return
}

// Treat HTTP 404 Not Found status as a signal to recreate resource
// and return early
if response.StatusCode() == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}

// Convert from the API data model to the Terraform data model
// and refresh any attribute values.
state.Username = types.StringValue(role.Username)
state.IAMRole = types.StringValue(role.IAMRole)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *AWSIAMRoleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)

var plan AWSIAMRoleResourceModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

role := AWSIAMRoleAPIModel{
Username: plan.Username.ValueString(),
IAMRole: plan.IAMRole.ValueString(),
}

response, err := r.ProviderData.Client.R().
SetBody(role).
Put(AWSIAMRolesEndpoint)

if err != nil {
utilfw.UnableToUpdateResourceError(resp, err.Error())
return
}

if response.IsError() {
utilfw.UnableToUpdateResourceError(resp, response.String())
return
}

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *AWSIAMRoleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName)

var state AWSIAMRoleResourceModel

diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

response, err := r.ProviderData.Client.R().
SetPathParam("username", state.Username.ValueString()).
Delete(AWSIAMRoleEndpoint)

if err != nil {
utilfw.UnableToDeleteResourceError(resp, err.Error())
return
}

if response.IsError() {
utilfw.UnableToDeleteResourceError(resp, response.String())
return
}

// If the logic reaches here, it implicitly succeeded and will remove
// the resource from state if there are no other errors.
}

func (r *AWSIAMRoleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("username"), req, resp)
}
Loading
Loading