Skip to content

Commit

Permalink
Merge pull request #699 from Juniper/688-freeform-tests
Browse files Browse the repository at this point in the history
fix tests and examples
  • Loading branch information
bwJuniper authored Jul 8, 2024
2 parents 3d8ae19 + f051463 commit 53c71ae
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 59 deletions.
22 changes: 14 additions & 8 deletions apstra/blueprint/freeform_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package blueprint
import (
"context"
"fmt"
"regexp"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
Expand Down Expand Up @@ -85,6 +86,7 @@ func (o FreeformSystem) DataSourceAttributes() map[string]dataSourceSchema.Attri
}

func (o FreeformSystem) ResourceAttributes() map[string]resourceSchema.Attribute {
hostnameRegexp := "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
return map[string]resourceSchema.Attribute{
"blueprint_id": resourceSchema.StringAttribute{
MarkdownDescription: "Apstra Blueprint ID.",
Expand All @@ -100,29 +102,33 @@ func (o FreeformSystem) ResourceAttributes() map[string]resourceSchema.Attribute
"name": resourceSchema.StringAttribute{
MarkdownDescription: "Freeform System name as shown in the Web UI.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile("^[a-zA-Z0-9.-_]+$"), "name may consist only of the following characters : a-zA-Z0-9.-_")},
},
"hostname": resourceSchema.StringAttribute{
MarkdownDescription: "Hostname of the Freeform System.",
Optional: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile(hostnameRegexp), "must match regex "+hostnameRegexp),
},
},
"deploy_mode": dataSourceSchema.StringAttribute{
"deploy_mode": resourceSchema.StringAttribute{
MarkdownDescription: "Deploy mode of the System",
Optional: true,
Validators: []validator.String{stringvalidator.OneOf(utils.AllNodeDeployModes()...)},
},
"type": dataSourceSchema.StringAttribute{
"type": resourceSchema.StringAttribute{
MarkdownDescription: fmt.Sprintf("Type of the System. Must be one of `%s` or `%s`", apstra.SystemTypeInternal, apstra.SystemTypeExternal),
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
Validators: []validator.String{stringvalidator.OneOf(apstra.SystemTypeInternal.String(), apstra.SystemTypeExternal.String())},
},
"device_profile_id": dataSourceSchema.StringAttribute{
"device_profile_id": resourceSchema.StringAttribute{
MarkdownDescription: "Device profile ID of the System",
Optional: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"system_id": dataSourceSchema.StringAttribute{
"system_id": resourceSchema.StringAttribute{
MarkdownDescription: "ID (usually serial number) of the Managed Device to associate with this System",
Optional: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
Expand Down Expand Up @@ -166,7 +172,7 @@ func (o *FreeformSystem) Request(ctx context.Context, diags *diag.Diagnostics) *
func (o *FreeformSystem) LoadApiData(ctx context.Context, in *apstra.FreeformSystemData, diags *diag.Diagnostics) {
o.Name = types.StringValue(in.Label)
o.Hostname = types.StringValue(in.Hostname)
o.Type = types.StringValue(string(rune(in.Type)))
o.Type = types.StringValue(in.Type.String())
o.DeviceProfileId = types.StringValue(string(in.DeviceProfileId))
o.SystemId = types.StringPointerValue((*string)(in.SystemId))
o.Tags = utils.SetValueOrNull(ctx, types.StringType, in.Tags, diags) // safe to ignore diagnostic here
Expand Down
1 change: 1 addition & 0 deletions apstra/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var (
ResourceDatacenterGenericSystem = resourceDatacenterGenericSystem{}
ResourceDatacenterRoutingZone = resourceDatacenterRoutingZone{}
ResourceFreeformConfigTemplate = resourceFreeformConfigTemplate{}
ResourceFreeformSystem = resourceFreeformSystem{}
ResourceFreeformPropertySet = resourceFreeformPropertySet{}
ResourceIpv4Pool = resourceIpv4Pool{}
ResourceTemplatePodBased = resourceTemplatePodBased{}
Expand Down
1 change: 0 additions & 1 deletion apstra/resource_freeform_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package tfapstra
import (
"context"
"fmt"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/blueprint"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
Expand Down
204 changes: 204 additions & 0 deletions apstra/resource_freeform_system_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//go:build integration

package tfapstra_test

import (
"context"
"fmt"
"github.com/Juniper/apstra-go-sdk/apstra"
"math/rand"
"strconv"
"testing"

tfapstra "github.com/Juniper/terraform-provider-apstra/apstra"
testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

const (
resourceFreeformSystemHcl = `
resource %q %q {
blueprint_id = %q
name = %q
device_profile_id = %q
hostname = %q
type = %q
deploy_mode = %s
tags = %s
}
`
)

type resourceFreeformSystem struct {
blueprintId string
name string
deviceProfileId string
hostname string
systemType string
deployMode string
tags []string
}

func (o resourceFreeformSystem) render(rType, rName string) string {
return fmt.Sprintf(resourceFreeformSystemHcl,
rType, rName,
o.blueprintId,
o.name,
o.deviceProfileId,
o.hostname,
o.systemType,
stringOrNull(o.deployMode),
stringSetOrNull(o.tags),
)
}

func (o resourceFreeformSystem) testChecks(t testing.TB, rType, rName string) testChecks {
result := newTestChecks(rType + "." + rName)

// required and computed attributes can always be checked
result.append(t, "TestCheckResourceAttrSet", "id")
result.append(t, "TestCheckResourceAttr", "blueprint_id", o.blueprintId)
result.append(t, "TestCheckResourceAttr", "name", o.name)
result.append(t, "TestCheckResourceAttr", "type", o.systemType)
result.append(t, "TestCheckResourceAttr", "device_profile_id", o.deviceProfileId)
result.append(t, "TestCheckResourceAttr", "hostname", o.hostname)
if o.deployMode != "" {
result.append(t, "TestCheckResourceAttr", "deploy_mode", o.deployMode)
}
if len(o.tags) > 0 {
result.append(t, "TestCheckResourceAttr", "tags.#", strconv.Itoa(len(o.tags)))
for _, tag := range o.tags {
result.append(t, "TestCheckTypeSetElemAttr", "tags.*", tag)
}
}

return result
}

func TestResourceFreeformSystem(t *testing.T) {
ctx := context.Background()
client := testutils.GetTestClient(t, ctx)
apiVersion := version.Must(version.NewVersion(client.ApiVersion()))

// create a blueprint
bp := testutils.FfBlueprintA(t, ctx)
// get a device profile
dpId, _ := bp.ImportDeviceProfile(ctx, "Juniper_vEX")

type testStep struct {
config resourceFreeformSystem
}
type testCase struct {
apiVersionConstraints version.Constraints
steps []testStep
}

testCases := map[string]testCase{
"start_with_no_tags": {
steps: []testStep{
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deviceProfileId: string(dpId),
systemType: apstra.SystemTypeInternal.String(),
},
},
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deviceProfileId: string(dpId),
deployMode: apstra.DeployModeDeploy.String(),
systemType: apstra.SystemTypeInternal.String(),
tags: randomStrings(rand.Intn(10)+2, 6),
},
},
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deployMode: apstra.DeployModeUndeploy.String(),
systemType: apstra.SystemTypeInternal.String(),
deviceProfileId: string(dpId),
},
},
},
},
"start_with_tags": {
steps: []testStep{
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deviceProfileId: string(dpId),
deployMode: apstra.DeployModeDeploy.String(),
systemType: apstra.SystemTypeInternal.String(),
tags: randomStrings(rand.Intn(10)+2, 6),
},
},
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deployMode: apstra.DeployModeUndeploy.String(),
systemType: apstra.SystemTypeExternal.String(),
deviceProfileId: string(dpId),
},
},
{
config: resourceFreeformSystem{
blueprintId: bp.Id().String(),
name: acctest.RandString(6),
hostname: acctest.RandString(6),
deviceProfileId: string(dpId),
deployMode: apstra.DeployModeDeploy.String(),
systemType: apstra.SystemTypeExternal.String(),
tags: randomStrings(rand.Intn(10)+2, 6),
},
},
},
},
}

resourceType := tfapstra.ResourceName(ctx, &tfapstra.ResourceFreeformSystem)

for tName, tCase := range testCases {
tName, tCase := tName, tCase
t.Run(tName, func(t *testing.T) {
t.Parallel()
if !tCase.apiVersionConstraints.Check(apiVersion) {
t.Skipf("test case %s requires Apstra %s", tName, tCase.apiVersionConstraints.String())
}

steps := make([]resource.TestStep, len(tCase.steps))
for i, step := range tCase.steps {
config := step.config.render(resourceType, tName)
checks := step.config.testChecks(t, resourceType, tName)

chkLog := checks.string()
stepName := fmt.Sprintf("test case %q step %d", tName, i+1)

t.Logf("\n// ------ begin config for %s ------\n%s// -------- end config for %s ------\n\n", stepName, config, stepName)
t.Logf("\n// ------ begin checks for %s ------\n%s// -------- end checks for %s ------\n\n", stepName, chkLog, stepName)

steps[i] = resource.TestStep{
Config: insecureProviderConfigHCL + config,
Check: resource.ComposeAggregateTestCheckFunc(checks.checks...),
}
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: steps,
})
})
}
}
28 changes: 14 additions & 14 deletions docs/data-sources/freeform_property_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ At least one optional attribute is required.
# first we create a property set so we can use a data source to retrieve it.
resource "apstra_freeform_property_set" "prop_set_foo" {
blueprint_id = "freeform_blueprint-5ba09d07"
blueprint_id = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
name = "prop_set_foo"
values = jsonencode({
values = jsonencode({
foo = "bar"
clown = 2
})
Expand All @@ -31,21 +31,21 @@ resource "apstra_freeform_property_set" "prop_set_foo" {
# here we retrieve the property_set.
data "apstra_freeform_property_set" "foo" {
blueprint_id = "freeform_blueprint-5ba09d07"
blueprint_id = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
name = apstra_freeform_property_set.prop_set_foo.name
}
#here we build an output block to display it.
output "foo" {value = data.apstra_freeform_property_set.foo}
#Output looks like this
#foo = {
# "blueprint_id" = "freeform_blueprint-5ba09d07"
# "id" = tostring(null)
# "name" = "prop_set_foo"
# "system_id" = tostring(null)
# "values" = "{\"clown\": 2, \"foo\": \"bar\"}"
#}
# here we build an output block to display it.
output "foo" { value = data.apstra_freeform_property_set.foo }
# Output looks like this
# foo = {
# "blueprint_id" = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
# "id" = tostring(null)
# "name" = "prop_set_foo"
# "system_id" = tostring(null)
# "values" = "{\"clown\": 2, \"foo\": \"bar\"}"
# }
```

<!-- schema generated by tfplugindocs -->
Expand Down
40 changes: 39 additions & 1 deletion docs/data-sources/freeform_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,45 @@ At least one optional attribute is required.
## Example Usage

```terraform
#
# This example defines a freeform system in a blueprint
resource "apstra_freeform_system" "test" {
blueprint_id = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
name = "test_system"
tags = ["a", "b", "c"]
type = "internal"
hostname = "testsystem"
deploy_mode = "deploy"
device_profile_id = "PtrWb4-VSwKiYRbCodk"
}
# here we retrieve the freeform system
data "apstra_freeform_system" "test" {
blueprint_id = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
id = apstra_freeform_system.test.id
}
# here we build an output bock to display it
output "test_System_out" {value = data.apstra_freeform_system.test}
# Output looks like this
# test_System_out = {
# "blueprint_id" = "043c5787-66e8-41c7-8925-c7e52fbe6e32"
# "deploy_mode" = tostring(null)
# "device_profile_id" = "PtrWb4-VSwKiYRbCodk"
# "hostname" = "systemfoo"
# "id" = "-63CYLAiWuAq0ljzX0Q"
# "name" = "test_system_foo"
# "system_id" = tostring(null)
# "tags" = toset([
# "a",
# "b",
# "c",
# ])
# "type" = "internal"
# }
```

<!-- schema generated by tfplugindocs -->
Expand Down
Loading

0 comments on commit 53c71ae

Please sign in to comment.