Skip to content

Commit

Permalink
LH68644- Feat: implement updating of asa device location (#12)
Browse files Browse the repository at this point in the history
* fix: implement updating of asa device location

* refactor: abstract literals into constants

* refactor: update location via usual update method

* style: remove commented code

* refactor: change if prefixed function to is
  • Loading branch information
Christopher Dickson authored Jul 27, 2023
1 parent f711eea commit 2ac82aa
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 54 deletions.
3 changes: 2 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ package client

import (
"context"
"net/http"

"github.com/cisco-lockhart/go-client/connector/sdc"
"github.com/cisco-lockhart/go-client/device/ios"
"github.com/cisco-lockhart/go-client/internal/device/asaconfig"
"net/http"

"github.com/cisco-lockhart/go-client/device/asa"
internalhttp "github.com/cisco-lockhart/go-client/internal/http"
Expand Down
3 changes: 2 additions & 1 deletion client/device/asa/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type ReadOutput struct {
Port string `json:"port"`
Host string `json:"host"`

IgnoreCertifcate bool `json:"ignoreCertificate"`
IgnoreCertifcate bool `json:"ignoreCertificate"`
ConnectivityState int `json:"connectivityState"`
}

func NewReadInput(uid string) *ReadInput {
Expand Down
62 changes: 42 additions & 20 deletions client/device/asa/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/cisco-lockhart/go-client/connector/sdc"
"github.com/cisco-lockhart/go-client/internal/device/asaconfig"
"github.com/cisco-lockhart/go-client/internal/retry"

"github.com/cisco-lockhart/go-client/device"
"github.com/cisco-lockhart/go-client/internal/http"
Expand All @@ -16,6 +17,7 @@ import (
type UpdateInput struct {
Uid string `json:"-"`
Name string `json:"name"`
Location string
Username string
Password string
}
Expand Down Expand Up @@ -44,7 +46,7 @@ func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*Up

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

if updateInp.Username != "" {
if isSpecificDeviceIsRequired(updateInp) {

asaReadSpecOutp, err := device.ReadSpecific(ctx, client, *device.NewReadSpecificInput(
updateInp.Uid,
Expand All @@ -60,32 +62,48 @@ func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*Up
return nil, err
}

var publicKey *sdc.PublicKey
if strings.EqualFold(asaReadOutp.LarType, "SDC") {
if asaReadOutp.LarUid == "" {
return nil, fmt.Errorf("sdc uid not found")
if updateInp.Username != "" || updateInp.Password != "" {
var publicKey *sdc.PublicKey
if strings.EqualFold(asaReadOutp.LarType, "SDC") {
if asaReadOutp.LarUid == "" {
return nil, fmt.Errorf("sdc uid not found")
}

larReadRes, err := sdc.ReadByUid(ctx, client, sdc.ReadInput{
LarUid: asaReadOutp.LarUid,
})
if err != nil {
return nil, err
}
publicKey = &larReadRes.PublicKey
}

larReadRes, err := sdc.ReadByUid(ctx, client, sdc.ReadInput{
LarUid: asaReadOutp.LarUid,
})
updateAsaConfigInp := asaconfig.NewUpdateInput(
asaReadSpecOutp.SpecificUid,
updateInp.Username,
updateInp.Password,
publicKey,
asaReadSpecOutp.State,
)
_, err = asaconfig.UpdateCredentials(ctx, client, *updateAsaConfigInp)
if err != nil {
_ = fmt.Errorf("failed to update credentials for ASA device: %s", err.Error())
return nil, err
}
publicKey = &larReadRes.PublicKey
}

updateAsaConfigInp := asaconfig.NewUpdateInput(
asaReadSpecOutp.SpecificUid,
updateInp.Username,
updateInp.Password,
publicKey,
asaReadSpecOutp.State,
)
_, err = asaconfig.UpdateCredentials(ctx, client, *updateAsaConfigInp)
if err != nil {
_ = fmt.Errorf("failed to update credentials for ASA device: %s", err.Error())
return nil, err
if updateInp.Location != "" {
_, err := asaconfig.UpdateLocation(ctx, client, asaconfig.UpdateLocationOptions{
SpecificUid: asaReadSpecOutp.SpecificUid,
Location: updateInp.Location,
})
if err != nil {
return nil, err
}

if err := retry.Do(asaconfig.UntilStateDone(ctx, client, asaReadSpecOutp.SpecificUid), retry.DefaultOpts); err != nil {
return nil, err
}
}
}

Expand All @@ -98,3 +116,7 @@ func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*Up

return &outp, nil
}

func isSpecificDeviceIsRequired(updateInput UpdateInput) bool {
return updateInput.Username != "" || updateInput.Password != "" || updateInput.Location != ""
}
55 changes: 43 additions & 12 deletions client/internal/device/asaconfig/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,39 @@ func UpdateCredentials(ctx context.Context, client http.Client, updateInput Upda
return &outp, nil
}

type UpdateLocationOptions struct {
SpecificUid string
Location string
}

type updateLocationRequestBody struct {
QueueTriggerState string `json:"queueTriggerState"`
SmContext pendingLocationUpdateSmContext `json:"stateMachineContext"`
}

type pendingLocationUpdateSmContext struct {
Ipv4 string `json:"ipv4"`
}

func UpdateLocation(ctx context.Context, client http.Client, options UpdateLocationOptions) (*UpdateOutput, error) {
url := url.UpdateAsaConfig(client.BaseUrl(), options.SpecificUid)

req := client.NewPut(ctx, url, updateLocationRequestBody{
QueueTriggerState: "PENDING_LOCATION_UPDATE",
SmContext: pendingLocationUpdateSmContext{
options.Location,
},
})

var outp UpdateOutput
err := req.Send(&outp)
if err != nil {
return nil, err
}

return &outp, nil
}

func makeReqBody(creds []byte) *updateBody {
return &updateBody{
State: "CERT_VALIDATED", // question: should this be hardcoded?
Expand Down Expand Up @@ -144,22 +177,20 @@ func encrypt(req *UpdateInput) error {
}

func makeCredentials(updateInp UpdateInput) ([]byte, error) {
var creds []byte
var err error
if updateInp.PublicKey != nil {
err = encrypt(&updateInp)
if err == nil {
creds, err = json.Marshal(credentials{
Username: updateInp.Username,
Password: updateInp.Password,
KeyId: updateInp.PublicKey.KeyId,
})
if err := encrypt(&updateInp); err != nil {
return nil, err
}
} else {
creds, err = json.Marshal(credentials{

return json.Marshal(credentials{
Username: updateInp.Username,
Password: updateInp.Password,
KeyId: updateInp.PublicKey.KeyId,
})
}
return creds, err

return json.Marshal(credentials{
Username: updateInp.Username,
Password: updateInp.Password,
})
}
36 changes: 36 additions & 0 deletions client/model/lar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package model

import (
"fmt"
"strings"
)

type LarType string

const (
LarTypeCloudDeviceGateway LarType = "CDG"
LarTypeSecureDeviceConnector LarType = "SDC"
)

func ParseLarType(input string) (LarType, error) {
switch strings.ToUpper(input) {
case string(LarTypeCloudDeviceGateway):
return LarTypeCloudDeviceGateway, nil

case string(LarTypeSecureDeviceConnector):
return LarTypeSecureDeviceConnector, nil

default:
return "", fmt.Errorf("'%s' is not a valid lar type", input)

}
}

func MustParseLarType(input string) LarType {
larType, err := ParseLarType(input)
if err != nil {
panic(err)
}

return larType
}
64 changes: 44 additions & 20 deletions provider/internal/device/asa/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,19 @@ func (r *AsaDeviceResource) Update(ctx context.Context, req resource.UpdateReque
return
}

var updateInp asa.UpdateInput
updateInp := asa.UpdateInput{Uid: stateData.ID.ValueString(), Name: stateData.Name.ValueString()}

if isNameUpdated(planData, stateData) {
updateInp.Name = planData.Name.ValueString()
}

if isLocationUpdated(planData, stateData) {
updateInp.Location = planData.Ipv4.ValueString()
}

if isCredentialUpdated(planData, stateData) {
updateInp = *asa.NewUpdateInput(
stateData.ID.ValueString(),
planData.Name.ValueString(),
planData.Username.ValueString(),
planData.Password.ValueString(),
)
} else {
updateInp = *asa.NewUpdateInput(
stateData.ID.ValueString(),
planData.Name.ValueString(),
"",
"",
)
updateInp.Username = planData.Username.ValueString()
updateInp.Password = planData.Password.ValueString()
}

updateOutp, err := r.client.UpdateAsa(ctx, updateInp)
Expand All @@ -269,17 +267,26 @@ func (r *AsaDeviceResource) Update(ctx context.Context, req resource.UpdateReque
return
}

port, err := parsePort(updateOutp.Port)
if err != nil {
res.Diagnostics.AddError("unable to parse port", err.Error())
return
}

stateData.ID = types.StringValue(updateOutp.Uid)
stateData.SdcType = types.StringValue(planData.SdcType.ValueString())
stateData.SdcName = types.StringValue(planData.SdcName.ValueString())
stateData.Name = types.StringValue(updateOutp.Name)
stateData.Username = types.StringValue(planData.Username.ValueString())
stateData.Password = types.StringValue(planData.Password.ValueString())
stateData.Ipv4 = types.StringValue(updateOutp.Ipv4)
stateData.Host = types.StringValue(updateOutp.Host)
stateData.Port = types.Int64Value(port)
stateData.Username = planData.Username
stateData.Password = planData.Password
stateData.IgnoreCertifcate = planData.IgnoreCertifcate

res.Diagnostics.Append(res.State.Set(ctx, &stateData)...)
}

func isCredentialUpdated(planData *AsaDeviceResourceModel, stateData *AsaDeviceResourceModel) bool {
return planData.Username.ValueString() != stateData.Username.ValueString() || planData.Password.ValueString() != stateData.Password.ValueString()
}

func (r *AsaDeviceResource) Delete(ctx context.Context, req resource.DeleteRequest, res *resource.DeleteResponse) {

tflog.Trace(ctx, "delete ASA device resource")
Expand All @@ -303,3 +310,20 @@ func (r *AsaDeviceResource) Delete(ctx context.Context, req resource.DeleteReque
func (r *AsaDeviceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, res *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, res)
}

func isCredentialUpdated(planData, stateData *AsaDeviceResourceModel) bool {
return planData.Username.ValueString() != stateData.Username.ValueString() || planData.Password.ValueString() != stateData.Password.ValueString()
}

func isNameUpdated(planData, stateData *AsaDeviceResourceModel) bool {
return !planData.Name.Equal(stateData.Name)
}

func isLocationUpdated(planData, stateData *AsaDeviceResourceModel) bool {
return !planData.Ipv4.Equal(stateData.Ipv4)
}

func parsePort(rawPort string) (int64, error) {
return strconv.ParseInt(rawPort, 10, 16)

}
21 changes: 21 additions & 0 deletions provider/internal/device/asa/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ var testAsaResource_SDC = testAsaResourceType{
Host: "vasa-gb-ravpn-03-mgmt.dev.lockhart.io",
Port: "443",
}

const alternativeDeviceLocation = "35.177.20.218:443"

var testAsaResourceConfig_SDC = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC)

// new name config.
Expand All @@ -60,6 +63,9 @@ var testAsaResource_SDC_NewCreds = acctest.MustOverrideFields(testAsaResource_SD
})
var testAsaResourceConfig_SDC_NewCreds = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC_NewCreds)

var testAsaResource_SDC_NewLocation = acctest.MustOverrideFields(testAsaResource_SDC, map[string]any{"Ipv4": alternativeDeviceLocation})
var testAsaResourceConfig_SDC_NewLocation = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC_NewLocation)

// CDG configs

// default config.
Expand Down Expand Up @@ -89,6 +95,9 @@ var testAsaResource_CDG_NewCreds = acctest.MustOverrideFields(testAsaResource_CD
})
var testAsaResourceConfig_CDG_NewCreds = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_CDG_NewCreds)

var testAsaResource_CDG_NewLocation = acctest.MustOverrideFields(testAsaResource_CDG, map[string]any{"Ipv4": alternativeDeviceLocation})
var testAsaResourceConfig_CDG_NewLocation = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_CDG_NewLocation)

func TestAccAsaDeviceResource_SDC(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: acctest.PreCheckFunc(t),
Expand Down Expand Up @@ -128,6 +137,12 @@ func TestAccAsaDeviceResource_SDC(t *testing.T) {
resource.TestCheckResourceAttr("cdo_asa_device.test", "password", testAsaResource_SDC.Password),
),
},
{
Config: acctest.ProviderConfig() + testAsaResourceConfig_SDC_NewLocation,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("cdo_asa_device.test", "ipv4", testAsaResource_SDC_NewLocation.Ipv4),
),
},
// Delete testing automatically occurs in TestCase
},
})
Expand Down Expand Up @@ -172,6 +187,12 @@ func TestAccAsaDeviceResource_CDG(t *testing.T) {
resource.TestCheckResourceAttr("cdo_asa_device.test", "password", testAsaResource_CDG.Password),
),
},
{
Config: acctest.ProviderConfig() + testAsaResourceConfig_CDG_NewLocation,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("cdo_asa_device.test", "ipv4", testAsaResource_CDG_NewLocation.Ipv4),
),
},
// Delete testing automatically occurs in TestCase
},
})
Expand Down

0 comments on commit 2ac82aa

Please sign in to comment.