Skip to content

Commit 596951c

Browse files
authored
Merge pull request #1164 from hashicorp/registry-gpg-keys
Add resource and data sources for private registry GPG keys
2 parents 632457d + fbd15fd commit 596951c

15 files changed

+944
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ FEATURES:
1313
* `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)
1414
* **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)'
1515
* `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)'
16+
* **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)
17+
* **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)
18+
* **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)
1619

1720
ENHANCEMENTS:
1821
* `d/tfe_organization`: Make `name` argument optional if configured for the provider, by @tmatilai [1133](https://github.com/hashicorp/terraform-provider-tfe/pull/1133)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-framework/path"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
// AttrGettable is a small enabler for helper functions that need to read one
15+
// attribute of a Configuration, Plan, or State.
16+
type AttrGettable interface {
17+
GetAttribute(ctx context.Context, path path.Path, target interface{}) diag.Diagnostics
18+
}
19+
20+
// dataOrDefaultOrganization returns the value of the "organization" attribute
21+
// from the Config/Plan/State data, defaulting to the provier configuration.
22+
// If neither is set, an error is returned.
23+
func (c *ConfiguredClient) dataOrDefaultOrganization(ctx context.Context, data AttrGettable, target *string) diag.Diagnostics {
24+
schemaPath := path.Root("organization")
25+
26+
var organization types.String
27+
diags := data.GetAttribute(ctx, schemaPath, &organization)
28+
if diags.HasError() {
29+
return diags
30+
}
31+
32+
if !organization.IsNull() && !organization.IsUnknown() {
33+
*target = organization.ValueString()
34+
} else if c.Organization == "" {
35+
diags.AddAttributeError(schemaPath, "No organization was specified on the resource or provider", "")
36+
} else {
37+
*target = c.Organization
38+
}
39+
40+
return diags
41+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/go-tfe"
11+
"github.com/hashicorp/terraform-plugin-framework/datasource"
12+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
14+
)
15+
16+
// Ensure the implementation satisfies the expected interfaces.
17+
var (
18+
_ datasource.DataSource = &dataSourceTFERegistryGPGKey{}
19+
_ datasource.DataSourceWithConfigure = &dataSourceTFERegistryGPGKey{}
20+
)
21+
22+
// NewRegistryGPGKeyDataSource is a helper function to simplify the provider implementation.
23+
func NewRegistryGPGKeyDataSource() datasource.DataSource {
24+
return &dataSourceTFERegistryGPGKey{}
25+
}
26+
27+
// dataSourceTFERegistryGPGKey is the data source implementation.
28+
type dataSourceTFERegistryGPGKey struct {
29+
config ConfiguredClient
30+
}
31+
32+
// Metadata returns the data source type name.
33+
func (d *dataSourceTFERegistryGPGKey) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
34+
resp.TypeName = req.ProviderTypeName + "_registry_gpg_key"
35+
}
36+
37+
// Schema defines the schema for the data source.
38+
func (d *dataSourceTFERegistryGPGKey) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
39+
resp.Schema = schema.Schema{
40+
Description: "This data source can be used to retrieve a private registry GPG key.",
41+
Attributes: map[string]schema.Attribute{
42+
"id": schema.StringAttribute{
43+
Required: true,
44+
},
45+
"organization": schema.StringAttribute{
46+
Description: "Name of the organization. If omitted, organization must be defined in the provider config.",
47+
Optional: true,
48+
Computed: true,
49+
},
50+
"ascii_armor": schema.StringAttribute{
51+
Description: "ASCII-armored representation of the GPG key.",
52+
Computed: true,
53+
},
54+
"created_at": schema.StringAttribute{
55+
Description: "The time when the GPG key was created.",
56+
Computed: true,
57+
},
58+
"updated_at": schema.StringAttribute{
59+
Description: "The time when the GPG key was last updated.",
60+
Computed: true,
61+
},
62+
},
63+
}
64+
}
65+
66+
// Configure adds the provider configured client to the data source.
67+
func (d *dataSourceTFERegistryGPGKey) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
68+
if req.ProviderData == nil {
69+
return
70+
}
71+
72+
client, ok := req.ProviderData.(ConfiguredClient)
73+
if !ok {
74+
resp.Diagnostics.AddError(
75+
"Unexpected Data Source Configure Type",
76+
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
77+
)
78+
79+
return
80+
}
81+
d.config = client
82+
}
83+
84+
// Read refreshes the Terraform state with the latest data.
85+
func (d *dataSourceTFERegistryGPGKey) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
86+
var data modelTFERegistryGPGKey
87+
88+
// Read Terraform configuration data into the model
89+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
90+
91+
if resp.Diagnostics.HasError() {
92+
return
93+
}
94+
95+
var organization string
96+
resp.Diagnostics.Append(d.config.dataOrDefaultOrganization(ctx, req.Config, &organization)...)
97+
98+
if resp.Diagnostics.HasError() {
99+
return
100+
}
101+
102+
keyID := tfe.GPGKeyID{
103+
RegistryName: "private",
104+
Namespace: organization,
105+
KeyID: data.ID.ValueString(),
106+
}
107+
108+
tflog.Debug(ctx, "Reading private registry GPG key")
109+
key, err := d.config.Client.GPGKeys.Read(ctx, keyID)
110+
if err != nil {
111+
resp.Diagnostics.AddError("Unable to read private registry GPG key", err.Error())
112+
return
113+
}
114+
115+
data = modelFromTFEVGPGKey(key)
116+
117+
// Save data into Terraform state
118+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
119+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"fmt"
8+
"math/rand"
9+
"testing"
10+
"time"
11+
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
13+
)
14+
15+
func TestAccTFERegistryGPGKeyDataSource_basic(t *testing.T) {
16+
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
17+
orgName := fmt.Sprintf("tst-terraform-%d", rInt)
18+
19+
resource.Test(t, resource.TestCase{
20+
PreCheck: func() { testAccPreCheck(t) },
21+
ProtoV5ProviderFactories: testAccMuxedProviders,
22+
Steps: []resource.TestStep{
23+
{
24+
Config: testAccTFERegistryGPGKeyDataSourceConfig(orgName),
25+
Check: resource.ComposeAggregateTestCheckFunc(
26+
resource.TestCheckResourceAttr("data.tfe_registry_gpg_key.foobar", "organization", orgName),
27+
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "id"),
28+
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "ascii_armor"),
29+
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "created_at"),
30+
resource.TestCheckResourceAttrSet("data.tfe_registry_gpg_key.foobar", "updated_at")),
31+
},
32+
},
33+
})
34+
}
35+
36+
func testAccTFERegistryGPGKeyDataSourceConfig(orgName string) string {
37+
return fmt.Sprintf(`
38+
%s
39+
40+
data "tfe_registry_gpg_key" "foobar" {
41+
organization = tfe_organization.foobar.name
42+
43+
id = tfe_registry_gpg_key.foobar.id
44+
}
45+
`, testAccTFERegistryGPGKeyResourceConfig(orgName))
46+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/go-tfe"
11+
"github.com/hashicorp/terraform-plugin-framework/attr"
12+
"github.com/hashicorp/terraform-plugin-framework/datasource"
13+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
15+
"github.com/hashicorp/terraform-plugin-log/tflog"
16+
)
17+
18+
// Ensure the implementation satisfies the expected interfaces.
19+
var (
20+
_ datasource.DataSource = &dataSourceTFERegistryGPGKeys{}
21+
_ datasource.DataSourceWithConfigure = &dataSourceTFERegistryGPGKeys{}
22+
)
23+
24+
// NewRegistryGPGKeysDataSource is a helper function to simplify the provider implementation.
25+
func NewRegistryGPGKeysDataSource() datasource.DataSource {
26+
return &dataSourceTFERegistryGPGKeys{}
27+
}
28+
29+
// dataSourceTFERegistryGPGKeys is the data source implementation.
30+
type dataSourceTFERegistryGPGKeys struct {
31+
config ConfiguredClient
32+
}
33+
34+
// modelTFERegistryGPGKeys maps the data source schema data.
35+
type modelTFERegistryGPGKeys struct {
36+
ID types.String `tfsdk:"id"`
37+
Organization types.String `tfsdk:"organization"`
38+
Keys []modelTFERegistryGPGKey `tfsdk:"keys"`
39+
}
40+
41+
// Metadata returns the data source type name.
42+
func (d *dataSourceTFERegistryGPGKeys) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
43+
resp.TypeName = req.ProviderTypeName + "_registry_gpg_keys"
44+
}
45+
46+
// Schema defines the schema for the data source.
47+
func (d *dataSourceTFERegistryGPGKeys) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
48+
resp.Schema = schema.Schema{
49+
Description: "This data source can be used to retrieve all private registry GPG keys of an organization.",
50+
Attributes: map[string]schema.Attribute{
51+
"id": schema.StringAttribute{
52+
Computed: true,
53+
},
54+
"organization": schema.StringAttribute{
55+
Description: "Name of the organization. If omitted, organization must be defined in the provider config.",
56+
Optional: true,
57+
Computed: true,
58+
},
59+
"keys": schema.ListAttribute{
60+
Description: "List of GPG keys in the organization.",
61+
Computed: true,
62+
ElementType: types.ObjectType{
63+
AttrTypes: map[string]attr.Type{
64+
"id": types.StringType,
65+
"organization": types.StringType,
66+
"ascii_armor": types.StringType,
67+
"created_at": types.StringType,
68+
"updated_at": types.StringType,
69+
},
70+
},
71+
},
72+
},
73+
}
74+
}
75+
76+
// Configure adds the provider configured client to the data source.
77+
func (d *dataSourceTFERegistryGPGKeys) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
78+
if req.ProviderData == nil {
79+
return
80+
}
81+
82+
client, ok := req.ProviderData.(ConfiguredClient)
83+
if !ok {
84+
resp.Diagnostics.AddError(
85+
"Unexpected Data Source Configure Type",
86+
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
87+
)
88+
89+
return
90+
}
91+
d.config = client
92+
}
93+
94+
// Read refreshes the Terraform state with the latest data.
95+
func (d *dataSourceTFERegistryGPGKeys) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
96+
var data modelTFERegistryGPGKeys
97+
98+
// Read Terraform configuration data into the model
99+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
100+
101+
if resp.Diagnostics.HasError() {
102+
return
103+
}
104+
105+
var organization string
106+
resp.Diagnostics.Append(d.config.dataOrDefaultOrganization(ctx, req.Config, &organization)...)
107+
108+
if resp.Diagnostics.HasError() {
109+
return
110+
}
111+
112+
options := tfe.GPGKeyListOptions{
113+
Namespaces: []string{organization},
114+
}
115+
tflog.Debug(ctx, "Listing private registry GPG keys")
116+
keyList, err := d.config.Client.GPGKeys.ListPrivate(ctx, options)
117+
if err != nil {
118+
resp.Diagnostics.AddError("Unable to list private registry GPG keys", err.Error())
119+
return
120+
}
121+
122+
data.ID = types.StringValue(organization)
123+
data.Organization = types.StringValue(organization)
124+
data.Keys = []modelTFERegistryGPGKey{}
125+
126+
for {
127+
for _, key := range keyList.Items {
128+
data.Keys = append(data.Keys, modelFromTFEVGPGKey(key))
129+
}
130+
131+
if keyList.CurrentPage >= keyList.TotalPages {
132+
break
133+
}
134+
options.PageNumber = keyList.NextPage
135+
136+
tflog.Debug(ctx, "Listing private registry GPG keys")
137+
keyList, err = d.config.Client.GPGKeys.ListPrivate(ctx, options)
138+
if err != nil {
139+
resp.Diagnostics.AddError("Unable to list private registry GPG keys", err.Error())
140+
return
141+
}
142+
}
143+
144+
// Save data into Terraform state
145+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
146+
}

0 commit comments

Comments
 (0)