Skip to content

Commit e632a0e

Browse files
committed
Refactoring and cleanup of new resources and data sources
1 parent 8167cb3 commit e632a0e

10 files changed

+341
-204
lines changed

internal/provider/client.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type ProviderHTTPClient interface {
2525
getApiEndpoint() string
2626
}
2727

28+
// Client -
29+
type AAPClient struct {
30+
HostURL string
31+
Username *string
32+
Password *string
33+
httpClient *http.Client
34+
ApiEndpoint string
35+
}
36+
2837
type AAPApiEndpointResponse struct {
2938
Apis struct {
3039
Controller string `json:"controller"`
@@ -68,15 +77,6 @@ func readApiEndpoint(client ProviderHTTPClient) (string, diag.Diagnostics) {
6877
return response.CurrentVersion, diags
6978
}
7079

71-
// Client -
72-
type AAPClient struct {
73-
HostURL string
74-
Username *string
75-
Password *string
76-
httpClient *http.Client
77-
ApiEndpoint string
78-
}
79-
8080
// NewClient - create new AAPClient instance
8181
func NewClient(host string, username *string, password *string, insecureSkipVerify bool, timeout int64) (*AAPClient, diag.Diagnostics) {
8282
hostURL, _ := url.JoinPath(host, "/")

internal/provider/customtypes/aapcustomstring_type_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ func TestAAPCustomStringTypeValueFromTerraform(t *testing.T) {
8282
expectation attr.Value
8383
expectedErr string
8484
}{
85+
"yaml string no newline": {
86+
in: tftypes.NewValue(tftypes.String, `<<-EOT
87+
os: Linux
88+
automation: ansible-devel
89+
EOT`),
90+
expectation: customtypes.NewAAPCustomStringValue(`<<-EOT
91+
os: Linux
92+
automation: ansible-devel
93+
EOT2`),
94+
},
8595
"true": {
8696
in: tftypes.NewValue(tftypes.String, `{"hello":"world"}`),
8797
expectation: customtypes.NewAAPCustomStringValue(`{"hello":"world"}`),
@@ -119,7 +129,7 @@ func TestAAPCustomStringTypeValueFromTerraform(t *testing.T) {
119129
t.Fatalf("Expected error to be %q, didn't get an error", testCase.expectedErr)
120130
}
121131
if !got.Equal(testCase.expectation) {
122-
t.Errorf("Expected %+v, got %+v", testCase.expectation, got)
132+
t.Errorf("Expected %d, got %d", testCase.expectation, got)
123133
}
124134
if testCase.expectation.IsNull() != testCase.in.IsNull() {
125135
t.Errorf("Expected null-ness match: expected %t, got %t", testCase.expectation.IsNull(), testCase.in.IsNull())

internal/provider/inventory_data_source.go

Lines changed: 102 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,50 @@ package provider
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"path"
89
"strings"
910

1011
"github.com/ansible/terraform-provider-aap/internal/provider/customtypes"
12+
"github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator"
1113
"github.com/hashicorp/terraform-plugin-framework/datasource"
1214
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
1315
"github.com/hashicorp/terraform-plugin-framework/diag"
16+
tfpath "github.com/hashicorp/terraform-plugin-framework/path"
17+
"github.com/hashicorp/terraform-plugin-framework/provider"
1418
"github.com/hashicorp/terraform-plugin-framework/types"
1519
)
1620

21+
// inventoryDataSourceModel maps the data source schema data.
22+
type InventoryDataSourceModel struct {
23+
Id types.Int64 `tfsdk:"id"`
24+
Organization types.Int64 `tfsdk:"organization"`
25+
OrganizationName types.String `tfsdk:"organization_name"`
26+
Url types.String `tfsdk:"url"`
27+
NamedUrl types.String `tfsdk:"named_url"`
28+
Name types.String `tfsdk:"name"`
29+
Description types.String `tfsdk:"description"`
30+
Variables customtypes.AAPCustomStringValue `tfsdk:"variables"`
31+
}
32+
33+
// InventoryDataSource is the data source implementation.
34+
type InventoryDataSource struct {
35+
client ProviderHTTPClient
36+
}
37+
1738
// Ensure the implementation satisfies the expected interfaces.
1839
var (
19-
_ datasource.DataSource = &InventoryDataSource{}
20-
_ datasource.DataSourceWithConfigure = &InventoryDataSource{}
40+
_ datasource.DataSource = &InventoryDataSource{}
41+
_ datasource.DataSourceWithConfigure = &InventoryDataSource{}
42+
_ datasource.DataSourceWithConfigValidators = &InventoryDataSource{}
2143
)
2244

2345
// NewInventoryDataSource is a helper function to simplify the provider implementation.
2446
func NewInventoryDataSource() datasource.DataSource {
2547
return &InventoryDataSource{}
2648
}
2749

28-
// inventoryDataSource is the data source implementation.
29-
type InventoryDataSource struct {
30-
client *AAPClient
31-
}
32-
3350
// Metadata returns the data source type name.
3451
func (d *InventoryDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
3552
resp.TypeName = req.ProviderTypeName + "_inventory"
@@ -40,16 +57,16 @@ func (d *InventoryDataSource) Schema(_ context.Context, _ datasource.SchemaReque
4057
resp.Schema = schema.Schema{
4158
Attributes: map[string]schema.Attribute{
4259
"id": schema.Int64Attribute{
43-
Optional: true,
60+
Optional: true,
4461
Description: "Inventory id",
4562
},
4663
"organization": schema.Int64Attribute{
47-
Computed: true,
64+
Computed: true,
4865
Description: "Identifier for the organization to which the inventory belongs",
4966
},
5067
"organization_name": schema.StringAttribute{
51-
Computed: true,
52-
Optional: true,
68+
Computed: true,
69+
Optional: true,
5370
Description: "The name for the organization to which the inventory belongs",
5471
},
5572
"url": schema.StringAttribute{
@@ -62,7 +79,7 @@ func (d *InventoryDataSource) Schema(_ context.Context, _ datasource.SchemaReque
6279
},
6380
"name": schema.StringAttribute{
6481
Computed: true,
65-
Optional: true,
82+
Optional: true,
6683
Description: "Name of the inventory",
6784
},
6885
"description": schema.StringAttribute{
@@ -90,18 +107,9 @@ func (d *InventoryDataSource) Read(ctx context.Context, req datasource.ReadReque
90107
return
91108
}
92109

93-
//Here is where we can get the "named" inventory, which is "Inventory Name"++"Organization Name" to derive uniqueness
94-
//we will take precedence if the Id is set to use that over the named_url attempt.
95-
96-
resourceURL := ""
97-
98-
if state.Id.String() != "<null>" {
99-
resourceURL = path.Join(d.client.getApiEndpoint(), "inventories", state.Id.String())
100-
} else if state.Name.String() != "<null>" && state.OrganizationName.String() != "<null>"{
101-
namedUrl := strings.Join([]string{state.Name.String()[1 : len(state.Name.String()) - 1], "++", state.OrganizationName.String()[1 : len(state.OrganizationName.String()) - 1]}, "")
102-
resourceURL = path.Join(d.client.getApiEndpoint(), "inventories", namedUrl)
103-
} else {
104-
resp.Diagnostics.AddError("Minimal Data Not Supplied", "Require [id] or [name and organization_name]")
110+
resourceURL, err := state.ResourceUrlFromParameters(d)
111+
if err != nil {
112+
resp.Diagnostics.AddError("Minimal Data Not Supplied", "Expected either [id] or [name + organization_name] pair")
105113
return
106114
}
107115

@@ -143,19 +151,55 @@ func (d *InventoryDataSource) Configure(_ context.Context, req datasource.Config
143151
d.client = client
144152
}
145153

146-
// inventoryDataSourceModel maps the data source schema data.
147-
type InventoryDataSourceModel struct {
148-
Id types.Int64 `tfsdk:"id"`
149-
Organization types.Int64 `tfsdk:"organization"`
150-
OrganizationName types.String `tfsdk:"organization_name"`
151-
Url types.String `tfsdk:"url"`
152-
NamedUrl types.String `tfsdk:"named_url"`
153-
Name types.String `tfsdk:"name"`
154-
Description types.String `tfsdk:"description"`
155-
Variables customtypes.AAPCustomStringValue `tfsdk:"variables"`
154+
func (d *InventoryDataSource) ConfigValidators(_ context.Context) []datasource.ConfigValidator {
155+
// You have at least an id or a name + organization_name pair
156+
return []datasource.ConfigValidator{
157+
datasourcevalidator.Any(
158+
datasourcevalidator.AtLeastOneOf(
159+
tfpath.MatchRoot("id")),
160+
datasourcevalidator.RequiredTogether(
161+
tfpath.MatchRoot("name"),
162+
tfpath.MatchRoot("organization_name")),
163+
),
164+
}
165+
}
166+
167+
func (d *InventoryDataSource) ValidateConfig(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
168+
var data InventoryDataSourceModel
169+
170+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
171+
172+
if resp.Diagnostics.HasError() {
173+
return
174+
}
175+
176+
if !data.Id.IsNull() {
177+
return
178+
}
179+
180+
if !data.Name.IsNull() && !data.OrganizationName.IsNull() {
181+
return
182+
}
183+
184+
if data.Id.IsNull() && data.Name.IsNull() {
185+
resp.Diagnostics.AddAttributeWarning(
186+
tfpath.Root("id"),
187+
"Missing Atribute Configuration",
188+
"Expected either [id] or [name + organization_name] pair",
189+
)
190+
}
191+
192+
if !data.Name.IsNull() && data.OrganizationName.IsNull() {
193+
resp.Diagnostics.AddAttributeWarning(
194+
tfpath.Root("organization_name"),
195+
"Missing Attribute Configuration",
196+
"Expected organization_name to be configured with name.",
197+
)
198+
}
199+
156200
}
157201

158-
func (d *InventoryDataSourceModel) ParseHttpResponse(body []byte) diag.Diagnostics {
202+
func (dm *InventoryDataSourceModel) ParseHttpResponse(body []byte) diag.Diagnostics {
159203
var diags diag.Diagnostics
160204

161205
// Unmarshal the JSON response
@@ -167,14 +211,29 @@ func (d *InventoryDataSourceModel) ParseHttpResponse(body []byte) diag.Diagnosti
167211
}
168212

169213
// Map response to the inventory datesource schema
170-
d.Id = types.Int64Value(apiInventory.Id)
171-
d.Organization = types.Int64Value(apiInventory.Organization)
172-
d.OrganizationName = types.StringValue(apiInventory.SummaryFields.Organization.Name)
173-
d.Url = types.StringValue(apiInventory.Url)
174-
d.NamedUrl = types.StringValue(apiInventory.Related.NamedUrl)
175-
d.Name = ParseStringValue(apiInventory.Name)
176-
d.Description = ParseStringValue(apiInventory.Description)
177-
d.Variables = ParseAAPCustomStringValue(apiInventory.Variables)
214+
dm.Id = types.Int64Value(apiInventory.Id)
215+
dm.Organization = types.Int64Value(apiInventory.Organization)
216+
dm.OrganizationName = types.StringValue(apiInventory.SummaryFields.Organization.Name)
217+
dm.Url = types.StringValue(apiInventory.Url)
218+
dm.NamedUrl = types.StringValue(apiInventory.Related.NamedUrl)
219+
dm.Name = ParseStringValue(apiInventory.Name)
220+
dm.Description = ParseStringValue(apiInventory.Description)
221+
dm.Variables = ParseAAPCustomStringValue(apiInventory.Variables)
178222

179223
return diags
180224
}
225+
226+
// ResourceUrlFromParameters Given the provided parameters and return the appropriate resource url
227+
func (dm *InventoryDataSourceModel) ResourceUrlFromParameters(datasource *InventoryDataSource) (string, error) {
228+
//Here is where we can get the "named" inventory, which is "Inventory Name"++"Organization Name" to derive uniqueness
229+
//We will take precedence if the Id is set to use that over the named_url attempt.
230+
if !dm.Id.IsNull() {
231+
return path.Join(datasource.client.getApiEndpoint(), "inventories", dm.Id.String()), nil
232+
} else if !dm.Name.IsNull() && !dm.OrganizationName.IsNull() {
233+
namedUrl := strings.Join([]string{dm.Name.String()[1 : len(dm.Name.String())-1], "++", dm.OrganizationName.String()[1 : len(dm.OrganizationName.String())-1]}, "")
234+
return path.Join(datasource.client.getApiEndpoint(), "inventories", namedUrl), nil
235+
} else {
236+
return types.StringNull().String(), errors.New("invalid inventory lookup parameters")
237+
}
238+
239+
}

internal/provider/inventory_data_source_test.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ func TestInventoryDataSourceParseHttpResponse(t *testing.T) {
5555
name: "missing values",
5656
input: []byte(`{"id":1,"organization":2,"url":"/inventories/1/"}`),
5757
expected: InventoryDataSourceModel{
58-
Id: types.Int64Value(1),
59-
Organization: types.Int64Value(2),
60-
Url: types.StringValue("/inventories/1/"),
61-
Name: types.StringNull(),
62-
Description: types.StringNull(),
63-
Variables: customtypes.NewAAPCustomStringNull(),
58+
Id: types.Int64Value(1),
59+
Organization: types.Int64Value(2),
60+
OrganizationName: types.StringValue(""),
61+
Url: types.StringValue("/inventories/1/"),
62+
NamedUrl: types.StringValue(""),
63+
Name: types.StringNull(),
64+
Description: types.StringNull(),
65+
Variables: customtypes.NewAAPCustomStringNull(),
6466
},
6567
errors: diag.Diagnostics{},
6668
},
@@ -70,12 +72,14 @@ func TestInventoryDataSourceParseHttpResponse(t *testing.T) {
7072
`{"id":1,"organization":2,"url":"/inventories/1/","name":"my inventory","description":"My Test Inventory","variables":"{\"foo\":\"bar\"}"}`,
7173
),
7274
expected: InventoryDataSourceModel{
73-
Id: types.Int64Value(1),
74-
Organization: types.Int64Value(2),
75-
Url: types.StringValue("/inventories/1/"),
76-
Name: types.StringValue("my inventory"),
77-
Description: types.StringValue("My Test Inventory"),
78-
Variables: customtypes.NewAAPCustomStringValue("{\"foo\":\"bar\"}"),
75+
Id: types.Int64Value(1),
76+
Organization: types.Int64Value(2),
77+
OrganizationName: types.StringValue(""),
78+
Url: types.StringValue("/inventories/1/"),
79+
NamedUrl: types.StringValue(""),
80+
Name: types.StringValue("my inventory"),
81+
Description: types.StringValue("My Test Inventory"),
82+
Variables: customtypes.NewAAPCustomStringValue("{\"foo\":\"bar\"}"),
7983
},
8084
errors: diag.Diagnostics{},
8185
},

0 commit comments

Comments
 (0)