From a1661ac502bc446690735e0a15db73bb743b5b77 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Wed, 19 Jul 2023 15:49:26 -0400 Subject: [PATCH] introduce datasource --- apstra/blueprint/datacenter_routing_policy.go | 84 +++++++++++++++ .../datacenter_routing_policy_export.go | 34 ++++++ apstra/blueprint/prefix_filter.go | 33 ++++++ .../data_source_datacenter_routing_policy.go | 100 ++++++++++++++++++ apstra/provider.go | 1 + 5 files changed, 252 insertions(+) create mode 100644 apstra/data_source_datacenter_routing_policy.go diff --git a/apstra/blueprint/datacenter_routing_policy.go b/apstra/blueprint/datacenter_routing_policy.go index 80325d06..4be34526 100644 --- a/apstra/blueprint/datacenter_routing_policy.go +++ b/apstra/blueprint/datacenter_routing_policy.go @@ -6,7 +6,9 @@ import ( "github.com/Juniper/apstra-go-sdk/apstra" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" @@ -132,6 +134,88 @@ func (o DatacenterRoutingPolicy) ResourceAttributes() map[string]resourceSchema. } } +func (o DatacenterRoutingPolicy) DataSourceAttributes() map[string]dataSourceSchema.Attribute { + nameRE := regexp.MustCompile("^[A-Za-z0-9_-]+$") + return map[string]dataSourceSchema.Attribute{ + "id": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Apstra graph node ID.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRelative(), + path.MatchRoot("name"), + }...), + }, + }, + "name": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Web UI 'name' field.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 18), + stringvalidator.RegexMatches(nameRE, "only underscore, dash and alphanumeric characters allowed."), + }, + }, + "description": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Web UI 'description' field.", + Computed: true, + }, + "blueprint_id": dataSourceSchema.StringAttribute{ + MarkdownDescription: "Apstra Blueprint ID.", + Required: true, + }, + "import_policy": dataSourceSchema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("One of '%s'", + strings.Join(utils.AllDcRoutingPolicyImportPolicy(), "', '")), + Computed: true, + }, + "export_policy": dataSourceSchema.SingleNestedAttribute{ + MarkdownDescription: "The export policy controls export of various types of fabric prefixes.", + Attributes: datacenterRoutingPolicyExport{}.dataSourceAttributes(), + Computed: true, + }, + "expect_default_ipv4": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Default IPv4 route is expected to be imported via protocol session using this " + + "policy. Used for rendering route expectations.", + Computed: true, + }, + "expect_default_ipv6": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Default IPv6 route is expected to be imported via protocol session using this " + + "policy. Used for rendering route expectations.", + Computed: true, + }, + "aggregate_prefixes": dataSourceSchema.ListAttribute{ + MarkdownDescription: "BGP Aggregate routes to be imported into a routing zone (VRF) on all border " + + "switches. This option can only be set on routing policies associated with routing zones, and cannot " + + "be set on per-connectivity point policies. The aggregated routes are sent to all external router " + + "peers in a SZ (VRF).", + Computed: true, + ElementType: types.StringType, + }, + "extra_imports": dataSourceSchema.ListNestedAttribute{ + MarkdownDescription: fmt.Sprintf("User defined import routes will be used in addition to any "+ + "routes generated by the import policies. Prefixes specified here are additive to the import policy, "+ + "unless 'import_policy' is set to %q, in which only these routes will be imported.", + apstra.DcRoutingPolicyImportPolicyExtraOnly), + Computed: true, + NestedObject: dataSourceSchema.NestedAttributeObject{ + Attributes: prefixFilter{}.dataSourceAttributes(), + Validators: []validator.Object{prefixFilterValidator()}, + }, + }, + "extra_exports": dataSourceSchema.ListNestedAttribute{ + MarkdownDescription: "User defined export routes will be used in addition to any other routes specified " + + "in export policies. These policies are additive. To advertise only extra routes, disable all export " + + "types within 'export_policy', and only the extra prefixes specified here will be advertised.", + Computed: true, + NestedObject: dataSourceSchema.NestedAttributeObject{ + Attributes: prefixFilter{}.dataSourceAttributes(), + Validators: []validator.Object{prefixFilterValidator()}, + }, + }, + } +} + func (o *DatacenterRoutingPolicy) Request(ctx context.Context, diags *diag.Diagnostics) *apstra.DcRoutingPolicyData { if o.ImportPolicy.IsUnknown() { o.ImportPolicy = types.StringValue(apstra.DcRoutingPolicyImportPolicyDefaultOnly.String()) diff --git a/apstra/blueprint/datacenter_routing_policy_export.go b/apstra/blueprint/datacenter_routing_policy_export.go index c81202cc..597a0994 100644 --- a/apstra/blueprint/datacenter_routing_policy_export.go +++ b/apstra/blueprint/datacenter_routing_policy_export.go @@ -4,6 +4,7 @@ import ( "context" "github.com/Juniper/apstra-go-sdk/apstra" "github.com/hashicorp/terraform-plugin-framework/attr" + dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" @@ -64,6 +65,39 @@ func (o datacenterRoutingPolicyExport) resourceAttributes() map[string]resourceS } } +func (o datacenterRoutingPolicyExport) dataSourceAttributes() map[string]dataSourceSchema.Attribute { + return map[string]dataSourceSchema.Attribute{ + "export_loopbacks": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all loopbacks within a routing zone (VRF) across spine, leaf, and L3 servers.", + Computed: true, + }, + "export_spine_superspine_links": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all spine-leaf (fabric) links within a VRF. EVPN routing zones do not have " + + "spine-leaf addressing, so this generated list may be empty. For routing zones of type Virtual L3 " + + "Fabric, subinterfaces between spine-leaf will be included.", + Computed: true, + }, + "export_spine_leaf_links": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all spine-supersine (fabric) links within the default routing zone (VRF)", + Computed: true, + }, + "export_l3_edge_server_links": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all leaf to L3 server links within a routing zone (VRF). This will be an " + + "empty list on a layer2 based blueprint", + Computed: true, + }, + "export_l2_edge_subnets": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all virtual networks (VLANs) that have L3 addresses within a routing zone (VRF).", + Computed: true, + }, + "export_static_routes": dataSourceSchema.BoolAttribute{ + MarkdownDescription: "Exports all subnets in a VRF associated with static routes from all fabric systems " + + "to external routers associated with this routing policy", + Computed: true, + }, + } +} + func (o datacenterRoutingPolicyExport) attrTypes() map[string]attr.Type { return map[string]attr.Type{ "export_loopbacks": types.BoolType, diff --git a/apstra/blueprint/prefix_filter.go b/apstra/blueprint/prefix_filter.go index 7cf702e7..f7628021 100644 --- a/apstra/blueprint/prefix_filter.go +++ b/apstra/blueprint/prefix_filter.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" @@ -62,6 +63,38 @@ func (o prefixFilter) resourceAttributes() map[string]resourceSchema.Attribute { } } +func (o prefixFilter) dataSourceAttributes() map[string]dataSourceSchema.Attribute { + return map[string]dataSourceSchema.Attribute{ + "prefix": dataSourceSchema.StringAttribute{ + MarkdownDescription: "IPv4 or IPv6 network address specified in the form of network/prefixlen.", + Computed: true, + }, + "ge_mask": dataSourceSchema.Int64Attribute{ + MarkdownDescription: "Match less-specific prefixes from a parent prefix, up from `ge_mask` to the prefix " + + "length of the route. Range is 0-32 for IPv4, 0-128 for IPv6. If not specified, implies the " + + "prefix-list entry should be an exact match. The option can be optionally be used in combination " + + "with `le_mask`. `ge_mask` must be longer than the subnet prefix length. If `le_mask` and `ge_mask` " + + "are both specified, then `le_mask` must be greater than `ge_mask`.", + Computed: true, + }, + "le_mask": dataSourceSchema.Int64Attribute{ + MarkdownDescription: "Match more-specific prefixes from a parent prefix, up until `le_mask` prefix len. " + + "Range is 0-32 for IPv4, 0-128 for IPv6. If not specified, implies the prefix-list entry should be " + + "an exact match. The option can be optionally be used in combination with `ge_mask`. `le_mask` must " + + "be longer than the subnet prefix length. If `le_mask` and `ge_mask` are both specified, then " + + "`le_mask` must be greater than `ge_mask`.", + Computed: true, + }, + "action": dataSourceSchema.StringAttribute{ + MarkdownDescription: fmt.Sprintf("If the action is %q, match the route. If the action is %q, do "+ + "not match the route. For composing complex policies, all prefix-list items will be processed in the "+ + "order specified, top-down. This allows the user to deny a subset of a route that may otherwise be "+ + "permitted.", apstra.PrefixFilterActionPermit, apstra.PrefixFilterActionDeny), + Computed: true, + }, + } +} + func (o prefixFilter) attrTypes() map[string]attr.Type { return map[string]attr.Type{ "prefix": types.StringType, diff --git a/apstra/data_source_datacenter_routing_policy.go b/apstra/data_source_datacenter_routing_policy.go new file mode 100644 index 00000000..6f00b29e --- /dev/null +++ b/apstra/data_source_datacenter_routing_policy.go @@ -0,0 +1,100 @@ +package tfapstra + +import ( + "context" + "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-apstra/apstra/blueprint" + "terraform-provider-apstra/apstra/utils" +) + +var _ datasource.DataSourceWithConfigure = &dataSourceDatacenterRoutingPolicy{} + +type dataSourceDatacenterRoutingPolicy struct { + client *apstra.Client +} + +func (o *dataSourceDatacenterRoutingPolicy) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_datacenter_routing_policy" +} + +func (o *dataSourceDatacenterRoutingPolicy) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + o.client = DataSourceGetClient(ctx, req, resp) +} + +func (o *dataSourceDatacenterRoutingPolicy) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "This resource returns details of a Datacenter Routing Policy.", + Attributes: blueprint.DatacenterRoutingPolicy{}.DataSourceAttributes(), + } +} + +func (o *dataSourceDatacenterRoutingPolicy) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + if o.client == nil { + resp.Diagnostics.AddError(errDataSourceUnconfiguredSummary, errDatasourceUnconfiguredDetail) + return + } + + // Retrieve values from config. + var config blueprint.DatacenterRoutingPolicy + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + bpClient, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(config.BlueprintId.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.Diagnostics.AddError(fmt.Sprintf("blueprint %s not found", + config.BlueprintId), err.Error()) + return + } + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + var api *apstra.DcRoutingPolicy + switch { + case !config.Id.IsNull(): + api, err = bpClient.GetRoutingPolicy(ctx, apstra.ObjectId(config.Id.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.Diagnostics.AddAttributeError( + path.Root("id"), + "Routing Policy not found", + fmt.Sprintf("Routing Policy with ID %s not found", config.Id)) + return + } + resp.Diagnostics.AddError( + "Failed reading Routing Policy", err.Error(), + ) + } + case !config.Name.IsNull(): + api, err = bpClient.GetRoutingPolicyByName(ctx, config.Name.ValueString()) + if err != nil { + if utils.IsApstra404(err) { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Routing Policy not found", + fmt.Sprintf("Routing Policy with Name %s not found", config.Name)) + return + } + resp.Diagnostics.AddError( + "Failed reading Routing Policy", err.Error(), + ) + } + } + + config.Id = types.StringValue(api.Id.String()) + config.LoadApiData(ctx, api.Data, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // set state + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/apstra/provider.go b/apstra/provider.go index 57db8289..49f4f1a3 100644 --- a/apstra/provider.go +++ b/apstra/provider.go @@ -341,6 +341,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource func() datasource.DataSource { return &dataSourceDatacenterBlueprint{} }, func() datasource.DataSource { return &dataSourceDatacenterPropertySet{} }, func() datasource.DataSource { return &dataSourceDatacenterPropertySets{} }, + func() datasource.DataSource { return &dataSourceDatacenterRoutingPolicy{} }, func() datasource.DataSource { return &dataSourceDatacenterRoutingZone{} }, func() datasource.DataSource { return &dataSourceDatacenterRoutingZones{} }, func() datasource.DataSource { return &dataSourceDatacenterSystemNode{} },