Skip to content

Commit

Permalink
Merge pull request #976 from Juniper/970-add-support-for-location-att…
Browse files Browse the repository at this point in the history
…ribute-to-apstra_managed_device-resource

Add `location` attribute to managed device resource and data sources
  • Loading branch information
chrismarget-j authored Dec 8, 2024
2 parents af39b98 + f3a2340 commit e86e4eb
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 28 deletions.
19 changes: 17 additions & 2 deletions apstra/data_source_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ 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"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"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
Expand Down Expand Up @@ -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)...)
}
Expand Down
22 changes: 19 additions & 3 deletions apstra/data_source_agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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()))
}

Expand Down
82 changes: 61 additions & 21 deletions apstra/resource_managed_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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(
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -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)...)
Expand All @@ -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
Expand Down
26 changes: 24 additions & 2 deletions apstra/system_agents/managed_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
},
}
}

Expand Down Expand Up @@ -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,
},
}
}

Expand Down Expand Up @@ -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"))},
},
}
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions docs/data-sources/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions docs/resources/managed_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e86e4eb

Please sign in to comment.