Skip to content

Commit

Permalink
Add implementation for "secret" datasource
Browse files Browse the repository at this point in the history
Update the boiler plate to actually implement the required API calls.
Add tests, examples, etc.

These changes mean that we can now read existing "secrets" and
add them to, eg, hypervisor server definitions.

```
data "hpegl_pc_secret" "my_secret" {
  name = "mysecret1"
}
.
.
.
esx_root_credential_id  = data.hpegl_pc_secret.my_secret.id
```
  • Loading branch information
stuart-mclaren-hpe committed Dec 16, 2024
1 parent 07c1864 commit d966ad0
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 2 deletions.
6 changes: 5 additions & 1 deletion examples/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ data "hpegl_pc_system" "my_system" {
name = "array-5305-grp"
}

data "hpegl_pc_secret" "my_secret" {
name = "mysecret1"
}

resource "hpegl_pc_datastore" "my_datastore" {
name = "mclaren-ds19"
datastore_type = "VMFS"
Expand All @@ -29,7 +33,7 @@ resource "hpegl_pc_hypervisor_cluster" "my_hypervisor_cluster" {

resource "hpegl_pc_server" "test" {
system_id = "126fd201-9e6e-5e31-9ffb-a766265b1fd3"
esx_root_credential_id = "cccfcad1-85b7-4162-b16e-f7cadc2c46b5"
esx_root_credential_id = data.hpegl_pc_secret.my_secret.id
ilo_admin_credential_id = "dddfcad1-85b7-4162-b16e-f7cadc2c46b5"
hypervisor_host = {
hypervisor_cluster_id = "acd4daea-e5e3-5f35-8be3-ce4a4b6d946c"
Expand Down
2 changes: 1 addition & 1 deletion examples/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ provider "hpegl" {

http_dump = true
# poll_interval in seconds
poll_interval = 10.0
poll_interval = 1.0
max_polls = 10
}
}
89 changes: 89 additions & 0 deletions internal/datasources/secret/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ package secret

import (
"context"
"errors"
"fmt"

"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/client"
"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/constants"

"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/sdk/dataservices/dataservices"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

Expand Down Expand Up @@ -55,6 +61,59 @@ func (s *DataSource) Configure(
s.client = req.ProviderData.(*client.PCBeClient)
}

func createNameFilter(name string) string {
return constants.NameFilter + name
}

func getSecretByName(
ctx context.Context,
client client.PCBeClient,
name string,
) (dataservices.V1beta1SecretsGetResponse_itemsable, error) {
dataServicesClient, _, err := client.NewDataServicesClient(ctx)
if err != nil {
tflog.Error(ctx, "failed to create client")

return nil, err
}

filter := createNameFilter(name)
qp := dataservices.V1beta1SecretsRequestBuilderGetQueryParameters{}
qp.Filter = &filter
grc := &dataservices.V1beta1SecretsRequestBuilderGetRequestConfiguration{}
grc.QueryParameters = &qp
secrets, err := dataServicesClient.
DataServices().
V1beta1().
Secrets().
GetAsSecretsGetResponse(ctx, grc)

if err != nil {
tflog.Error(ctx, "failed to get secret by name: "+name)

return nil, err
}

if secrets.GetTotal() == nil {
msg := "total is nil"
tflog.Error(ctx, msg)

return nil, errors.New(msg)
}

total := *(secrets.GetTotal())
if total != 1 {
msg := fmt.Sprintf(
"required 1 secret with name %s, got %d secrets", name, total,
)
tflog.Error(ctx, msg)

return nil, errors.New(msg)
}

return secrets.GetItems()[0], err
}

func (s *DataSource) Read(
ctx context.Context,
req datasource.ReadRequest,
Expand All @@ -68,5 +127,35 @@ func (s *DataSource) Read(
return
}

client := *s.client
secret, err := getSecretByName(ctx, client, data.Name.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"error reading secret",
"unexpected error: "+err.Error(),
)

return
}
name := secret.GetName()
if name == nil {
resp.Diagnostics.AddError(
"error reading secret",
"secret name is nil",
)

return
}

id := secret.GetId()
if id == nil {
resp.Diagnostics.AddError(
"error reading system",
"secret id is nil",
)

return
}
data.Id = types.StringValue(id.String())
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
12 changes: 12 additions & 0 deletions internal/datasources/secret/simulation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
//go:build simulation

package secret

import (
"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/simulator"
)

func init() {
simulator.Secret()
}
2 changes: 2 additions & 0 deletions internal/provider/experimental_datasources.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package provider

import (
"context"
"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/datasources/secret"
"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/datasources/system"
"github.com/hashicorp/terraform-plugin-framework/datasource"
)
Expand All @@ -16,5 +17,6 @@ func (p *PCBeProvider) DataSources(
) []func() datasource.DataSource {
return []func() datasource.DataSource{
system.NewDataSource,
secret.NewDataSource,
}
}
1 change: 1 addition & 0 deletions internal/simulator/fixtures/secrets/getByName.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"count":1,"total":1,"offset":0,"items":[{"customerId":"123456da3faa11efb5afca2247c626ef","service":"Private Cloud Business Edition","id":"cccfcad1-85b7-4162-b16e-f7cadc2c46b5","name":"mysecret1","type":"/data-services/secret","resourceUri":"/data-services/v1beta1/secrets/cccfcad1-85b7-4162-b16e-f7cadc2c46b5","generation":5,"updatedAt":"2024-12-04T14:39:23.912452+00:00","createdAt":"2024-10-22T15:56:55.824878+00:00","groups":[{"id":"123456da3faa11efb5afca2247c626ef","name":"Default Group"}],"label":"PCBE-NIMBLE-SYSTEM","domain":{"name":"CONFIGURATION","properties":{"CREATED_BY":"[email protected]","LAST_UPDATED_BY":"[email protected]","LIFECYCLE_EVENT_KEY":"pluginSecrets"}},"classifier":{"name":"SECRET"},"subclassifier":{"name":"BASIC_AUTH","properties":{"USERNAME":"admin"}},"status":"NOT_APPLIED","statusUpdatedAt":"2024-12-04T14:39:23.912452+00:00","assignmentsCount":0,"policy":"pcbeBasicAuth.nimbleSystem"}]}
39 changes: 39 additions & 0 deletions internal/simulator/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// (C) Copyright 2024 Hewlett Packard Enterprise Development LP

package simulator

import (
_ "embed"

"github.com/h2non/gock"
)

// TODO: (API) Replace fake data with real data when possible
//
//go:embed fixtures/secrets/getByName.json
var secretByName string

func simulateSecretGetByName() {
secretName := "mysecret1"

gock.New("http://localhost").
Get("/data-services/v1beta1/secrets").
MatchParam("filter", "name eq "+secretName).
MatchHeader("Authorization", "Bearer abcdefghijklmnopqrstuvwxyz-0123456789").
Reply(200).
SetHeader("Content-Type", "application/json").
BodyString(secretByName)

gock.New("http://localhost").
Get("/data-services/v1beta1/secrets").
MatchParam("filter", "name eq "+secretName).
MatchHeader("Authorization", "Bearer expired-token").
Reply(401).
SetHeader("Content-Type", "text/plain").
BodyString("Jwt is not in the form of Header.Payload.Signature " +
"with two dots and 3 sections")
}

func Secret() {
simulateSecretGetByName()
}
167 changes: 167 additions & 0 deletions test/secret/secret_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// (C) Copyright 2024 Hewlett Packard Enterprise Development LP

package acceptance

import (
"fmt"
"regexp"
"testing"

"github.com/HewlettPackard/hpegl-pcbe-terraform-resources/internal/provider"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

const (
providerConfig = `
terraform {
required_providers {
hpegl = {
source = "github.com/HewlettPackard/hpegl-pcbe-terraform-resources"
}
}
}
provider "hpegl" {
pc {
host = "http://localhost:8080"
token = "abcdefghijklmnopqrstuvwxyz-0123456789"
http_dump = true
poll_interval = 0.001
max_polls = 10
}
}
`
)

var simulation = false

var testAccProtoV6ProviderFactories = map[string]func() (
tfprotov6.ProviderServer, error,
){
"scaffolding": providerserver.NewProtocol6WithError(
provider.New("test")(),
),
}

func checkUUIDAttr(resource string, attr string) func(*terraform.State) error {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resource]
if !ok {
return fmt.Errorf("resource not found: %s", resource)
}

attrValue := rs.Primary.Attributes[attr]
_, err := uuid.Parse(attrValue)

return err
}
}

func TestAccSecretDataSource(t *testing.T) {
config := providerConfig + `
data "hpegl_pc_secret" "test" {
name = "mysecret1"
}
`

checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttr(
"data.hpegl_pc_secret.test",
"name",
"mysecret1",
),
checkUUIDAttr("data.hpegl_pc_secret.test", "id"),
}

if simulation {
// In simulation mode the ID value is known in advance
checks = append(checks,
resource.TestCheckResourceAttr(
"data.hpegl_pc_secret.test",
"id",
"cccfcad1-85b7-4162-b16e-f7cadc2c46b5",
),
resource.TestCheckResourceAttr(
"data.hpegl_pc_secret.test",
"name",
"mysecret1",
),
)
}

checkFn := resource.ComposeAggregateTestCheckFunc(checks...)

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: config,
Check: checkFn,
},
},
})
}

func TestAccSecretDataSourceMissingName(t *testing.T) {
config := providerConfig + `
data "hpegl_pc_secret" "test" {
}
`
expected := `The argument "name" is required, but no definition was found.`
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile(expected),
PlanOnly: true,
},
},
})
}

func TestAccSystemDataSourceBadAuth(t *testing.T) {
providerConfigBadAuth := `
terraform {
required_providers {
hpegl = {
source = "github.com/HewlettPackard/hpegl-pcbe-terraform-resources"
}
}
}
provider "hpegl" {
pc {
host = "http://localhost:8080"
token = "expired-token"
http_dump = true
poll_interval = 0.001
max_polls = 10
}
}
`
config := providerConfigBadAuth + `
data "hpegl_pc_secret" "test" {
name = "mysecret1"
}
`
// TODO: return more informative error message - including
// http response code (requires change to request handler)
expected := `text does not support structured data`
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile(expected),
PlanOnly: true,
},
},
})
}
9 changes: 9 additions & 0 deletions test/secret/simulation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
//go:build simulation
// +build simulation

package acceptance

func init() {
simulation = true
}

0 comments on commit d966ad0

Please sign in to comment.