diff --git a/apstra/data_source_agent.go b/apstra/data_source_agent.go index 424db5d3..48f38050 100644 --- a/apstra/data_source_agent.go +++ b/apstra/data_source_agent.go @@ -3,6 +3,7 @@ package tfapstra import ( "context" "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" systemAgents "github.com/Juniper/terraform-provider-apstra/apstra/system_agents" "github.com/Juniper/terraform-provider-apstra/apstra/utils" @@ -10,8 +11,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" ) -var _ datasource.DataSourceWithConfigure = &dataSourceAgent{} -var _ datasourceWithSetClient = &dataSourceAgent{} +var ( + _ datasource.DataSourceWithConfigure = &dataSourceAgent{} + _ datasourceWithSetClient = &dataSourceAgent{} +) type dataSourceAgent struct { client *apstra.Client @@ -61,6 +64,18 @@ func (o *dataSourceAgent) Read(ctx context.Context, req datasource.ReadRequest, return } + // Get System info from Api + systemInfo, err := o.client.GetSystemInfo(ctx, agent.Status.SystemId) + if err != nil { + resp.Diagnostics.AddError( + "error fetching system info", + fmt.Sprintf("Could not Read system info for %q - %s", agent.Status.SystemId, err.Error()), + ) + return + } + + config.LoadUserConfig(ctx, systemInfo.UserConfig, &resp.Diagnostics) + // set state resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) } diff --git a/apstra/data_source_agents.go b/apstra/data_source_agents.go index 1aa2646a..dd5e4e89 100644 --- a/apstra/data_source_agents.go +++ b/apstra/data_source_agents.go @@ -2,6 +2,9 @@ package tfapstra import ( "context" + "fmt" + "net" + "github.com/Juniper/apstra-go-sdk/apstra" systemAgents "github.com/Juniper/terraform-provider-apstra/apstra/system_agents" "github.com/Juniper/terraform-provider-apstra/apstra/utils" @@ -10,11 +13,12 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "net" ) -var _ datasource.DataSourceWithConfigure = &dataSourceAgents{} -var _ datasourceWithSetClient = &dataSourceAgents{} +var ( + _ datasource.DataSourceWithConfigure = &dataSourceAgents{} + _ datasourceWithSetClient = &dataSourceAgents{} +) type dataSourceAgents struct { client *apstra.Client @@ -125,6 +129,18 @@ func (o *dataSourceAgents) Read(ctx context.Context, req datasource.ReadRequest, continue } + if !filter.Location.IsNull() { + systemInfo, err := o.client.GetSystemInfo(ctx, agent.Status.SystemId) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("While getting info for system %q", agent.Status.SystemId), err.Error()) + return + } + + if filter.Location.ValueString() != systemInfo.UserConfig.Location { + continue + } + } + agentIdVals = append(agentIdVals, types.StringValue(agent.Id.String())) } diff --git a/apstra/resource_managed_device.go b/apstra/resource_managed_device.go index 043bc4f2..a7fc2c7c 100644 --- a/apstra/resource_managed_device.go +++ b/apstra/resource_managed_device.go @@ -3,6 +3,7 @@ package tfapstra import ( "context" "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" systemAgents "github.com/Juniper/terraform-provider-apstra/apstra/system_agents" "github.com/Juniper/terraform-provider-apstra/apstra/utils" @@ -12,8 +13,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -var _ resource.ResourceWithConfigure = &resourceManagedDevice{} -var _ resourceWithSetClient = &resourceManagedDevice{} +var ( + _ resource.ResourceWithConfigure = &resourceManagedDevice{} + _ resourceWithSetClient = &resourceManagedDevice{} +) type resourceManagedDevice struct { client *apstra.Client @@ -76,9 +79,7 @@ func (o *resourceManagedDevice) Create(ctx context.Context, req resource.CreateR // figure out the new switch system ID agentInfo, err := o.client.GetSystemAgent(ctx, agentId) if err != nil { - resp.Diagnostics.AddError( - "error fetching Agent info", - err.Error()) + resp.Diagnostics.AddError("error fetching Agent info", err.Error()) return } plan.SystemId = types.StringValue(string(agentInfo.Status.SystemId)) @@ -87,15 +88,14 @@ func (o *resourceManagedDevice) Create(ctx context.Context, req resource.CreateR // figure out the actual serial number (device_key) systemInfo, err := o.client.GetSystemInfo(ctx, agentInfo.Status.SystemId) if err != nil { - resp.Diagnostics.AddError( - "error fetching system info", - err.Error()) + resp.Diagnostics.AddError("error fetching system info", err.Error()) + return } // validate discovered device_key (serial number) if plan.DeviceKey.ValueString() == systemInfo.DeviceKey { // "acknowledge" the managed device:q - plan.Acknowledge(ctx, systemInfo, o.client, &resp.Diagnostics) + plan.SetUserConfig(ctx, systemInfo, o.client, &resp.Diagnostics) } else { // device_key supplied by config does not match discovered asset resp.Diagnostics.AddAttributeError( @@ -104,6 +104,7 @@ func (o *resourceManagedDevice) Create(ctx context.Context, req resource.CreateR fmt.Sprintf("config expects switch device_key %q, device reports %q", plan.DeviceKey.ValueString(), systemInfo.DeviceKey), ) + // do not return here -- the state needs to be set below } } @@ -142,6 +143,18 @@ func (o *resourceManagedDevice) Read(ctx context.Context, req resource.ReadReque return } + // Get System info from Api + systemInfo, err := o.client.GetSystemInfo(ctx, agentInfo.Status.SystemId) + if err != nil { + resp.Diagnostics.AddError( + "error fetching system info", + fmt.Sprintf("Could not Read system info for %q - %s", agentInfo.Status.SystemId, err.Error()), + ) + return + } + + newState.LoadUserConfig(ctx, systemInfo.UserConfig, &resp.Diagnostics) + // Device_key has 'requiresReplace()', so if it's not set in the state, // then it's also not set in the config. Only fetch the serial number if // the config is expecting a serial number. @@ -158,6 +171,13 @@ func (o *resourceManagedDevice) Read(ctx context.Context, req resource.ReadReque // Update resource func (o *resourceManagedDevice) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Get state values + var state systemAgents.ManagedDevice + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + // Get plan values var plan systemAgents.ManagedDevice resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) @@ -171,18 +191,38 @@ func (o *resourceManagedDevice) Update(ctx context.Context, req resource.UpdateR return } - // agent profile ID is the only value permitted to change (others trigger replacement) - err := o.client.AssignAgentProfile(ctx, &apstra.AssignAgentProfileRequest{ - SystemAgents: []apstra.ObjectId{apstra.ObjectId(plan.AgentId.ValueString())}, - ProfileId: apstra.ObjectId(plan.AgentProfileId.ValueString()), - }) - if err != nil { - resp.Diagnostics.AddError( - "error updating managed device agent", - fmt.Sprintf("error while updating managed device agent %q (%s) - %s", - plan.AgentId.ValueString(), plan.ManagementIp.ValueString(), err.Error()), - ) - return + // agent profile ID is one of only a handful of values permitted to change (others trigger replacement) + if !plan.AgentProfileId.Equal(state.AgentProfileId) { + err := o.client.AssignAgentProfile(ctx, &apstra.AssignAgentProfileRequest{ + SystemAgents: []apstra.ObjectId{apstra.ObjectId(plan.AgentId.ValueString())}, + ProfileId: apstra.ObjectId(plan.AgentProfileId.ValueString()), + }) + if err != nil { + resp.Diagnostics.AddError( + "error updating managed device agent", + fmt.Sprintf("error while updating managed device agent %q (%s) - %s", + plan.AgentId.ValueString(), plan.ManagementIp.ValueString(), err.Error()), + ) + return + } + } + + // Location is one of only a handful of values permitted to change (others trigger replacement) + if !plan.Location.Equal(state.Location) { + // Get System info from Api + systemInfo, err := o.client.GetSystemInfo(ctx, apstra.SystemId(state.DeviceKey.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "error fetching system info", + fmt.Sprintf("Could not Read system info for %s - %s", state.DeviceKey, err.Error()), + ) + return + } + + plan.SetUserConfig(ctx, systemInfo, o.client, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } } // set state to match plan diff --git a/apstra/system_agents/managed_device.go b/apstra/system_agents/managed_device.go index f2e73e1b..e0bd0a8d 100644 --- a/apstra/system_agents/managed_device.go +++ b/apstra/system_agents/managed_device.go @@ -29,6 +29,7 @@ type ManagedDevice struct { DeviceKey types.String `tfsdk:"device_key"` AgentProfileId types.String `tfsdk:"agent_profile_id"` OffBox types.Bool `tfsdk:"off_box"` + Location types.String `tfsdk:"location"` } func (o ManagedDevice) DataSourceAttributes() map[string]dataSourceSchema.Attribute { @@ -58,6 +59,10 @@ func (o ManagedDevice) DataSourceAttributes() map[string]dataSourceSchema.Attrib MarkdownDescription: "Indicates whether the agent runs on the switch (true) or on an Apstra node (false).", Computed: true, }, + "location": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Device `location` field.", + Computed: true, + }, } } @@ -98,8 +103,13 @@ func (o ManagedDevice) DataSourceFilterAttributes() map[string]dataSourceSchema. path.MatchRoot("filter").AtName("device_key"), path.MatchRoot("filter").AtName("agent_profile_id"), path.MatchRoot("filter").AtName("off_box"), + path.MatchRoot("filter").AtName("location"), )}, }, + "location": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Device `location` field.", + Optional: true, + }, } } @@ -141,6 +151,11 @@ func (o ManagedDevice) ResourceAttributes() map[string]resourceSchema.Attribute Default: booldefault.StaticBool(true), PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, }, + "location": resourceSchema.StringAttribute{ + MarkdownDescription: "Device `location` field.", + Optional: true, + Validators: []validator.String{stringvalidator.AlsoRequires(path.MatchRoot("device_key"))}, + }, } } @@ -161,6 +176,13 @@ func (o *ManagedDevice) LoadApiData(_ context.Context, in *apstra.SystemAgent, _ o.AgentId = types.StringValue(string(in.Id)) } +func (o *ManagedDevice) LoadUserConfig(_ context.Context, in apstra.SystemUserConfig, _ *diag.Diagnostics) { + o.Location = types.StringNull() + if in.Location != "" { + o.Location = types.StringValue(in.Location) + } +} + func (o *ManagedDevice) ValidateAgentProfile(ctx context.Context, client *apstra.Client, diags *diag.Diagnostics) { agentProfile, err := client.GetAgentProfile(ctx, apstra.ObjectId(o.AgentProfileId.ValueString())) if err != nil { @@ -193,11 +215,11 @@ func (o *ManagedDevice) ValidateAgentProfile(ctx context.Context, client *apstra } } -func (o *ManagedDevice) Acknowledge(ctx context.Context, si *apstra.ManagedSystemInfo, client *apstra.Client, diags *diag.Diagnostics) { - // update with new SystemUserConfig +func (o *ManagedDevice) SetUserConfig(ctx context.Context, si *apstra.ManagedSystemInfo, client *apstra.Client, diags *diag.Diagnostics) { err := client.UpdateSystem(ctx, apstra.SystemId(o.SystemId.ValueString()), &apstra.SystemUserConfig{ AosHclModel: si.Facts.AosHclModel, AdminState: apstra.SystemAdminStateNormal, + Location: o.Location.ValueString(), }) if err != nil { diags.AddError( diff --git a/docs/data-sources/agent.md b/docs/data-sources/agent.md index 69d9d2e9..abcd6b76 100644 --- a/docs/data-sources/agent.md +++ b/docs/data-sources/agent.md @@ -32,6 +32,7 @@ data "apstra_agent" "foo" { - `agent_profile_id` (String) Agent Profile ID associated with the Agent. - `device_key` (String) Key which uniquely identifies a System asset probably the serial number. +- `location` (String) Device `location` field. - `management_ip` (String) Management IP address of the system managed by the Agent. - `off_box` (Boolean) Indicates whether the agent runs on the switch (true) or on an Apstra node (false). - `system_id` (String) Apstra ID for the System managed by the Agent. diff --git a/docs/data-sources/agents.md b/docs/data-sources/agents.md index 12d6c11e..f6553dba 100644 --- a/docs/data-sources/agents.md +++ b/docs/data-sources/agents.md @@ -43,6 +43,7 @@ Optional: - `agent_id` (String) Apstra ID for the Managed Device Agent. - `agent_profile_id` (String) ID of the Agent Profile associated with the Agent. - `device_key` (String) Key which uniquely identifies a System asset, probably a serial number. +- `location` (String) Device `location` field. - `management_ip` (String) Management IP address of the System. - `off_box` (Boolean) Indicates whether the agent runs on the switch (true) or on an Apstra node (false). - `system_id` (String) Apstra ID for the System onboarded by the Managed Device Agent. diff --git a/docs/resources/managed_device.md b/docs/resources/managed_device.md index 0186376a..6c6c92f4 100644 --- a/docs/resources/managed_device.md +++ b/docs/resources/managed_device.md @@ -65,6 +65,7 @@ resource "apstra_managed_device" "example" { ### Optional - `device_key` (String) Key which uniquely identifies a System asset. Possibly a MAC address or serial number. +- `location` (String) Device `location` field. - `off_box` (Boolean) Indicates that an *offbox* agent should be created (required for Junos devices, default: `true`) ### Read-Only