Skip to content

Commit

Permalink
Merge pull request #1164 from hashicorp/registry-gpg-keys
Browse files Browse the repository at this point in the history
Add resource and data sources for private registry GPG keys
  • Loading branch information
brandonc committed Dec 7, 2023
2 parents 632457d + fbd15fd commit 596951c
Show file tree
Hide file tree
Showing 15 changed files with 944 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ FEATURES:
* `d/tfe_registry_module`: Add `vcs_repo.tags` and `vcs_repo.branch` attributes to allow configuration of `publishing_mechanism`. Add `test_config` to support running tests on `branch`-based registry modules, by @hashimoon [1096](https://github.com/hashicorp/terraform-provider-tfe/pull/1096)
* **New Resource**: `r/tfe_organization_default_execution_mode` is a new resource to set the `default_execution_mode` and `default_agent_pool_id` for an organization, by @SwiftEngineer [1137](https://github.com/hashicorp/terraform-provider-tfe/pull/1137)'
* `r/tfe_workspace`: Now uses the organization's `default_execution_mode` and `default_agent_pool_id` as the default `execution_mode`, by @SwiftEngineer [1137](https://github.com/hashicorp/terraform-provider-tfe/pull/1137)'
* **New Resource**: `r/tfe_registry_gpg_key` is a new resource for managing private registry GPG keys, by @tmatilai [1160](https://github.com/hashicorp/terraform-provider-tfe/pull/1160)
* **New Data Source**: `d/tfe_registry_gpg_key` is a new data source to retrieve a private registry GPG key, by @tmatilai [1160](https://github.com/hashicorp/terraform-provider-tfe/pull/1160)
* **New Data Source**: `d/tfe_registry_gpg_keys` is a new data source to retrieve all private registry GPG keys of an organization, by @tmatilai [1160](https://github.com/hashicorp/terraform-provider-tfe/pull/1160)

ENHANCEMENTS:
* `d/tfe_organization`: Make `name` argument optional if configured for the provider, by @tmatilai [1133](https://github.com/hashicorp/terraform-provider-tfe/pull/1133)
Expand Down
41 changes: 41 additions & 0 deletions internal/provider/attribute_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// AttrGettable is a small enabler for helper functions that need to read one
// attribute of a Configuration, Plan, or State.
type AttrGettable interface {
GetAttribute(ctx context.Context, path path.Path, target interface{}) diag.Diagnostics
}

// dataOrDefaultOrganization returns the value of the "organization" attribute
// from the Config/Plan/State data, defaulting to the provier configuration.
// If neither is set, an error is returned.
func (c *ConfiguredClient) dataOrDefaultOrganization(ctx context.Context, data AttrGettable, target *string) diag.Diagnostics {
schemaPath := path.Root("organization")

var organization types.String
diags := data.GetAttribute(ctx, schemaPath, &organization)
if diags.HasError() {
return diags
}

if !organization.IsNull() && !organization.IsUnknown() {
*target = organization.ValueString()
} else if c.Organization == "" {
diags.AddAttributeError(schemaPath, "No organization was specified on the resource or provider", "")
} else {
*target = c.Organization
}

return diags
}
119 changes: 119 additions & 0 deletions internal/provider/data_source_registry_gpg_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"

"github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &dataSourceTFERegistryGPGKey{}
_ datasource.DataSourceWithConfigure = &dataSourceTFERegistryGPGKey{}
)

// NewRegistryGPGKeyDataSource is a helper function to simplify the provider implementation.
func NewRegistryGPGKeyDataSource() datasource.DataSource {
return &dataSourceTFERegistryGPGKey{}
}

// dataSourceTFERegistryGPGKey is the data source implementation.
type dataSourceTFERegistryGPGKey struct {
config ConfiguredClient
}

// Metadata returns the data source type name.
func (d *dataSourceTFERegistryGPGKey) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_registry_gpg_key"
}

// Schema defines the schema for the data source.
func (d *dataSourceTFERegistryGPGKey) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "This data source can be used to retrieve a private registry GPG key.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Required: true,
},
"organization": schema.StringAttribute{
Description: "Name of the organization. If omitted, organization must be defined in the provider config.",
Optional: true,
Computed: true,
},
"ascii_armor": schema.StringAttribute{
Description: "ASCII-armored representation of the GPG key.",
Computed: true,
},
"created_at": schema.StringAttribute{
Description: "The time when the GPG key was created.",
Computed: true,
},
"updated_at": schema.StringAttribute{
Description: "The time when the GPG key was last updated.",
Computed: true,
},
},
}
}

// Configure adds the provider configured client to the data source.
func (d *dataSourceTFERegistryGPGKey) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(ConfiguredClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
)

return
}
d.config = client
}

// Read refreshes the Terraform state with the latest data.
func (d *dataSourceTFERegistryGPGKey) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data modelTFERegistryGPGKey

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

var organization string
resp.Diagnostics.Append(d.config.dataOrDefaultOrganization(ctx, req.Config, &organization)...)

if resp.Diagnostics.HasError() {
return
}

keyID := tfe.GPGKeyID{
RegistryName: "private",
Namespace: organization,
KeyID: data.ID.ValueString(),
}

tflog.Debug(ctx, "Reading private registry GPG key")
key, err := d.config.Client.GPGKeys.Read(ctx, keyID)
if err != nil {
resp.Diagnostics.AddError("Unable to read private registry GPG key", err.Error())
return
}

data = modelFromTFEVGPGKey(key)

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
46 changes: 46 additions & 0 deletions internal/provider/data_source_registry_gpg_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"fmt"
"math/rand"
"testing"
"time"

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

func TestAccTFERegistryGPGKeyDataSource_basic(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
orgName := fmt.Sprintf("tst-terraform-%d", rInt)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV5ProviderFactories: testAccMuxedProviders,
Steps: []resource.TestStep{
{
Config: testAccTFERegistryGPGKeyDataSourceConfig(orgName),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.tfe_registry_gpg_key.foobar", "organization", orgName),
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "id"),
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "ascii_armor"),
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "created_at"),
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "updated_at")),
},
},
})
}

func testAccTFERegistryGPGKeyDataSourceConfig(orgName string) string {
return fmt.Sprintf(`
%s
data "tfe_registry_gpg_key" "foobar" {
organization = tfe_organization.foobar.name
id = tfe_registry_gpg_key.foobar.id
}
`, testAccTFERegistryGPGKeyResourceConfig(orgName))
}
146 changes: 146 additions & 0 deletions internal/provider/data_source_registry_gpg_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"

"github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &dataSourceTFERegistryGPGKeys{}
_ datasource.DataSourceWithConfigure = &dataSourceTFERegistryGPGKeys{}
)

// NewRegistryGPGKeysDataSource is a helper function to simplify the provider implementation.
func NewRegistryGPGKeysDataSource() datasource.DataSource {
return &dataSourceTFERegistryGPGKeys{}
}

// dataSourceTFERegistryGPGKeys is the data source implementation.
type dataSourceTFERegistryGPGKeys struct {
config ConfiguredClient
}

// modelTFERegistryGPGKeys maps the data source schema data.
type modelTFERegistryGPGKeys struct {
ID types.String `tfsdk:"id"`
Organization types.String `tfsdk:"organization"`
Keys []modelTFERegistryGPGKey `tfsdk:"keys"`
}

// Metadata returns the data source type name.
func (d *dataSourceTFERegistryGPGKeys) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_registry_gpg_keys"
}

// Schema defines the schema for the data source.
func (d *dataSourceTFERegistryGPGKeys) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "This data source can be used to retrieve all private registry GPG keys of an organization.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"organization": schema.StringAttribute{
Description: "Name of the organization. If omitted, organization must be defined in the provider config.",
Optional: true,
Computed: true,
},
"keys": schema.ListAttribute{
Description: "List of GPG keys in the organization.",
Computed: true,
ElementType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"id": types.StringType,
"organization": types.StringType,
"ascii_armor": types.StringType,
"created_at": types.StringType,
"updated_at": types.StringType,
},
},
},
},
}
}

// Configure adds the provider configured client to the data source.
func (d *dataSourceTFERegistryGPGKeys) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(ConfiguredClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
)

return
}
d.config = client
}

// Read refreshes the Terraform state with the latest data.
func (d *dataSourceTFERegistryGPGKeys) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data modelTFERegistryGPGKeys

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

var organization string
resp.Diagnostics.Append(d.config.dataOrDefaultOrganization(ctx, req.Config, &organization)...)

if resp.Diagnostics.HasError() {
return
}

options := tfe.GPGKeyListOptions{
Namespaces: []string{organization},
}
tflog.Debug(ctx, "Listing private registry GPG keys")
keyList, err := d.config.Client.GPGKeys.ListPrivate(ctx, options)
if err != nil {
resp.Diagnostics.AddError("Unable to list private registry GPG keys", err.Error())
return
}

data.ID = types.StringValue(organization)
data.Organization = types.StringValue(organization)
data.Keys = []modelTFERegistryGPGKey{}

for {
for _, key := range keyList.Items {
data.Keys = append(data.Keys, modelFromTFEVGPGKey(key))
}

if keyList.CurrentPage >= keyList.TotalPages {
break
}
options.PageNumber = keyList.NextPage

tflog.Debug(ctx, "Listing private registry GPG keys")
keyList, err = d.config.Client.GPGKeys.ListPrivate(ctx, options)
if err != nil {
resp.Diagnostics.AddError("Unable to list private registry GPG keys", err.Error())
return
}
}

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

0 comments on commit 596951c

Please sign in to comment.