Skip to content

Commit

Permalink
feat(LH-69564): Supports ASA Onboarding Using New Model (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
weilueluo authored Sep 22, 2023
1 parent a293f7b commit e5988e2
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 14 deletions.
64 changes: 64 additions & 0 deletions client/device/asa/asafixture_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package asa_test

import (
"encoding/json"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/asa"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/asa/asaconfig"
internalhttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/user"
"github.com/stretchr/testify/assert"
"net/http"
"reflect"
"testing"

"github.com/CiscoDevnet/terraform-provider-cdo/go-client/device"
Expand Down Expand Up @@ -41,6 +48,47 @@ func configureDeviceCreateToRespondSuccessfully(createOutput device.CreateOutput
)
}

func configureDeviceCreateToRespondSuccessfullyWithNewModel(t *testing.T, createOutput device.CreateOutput) {
httpmock.RegisterResponder(
http.MethodPost,
deviceCreatePath,
func(req *http.Request) (*http.Response, error) {
createInp, err := internalhttp.ReadRequestBody[device.CreateInput](req)
if err != nil {
return nil, err
}
expectedMetadata := &asa.Metadata{IsNewPolicyObjectModel: "true"}
expectedBytes, err := json.Marshal(expectedMetadata)
if err != nil {
return nil, err
}
actualBytes, err := json.Marshal(createInp.Metadata)
if err != nil {
return nil, err
}
expectedMetadataPayload := string(expectedBytes)
actualMetadataPayload := string(actualBytes)
assert.Equal(t, expectedMetadataPayload, actualMetadataPayload)
return httpmock.NewJsonResponse(http.StatusOK, createOutput)
},
)
}

func configureDeviceCreateToRespondSuccessfullyWithoutNewModel(t *testing.T, createOutput device.CreateOutput) {
httpmock.RegisterResponder(
http.MethodPost,
deviceCreatePath,
func(req *http.Request) (*http.Response, error) {
createInp, err := internalhttp.ReadRequestBody[device.CreateInput](req)
if err != nil {
return nil, err
}
assert.True(t, reflect.TypeOf(createInp.Metadata).Kind() == reflect.Pointer)
return httpmock.NewJsonResponse(http.StatusOK, createOutput)
},
)
}

func configureDeviceCreateToRespondWithError() {
httpmock.RegisterResponder(
http.MethodPost,
Expand Down Expand Up @@ -175,6 +223,22 @@ func configureConnectorReadToRespondWithError(connectorUid string) {
)
}

func configureReadApiTokenInfoSuccessfully(tokenInfo user.GetTokenInfoOutput) {
httpmock.RegisterResponder(
http.MethodGet,
url.ReadTokenInfo(baseUrl),
httpmock.NewJsonResponderOrPanic(http.StatusOK, tokenInfo),
)
}

func configureReadApiTokenInfoFailed() {
httpmock.RegisterResponder(
http.MethodGet,
url.ReadTokenInfo(baseUrl),
httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, "internal server error"),
)
}

func assertDeviceCreateWasCalledOnce(t *testing.T) {
internalTesting.AssertEndpointCalledTimes(http.MethodPost, deviceCreatePath, 1, t)
}
Expand Down
26 changes: 24 additions & 2 deletions client/device/asa/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/retry"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/devicetype"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/featureflag"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/user"
"strings"
)

Expand All @@ -36,6 +38,10 @@ type CreateOutput struct {
ConnectorUid string `json:"larUid"`
}

type Metadata struct {
IsNewPolicyObjectModel string `json:"isNewPolicyObjectModel"` // yes it is a string, but it should be either "true" or "false" :/
}

type CreateError struct {
Err error
CreatedResourceId *string
Expand All @@ -61,8 +67,23 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr

client.Logger.Println("creating asa device")

// 1. create a general CDO device with device type ASA
// 1.1 check if we should use the new asa model
userInfo, err := user.GetTokenInfo(ctx, client, user.NewGetTokenInfoInput())
if err != nil {
return nil, &CreateError{
CreatedResourceId: nil,
Err: err,
}
}
// 1.2 set metadata according to whether we want to use new asa model
var metadata *Metadata = nil
if userInfo.HasFeatureFlagEnabled(featureflag.AsaConfigurationObjectMigration) {
metadata = &Metadata{IsNewPolicyObjectModel: "true"}
}
// 1.3 create the device
deviceCreateOutp, err := device.Create(ctx, client, *device.NewCreateRequestInput(
createInp.Name, "ASA", createInp.ConnectorUid, createInp.ConnectorType, createInp.SocketAddress, false, createInp.IgnoreCertificate,
createInp.Name, "ASA", createInp.ConnectorUid, createInp.ConnectorType, createInp.SocketAddress, false, createInp.IgnoreCertificate, metadata,
))
var createdResourceId *string = nil
if deviceCreateOutp != nil {
Expand All @@ -75,6 +96,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr
}
}

// 2. wait for creation to be done, by waiting until asa specific device state is done
client.Logger.Println("reading specific device uid")

asaReadSpecOutp, err := device.ReadSpecific(ctx, client, *device.NewReadSpecificInput(
Expand Down Expand Up @@ -118,7 +140,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr
}
}

// encrypt credentials on prem connector
// 3. encrypt credentials for on prem connector (sdc)
var publicKey *model.PublicKey
if strings.EqualFold(deviceCreateOutp.ConnectorType, "SDC") {

Expand Down
122 changes: 122 additions & 0 deletions client/device/asa/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package asa_test

import (
"context"
"fmt"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/asa/asaconfig"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/featureflag"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/user/auth"
"github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/user/auth/role"
"github.com/stretchr/testify/assert"
"testing"
"time"
Expand Down Expand Up @@ -45,6 +49,43 @@ func TestAsaCreate(t *testing.T) {
Uid: asaSpecificDevice.SpecificUid,
State: state.DONE,
}
readApiTokenInfo_NoNewModelFeatureFlag := auth.Info{UserAuthentication: auth.Authentication{
Authorities: []auth.Authority{
{Authority: role.Admin},
},
Details: auth.Details{
TenantUid: "11111111-1111-1111-1111-111111111111",
TenantName: "",
SseTenantUid: "",
TenantOrganizationName: "",
TenantDbFeatures: "{}",
TenantUserRoles: "",
TenantDatabaseName: "",
TenantPayType: "",
},
Authenticated: false,
Principle: "",
Name: "",
}}

readApiTokenInfo_NewModelFeatureFlag := auth.Info{UserAuthentication: auth.Authentication{
Authorities: []auth.Authority{
{Authority: role.Admin},
},
Details: auth.Details{
TenantUid: "11111111-1111-1111-1111-111111111111",
TenantName: "",
SseTenantUid: "",
TenantOrganizationName: "",
TenantDbFeatures: fmt.Sprintf("{\"%s\":true}", featureflag.AsaConfigurationObjectMigration),
TenantUserRoles: "",
TenantDatabaseName: "",
TenantPayType: "",
},
Authenticated: false,
Principle: "",
Name: "",
}}

validConnector := connector.NewConnectorOutputBuilder().
WithName("CloudDeviceGateway").
Expand All @@ -71,6 +112,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfully(asaDevice)
configureDeviceReadSpecificToRespondSuccessfully(asaDevice.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondSuccessfully(asaSpecificDevice.SpecificUid, asaConfig)
Expand Down Expand Up @@ -100,6 +142,81 @@ func TestAsaCreate(t *testing.T) {
},
},

{
testName: "returns error when invalid api token is given",
input: asa.CreateInput{
Name: asaDevice.Name,
ConnectorType: asaDevice.ConnectorType,
SocketAddress: asaDevice.SocketAddress,
Username: "unittestuser",
Password: "not a real password",
IgnoreCertificate: false,
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoFailed()
configureDeviceCreateToRespondSuccessfully(asaDevice)
configureDeviceReadSpecificToRespondSuccessfully(asaDevice.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondSuccessfully(asaSpecificDevice.SpecificUid, asaConfig)
configureAsaConfigUpdateToRespondSuccessfully(asaConfig.Uid, asaconfig.UpdateOutput{Uid: asaConfig.Uid})
},

assertFunc: func(output *asa.CreateOutput, err *asa.CreateError, t *testing.T) {
assert.NotNil(t, err)
assert.Nil(t, output)
},
},

{
testName: "should call create device with new policy model metadata if feature flag for new model is enable",
input: asa.CreateInput{
Name: asaDevice.Name,
ConnectorType: asaDevice.ConnectorType,
SocketAddress: asaDevice.SocketAddress,
Username: "unittestuser",
Password: "not a real password",
IgnoreCertificate: false,
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfullyWithNewModel(t, asaDevice)
configureDeviceReadSpecificToRespondSuccessfully(asaDevice.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondSuccessfully(asaSpecificDevice.SpecificUid, asaConfig)
configureAsaConfigUpdateToRespondSuccessfully(asaConfig.Uid, asaconfig.UpdateOutput{Uid: asaConfig.Uid})
},

assertFunc: func(output *asa.CreateOutput, err *asa.CreateError, t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, output)
},
},

{
testName: "should not call create device with new policy model metadata if feature flag for new model is not enabled",
input: asa.CreateInput{
Name: asaDevice.Name,
ConnectorType: asaDevice.ConnectorType,
SocketAddress: asaDevice.SocketAddress,
Username: "unittestuser",
Password: "not a real password",
IgnoreCertificate: false,
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfullyWithoutNewModel(t, asaDevice)
configureDeviceReadSpecificToRespondSuccessfully(asaDevice.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondSuccessfully(asaSpecificDevice.SpecificUid, asaConfig)
configureAsaConfigUpdateToRespondSuccessfully(asaConfig.Uid, asaconfig.UpdateOutput{Uid: asaConfig.Uid})
},

assertFunc: func(output *asa.CreateOutput, err *asa.CreateError, t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, output)
},
},

{
testName: "successfully onboards ASA when using CDG after recovering from certificate error",

Expand All @@ -113,6 +230,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfully(asaDevice)
configureDeviceReadSpecificToRespondSuccessfully(asaDevice.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondWithCalls(asaConfig.Uid, []httpmock.Responder{
Expand Down Expand Up @@ -163,6 +281,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfully(asaDeviceUsingSdc)
configureDeviceReadSpecificToRespondSuccessfully(asaDeviceUsingSdc.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondSuccessfully(asaSpecificDevice.SpecificUid, asaConfig)
Expand Down Expand Up @@ -208,6 +327,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfully(asaDeviceUsingSdc)
configureDeviceReadSpecificToRespondSuccessfully(asaDeviceUsingSdc.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondWithCalls(asaConfig.Uid, []httpmock.Responder{
Expand Down Expand Up @@ -261,6 +381,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondWithError()
configureDeviceReadSpecificToRespondSuccessfully(asaDeviceUsingSdc.Uid, asaSpecificDevice)
configureAsaConfigReadToRespondWithCalls(asaConfig.Uid, []httpmock.Responder{
Expand Down Expand Up @@ -295,6 +416,7 @@ func TestAsaCreate(t *testing.T) {
},

setupFunc: func(input asa.CreateInput) {
configureReadApiTokenInfoSuccessfully(readApiTokenInfo_NoNewModelFeatureFlag)
configureDeviceCreateToRespondSuccessfully(asaDeviceUsingSdc)
configureDeviceReadSpecificToRespondWithError(asaDeviceUsingSdc.Uid)
configureAsaConfigReadToRespondWithCalls(asaConfig.Uid, []httpmock.Responder{
Expand Down
25 changes: 16 additions & 9 deletions client/device/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ import (
)

type CreateInput struct {
Name string `json:"name"`
DeviceType string `json:"deviceType"`
ConnectorUid string `json:"larUid,omitempty"`
ConnectorType string `json:"larType"`
SocketAddress string `json:"ipv4"`
Model bool `json:"model"`

IgnoreCertificate bool `json:"ignoreCertificate"`
Name string `json:"name"`
DeviceType string `json:"deviceType"`
ConnectorUid string `json:"larUid,omitempty"`
ConnectorType string `json:"larType"`
SocketAddress string `json:"ipv4"`
Model bool `json:"model"`
IgnoreCertificate bool `json:"ignoreCertificate"`
Metadata *interface{} `json:"metadata,omitempty"`
}

type CreateOutput = ReadOutput

func NewCreateRequestInput(name, deviceType, connectorUid, connectorType, socketAddress string, model bool, ignoreCertificate bool) *CreateInput {
func NewCreateRequestInput(name, deviceType, connectorUid, connectorType, socketAddress string, model bool, ignoreCertificate bool, metadata interface{}) *CreateInput {
// convert interface{} to a pointer
var metadataPtr *interface{} = nil
if metadata != nil {
metadataPtr = &metadata
}

return &CreateInput{
Name: name,
DeviceType: deviceType,
Expand All @@ -29,6 +35,7 @@ func NewCreateRequestInput(name, deviceType, connectorUid, connectorType, socket
SocketAddress: socketAddress,
Model: model,
IgnoreCertificate: ignoreCertificate,
Metadata: metadataPtr,
}
}

Expand Down
2 changes: 1 addition & 1 deletion client/device/genericssh/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr

client.Logger.Println("creating generic ssh")

deviceInput := device.NewCreateRequestInput(createInp.Name, "GENERIC_SSH", createInp.ConnectorUid, createInp.ConnectorType, createInp.SocketAddress, false, false)
deviceInput := device.NewCreateRequestInput(createInp.Name, "GENERIC_SSH", createInp.ConnectorUid, createInp.ConnectorType, createInp.SocketAddress, false, false, nil)
outp, err := device.Create(ctx, client, *deviceInput)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit e5988e2

Please sign in to comment.