Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add location attribute to managed device resource and data sources #976

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading