Skip to content

Commit

Permalink
Merge pull request #6 from alinabuzachis/tf_resource
Browse files Browse the repository at this point in the history
Add host resource
  • Loading branch information
alinabuzachis authored Feb 8, 2024
2 parents d6d72cf + cf0a7a7 commit d53ac59
Show file tree
Hide file tree
Showing 12 changed files with 1,272 additions and 23 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,22 @@ export AAP_PASSWORD=<your admin password>

Then you can run acceptance tests with `make testacc`.

Acceptance tests for the job resource will fail unless the following environment variables are also set:
Following environment variable must be exported in order to run acceptance tests for job, host and group resources:

```bash
export AAP_TEST_INVENTORY_ID=<the ID of an inventory in your AAP instance>
```

In addition, acceptance tests will fail unless the following environment variables are also set:

- for the job resource
```bash
export AAP_TEST_JOB_TEMPLATE_ID=<the ID of a job template in your AAP instance>
export AAP_TEST_JOB_INVENTORY_ID=<the ID of an inventory in your AAP instance>
```

- for the host resource
```bash
export AAP_TEST_GROUP_ID=<the ID of a group in your AAP instance>
```

**WARNING**: running acceptance tests for the job resource will launch several jobs for the specified job template. It's strongly recommended that you create a "check" type job template for testing to ensure the launched jobs do not deploy any actual infrastructure.
Expand Down
29 changes: 29 additions & 0 deletions examples/resources/host/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
terraform {
required_providers {
aap = {
source = "ansible/aap"
}
}
}

provider "aap" {
host = "https://localhost:8043"
username = "test"
password = "test"
insecure_skip_verify = true
}

resource "aap_host" "sample" {
inventory_id = 1
name = "tf_host"
variables = jsonencode(
{
"foo": "bar"
}
)
groups = [2, 3, 4]
}

output "host" {
value = aap_host.sample
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.19.0 // indirect
github.com/hashicorp/terraform-json v0.18.0 // indirect
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5O
github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 h1:b8vZYB/SkXJT4YPbT3trzE6oJ7dPyMy68+9dEDKsJjE=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0/go.mod h1:tP9BC3icoXBz72evMS5UTFvi98CiKhPdXF6yLs1wS8A=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc=
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg=
github.com/hashicorp/terraform-plugin-go v0.19.1 h1:lf/jTGTeELcz5IIbn/94mJdmnTjRYm6S6ct/JqCSr50=
github.com/hashicorp/terraform-plugin-go v0.19.1/go.mod h1:5NMIS+DXkfacX6o5HCpswda5yjkSYfKzn1Nfl9l+qRs=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
Expand Down
35 changes: 22 additions & 13 deletions internal/provider/group_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"io"
"net/http"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"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"
Expand Down Expand Up @@ -63,20 +65,28 @@ func (r *GroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
stringplanmodifier.UseStateForUnknown(),
},
},
"id": schema.Int64Attribute{
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"variables": schema.StringAttribute{
Optional: true,
Optional: true,
CustomType: jsontypes.NormalizedType{},
},
},
}
}

// GroupResourceModel maps the resource schema data.
type GroupResourceModel struct {
InventoryId types.Int64 `tfsdk:"inventory_id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
URL types.String `tfsdk:"group_url"`
Variables types.String `tfsdk:"variables"`
InventoryId types.Int64 `tfsdk:"inventory_id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
URL types.String `tfsdk:"group_url"`
Variables jsontypes.Normalized `tfsdk:"variables"`
Id types.Int64 `tfsdk:"id"`
}

func (d *GroupResourceModel) GetURL() string {
Expand All @@ -89,6 +99,7 @@ func (d *GroupResourceModel) GetURL() string {
func (d *GroupResourceModel) CreateRequestBody() ([]byte, diag.Diagnostics) {
body := make(map[string]interface{})
var diags diag.Diagnostics

// Inventory id
body["inventory"] = d.InventoryId.ValueInt64()

Expand Down Expand Up @@ -122,23 +133,22 @@ func (d *GroupResourceModel) ParseHttpResponse(body []byte) error {
return err
}

invid := int64(result["inventory"].(float64))
d.InventoryId = types.Int64Value(invid)
d.Name = types.StringValue(result["name"].(string))
d.Id = types.Int64Value(int64(result["id"].(float64)))
d.InventoryId = types.Int64Value(int64(result["inventory"].(float64)))
d.URL = types.StringValue(result["url"].(string))

if result["description"] != "" {
d.Description = types.StringValue(result["description"].(string))
} else {
d.Description = types.StringNull()
}
d.URL = types.StringValue(result["url"].(string))

if result["variables"] != "" {
d.Variables = types.StringValue(result["variables"].(string))
d.Variables = jsontypes.NewNormalizedValue(result["variables"].(string))
} else {
d.Variables = types.StringNull()
d.Variables = jsontypes.NewNormalizedNull()
}

return nil
}

Expand Down Expand Up @@ -261,7 +271,6 @@ func (r GroupResource) UpdateGroup(data GroupResourceModelInterface) diag.Diagno
req_data = bytes.NewReader(req_body)
}
resp, body, err := r.client.doRequest(http.MethodPut, data.GetURL(), req_data)

if err != nil {
diags.AddError("Body JSON Marshal Error", err.Error())
return diags
Expand Down
176 changes: 170 additions & 6 deletions internal/provider/group_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ package provider
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stretchr/testify/assert"
)

Expand All @@ -18,11 +25,12 @@ func TestGroupParseHttpResponse(t *testing.T) {
InventoryId: types.Int64Value(1),
Name: types.StringValue("group1"),
Description: types.StringNull(),
Variables: jsontypes.NewNormalizedNull(),
URL: types.StringValue("/api/v2/groups/24/"),
Variables: types.StringNull(),
Id: types.Int64Value(1),
}
g := GroupResourceModel{}
body := []byte(`{"inventory": 1, "name": "group1", "url": "/api/v2/groups/24/", "description": "", "variables": ""}`)
body := []byte(`{"inventory": 1, "name": "group1", "url": "/api/v2/groups/24/", "id": 1, "description": "", "variables": ""}`)
err := g.ParseHttpResponse(body)
assert.NoError(t, err)
if expected != g {
Expand All @@ -35,10 +43,11 @@ func TestGroupParseHttpResponse(t *testing.T) {
Name: types.StringValue("group1"),
URL: types.StringValue("/api/v2/groups/24/"),
Description: types.StringNull(),
Variables: types.StringValue("{\"ansible_network_os\":\"ios\"}"),
Variables: jsontypes.NewNormalizedValue("{\"ansible_network_os\":\"ios\"}"),
Id: types.Int64Value(1),
}
g := GroupResourceModel{}
body := []byte(`{"inventory": 1, "name": "group1", "url": "/api/v2/groups/24/", "description": "", "variables": "{\"ansible_network_os\":\"ios\"}"}`)
body := []byte(`{"inventory": 1, "name": "group1", "url": "/api/v2/groups/24/", "variables": "{\"ansible_network_os\":\"ios\"}", "id": 1, "description": ""}`)
err := g.ParseHttpResponse(body)
assert.NoError(t, err)
if expected != g {
Expand Down Expand Up @@ -84,7 +93,7 @@ func TestGroupCreateRequestBody(t *testing.T) {
InventoryId: basetypes.NewInt64Value(5),
Name: types.StringValue("group1"),
URL: types.StringValue("/api/v2/groups/24/"),
Variables: types.StringValue("{\"ansible_network_os\":\"ios\"}"),
Variables: jsontypes.NewNormalizedValue("{\"ansible_network_os\":\"ios\"}"),
Description: types.StringValue("New Group"),
}
body := []byte(`{"name": "group1", "inventory": 5,
Expand All @@ -102,7 +111,7 @@ func TestGroupCreateRequestBody(t *testing.T) {
InventoryId: basetypes.NewInt64Value(5),
Name: types.StringValue("group1"),
URL: types.StringValue("/api/v2/groups/24/"),
Variables: types.StringValue(
Variables: jsontypes.NewNormalizedValue(
"{\"ansible_network_os\":\"ios\",\"ansible_connection\":\"network_cli\",\"ansible_ssh_user\":\"ansible\",\"ansible_ssh_pass\":\"ansi\"}",
),
Description: types.StringValue("New Group"),
Expand Down Expand Up @@ -241,3 +250,158 @@ func TestReadGroup(t *testing.T) {
}
})
}

// Acceptance tests

func getGroupResourceFromStateFile(s *terraform.State) (map[string]interface{}, error) {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aap_group" {
continue
}
groupURL := rs.Primary.Attributes["group_url"]
body, err := testGetResource(groupURL)
if err != nil {
return nil, err
}

var result map[string]interface{}
err = json.Unmarshal(body, &result)
return result, err
}
return nil, fmt.Errorf("Group resource not found from state file")
}

func testAccCheckGroupExists(s *terraform.State) error {
_, err := getGroupResourceFromStateFile(s)
return err
}

func testAccCheckGroupValues(urlBefore *string, groupInventoryId string, groupDescription string,
groupName string, groupVariables string, shouldDiffer bool) func(s *terraform.State) error {
return func(s *terraform.State) error {
var groupURL, description, inventoryId, name, variables string
var differ = false
for _, rs := range s.RootModule().Resources {
if rs.Type != "aap_group" {
continue
}
groupURL = rs.Primary.Attributes["group_url"]
description = rs.Primary.Attributes["description"]
inventoryId = rs.Primary.Attributes["inventory_id"]
name = rs.Primary.Attributes["name"]
variables = rs.Primary.Attributes["variables"]
}
if len(groupURL) == 0 {
return fmt.Errorf("Group resource not found from state file")
}
if len(*urlBefore) == 0 {
*urlBefore = groupURL
return nil
}

if description != groupDescription || inventoryId != groupInventoryId || name != groupName ||
variables != groupVariables || groupURL != *urlBefore {
differ = true
}

if shouldDiffer && differ {
return fmt.Errorf("Group resources are equal while expecting them to differ. "+
"Before [URL: %s, Description: %s, Inventory ID: %s, Name: %s, Variables: %s] "+
"After [URL: %s, Description: %s, Inventory ID: %s, Name: %s, Variables: %s]",
*urlBefore, description, inventoryId, name, variables,
groupURL, groupDescription, groupInventoryId, groupName, groupVariables)
} else if !shouldDiffer && differ {
return fmt.Errorf("Group resources are equal while expecting them to not differ. "+
"Before [URL: %s, Description: %s, Inventory ID: %s, Name: %s, Variables: %s] "+
"After [URL: %s, Description: %s, Inventory ID: %s, Name: %s, Variables: %s]",
*urlBefore, description, inventoryId, name, variables,
groupURL, groupDescription, groupInventoryId, groupName, groupVariables)
}

return nil
}
}

func testAccGroupResourcePreCheck(t *testing.T) {
// ensure provider requirements
testAccPreCheck(t)

requiredAAPGroupEnvVars := []string{
"AAP_TEST_INVENTORY_ID",
}

for _, key := range requiredAAPGroupEnvVars {
if v := os.Getenv(key); v == "" {
t.Fatalf("'%s' environment variable must be set when running acceptance tests for group resource", key)
}
}
}

func TestAccAAPGroup_basic(t *testing.T) {
var groupURLBefore string
var description = "A test group"
var variables = "{\"foo\": \"bar\"}"
groupInventoryId := os.Getenv("AAP_TEST_INVENTORY_ID")
randomName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
updatedName := "updated" + randomName

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccGroupResourcePreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: testAccBasicGroup(randomName, groupInventoryId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGroupExists,
testAccCheckGroupValues(&groupURLBefore, groupInventoryId, "", randomName, "", false),
resource.TestCheckResourceAttr("aap_group.test", "name", randomName),
resource.TestCheckResourceAttr("aap_group.test", "inventory_id", groupInventoryId),
resource.TestMatchResourceAttr("aap_group.test", "group_url", regexp.MustCompile("^/api/v2/groups/[0-9]*/$")),
),
},
// Create and Read testing with same parameters
{
Config: testAccBasicGroup(randomName, groupInventoryId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGroupExists,
testAccCheckGroupValues(&groupURLBefore, groupInventoryId, "", randomName, "", false),
resource.TestCheckResourceAttr("aap_group.test", "name", randomName),
resource.TestCheckResourceAttr("aap_group.test", "inventory_id", groupInventoryId),
resource.TestMatchResourceAttr("aap_group.test", "group_url", regexp.MustCompile("^/api/v2/groups/[0-9]*/$")),
),
},
// Update and Read testing
{
Config: testAccUpdateGroupComplete(updatedName, groupInventoryId),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckGroupExists,
testAccCheckGroupValues(&groupURLBefore, groupInventoryId, description, updatedName, variables, false),
resource.TestCheckResourceAttr("aap_group.test", "name", updatedName),
resource.TestCheckResourceAttr("aap_group.test", "inventory_id", groupInventoryId),
resource.TestCheckResourceAttr("aap_group.test", "description", description),
resource.TestCheckResourceAttr("aap_group.test", "variables", variables),
resource.TestMatchResourceAttr("aap_group.test", "group_url", regexp.MustCompile("^/api/v2/groups/[0-9]*/$")),
),
},
},
})
}

func testAccBasicGroup(name, groupInventoryId string) string {
return fmt.Sprintf(`
resource "aap_group" "test" {
name = "%s"
inventory_id = %s
}`, name, groupInventoryId)
}

func testAccUpdateGroupComplete(name, groupInventoryId string) string {
return fmt.Sprintf(`
resource "aap_group" "test" {
name = "%s"
inventory_id = %s
description = "A test group"
variables = "{\"foo\": \"bar\"}"
}`, name, groupInventoryId)
}
Loading

0 comments on commit d53ac59

Please sign in to comment.