From d10f3f11371fcd228400ce1b5258dd29dc4d72e4 Mon Sep 17 00:00:00 2001 From: Weilue Luo Date: Mon, 31 Jul 2023 09:22:29 +0100 Subject: [PATCH] Lh 68457/update ios credentials and ip when onboarded (#11) --- client/client.go | 50 ++++---- client/device/asa/update.go | 16 +-- client/device/ios/iosconfig/update.go | 1 + client/device/ios/update.go | 13 +- client/internal/device/asaconfig/update.go | 1 + .../examples/resources/ios/ios_example.tf | 2 +- provider/internal/device/ios/data_source.go | 4 - provider/internal/device/ios/operation.go | 98 ++++++++++++++ provider/internal/device/ios/resource.go | 120 +++++------------- 9 files changed, 165 insertions(+), 140 deletions(-) create mode 100644 provider/internal/device/ios/operation.go diff --git a/client/client.go b/client/client.go index 69f8680a..f0fd6502 100644 --- a/client/client.go +++ b/client/client.go @@ -8,9 +8,9 @@ import ( "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" "github.com/cisco-lockhart/go-client/device/asa" + "github.com/cisco-lockhart/go-client/internal/device/asaconfig" internalhttp "github.com/cisco-lockhart/go-client/internal/http" ) @@ -31,50 +31,50 @@ func NewWithHttpClient(httpClient *http.Client, hostname, apiToken string) *Clie } } -func (c *Client) ReadAllSdcs(ctx context.Context, r sdc.ReadAllInput) (*sdc.ReadAllOutput, error) { - return sdc.ReadAll(ctx, c.client, r) +func (c *Client) ReadAllSdcs(ctx context.Context, inp sdc.ReadAllInput) (*sdc.ReadAllOutput, error) { + return sdc.ReadAll(ctx, c.client, inp) } -func (c *Client) ReadSdcByName(ctx context.Context, r sdc.ReadByNameInput) (*sdc.ReadOutput, error) { - return sdc.ReadByName(ctx, c.client, r) +func (c *Client) ReadSdcByName(ctx context.Context, inp sdc.ReadByNameInput) (*sdc.ReadOutput, error) { + return sdc.ReadByName(ctx, c.client, inp) } -func (c *Client) ReadAsa(ctx context.Context, r asa.ReadInput) (*asa.ReadOutput, error) { - return asa.Read(ctx, c.client, r) +func (c *Client) ReadAsa(ctx context.Context, inp asa.ReadInput) (*asa.ReadOutput, error) { + return asa.Read(ctx, c.client, inp) } -func (c *Client) CreateAsa(ctx context.Context, r asa.CreateInput) (*asa.CreateOutput, error) { - return asa.Create(ctx, c.client, r) +func (c *Client) CreateAsa(ctx context.Context, inp asa.CreateInput) (*asa.CreateOutput, error) { + return asa.Create(ctx, c.client, inp) } -func (c *Client) UpdateAsa(ctx context.Context, r asa.UpdateInput) (*asa.UpdateOutput, error) { - return asa.Update(ctx, c.client, r) +func (c *Client) UpdateAsa(ctx context.Context, inp asa.UpdateInput) (*asa.UpdateOutput, error) { + return asa.Update(ctx, c.client, inp) } -func (c *Client) DeleteAsa(ctx context.Context, r asa.DeleteInput) (*asa.DeleteOutput, error) { - return asa.Delete(ctx, c.client, r) +func (c *Client) DeleteAsa(ctx context.Context, inp asa.DeleteInput) (*asa.DeleteOutput, error) { + return asa.Delete(ctx, c.client, inp) } -func (c *Client) ReadIos(ctx context.Context, r ios.ReadInput) (*ios.ReadOutput, error) { - return ios.Read(ctx, c.client, r) +func (c *Client) ReadIos(ctx context.Context, inp ios.ReadInput) (*ios.ReadOutput, error) { + return ios.Read(ctx, c.client, inp) } -func (c *Client) CreateIos(ctx context.Context, r ios.CreateInput) (*ios.CreateOutput, error) { - return ios.Create(ctx, c.client, r) +func (c *Client) CreateIos(ctx context.Context, inp ios.CreateInput) (*ios.CreateOutput, error) { + return ios.Create(ctx, c.client, inp) } -func (c *Client) UpdateIos(ctx context.Context, r ios.UpdateInput) (*ios.UpdateOutput, error) { - return ios.Update(ctx, c.client, r) +func (c *Client) UpdateIos(ctx context.Context, inp ios.UpdateInput) (*ios.UpdateOutput, error) { + return ios.Update(ctx, c.client, inp) } -func (c *Client) DeleteIos(ctx context.Context, r ios.DeleteInput) (*ios.DeleteOutput, error) { - return ios.Delete(ctx, c.client, r) +func (c *Client) DeleteIos(ctx context.Context, inp ios.DeleteInput) (*ios.DeleteOutput, error) { + return ios.Delete(ctx, c.client, inp) } -func (c *Client) ReadAsaConfig(ctx context.Context, r asaconfig.ReadInput) (*asaconfig.ReadOutput, error) { - return asaconfig.Read(ctx, c.client, r) +func (c *Client) ReadAsaConfig(ctx context.Context, inp asaconfig.ReadInput) (*asaconfig.ReadOutput, error) { + return asaconfig.Read(ctx, c.client, inp) } -func (c *Client) ReadSpecificAsa(ctx context.Context, r asa.ReadSpecificInput) (*asa.ReadSpecificOutput, error) { - return asa.ReadSpecific(ctx, c.client, r) +func (c *Client) ReadSpecificAsa(ctx context.Context, inp asa.ReadSpecificInput) (*asa.ReadSpecificOutput, error) { + return asa.ReadSpecific(ctx, c.client, inp) } diff --git a/client/device/asa/update.go b/client/device/asa/update.go index 60b56232..869dd203 100644 --- a/client/device/asa/update.go +++ b/client/device/asa/update.go @@ -5,10 +5,11 @@ import ( "fmt" "strings" - "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/connector/sdc" + "github.com/cisco-lockhart/go-client/device" "github.com/cisco-lockhart/go-client/internal/http" "github.com/cisco-lockhart/go-client/internal/url" @@ -33,15 +34,6 @@ func NewUpdateInput(uid string, name string, username string, password string) * } } -func NewUpdateRequest(ctx context.Context, client http.Client, updateInp UpdateInput) *http.Request { - - url := url.UpdateDevice(client.BaseUrl(), updateInp.Uid) - - req := client.NewPut(ctx, url, updateInp) - - return req -} - func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*UpdateOutput, error) { client.Logger.Println("updating asa device") @@ -107,7 +99,9 @@ func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*Up } } - req := NewUpdateRequest(ctx, client, updateInp) + url := url.UpdateDevice(client.BaseUrl(), updateInp.Uid) + + req := client.NewPut(ctx, url, updateInp) var outp UpdateOutput if err := req.Send(&outp); err != nil { diff --git a/client/device/ios/iosconfig/update.go b/client/device/ios/iosconfig/update.go index 3fe39247..1e4d09c1 100644 --- a/client/device/ios/iosconfig/update.go +++ b/client/device/ios/iosconfig/update.go @@ -3,6 +3,7 @@ package iosconfig import ( "context" "encoding/json" + "github.com/cisco-lockhart/go-client/connector/sdc" "github.com/cisco-lockhart/go-client/internal/crypto/rsa" diff --git a/client/device/ios/update.go b/client/device/ios/update.go index ebf727f0..f1a56f00 100644 --- a/client/device/ios/update.go +++ b/client/device/ios/update.go @@ -22,20 +22,13 @@ func NewUpdateInput(uid string, name string) *UpdateInput { } } -func NewUpdateRequest(ctx context.Context, client http.Client, updateInp UpdateInput) *http.Request { - - url := url.UpdateDevice(client.BaseUrl(), updateInp.Uid) - - req := client.NewPut(ctx, url, updateInp) - - return req -} - func Update(ctx context.Context, client http.Client, updateInp UpdateInput) (*UpdateOutput, error) { client.Logger.Println("updating ios device") - req := NewUpdateRequest(ctx, client, updateInp) + url := url.UpdateDevice(client.BaseUrl(), updateInp.Uid) + + req := client.NewPut(ctx, url, updateInp) var outp UpdateOutput if err := req.Send(&outp); err != nil { diff --git a/client/internal/device/asaconfig/update.go b/client/internal/device/asaconfig/update.go index 4b68b591..7646e290 100644 --- a/client/internal/device/asaconfig/update.go +++ b/client/internal/device/asaconfig/update.go @@ -178,6 +178,7 @@ func encrypt(req *UpdateInput) error { func makeCredentials(updateInp UpdateInput) ([]byte, error) { if updateInp.PublicKey != nil { + if err := encrypt(&updateInp); err != nil { return nil, err } diff --git a/provider/examples/resources/ios/ios_example.tf b/provider/examples/resources/ios/ios_example.tf index 2449e4c2..2cda51ee 100644 --- a/provider/examples/resources/ios/ios_example.tf +++ b/provider/examples/resources/ios/ios_example.tf @@ -18,4 +18,4 @@ resource "cdo_ios_device" "my_ios" { ipv4 = "" username = "" password = "" -} +} \ No newline at end of file diff --git a/provider/internal/device/ios/data_source.go b/provider/internal/device/ios/data_source.go index adcb36d9..ba7fbf9e 100644 --- a/provider/internal/device/ios/data_source.go +++ b/provider/internal/device/ios/data_source.go @@ -160,10 +160,6 @@ func (d *IosDataSource) Read(ctx context.Context, req datasource.ReadRequest, re configData.Host = types.StringValue(readOutp.Host) configData.IgnoreCertifcate = types.BoolValue(readOutp.IgnoreCertifcate) - // Fix: where to find them? We need them for import statement - // stateData.Username = types.StringNull() - // stateData.Password = types.StringNull() - tflog.Trace(ctx, "done read IOS device data source") // Save data into Terraform state diff --git a/provider/internal/device/ios/operation.go b/provider/internal/device/ios/operation.go new file mode 100644 index 00000000..49e49a92 --- /dev/null +++ b/provider/internal/device/ios/operation.go @@ -0,0 +1,98 @@ +package ios + +import ( + "context" + "fmt" + "strconv" + + "github.com/cisco-lockhart/go-client/connector/sdc" + "github.com/cisco-lockhart/go-client/device/ios" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func Read(ctx context.Context, resource *IosDeviceResource, stateData *IosDeviceResourceModel) error { + + readInp := ios.ReadInput{ + Uid: stateData.ID.ValueString(), + } + + readOutp, err := resource.client.ReadIos(ctx, readInp) + if err != nil { + return err + } + + port, err := strconv.ParseInt(readOutp.Port, 10, 16) + if err != nil { + return err + } + + stateData.Port = types.Int64Value(port) + stateData.ID = types.StringValue(readOutp.Uid) + stateData.SdcType = types.StringValue(readOutp.LarType) + stateData.Name = types.StringValue(readOutp.Name) + stateData.Ipv4 = types.StringValue(readOutp.Ipv4) + stateData.Host = types.StringValue(readOutp.Host) + stateData.IgnoreCertifcate = types.BoolValue(readOutp.IgnoreCertifcate) + + return nil +} + +func Create(ctx context.Context, resource *IosDeviceResource, planData *IosDeviceResourceModel) error { + + readSdcByNameInp := sdc.NewReadByNameInput( + planData.SdcName.ValueString(), + ) + + readSdcOutp, err := resource.client.ReadSdcByName(ctx, *readSdcByNameInp) + if err != nil { + return err + } + + createInp := ios.NewCreateRequestInput( + planData.Name.ValueString(), + readSdcOutp.Uid, + planData.SdcType.ValueString(), + planData.Ipv4.ValueString(), + planData.Username.ValueString(), + planData.Password.ValueString(), + planData.IgnoreCertifcate.ValueBool(), + ) + + createOutp, err := resource.client.CreateIos(ctx, *createInp) + if err != nil { + return err + } + + planData.ID = types.StringValue(createOutp.Uid) + planData.SdcType = types.StringValue(createOutp.LarType) + planData.SdcName = types.StringValue(planData.SdcName.ValueString()) + planData.Name = types.StringValue(createOutp.Name) + planData.Host = types.StringValue(createOutp.Host) + + port, err := strconv.ParseInt(createOutp.Port, 10, 16) + if err != nil { + return fmt.Errorf("failed to parse IOS port, cause=%w", err) + } + planData.Port = types.Int64Value(port) + + return nil +} + +func Update(ctx context.Context, resource *IosDeviceResource, planData *IosDeviceResourceModel, stateData *IosDeviceResourceModel) error { + updateInp := *ios.NewUpdateInput( + stateData.ID.ValueString(), + planData.Name.ValueString(), + ) + updateOutp, err := resource.client.UpdateIos(ctx, updateInp) + if err != nil { + return err + } + stateData.Name = types.StringValue(updateOutp.Name) + return nil +} + +func Delete(ctx context.Context, resource *IosDeviceResource, stateData *IosDeviceResourceModel) error { + deleteInp := ios.NewDeleteInput(stateData.ID.ValueString()) + _, err := resource.client.DeleteIos(ctx, *deleteInp) + return err +} diff --git a/provider/internal/device/ios/resource.go b/provider/internal/device/ios/resource.go index 03e6e1a7..24621e2e 100644 --- a/provider/internal/device/ios/resource.go +++ b/provider/internal/device/ios/resource.go @@ -3,19 +3,18 @@ package ios import ( "context" "fmt" - "strconv" - "github.com/cisco-lockhart/go-client/connector/sdc" "github.com/cisco-lockhart/terraform-provider-cdo/validators" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" cdoClient "github.com/cisco-lockhart/go-client" - "github.com/cisco-lockhart/go-client/device/ios" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-log/tflog" ) @@ -77,6 +76,9 @@ func (r *IosDeviceResource) Schema(ctx context.Context, req resource.SchemaReque "ipv4": schema.StringAttribute{ MarkdownDescription: "The ipv4 address of the device", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "port": schema.Int64Attribute{ MarkdownDescription: "The port used to connect to the device", @@ -89,11 +91,17 @@ func (r *IosDeviceResource) Schema(ctx context.Context, req resource.SchemaReque "username": schema.StringAttribute{ MarkdownDescription: "The username used to authenticate with the device", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "password": schema.StringAttribute{ MarkdownDescription: "The password used to authenticate with the device", Required: true, Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "ignore_certificate": schema.BoolAttribute{ MarkdownDescription: "Whether to ignore certificate validation", @@ -128,45 +136,19 @@ func (r *IosDeviceResource) Read(ctx context.Context, req resource.ReadRequest, tflog.Trace(ctx, "read IOS device resource") - var stateData *IosDeviceResourceModel - - // Read Terraform plan data into the model + // 1. read state data + var stateData IosDeviceResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &stateData)...) if resp.Diagnostics.HasError() { return } - // read ios - readInp := ios.ReadInput{ - Uid: stateData.ID.ValueString(), + // 2. do read + if err := Read(ctx, r, &stateData); err != nil { + resp.Diagnostics.AddError("failed to read IOS device", err.Error()) } - readOutp, err := r.client.ReadIos(ctx, readInp) - if err != nil { - resp.Diagnostics.AddError("unable to read IOS Device", err.Error()) - return - } - - port, err := strconv.ParseInt(readOutp.Port, 10, 16) - if err != nil { - resp.Diagnostics.AddError("unable to read IOS Device", err.Error()) - return - } - stateData.Port = types.Int64Value(port) - - stateData.ID = types.StringValue(readOutp.Uid) - stateData.SdcType = types.StringValue(readOutp.LarType) - stateData.Name = types.StringValue(readOutp.Name) - stateData.Ipv4 = types.StringValue(readOutp.Ipv4) - stateData.Host = types.StringValue(readOutp.Host) - stateData.IgnoreCertifcate = types.BoolValue(readOutp.IgnoreCertifcate) - - // Fix: where to find them? We need them for import statement - // stateData.Username = types.StringNull() - // stateData.Password = types.StringNull() - - tflog.Trace(ctx, "done read IOS device resource") - // Save data into Terraform state + // 3. save data into terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &stateData)...) } @@ -174,50 +156,20 @@ func (r *IosDeviceResource) Create(ctx context.Context, req resource.CreateReque tflog.Trace(ctx, "create IOS device resource") + // 1. read plan data into planData var planData IosDeviceResourceModel res.Diagnostics.Append(req.Plan.Get(ctx, &planData)...) if res.Diagnostics.HasError() { return } - readSdcByNameInp := sdc.NewReadByNameInput( - planData.SdcName.ValueString(), - ) - - specificSdcOutp, err := r.client.ReadSdcByName(ctx, *readSdcByNameInp) - if err != nil { - res.Diagnostics.AddError("failed to read SDC by name", err.Error()) - return - } - - createInp := ios.NewCreateRequestInput( - planData.Name.ValueString(), - specificSdcOutp.Uid, - planData.SdcType.ValueString(), - planData.Ipv4.ValueString(), - planData.Username.ValueString(), - planData.Password.ValueString(), - planData.IgnoreCertifcate.ValueBool(), - ) - - createOutp, err := r.client.CreateIos(ctx, *createInp) - if err != nil { - res.Diagnostics.AddError("failed to create IOS", err.Error()) + // 2. use plan data to create device and fill up rest of the model + if err := Create(ctx, r, &planData); err != nil { + res.Diagnostics.AddError("failed to create IOS device", err.Error()) return } - planData.ID = types.StringValue(createOutp.Uid) - planData.SdcType = types.StringValue(createOutp.LarType) - planData.SdcName = types.StringValue(planData.SdcName.ValueString()) - planData.Name = types.StringValue(createOutp.Name) - planData.Host = types.StringValue(createOutp.Host) - - port, err := strconv.ParseInt(createOutp.Port, 10, 16) - if err != nil { - res.Diagnostics.AddError("failed to parse IOS port", err.Error()) - } - planData.Port = types.Int64Value(port) - + // 3. set state using filled model res.Diagnostics.Append(res.State.Set(ctx, &planData)...) } @@ -225,31 +177,26 @@ func (r *IosDeviceResource) Update(ctx context.Context, req resource.UpdateReque tflog.Trace(ctx, "update IOS device resource") - var planData *IosDeviceResourceModel + // 1. read plan data + var planData IosDeviceResourceModel res.Diagnostics.Append(req.Plan.Get(ctx, &planData)...) if res.Diagnostics.HasError() { return } - var stateData *IosDeviceResourceModel + // 2. read state data + var stateData IosDeviceResourceModel res.Diagnostics.Append(req.State.Get(ctx, &stateData)...) if res.Diagnostics.HasError() { return } - updateInp := ios.NewUpdateInput( - stateData.ID.ValueString(), - planData.Name.ValueString(), - ) - - updateOutp, err := r.client.UpdateIos(ctx, *updateInp) - if err != nil { + // 3. do update + if err := Update(ctx, r, &planData, &stateData); err != nil { res.Diagnostics.AddError("failed to update IOS device", err.Error()) - return } - stateData.Name = types.StringValue(updateOutp.Name) - + // 4. set resulting state res.Diagnostics.Append(res.State.Set(ctx, &stateData)...) } @@ -258,19 +205,14 @@ func (r *IosDeviceResource) Delete(ctx context.Context, req resource.DeleteReque tflog.Trace(ctx, "delete IOS device resource") var stateData IosDeviceResourceModel - res.Diagnostics.Append(req.State.Get(ctx, &stateData)...) if res.Diagnostics.HasError() { return } - deleteInp := ios.NewDeleteInput(stateData.ID.ValueString()) - _, err := r.client.DeleteIos(ctx, *deleteInp) - if err != nil { + if err := Delete(ctx, r, &stateData); err != nil { res.Diagnostics.AddError("failed to delete IOS device", err.Error()) - return } - } func (r *IosDeviceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, res *resource.ImportStateResponse) {