diff --git a/examples/dc/bgp_instance/base-router.tf b/examples/dc/bgp_instance/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/bgp_instance/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/bgp_instance/main.tf b/examples/dc/bgp_instance/main.tf new file mode 100644 index 00000000..d529f1f5 --- /dev/null +++ b/examples/dc/bgp_instance/main.tf @@ -0,0 +1,10 @@ +resource "vkcs_dc_bgp_instance" "dc_bgp_instance" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + bgp_router_id = "192.168.1.2" + asn = 12345 + ecmp_enabled = true + enabled = true + graceful_restart = true +} diff --git a/examples/dc/bgp_neighbor/base-bgp-instance.tf b/examples/dc/bgp_neighbor/base-bgp-instance.tf new file mode 100644 index 00000000..d529f1f5 --- /dev/null +++ b/examples/dc/bgp_neighbor/base-bgp-instance.tf @@ -0,0 +1,10 @@ +resource "vkcs_dc_bgp_instance" "dc_bgp_instance" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + bgp_router_id = "192.168.1.2" + asn = 12345 + ecmp_enabled = true + enabled = true + graceful_restart = true +} diff --git a/examples/dc/bgp_neighbor/base-router.tf b/examples/dc/bgp_neighbor/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/bgp_neighbor/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/bgp_neighbor/main.tf b/examples/dc/bgp_neighbor/main.tf new file mode 100644 index 00000000..8396792d --- /dev/null +++ b/examples/dc/bgp_neighbor/main.tf @@ -0,0 +1,9 @@ +resource "vkcs_dc_bgp_neighbor" "dc_bgp_neighbor" { + name = "tf-example" + add_paths = "on" + description = "tf-example-description" + dc_bgp_id = vkcs_dc_bgp_instance.dc_bgp_instance.id + remote_asn = 1 + remote_ip = "192.168.1.3" + enabled = true +} diff --git a/examples/dc/bgp_static_announce/base-bgp-instance.tf b/examples/dc/bgp_static_announce/base-bgp-instance.tf new file mode 100644 index 00000000..d529f1f5 --- /dev/null +++ b/examples/dc/bgp_static_announce/base-bgp-instance.tf @@ -0,0 +1,10 @@ +resource "vkcs_dc_bgp_instance" "dc_bgp_instance" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + bgp_router_id = "192.168.1.2" + asn = 12345 + ecmp_enabled = true + enabled = true + graceful_restart = true +} diff --git a/examples/dc/bgp_static_announce/base-router.tf b/examples/dc/bgp_static_announce/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/bgp_static_announce/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/bgp_static_announce/main.tf b/examples/dc/bgp_static_announce/main.tf new file mode 100644 index 00000000..efbd849a --- /dev/null +++ b/examples/dc/bgp_static_announce/main.tf @@ -0,0 +1,8 @@ +resource "vkcs_dc_bgp_static_announce" "dc_bgp_static_announce" { + name = "tf-example" + description = "tf-example-description" + dc_bgp_id = vkcs_dc_bgp_instance.dc_bgp_instance.id + network = "192.168.1.0/24" + gateway = "192.168.1.3" + enabled = true +} diff --git a/examples/dc/interface/base-networking.tf b/examples/dc/interface/base-networking.tf new file mode 120000 index 00000000..95f9d0d9 --- /dev/null +++ b/examples/dc/interface/base-networking.tf @@ -0,0 +1 @@ +../../networking/main.tf \ No newline at end of file diff --git a/examples/dc/interface/base-router.tf b/examples/dc/interface/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/interface/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/interface/main.tf b/examples/dc/interface/main.tf new file mode 100644 index 00000000..8bf2229d --- /dev/null +++ b/examples/dc/interface/main.tf @@ -0,0 +1,8 @@ +resource "vkcs_dc_interface" "dc_interface" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + network_id = vkcs_networking_network.app.id + subnet_id = vkcs_networking_subnet.app.id + bgp_announce_enabled = true +} diff --git a/examples/dc/router/main.tf b/examples/dc/router/main.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/router/main.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/static_route/base-router.tf b/examples/dc/static_route/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/static_route/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/static_route/main.tf b/examples/dc/static_route/main.tf new file mode 100644 index 00000000..67bb6efa --- /dev/null +++ b/examples/dc/static_route/main.tf @@ -0,0 +1,8 @@ +resource "vkcs_dc_static_route" "dc_static_route" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + network = "192.168.1.0/24" + gateway = "192.168.1.3" + metric = 1 +} diff --git a/examples/dc/vrrp/base-networking.tf b/examples/dc/vrrp/base-networking.tf new file mode 100644 index 00000000..1a0e9b77 --- /dev/null +++ b/examples/dc/vrrp/base-networking.tf @@ -0,0 +1,45 @@ +# Create networks +resource "vkcs_networking_network" "app" { + name = "app-tf-example" + description = "Application network" +} + +resource "vkcs_networking_subnet" "app" { + name = "app-tf-example" + network_id = vkcs_networking_network.app.id + cidr = "192.168.199.0/24" +} + +resource "vkcs_networking_network" "db" { + name = "db-tf-example" + description = "Database network" +} + +resource "vkcs_networking_subnet" "db" { + name = "db-tf-example" + network_id = vkcs_networking_network.db.id + cidr = "192.168.166.0/24" +} + +# Get external network with Inernet access +data "vkcs_networking_network" "extnet" { + name = "ext-net" +} + +# Create a router to connect netwoks +resource "vkcs_networking_router" "router" { + name = "router-tf-example" + # Connect router to Internet + external_network_id = data.vkcs_networking_network.extnet.id +} + +# Connect networks to the router +resource "vkcs_networking_router_interface" "app" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.app.id +} + +resource "vkcs_networking_router_interface" "db" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.db.id +} diff --git a/examples/dc/vrrp/main.tf b/examples/dc/vrrp/main.tf new file mode 100644 index 00000000..e8e13c09 --- /dev/null +++ b/examples/dc/vrrp/main.tf @@ -0,0 +1,9 @@ +resource "vkcs_dc_vrrp" "dc_vrrp" { + name = "tf-example" + description = "tf-example-description" + group_id = 100 + network_id = vkcs_networking_network.app.id + subnet_id = vkcs_networking_subnet.app.id + advert_interval = 1 + enabled = true +} diff --git a/examples/dc/vrrp_address/base-networking.tf b/examples/dc/vrrp_address/base-networking.tf new file mode 100644 index 00000000..1a0e9b77 --- /dev/null +++ b/examples/dc/vrrp_address/base-networking.tf @@ -0,0 +1,45 @@ +# Create networks +resource "vkcs_networking_network" "app" { + name = "app-tf-example" + description = "Application network" +} + +resource "vkcs_networking_subnet" "app" { + name = "app-tf-example" + network_id = vkcs_networking_network.app.id + cidr = "192.168.199.0/24" +} + +resource "vkcs_networking_network" "db" { + name = "db-tf-example" + description = "Database network" +} + +resource "vkcs_networking_subnet" "db" { + name = "db-tf-example" + network_id = vkcs_networking_network.db.id + cidr = "192.168.166.0/24" +} + +# Get external network with Inernet access +data "vkcs_networking_network" "extnet" { + name = "ext-net" +} + +# Create a router to connect netwoks +resource "vkcs_networking_router" "router" { + name = "router-tf-example" + # Connect router to Internet + external_network_id = data.vkcs_networking_network.extnet.id +} + +# Connect networks to the router +resource "vkcs_networking_router_interface" "app" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.app.id +} + +resource "vkcs_networking_router_interface" "db" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.db.id +} diff --git a/examples/dc/vrrp_address/base-vrrp.tf b/examples/dc/vrrp_address/base-vrrp.tf new file mode 100644 index 00000000..e8e13c09 --- /dev/null +++ b/examples/dc/vrrp_address/base-vrrp.tf @@ -0,0 +1,9 @@ +resource "vkcs_dc_vrrp" "dc_vrrp" { + name = "tf-example" + description = "tf-example-description" + group_id = 100 + network_id = vkcs_networking_network.app.id + subnet_id = vkcs_networking_subnet.app.id + advert_interval = 1 + enabled = true +} diff --git a/examples/dc/vrrp_address/main.tf b/examples/dc/vrrp_address/main.tf new file mode 100644 index 00000000..5e743770 --- /dev/null +++ b/examples/dc/vrrp_address/main.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_vrrp_address" "dc_vrrp_address" { + name = "tf-example" + description = "tf-example-description" + dc_vrrp_id = vkcs_dc_vrrp.dc_vrrp.id + ip_address = "192.168.199.1" +} diff --git a/examples/dc/vrrp_interface/base-interface.tf b/examples/dc/vrrp_interface/base-interface.tf new file mode 100644 index 00000000..8bf2229d --- /dev/null +++ b/examples/dc/vrrp_interface/base-interface.tf @@ -0,0 +1,8 @@ +resource "vkcs_dc_interface" "dc_interface" { + name = "tf-example" + description = "tf-example-description" + dc_router_id = vkcs_dc_router.dc_router.id + network_id = vkcs_networking_network.app.id + subnet_id = vkcs_networking_subnet.app.id + bgp_announce_enabled = true +} diff --git a/examples/dc/vrrp_interface/base-networking.tf b/examples/dc/vrrp_interface/base-networking.tf new file mode 100644 index 00000000..1a0e9b77 --- /dev/null +++ b/examples/dc/vrrp_interface/base-networking.tf @@ -0,0 +1,45 @@ +# Create networks +resource "vkcs_networking_network" "app" { + name = "app-tf-example" + description = "Application network" +} + +resource "vkcs_networking_subnet" "app" { + name = "app-tf-example" + network_id = vkcs_networking_network.app.id + cidr = "192.168.199.0/24" +} + +resource "vkcs_networking_network" "db" { + name = "db-tf-example" + description = "Database network" +} + +resource "vkcs_networking_subnet" "db" { + name = "db-tf-example" + network_id = vkcs_networking_network.db.id + cidr = "192.168.166.0/24" +} + +# Get external network with Inernet access +data "vkcs_networking_network" "extnet" { + name = "ext-net" +} + +# Create a router to connect netwoks +resource "vkcs_networking_router" "router" { + name = "router-tf-example" + # Connect router to Internet + external_network_id = data.vkcs_networking_network.extnet.id +} + +# Connect networks to the router +resource "vkcs_networking_router_interface" "app" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.app.id +} + +resource "vkcs_networking_router_interface" "db" { + router_id = vkcs_networking_router.router.id + subnet_id = vkcs_networking_subnet.db.id +} diff --git a/examples/dc/vrrp_interface/base-router.tf b/examples/dc/vrrp_interface/base-router.tf new file mode 100644 index 00000000..2964a0b0 --- /dev/null +++ b/examples/dc/vrrp_interface/base-router.tf @@ -0,0 +1,6 @@ +resource "vkcs_dc_router" "dc_router" { + availability_zone = "GZ1" + flavor = "standard" + name = "tf-example" + description = "tf-example-description" +} diff --git a/examples/dc/vrrp_interface/base-vrrp.tf b/examples/dc/vrrp_interface/base-vrrp.tf new file mode 100644 index 00000000..e8e13c09 --- /dev/null +++ b/examples/dc/vrrp_interface/base-vrrp.tf @@ -0,0 +1,9 @@ +resource "vkcs_dc_vrrp" "dc_vrrp" { + name = "tf-example" + description = "tf-example-description" + group_id = 100 + network_id = vkcs_networking_network.app.id + subnet_id = vkcs_networking_subnet.app.id + advert_interval = 1 + enabled = true +} diff --git a/examples/dc/vrrp_interface/main.tf b/examples/dc/vrrp_interface/main.tf new file mode 100644 index 00000000..9b3777ca --- /dev/null +++ b/examples/dc/vrrp_interface/main.tf @@ -0,0 +1,9 @@ +resource "vkcs_dc_vrrp_interface" "dc_vrrp_interface" { + name = "tf-example" + description = "tf-example-description" + dc_vrrp_id = vkcs_dc_vrrp.dc_vrrp.id + dc_interface_id = vkcs_dc_interface.dc_interface.id + priority = 100 + preempt = true + master = true +} diff --git a/vkcs/dc/resource_bgp_instance.go b/vkcs/dc/resource_bgp_instance.go new file mode 100644 index 00000000..5f38f5de --- /dev/null +++ b/vkcs/dc/resource_bgp_instance.go @@ -0,0 +1,327 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/bgp_instances" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &BGPInstanceResource{} + +func NewBGPInstanceResource() resource.Resource { + return &BGPInstanceResource{} +} + +type BGPInstanceResource struct { + config clients.Config +} + +func (r *BGPInstanceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_bgp_instance" +} + +type BGPInstanceResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCRouterID types.String `tfsdk:"dc_router_id"` + BGPRouterID types.String `tfsdk:"bgp_router_id"` + ASN types.Int64 `tfsdk:"asn"` + ECMPEnabled types.Bool `tfsdk:"ecmp_enabled"` + Enabled types.Bool `tfsdk:"enabled"` + GracefulRestart types.Bool `tfsdk:"graceful_restart"` + LongLivedGracefulRestart types.Bool `tfsdk:"long_lived_graceful_restart"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *BGPInstanceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the router", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the router", + }, + + "dc_router_id": schema.StringAttribute{ + Required: true, + Description: "Direct Connect Router ID to attach", + }, + + "bgp_router_id": schema.StringAttribute{ + Required: true, + Description: "BGP Router ID (IP address that represent BGP router in BGP network)", + }, + + "asn": schema.Int64Attribute{ + Required: true, + Description: "BGP Autonomous System Number (integer representation supports only)", + }, + + "ecmp_enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable BGP ECMP behaviour on router", + }, + + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable or disable item", + }, + + "graceful_restart": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable BGP Graceful Restart feature", + }, + + "long_lived_graceful_restart": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable BGP Long Lived Graceful Restart feature", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect BGP instance resource.", + } +} + +func (r *BGPInstanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *BGPInstanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan BGPInstanceResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpInstanceCreateOpts := bgp_instances.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCRouterID: plan.DCRouterID.ValueString(), + BGPRouterID: plan.BGPRouterID.ValueString(), + ASN: int(plan.ASN.ValueInt64()), + ECMPEnabled: plan.ECMPEnabled.ValueBoolPointer(), + Enabled: plan.Enabled.ValueBoolPointer(), + GracefulRestart: plan.GracefulRestart.ValueBoolPointer(), + LongLivedGracefulRestart: plan.LongLivedGracefulRestart.ValueBoolPointer(), + } + + bgpInstanceResp, err := bgp_instances.Create(networkingClient, &bgp_instances.BGPInstanceCreate{BGPInstance: &bgpInstanceCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_bgp_instance", err.Error()) + return + } + bgpInstanceID := bgpInstanceResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), bgpInstanceID) + + plan.ID = types.StringValue(bgpInstanceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpInstanceResp.Name) + plan.Description = types.StringValue(bgpInstanceResp.Description) + plan.DCRouterID = types.StringValue(bgpInstanceResp.DCRouterID) + plan.BGPRouterID = types.StringValue(bgpInstanceResp.BGPRouterID) + plan.ASN = types.Int64Value(int64(bgpInstanceResp.ASN)) + plan.ECMPEnabled = types.BoolValue(bgpInstanceResp.ECMPEnabled) + plan.Enabled = types.BoolValue(bgpInstanceResp.Enabled) + plan.GracefulRestart = types.BoolValue(bgpInstanceResp.GracefulRestart) + plan.LongLivedGracefulRestart = types.BoolValue(bgpInstanceResp.LongLivedGracefulRestart) + + plan.CreatedAt = types.StringValue(bgpInstanceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpInstanceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPInstanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan BGPInstanceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpInstanceID := plan.ID.ValueString() + + bgpInstanceResp, err := bgp_instances.Get(networkingClient, bgpInstanceID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_bgp_instance", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(bgpInstanceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpInstanceResp.Name) + plan.Description = types.StringValue(bgpInstanceResp.Description) + plan.DCRouterID = types.StringValue(bgpInstanceResp.DCRouterID) + plan.BGPRouterID = types.StringValue(bgpInstanceResp.BGPRouterID) + plan.ASN = types.Int64Value(int64(bgpInstanceResp.ASN)) + plan.ECMPEnabled = types.BoolValue(bgpInstanceResp.ECMPEnabled) + plan.Enabled = types.BoolValue(bgpInstanceResp.Enabled) + plan.GracefulRestart = types.BoolValue(bgpInstanceResp.GracefulRestart) + plan.LongLivedGracefulRestart = types.BoolValue(bgpInstanceResp.LongLivedGracefulRestart) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *BGPInstanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan BGPInstanceResourceModel + var state BGPInstanceResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpInstanceID := state.ID.ValueString() + + bgpInstanceUpdateOpts := bgp_instances.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + ECMPEnabled: plan.ECMPEnabled.ValueBoolPointer(), + Enabled: plan.Enabled.ValueBoolPointer(), + GracefulRestart: plan.GracefulRestart.ValueBoolPointer(), + LongLivedGracefulRestart: plan.LongLivedGracefulRestart.ValueBoolPointer(), + } + + bgpInstanceResp, err := bgp_instances.Update(networkingClient, bgpInstanceID, &bgp_instances.BGPInstanceUpdate{BGPInstance: &bgpInstanceUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_bgp_instance", err.Error()) + return + } + + plan.ID = types.StringValue(bgpInstanceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpInstanceResp.Name) + plan.Description = types.StringValue(bgpInstanceResp.Description) + plan.DCRouterID = types.StringValue(bgpInstanceResp.DCRouterID) + plan.BGPRouterID = types.StringValue(bgpInstanceResp.BGPRouterID) + plan.ASN = types.Int64Value(int64(bgpInstanceResp.ASN)) + plan.ECMPEnabled = types.BoolValue(bgpInstanceResp.ECMPEnabled) + plan.Enabled = types.BoolValue(bgpInstanceResp.Enabled) + plan.GracefulRestart = types.BoolValue(bgpInstanceResp.GracefulRestart) + plan.LongLivedGracefulRestart = types.BoolValue(bgpInstanceResp.LongLivedGracefulRestart) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPInstanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan BGPInstanceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = bgp_instances.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_bgp_instance", err.Error()) + return + } +} + +func (r *BGPInstanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_bgp_neighbor.go b/vkcs/dc/resource_bgp_neighbor.go new file mode 100644 index 00000000..020ad48f --- /dev/null +++ b/vkcs/dc/resource_bgp_neighbor.go @@ -0,0 +1,355 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/bgp_neighbors" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &BGPNeighborResource{} + +func NewBGPNeighborResource() resource.Resource { + return &BGPNeighborResource{} +} + +type BGPNeighborResource struct { + config clients.Config +} + +func (r *BGPNeighborResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_bgp_neighbor" +} + +type BGPNeighborResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCBGPID types.String `tfsdk:"dc_bgp_id"` + RemoteASN types.Int64 `tfsdk:"remote_asn"` + RemoteIP types.String `tfsdk:"remote_ip"` + ForceIBGPNextHopSelf types.Bool `tfsdk:"force_ibgp_next_hop_self"` + AddPaths types.String `tfsdk:"add_paths"` + BFDEnabled types.Bool `tfsdk:"bfd_enabled"` + FilterIn types.String `tfsdk:"filter_in"` + FilterOut types.String `tfsdk:"filter_out"` + Enabled types.Bool `tfsdk:"enabled"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *BGPNeighborResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the BGP neighbor", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the BGP neighbor", + }, + + "dc_bgp_id": schema.StringAttribute{ + Required: true, + Description: "Direct Connect BGP ID to attach", + }, + + "remote_asn": schema.Int64Attribute{ + Required: true, + Description: "BGP Neighbor ASN", + }, + + "remote_ip": schema.StringAttribute{ + Required: true, + Description: "BGP Neighbor IP address", + }, + + "force_ibgp_next_hop_self": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Force set IP address of next-hop on BGP prefix to self even in iBGP", + }, + + "add_paths": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Activate BGP Add-Paths feature on peer.", + // sch: add validation + }, + + "bfd_enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Control BGP session activity with BFD protocol", + }, + + "filter_in": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Input filter that pass incoming BGP prefixes (allow any)", + }, + + "filter_out": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Output filter that pass incoming BGP prefixes (allow any)", + }, + + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable or disable item", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect BGP neighbor resource.", + } +} + +func (r *BGPNeighborResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *BGPNeighborResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan BGPNeighborResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpNeighborCreateOpts := bgp_neighbors.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCBGPID: plan.DCBGPID.ValueString(), + RemoteASN: int(plan.RemoteASN.ValueInt64()), + RemoteIP: plan.RemoteIP.ValueString(), + ForceIBGPNextHopSelf: plan.ForceIBGPNextHopSelf.ValueBoolPointer(), + AddPaths: plan.AddPaths.ValueString(), + BFDEnabled: plan.BFDEnabled.ValueBoolPointer(), + FilterIn: plan.FilterIn.ValueString(), + FilterOut: plan.FilterOut.ValueString(), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + bgpNeighborResp, err := bgp_neighbors.Create(networkingClient, &bgp_neighbors.BGPNeighborCreate{BGPNeighbor: &bgpNeighborCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_bgp_neighbor", err.Error()) + return + } + bgpNeighborID := bgpNeighborResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), bgpNeighborID) + + plan.ID = types.StringValue(bgpNeighborID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpNeighborResp.Name) + plan.Description = types.StringValue(bgpNeighborResp.Description) + plan.DCBGPID = types.StringValue(bgpNeighborResp.DCBGPID) + plan.RemoteASN = types.Int64Value(int64(bgpNeighborResp.RemoteASN)) + plan.RemoteIP = types.StringValue(bgpNeighborResp.RemoteIP) + plan.ForceIBGPNextHopSelf = types.BoolValue(bgpNeighborResp.ForceIBGPNextHopSelf) + plan.AddPaths = types.StringValue(bgpNeighborResp.AddPaths) + plan.BFDEnabled = types.BoolValue(bgpNeighborResp.BFDEnabled) + plan.FilterIn = types.StringValue(bgpNeighborResp.FilterIn) + plan.FilterOut = types.StringValue(bgpNeighborResp.FilterOut) + plan.Enabled = types.BoolValue(bgpNeighborResp.Enabled) + plan.CreatedAt = types.StringValue(bgpNeighborResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpNeighborResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPNeighborResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan BGPNeighborResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpNeighborID := plan.ID.ValueString() + + bgpNeighborResp, err := bgp_neighbors.Get(networkingClient, bgpNeighborID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_bgp_neighbor", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(bgpNeighborID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpNeighborResp.Name) + plan.Description = types.StringValue(bgpNeighborResp.Description) + plan.DCBGPID = types.StringValue(bgpNeighborResp.DCBGPID) + plan.RemoteASN = types.Int64Value(int64(bgpNeighborResp.RemoteASN)) + plan.RemoteIP = types.StringValue(bgpNeighborResp.RemoteIP) + plan.ForceIBGPNextHopSelf = types.BoolValue(bgpNeighborResp.ForceIBGPNextHopSelf) + plan.AddPaths = types.StringValue(bgpNeighborResp.AddPaths) + plan.BFDEnabled = types.BoolValue(bgpNeighborResp.BFDEnabled) + plan.FilterIn = types.StringValue(bgpNeighborResp.FilterIn) + plan.FilterOut = types.StringValue(bgpNeighborResp.FilterOut) + plan.Enabled = types.BoolValue(bgpNeighborResp.Enabled) + plan.CreatedAt = types.StringValue(bgpNeighborResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpNeighborResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *BGPNeighborResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan BGPNeighborResourceModel + var state BGPNeighborResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpNeighborID := state.ID.ValueString() + + bgpNeighborUpdateOpts := bgp_neighbors.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + ForceIBGPNextHopSelf: plan.ForceIBGPNextHopSelf.ValueBoolPointer(), + AddPaths: plan.AddPaths.ValueString(), + BFDEnabled: plan.BFDEnabled.ValueBoolPointer(), + FilterIn: plan.FilterIn.ValueString(), + FilterOut: plan.FilterOut.ValueString(), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + bgpNeighborResp, err := bgp_neighbors.Update(networkingClient, bgpNeighborID, &bgp_neighbors.BGPNeighborUpdate{BGPNeighbor: &bgpNeighborUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_bgp_instance", err.Error()) + return + } + + plan.ID = types.StringValue(bgpNeighborID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpNeighborResp.Name) + plan.Description = types.StringValue(bgpNeighborResp.Description) + plan.DCBGPID = types.StringValue(bgpNeighborResp.DCBGPID) + plan.RemoteASN = types.Int64Value(int64(bgpNeighborResp.RemoteASN)) + plan.RemoteIP = types.StringValue(bgpNeighborResp.RemoteIP) + plan.ForceIBGPNextHopSelf = types.BoolValue(bgpNeighborResp.ForceIBGPNextHopSelf) + plan.AddPaths = types.StringValue(bgpNeighborResp.AddPaths) + plan.BFDEnabled = types.BoolValue(bgpNeighborResp.BFDEnabled) + plan.FilterIn = types.StringValue(bgpNeighborResp.FilterIn) + plan.FilterOut = types.StringValue(bgpNeighborResp.FilterOut) + plan.Enabled = types.BoolValue(bgpNeighborResp.Enabled) + plan.CreatedAt = types.StringValue(bgpNeighborResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpNeighborResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPNeighborResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan BGPNeighborResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = bgp_neighbors.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_bgp_neighbor", err.Error()) + return + } +} + +func (r *BGPNeighborResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_bgp_static_announce.go b/vkcs/dc/resource_bgp_static_announce.go new file mode 100644 index 00000000..a23ff80b --- /dev/null +++ b/vkcs/dc/resource_bgp_static_announce.go @@ -0,0 +1,294 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/bgp_static_announces" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &BGPStaticAnnounceResource{} + +func NewBGPStaticAnnounceResource() resource.Resource { + return &BGPStaticAnnounceResource{} +} + +type BGPStaticAnnounceResource struct { + config clients.Config +} + +func (r *BGPStaticAnnounceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_bgp_static_announce" +} + +type BGPStaticAnnounceResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCBGPID types.String `tfsdk:"dc_bgp_id"` + Network types.String `tfsdk:"network"` + Gateway types.String `tfsdk:"gateway"` + Enabled types.Bool `tfsdk:"enabled"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *BGPStaticAnnounceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the BGP neighbor", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the BGP neighbor", + }, + + "dc_bgp_id": schema.StringAttribute{ + Required: true, + Description: "Direct Connect BGP ID to attach", + }, + + "network": schema.StringAttribute{ + Required: true, + Description: "Subnet in CIDR notation", + }, + + "gateway": schema.StringAttribute{ + Required: true, + Description: "IP address of gateway", + }, + + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable or disable item", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect BGP Static Announce resource.", + } +} + +func (r *BGPStaticAnnounceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *BGPStaticAnnounceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan BGPStaticAnnounceResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpStaticAnnounceCreateOpts := bgp_static_announces.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCBGPID: plan.DCBGPID.ValueString(), + Network: plan.Network.ValueString(), + Gateway: plan.Gateway.ValueString(), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + bgpStaticAnnounceResp, err := bgp_static_announces.Create(networkingClient, &bgp_static_announces.BGPStaticAnnounceCreate{BGPStaticAnnounce: &bgpStaticAnnounceCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_bgp_static_announce", err.Error()) + return + } + bgpStaticAnnounceID := bgpStaticAnnounceResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), bgpStaticAnnounceID) + + plan.ID = types.StringValue(bgpStaticAnnounceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpStaticAnnounceResp.Name) + plan.Description = types.StringValue(bgpStaticAnnounceResp.Description) + plan.DCBGPID = types.StringValue(bgpStaticAnnounceResp.DCBGPID) + plan.Network = types.StringValue(bgpStaticAnnounceResp.Network) + plan.Gateway = types.StringValue(bgpStaticAnnounceResp.Gateway) + plan.Enabled = types.BoolValue(bgpStaticAnnounceResp.Enabled) + plan.CreatedAt = types.StringValue(bgpStaticAnnounceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpStaticAnnounceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPStaticAnnounceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan BGPStaticAnnounceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpStaticAnnounceID := plan.ID.ValueString() + + bgpStaticAnnounceResp, err := bgp_static_announces.Get(networkingClient, bgpStaticAnnounceID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_bgp_static_announce", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(bgpStaticAnnounceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpStaticAnnounceResp.Name) + plan.Description = types.StringValue(bgpStaticAnnounceResp.Description) + plan.DCBGPID = types.StringValue(bgpStaticAnnounceResp.DCBGPID) + plan.Network = types.StringValue(bgpStaticAnnounceResp.Network) + plan.Gateway = types.StringValue(bgpStaticAnnounceResp.Gateway) + plan.Enabled = types.BoolValue(bgpStaticAnnounceResp.Enabled) + plan.CreatedAt = types.StringValue(bgpStaticAnnounceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpStaticAnnounceResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *BGPStaticAnnounceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan BGPStaticAnnounceResourceModel + var state BGPStaticAnnounceResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + bgpStaticAnnounceID := state.ID.ValueString() + + bgpStaticAnnounceUpdateOpts := bgp_static_announces.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + bgpStaticAnnounceResp, err := bgp_static_announces.Update(networkingClient, bgpStaticAnnounceID, &bgp_static_announces.BGPStaticAnnounceUpdate{BGPStaticAnnounce: &bgpStaticAnnounceUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_bgp_static_announce", err.Error()) + return + } + + plan.ID = types.StringValue(bgpStaticAnnounceID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(bgpStaticAnnounceResp.Name) + plan.Description = types.StringValue(bgpStaticAnnounceResp.Description) + plan.DCBGPID = types.StringValue(bgpStaticAnnounceResp.DCBGPID) + plan.Network = types.StringValue(bgpStaticAnnounceResp.Network) + plan.Gateway = types.StringValue(bgpStaticAnnounceResp.Gateway) + plan.Enabled = types.BoolValue(bgpStaticAnnounceResp.Enabled) + plan.CreatedAt = types.StringValue(bgpStaticAnnounceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(bgpStaticAnnounceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *BGPStaticAnnounceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan BGPStaticAnnounceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = bgp_static_announces.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_bgp_static_announce", err.Error()) + return + } +} + +func (r *BGPStaticAnnounceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_interface.go b/vkcs/dc/resource_interface.go new file mode 100644 index 00000000..df814d37 --- /dev/null +++ b/vkcs/dc/resource_interface.go @@ -0,0 +1,351 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/interfaces" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &InterfaceResource{} + +func NewInterfaceResource() resource.Resource { + return &InterfaceResource{} +} + +type InterfaceResource struct { + config clients.Config +} + +func (r *InterfaceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_interface" +} + +type InterfaceResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCRouterID types.String `tfsdk:"dc_router_id"` + NetworkID types.String `tfsdk:"network_id"` + SubnetID types.String `tfsdk:"subnet_id"` + BGPAnnounceEnabled types.Bool `tfsdk:"bgp_announce_enabled"` + PortID types.String `tfsdk:"port_id"` + SDN types.String `tfsdk:"sdn"` + IPAddress types.String `tfsdk:"ip_address"` + IPNetmask types.Int64 `tfsdk:"ip_netmask"` + MACAddress types.String `tfsdk:"mac_address"` + MTU types.Int64 `tfsdk:"mtu"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + + Region types.String `tfsdk:"region"` +} + +func (r *InterfaceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the interface", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the interface", + }, + + "dc_router_id": schema.StringAttribute{ + Required: true, + Description: "Direct Connect Router ID to attach", + }, + + "network_id": schema.StringAttribute{ + Required: true, + Description: "Network ID to attach", + }, + + "subnet_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Subnet ID to attach", + }, + + "bgp_announce_enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable BGP announce of subnet attached to interface", + }, + + "port_id": schema.StringAttribute{ + Computed: true, + Description: "Port ID", + }, + + "sdn": schema.StringAttribute{ + Computed: true, + Description: "SDN", + }, + + "ip_address": schema.StringAttribute{ + Computed: true, + Description: "IP Address", + }, + + "ip_netmask": schema.Int64Attribute{ + Computed: true, + Description: "IP Netmask", + }, + + "mac_address": schema.StringAttribute{ + Computed: true, + Description: "IP Netmask", + }, + + "mtu": schema.Int64Attribute{ + Computed: true, + Description: "MTU", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect interface resource.", + } +} + +func (r *InterfaceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *InterfaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan InterfaceResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + interfaceCreateOpts := interfaces.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCRouterID: plan.DCRouterID.ValueString(), + NetworkID: plan.NetworkID.ValueString(), + SubnetID: plan.SubnetID.ValueString(), + BGPAnnounceEnabled: plan.BGPAnnounceEnabled.ValueBoolPointer(), + } + + interfaceResp, err := interfaces.Create(networkingClient, &interfaces.InterfaceCreate{Interface: &interfaceCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_interface", err.Error()) + return + } + interfaceID := interfaceResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), interfaceID) + + plan.ID = types.StringValue(interfaceResp.ID) + plan.Region = types.StringValue(region) + + plan.Name = types.StringValue(interfaceResp.Name) + plan.Description = types.StringValue(interfaceResp.Description) + plan.DCRouterID = types.StringValue(interfaceResp.DCRouterID) + plan.NetworkID = types.StringValue(interfaceResp.NetworkID) + plan.SubnetID = types.StringValue(interfaceResp.SubnetID) + plan.BGPAnnounceEnabled = types.BoolValue(interfaceResp.BGPAnnounceEnabled) + plan.PortID = types.StringValue(interfaceResp.PortID) + plan.SDN = types.StringValue(interfaceResp.SDN) + plan.IPAddress = types.StringValue(interfaceResp.IPAddress) + plan.IPNetmask = types.Int64Value(int64(interfaceResp.IPNetmask)) + plan.MACAddress = types.StringValue(interfaceResp.MACAddress) + plan.MTU = types.Int64Value(int64(interfaceResp.MTU)) + plan.CreatedAt = types.StringValue(interfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(interfaceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *InterfaceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan InterfaceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + interfaceID := plan.ID.ValueString() + + interfaceResp, err := interfaces.Get(networkingClient, interfaceID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_interface", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(interfaceResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(interfaceResp.Name) + plan.Description = types.StringValue(interfaceResp.Description) + plan.DCRouterID = types.StringValue(interfaceResp.DCRouterID) + plan.NetworkID = types.StringValue(interfaceResp.NetworkID) + plan.SubnetID = types.StringValue(interfaceResp.SubnetID) + plan.BGPAnnounceEnabled = types.BoolValue(interfaceResp.BGPAnnounceEnabled) + plan.PortID = types.StringValue(interfaceResp.PortID) + plan.SDN = types.StringValue(interfaceResp.SDN) + plan.IPAddress = types.StringValue(interfaceResp.IPAddress) + plan.IPNetmask = types.Int64Value(int64(interfaceResp.IPNetmask)) + plan.MACAddress = types.StringValue(interfaceResp.MACAddress) + plan.MTU = types.Int64Value(int64(interfaceResp.MTU)) + plan.CreatedAt = types.StringValue(interfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(interfaceResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *InterfaceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan InterfaceResourceModel + var state InterfaceResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + interfaceID := state.ID.ValueString() + + interfaceUpdateOpts := interfaces.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + BGPAnnounceEnabled: plan.BGPAnnounceEnabled.ValueBoolPointer(), + } + + interfaceResp, err := interfaces.Update(networkingClient, interfaceID, &interfaces.InterfaceUpdate{Interface: &interfaceUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_interface", err.Error()) + return + } + + plan.ID = types.StringValue(interfaceResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(interfaceResp.Name) + plan.Description = types.StringValue(interfaceResp.Description) + plan.DCRouterID = types.StringValue(interfaceResp.DCRouterID) + plan.NetworkID = types.StringValue(interfaceResp.NetworkID) + plan.SubnetID = types.StringValue(interfaceResp.SubnetID) + plan.BGPAnnounceEnabled = types.BoolValue(interfaceResp.BGPAnnounceEnabled) + plan.PortID = types.StringValue(interfaceResp.PortID) + plan.SDN = types.StringValue(interfaceResp.SDN) + plan.IPAddress = types.StringValue(interfaceResp.IPAddress) + plan.IPNetmask = types.Int64Value(int64(interfaceResp.IPNetmask)) + plan.MACAddress = types.StringValue(interfaceResp.MACAddress) + plan.MTU = types.Int64Value(int64(interfaceResp.MTU)) + plan.CreatedAt = types.StringValue(interfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(interfaceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *InterfaceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan InterfaceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = interfaces.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_interface", err.Error()) + return + } +} + +func (r *InterfaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_router.go b/vkcs/dc/resource_router.go new file mode 100644 index 00000000..e1786102 --- /dev/null +++ b/vkcs/dc/resource_router.go @@ -0,0 +1,268 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/routers" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &RouterResource{} + +func NewRouterResource() resource.Resource { + return &RouterResource{} +} + +type RouterResource struct { + config clients.Config +} + +func (r *RouterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_router" +} + +type RouterResourceModel struct { + ID types.String `tfsdk:"id"` + AvailabilityZone types.String `tfsdk:"availability_zone"` + Flavor types.String `tfsdk:"flavor"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *RouterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "availability_zone": schema.StringAttribute{ + Required: true, + Description: "The availability zone in which to create the router. Changing this creates a new router", + }, + + "flavor": schema.StringAttribute{ + Required: true, + Description: "Flavor of the router. Changing this creates a new router", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the router", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the router", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect router resource.", + } +} + +func (r *RouterResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *RouterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan RouterResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + routerCreateOpts := routers.CreateOpts{ + AvailabilityZone: plan.AvailabilityZone.ValueString(), + Flavor: plan.Flavor.ValueString(), + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + } + + routerResp, err := routers.Create(networkingClient, &routers.RouterCreate{Router: &routerCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_router", err.Error()) + return + } + routerID := routerResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), routerID) + + plan.ID = types.StringValue(routerID) + plan.Region = types.StringValue(region) + plan.AvailabilityZone = types.StringValue(routerResp.AvailabilityZone) + plan.Flavor = types.StringValue(routerResp.Flavor) + plan.Name = types.StringValue(routerResp.Name) + plan.Description = types.StringValue(routerResp.Description) + plan.CreatedAt = types.StringValue(routerResp.CreatedAt) + plan.UpdatedAt = types.StringValue(routerResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *RouterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan RouterResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + routerID := plan.ID.ValueString() + + routerResp, err := routers.Get(networkingClient, routerID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_router", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.AvailabilityZone = types.StringValue(routerResp.AvailabilityZone) + plan.Flavor = types.StringValue(routerResp.Flavor) + plan.Name = types.StringValue(routerResp.Name) + plan.Description = types.StringValue(routerResp.Description) + plan.CreatedAt = types.StringValue(routerResp.CreatedAt) + plan.UpdatedAt = types.StringValue(routerResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *RouterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan RouterResourceModel + var state RouterResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + routerID := state.ID.ValueString() + + routerUpdateOpts := routers.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + } + + routerResp, err := routers.Update(networkingClient, routerID, &routers.RouterUpdate{Router: &routerUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_router", err.Error()) + return + } + + plan.ID = types.StringValue(routerResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(routerResp.Name) + plan.Description = types.StringValue(routerResp.Description) + plan.CreatedAt = types.StringValue(routerResp.CreatedAt) + plan.UpdatedAt = types.StringValue(routerResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *RouterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan RouterResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = routers.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_router", err.Error()) + return + } +} + +func (r *RouterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_static_route.go b/vkcs/dc/resource_static_route.go new file mode 100644 index 00000000..293f6c72 --- /dev/null +++ b/vkcs/dc/resource_static_route.go @@ -0,0 +1,293 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/static_routes" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &StaticRouteResource{} + +func NewStaticRouteResource() resource.Resource { + return &StaticRouteResource{} +} + +type StaticRouteResource struct { + config clients.Config +} + +func (r *StaticRouteResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_static_route" +} + +type StaticRouteResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCRouterID types.String `tfsdk:"dc_router_id"` + Network types.String `tfsdk:"network"` + Gateway types.String `tfsdk:"gateway"` + Metric types.Int64 `tfsdk:"metric"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *StaticRouteResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the static route", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the static route", + }, + + "dc_router_id": schema.StringAttribute{ + Required: true, + Description: "Direct Connect Router ID to attachh", + }, + + "network": schema.StringAttribute{ + Required: true, + Description: "Subnet in CIDR notation", + }, + + "gateway": schema.StringAttribute{ + Required: true, + Description: "IP address of gateway", + }, + + "metric": schema.Int64Attribute{ + Required: true, + Description: "Metric to use for route", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect BGP Static Announce resource.", + } +} + +func (r *StaticRouteResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *StaticRouteResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan StaticRouteResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + staticRouteCreateOpts := static_routes.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCRouterID: plan.DCRouterID.ValueString(), + Network: plan.Network.ValueString(), + Gateway: plan.Gateway.ValueString(), + Metric: int(plan.Metric.ValueInt64()), + } + + staticRouteResp, err := static_routes.Create(networkingClient, &static_routes.StaticRouteCreate{StaticRoute: &staticRouteCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_static_route", err.Error()) + return + } + staticRouteID := staticRouteResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), staticRouteID) + + plan.ID = types.StringValue(staticRouteID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(staticRouteResp.Name) + plan.Description = types.StringValue(staticRouteResp.Description) + plan.DCRouterID = types.StringValue(staticRouteResp.DCRouterID) + plan.Network = types.StringValue(staticRouteResp.Network) + plan.Gateway = types.StringValue(staticRouteResp.Gateway) + plan.Metric = types.Int64Value(int64(staticRouteResp.Metric)) + plan.CreatedAt = types.StringValue(staticRouteResp.CreatedAt) + plan.UpdatedAt = types.StringValue(staticRouteResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *StaticRouteResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan StaticRouteResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + staticRouteID := plan.ID.ValueString() + + staticRouteResp, err := static_routes.Get(networkingClient, staticRouteID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_static_route", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(staticRouteID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(staticRouteResp.Name) + plan.Description = types.StringValue(staticRouteResp.Description) + plan.DCRouterID = types.StringValue(staticRouteResp.DCRouterID) + plan.Network = types.StringValue(staticRouteResp.Network) + plan.Gateway = types.StringValue(staticRouteResp.Gateway) + plan.Metric = types.Int64Value(int64(staticRouteResp.Metric)) + plan.CreatedAt = types.StringValue(staticRouteResp.CreatedAt) + plan.UpdatedAt = types.StringValue(staticRouteResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *StaticRouteResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan StaticRouteResourceModel + var state StaticRouteResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + staticRouteID := state.ID.ValueString() + + staticRouteUpdateOpts := static_routes.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + Metric: int(plan.Metric.ValueInt64()), + } + + staticRouteResp, err := static_routes.Update(networkingClient, staticRouteID, &static_routes.StaticRouteUpdate{StaticRoute: &staticRouteUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_static_route", err.Error()) + return + } + + plan.ID = types.StringValue(staticRouteID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(staticRouteResp.Name) + plan.Description = types.StringValue(staticRouteResp.Description) + plan.DCRouterID = types.StringValue(staticRouteResp.DCRouterID) + plan.Network = types.StringValue(staticRouteResp.Network) + plan.Gateway = types.StringValue(staticRouteResp.Gateway) + plan.Metric = types.Int64Value(int64(staticRouteResp.Metric)) + plan.CreatedAt = types.StringValue(staticRouteResp.CreatedAt) + plan.UpdatedAt = types.StringValue(staticRouteResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *StaticRouteResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan StaticRouteResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = static_routes.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_static_route", err.Error()) + return + } +} + +func (r *StaticRouteResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_vrrp.go b/vkcs/dc/resource_vrrp.go new file mode 100644 index 00000000..46d0f5c4 --- /dev/null +++ b/vkcs/dc/resource_vrrp.go @@ -0,0 +1,317 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/vrrps" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &VRRPResource{} + +func NewVRRPResource() resource.Resource { + return &VRRPResource{} +} + +type VRRPResource struct { + config clients.Config +} + +func (r *VRRPResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_vrrp" +} + +type VRRPResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + GroupID types.Int64 `tfsdk:"group_id"` + NetworkID types.String `tfsdk:"network_id"` + SubnetID types.String `tfsdk:"subnet_id"` + AdvertInterval types.Int64 `tfsdk:"advert_interval"` + Enabled types.Bool `tfsdk:"enabled"` + SDN types.String `tfsdk:"sdn"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + + Region types.String `tfsdk:"region"` +} + +func (r *VRRPResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the VRRP", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the VRRP", + }, + + "group_id": schema.Int64Attribute{ + Required: true, + Description: "VRRP Group ID", + }, + + "network_id": schema.StringAttribute{ + Required: true, + Description: "Network ID to attach", + }, + + "subnet_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Subnet ID to attach", + }, + + "advert_interval": schema.Int64Attribute{ + Optional: true, + Computed: true, + Description: "VRRP Advertise interval", + }, + + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Enable or disable item", + }, + + "sdn": schema.StringAttribute{ + Computed: true, + Description: "SDN", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect VRRP resource.", + } +} + +func (r *VRRPResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *VRRPResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan VRRPResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpCreateOpts := vrrps.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + GroupID: int(plan.GroupID.ValueInt64()), + NetworkID: plan.NetworkID.ValueString(), + SubnetID: plan.SubnetID.ValueString(), + AdvertInterval: int(plan.AdvertInterval.ValueInt64()), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + vrrpResp, err := vrrps.Create(networkingClient, &vrrps.VRRPCreate{VRRP: &vrrpCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_vrrp", err.Error()) + return + } + vrrpID := vrrpResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), vrrpID) + + plan.ID = types.StringValue(vrrpResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpResp.Name) + plan.Description = types.StringValue(vrrpResp.Description) + plan.GroupID = types.Int64Value(int64(vrrpResp.GroupID)) + plan.NetworkID = types.StringValue(vrrpResp.NetworkID) + plan.SubnetID = types.StringValue(vrrpResp.SubnetID) + plan.AdvertInterval = types.Int64Value(int64(vrrpResp.AdvertInterval)) + plan.Enabled = types.BoolValue(plan.Enabled.ValueBool()) + plan.SDN = types.StringValue(vrrpResp.SDN) + plan.CreatedAt = types.StringValue(vrrpResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan VRRPResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpID := plan.ID.ValueString() + + vrrpResp, err := vrrps.Get(networkingClient, vrrpID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_vrrp", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(vrrpResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpResp.Name) + plan.Description = types.StringValue(vrrpResp.Description) + plan.GroupID = types.Int64Value(int64(vrrpResp.GroupID)) + plan.NetworkID = types.StringValue(vrrpResp.NetworkID) + plan.SubnetID = types.StringValue(vrrpResp.SubnetID) + plan.AdvertInterval = types.Int64Value(int64(vrrpResp.AdvertInterval)) + plan.Enabled = types.BoolValue(plan.Enabled.ValueBool()) + plan.SDN = types.StringValue(vrrpResp.SDN) + plan.CreatedAt = types.StringValue(vrrpResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *VRRPResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan VRRPResourceModel + var state VRRPResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpID := state.ID.ValueString() + + vrrpUpdateOpts := vrrps.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + AdvertInterval: int(plan.AdvertInterval.ValueInt64()), + Enabled: plan.Enabled.ValueBoolPointer(), + } + + vrrpResp, err := vrrps.Update(networkingClient, vrrpID, &vrrps.VRRPUpdate{VRRP: &vrrpUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_vrrp", err.Error()) + return + } + + plan.ID = types.StringValue(vrrpResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpResp.Name) + plan.Description = types.StringValue(vrrpResp.Description) + plan.GroupID = types.Int64Value(int64(vrrpResp.GroupID)) + plan.NetworkID = types.StringValue(vrrpResp.NetworkID) + plan.SubnetID = types.StringValue(vrrpResp.SubnetID) + plan.AdvertInterval = types.Int64Value(int64(vrrpResp.AdvertInterval)) + plan.Enabled = types.BoolValue(plan.Enabled.ValueBool()) + plan.SDN = types.StringValue(vrrpResp.SDN) + plan.CreatedAt = types.StringValue(vrrpResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan VRRPResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = vrrps.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_vrrp", err.Error()) + return + } +} + +func (r *VRRPResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_vrrp_address.go b/vkcs/dc/resource_vrrp_address.go new file mode 100644 index 00000000..2b898e2d --- /dev/null +++ b/vkcs/dc/resource_vrrp_address.go @@ -0,0 +1,281 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/vrrp_addresses" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &VRRPAddressResource{} + +func NewVRRPAddressResource() resource.Resource { + return &VRRPAddressResource{} +} + +type VRRPAddressResource struct { + config clients.Config +} + +func (r *VRRPAddressResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_vrrp_address" +} + +type VRRPAddressResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCVRRPID types.String `tfsdk:"dc_vrrp_id"` + IPAddress types.String `tfsdk:"ip_address"` + PortID types.String `tfsdk:"port_id"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *VRRPAddressResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the VRRP", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the VRRP", + }, + + "dc_vrrp_id": schema.StringAttribute{ + Required: true, + Description: "VRRP ID to attach", + }, + + "ip_address": schema.StringAttribute{ + Required: true, + Description: "IP address to assign", + }, + + "port_id": schema.StringAttribute{ + Computed: true, + Description: "Port ID used to assign IP address", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect VRRP address resource.", + } +} + +func (r *VRRPAddressResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *VRRPAddressResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan VRRPAddressResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpAddressCreateOpts := vrrp_addresses.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCVRRPID: plan.DCVRRPID.ValueString(), + IPAddress: plan.IPAddress.ValueString(), + } + + vrrpAddressResp, err := vrrp_addresses.Create(networkingClient, &vrrp_addresses.VRRPAddressCreate{VRRPAddress: &vrrpAddressCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_vrrp_address", err.Error()) + return + } + vrrpAddressID := vrrpAddressResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), vrrpAddressID) + + plan.ID = types.StringValue(vrrpAddressResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpAddressResp.Name) + plan.Description = types.StringValue(vrrpAddressResp.Description) + plan.DCVRRPID = types.StringValue(vrrpAddressResp.DCVRRPID) + plan.IPAddress = types.StringValue(vrrpAddressResp.IPAddress) + plan.PortID = types.StringValue(vrrpAddressResp.PortID) + plan.CreatedAt = types.StringValue(vrrpAddressResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpAddressResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPAddressResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan VRRPAddressResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpAddressID := plan.ID.ValueString() + + vrrpAddressResp, err := vrrp_addresses.Get(networkingClient, vrrpAddressID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_vrrp_address", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(vrrpAddressResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpAddressResp.Name) + plan.Description = types.StringValue(vrrpAddressResp.Description) + plan.DCVRRPID = types.StringValue(vrrpAddressResp.DCVRRPID) + plan.IPAddress = types.StringValue(vrrpAddressResp.IPAddress) + plan.PortID = types.StringValue(vrrpAddressResp.PortID) + plan.CreatedAt = types.StringValue(vrrpAddressResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpAddressResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *VRRPAddressResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan VRRPAddressResourceModel + var state VRRPAddressResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpAddressID := state.ID.ValueString() + + vrrpAddressUpdateOpts := vrrp_addresses.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + } + + vrrpAddressResp, err := vrrp_addresses.Update(networkingClient, vrrpAddressID, &vrrp_addresses.VRRPAddressUpdate{VRRPAddress: &vrrpAddressUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_vrrp_address", err.Error()) + return + } + + plan.ID = types.StringValue(vrrpAddressResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpAddressResp.Name) + plan.Description = types.StringValue(vrrpAddressResp.Description) + plan.DCVRRPID = types.StringValue(vrrpAddressResp.DCVRRPID) + plan.IPAddress = types.StringValue(vrrpAddressResp.IPAddress) + plan.PortID = types.StringValue(vrrpAddressResp.PortID) + plan.CreatedAt = types.StringValue(vrrpAddressResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpAddressResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPAddressResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan VRRPAddressResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = vrrp_addresses.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_vrrp_address", err.Error()) + return + } +} + +func (r *VRRPAddressResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/dc/resource_vrrp_interface.go b/vkcs/dc/resource_vrrp_interface.go new file mode 100644 index 00000000..21c6b096 --- /dev/null +++ b/vkcs/dc/resource_vrrp_interface.go @@ -0,0 +1,307 @@ +package dc + +import ( + "context" + + "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/types" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/dc/v2/vrrp_interfaces" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/services/networking" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/util" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ resource.Resource = &VRRPInterfaceResource{} + +func NewVRRPInterfaceResource() resource.Resource { + return &VRRPInterfaceResource{} +} + +type VRRPInterfaceResource struct { + config clients.Config +} + +func (r *VRRPInterfaceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "vkcs_dc_vrrp_interface" +} + +type VRRPInterfaceResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + DCVRRPID types.String `tfsdk:"dc_vrrp_id"` + DCInterfaceID types.String `tfsdk:"dc_interface_id"` + Priority types.Int64 `tfsdk:"priority"` + Preempt types.Bool `tfsdk:"preempt"` + Master types.Bool `tfsdk:"master"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + Region types.String `tfsdk:"region"` +} + +func (r *VRRPInterfaceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the resource", + }, + + "name": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Name of the VRRP", + }, + + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Description of the VRRP", + }, + + "dc_vrrp_id": schema.StringAttribute{ + Required: true, + Description: "VRRP ID to attach", + }, + + "dc_interface_id": schema.StringAttribute{ + Required: true, + Description: "DC Interface ID to attach", + }, + + "priority": schema.Int64Attribute{ + Required: true, + Description: "VRRP interface priority", + }, + + "preempt": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "VRRP interface preempt behavior", + }, + + "master": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Start VRRP instance on interface as VRRP Master", + }, + + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Creation timestamp", + }, + + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "Update timestamp", + }, + + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The `region` to fetch availability zones from, defaults to the provider's `region`.", + }, + }, + Description: "Manages a direct connect VRRP interface resource.", + } +} + +func (r *VRRPInterfaceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.config = req.ProviderData.(clients.Config) +} + +func (r *VRRPInterfaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan VRRPInterfaceResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpInterfaceCreateOpts := vrrp_interfaces.CreateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + DCVRRPID: plan.DCVRRPID.ValueString(), + DCInterfaceID: plan.DCInterfaceID.ValueString(), + Priority: int(plan.Priority.ValueInt64()), + Preempt: plan.Preempt.ValueBoolPointer(), + Master: plan.Master.ValueBoolPointer(), + } + + vrrpInterfaceResp, err := vrrp_interfaces.Create(networkingClient, &vrrp_interfaces.VRRPInterfaceCreate{VRRPInterface: &vrrpInterfaceCreateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error creating vkcs_dc_vrrp_interface", err.Error()) + return + } + vrrpInterfaceID := vrrpInterfaceResp.ID + resp.State.SetAttribute(ctx, path.Root("id"), vrrpInterfaceID) + + plan.ID = types.StringValue(vrrpInterfaceResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpInterfaceResp.Name) + plan.Description = types.StringValue(vrrpInterfaceResp.Description) + plan.DCVRRPID = types.StringValue(vrrpInterfaceResp.DCVRRPID) + plan.DCInterfaceID = types.StringValue(vrrpInterfaceResp.DCInterfaceID) + plan.Priority = types.Int64Value(int64(vrrpInterfaceResp.Priority)) + plan.Preempt = types.BoolValue(vrrpInterfaceResp.Preempt) + plan.Master = types.BoolValue(vrrpInterfaceResp.Master) + plan.CreatedAt = types.StringValue(vrrpInterfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpInterfaceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPInterfaceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var plan VRRPInterfaceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpInterfaceID := plan.ID.ValueString() + + vrrpInterfaceResp, err := vrrp_interfaces.Get(networkingClient, vrrpInterfaceID).Extract() + if err != nil { + resp.Diagnostics.AddError("Error retrieving vkcs_dc_vrrp_interface", + util.CheckDeletedResource(ctx, resp, err).Error()) + return + } + + plan.ID = types.StringValue(vrrpInterfaceResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpInterfaceResp.Name) + plan.Description = types.StringValue(vrrpInterfaceResp.Description) + plan.DCVRRPID = types.StringValue(vrrpInterfaceResp.DCVRRPID) + plan.DCInterfaceID = types.StringValue(vrrpInterfaceResp.DCInterfaceID) + plan.Priority = types.Int64Value(int64(vrrpInterfaceResp.Priority)) + plan.Preempt = types.BoolValue(vrrpInterfaceResp.Preempt) + plan.Master = types.BoolValue(vrrpInterfaceResp.Master) + plan.CreatedAt = types.StringValue(vrrpInterfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpInterfaceResp.UpdatedAt) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *VRRPInterfaceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan VRRPInterfaceResourceModel + var state VRRPInterfaceResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + vrrpInterfaceID := state.ID.ValueString() + + vrrpInterfaceUpdateOpts := vrrp_interfaces.UpdateOpts{ + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + Priority: int(plan.Priority.ValueInt64()), + Preempt: plan.Preempt.ValueBoolPointer(), + Master: plan.Master.ValueBoolPointer(), + } + + vrrpInterfaceResp, err := vrrp_interfaces.Update(networkingClient, vrrpInterfaceID, &vrrp_interfaces.VRRPInterfaceUpdate{VRRPInterface: &vrrpInterfaceUpdateOpts}).Extract() + if err != nil { + resp.Diagnostics.AddError("Error updating vkcs_dc_vrrp_interface", err.Error()) + return + } + + plan.ID = types.StringValue(vrrpInterfaceResp.ID) + plan.Region = types.StringValue(region) + plan.Name = types.StringValue(vrrpInterfaceResp.Name) + plan.Description = types.StringValue(vrrpInterfaceResp.Description) + plan.DCVRRPID = types.StringValue(vrrpInterfaceResp.DCVRRPID) + plan.DCInterfaceID = types.StringValue(vrrpInterfaceResp.DCInterfaceID) + plan.Priority = types.Int64Value(int64(vrrpInterfaceResp.Priority)) + plan.Preempt = types.BoolValue(vrrpInterfaceResp.Preempt) + plan.Master = types.BoolValue(vrrpInterfaceResp.Master) + plan.CreatedAt = types.StringValue(vrrpInterfaceResp.CreatedAt) + plan.UpdatedAt = types.StringValue(vrrpInterfaceResp.UpdatedAt) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VRRPInterfaceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan VRRPInterfaceResourceModel + + diags := req.State.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := plan.Region.ValueString() + if region == "" { + region = r.config.GetRegion() + } + + networkingClient, err := r.config.NetworkingV2Client(region, networking.SprutSDN) + if err != nil { + resp.Diagnostics.AddError("Error creating VKCS networking client", err.Error()) + return + } + + planID := plan.ID.ValueString() + + err = vrrp_interfaces.Delete(networkingClient, planID).ExtractErr() + if err != nil { + resp.Diagnostics.AddError("Unable to delete resource vkcs_dc_vrrp_interface", err.Error()) + return + } +} + +func (r *VRRPInterfaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/vkcs/internal/services/dc/v2/bgp_instances/requests.go b/vkcs/internal/services/dc/v2/bgp_instances/requests.go new file mode 100644 index 00000000..8c3a5e48 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_instances/requests.go @@ -0,0 +1,92 @@ +package bgp_instances + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type BGPInstanceCreate struct { + BGPInstance *CreateOpts `json:"dc_bgp"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCRouterID string `json:"dc_router_id"` + BGPRouterID string `json:"bgp_router_id"` + ASN int `json:"asn"` + ECMPEnabled *bool `json:"ecmp_enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + GracefulRestart *bool `json:"graceful_restart,omitempty"` + LongLivedGracefulRestart *bool `json:"long_lived_graceful_restart,omitempty"` +} + +func (opts *BGPInstanceCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(bgpsURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(bgpURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type BGPInstanceUpdate struct { + BGPInstance *UpdateOpts `json:"dc_bgp"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + ECMPEnabled *bool `json:"ecmp_enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + GracefulRestart *bool `json:"graceful_restart,omitempty"` + LongLivedGracefulRestart *bool `json:"long_lived_graceful_restart,omitempty"` +} + +func (opts *BGPInstanceUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(bgpURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(bgpURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/bgp_instances/results.go b/vkcs/internal/services/dc/v2/bgp_instances/results.go new file mode 100644 index 00000000..cc7bef5b --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_instances/results.go @@ -0,0 +1,52 @@ +package bgp_instances + +import ( + "github.com/gophercloud/gophercloud" +) + +type BGPInstanceResp struct { + BGPInstance BGPInstanceResponse `json:"dc_bgp"` +} + +type BGPInstanceResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCRouterID string `json:"dc_router_id"` + BGPRouterID string `json:"bgp_router_id"` + ASN int `json:"asn"` + ECMPEnabled bool `json:"ecmp_enabled"` + Enabled bool `json:"enabled"` + GracefulRestart bool `json:"graceful_restart"` + LongLivedGracefulRestart bool `json:"long_lived_graceful_restart"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*BGPInstanceResponse, error) { + var res *BGPInstanceResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.BGPInstance, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/bgp_instances/urls.go b/vkcs/internal/services/dc/v2/bgp_instances/urls.go new file mode 100644 index 00000000..66864eb0 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_instances/urls.go @@ -0,0 +1,15 @@ +package bgp_instances + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_bgps" +} + +func bgpsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func bgpURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/bgp_neighbors/requests.go b/vkcs/internal/services/dc/v2/bgp_neighbors/requests.go new file mode 100644 index 00000000..ccf9e1c2 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_neighbors/requests.go @@ -0,0 +1,96 @@ +package bgp_neighbors + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type BGPNeighborCreate struct { + BGPNeighbor *CreateOpts `json:"dc_bgp_neighbor"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCBGPID string `json:"dc_bgp_id"` + RemoteASN int `json:"remote_asn"` + RemoteIP string `json:"remote_ip"` + ForceIBGPNextHopSelf *bool `json:"force_ibgp_next_hop_self,omitempty"` + AddPaths string `json:"add_paths,omitempty"` + BFDEnabled *bool `json:"bfd_enabled,omitempty"` + FilterIn string `json:"filter_in,omitempty"` + FilterOut string `json:"filter_out,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *BGPNeighborCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(bgpneighborsURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(bgpneighborURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type BGPNeighborUpdate struct { + BGPNeighbor *UpdateOpts `json:"dc_bgp_neighbor"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + ForceIBGPNextHopSelf *bool `json:"force_ibpg_next_hop_self,omitempty"` + AddPaths string `json:"add_paths,omitempty"` + BFDEnabled *bool `json:"bfd_enabled,omitempty"` + FilterIn string `json:"filter_in,omitempty"` + FilterOut string `json:"filter_out,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *BGPNeighborUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(bgpneighborURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(bgpneighborURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/bgp_neighbors/results.go b/vkcs/internal/services/dc/v2/bgp_neighbors/results.go new file mode 100644 index 00000000..28816dc6 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_neighbors/results.go @@ -0,0 +1,54 @@ +package bgp_neighbors + +import ( + "github.com/gophercloud/gophercloud" +) + +type BGPNeighborResp struct { + BGPNeighbor BGPNeighborResponse `json:"dc_bgp_neighbor"` +} + +type BGPNeighborResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCBGPID string `json:"dc_bgp_id"` + RemoteASN int `json:"remote_asn"` + RemoteIP string `json:"remote_ip"` + ForceIBGPNextHopSelf bool `json:"force_ibgp_next_hop_self"` + AddPaths string `json:"add_paths"` + BFDEnabled bool `json:"bfd_enabled"` + FilterIn string `json:"filter_in"` + FilterOut string `json:"filter_out"` + Enabled bool `json:"enabled"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*BGPNeighborResponse, error) { + var res *BGPNeighborResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.BGPNeighbor, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/bgp_neighbors/urls.go b/vkcs/internal/services/dc/v2/bgp_neighbors/urls.go new file mode 100644 index 00000000..63d2fa01 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_neighbors/urls.go @@ -0,0 +1,15 @@ +package bgp_neighbors + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_bgp_neighbors" +} + +func bgpneighborsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func bgpneighborURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/bgp_static_announces/requests.go b/vkcs/internal/services/dc/v2/bgp_static_announces/requests.go new file mode 100644 index 00000000..ac398740 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_static_announces/requests.go @@ -0,0 +1,86 @@ +package bgp_static_announces + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type BGPStaticAnnounceCreate struct { + BGPStaticAnnounce *CreateOpts `json:"dc_bgp_static_announce"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCBGPID string `json:"dc_bgp_id"` + Network string `json:"network"` + Gateway string `json:"gateway"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *BGPStaticAnnounceCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(bgpstaticannouncesURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(bgpstaticannounceURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type BGPStaticAnnounceUpdate struct { + BGPStaticAnnounce *UpdateOpts `json:"dc_bgp_static_announce"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *BGPStaticAnnounceUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(bgpstaticannounceURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(bgpstaticannounceURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/bgp_static_announces/results.go b/vkcs/internal/services/dc/v2/bgp_static_announces/results.go new file mode 100644 index 00000000..3643d34e --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_static_announces/results.go @@ -0,0 +1,49 @@ +package bgp_static_announces + +import ( + "github.com/gophercloud/gophercloud" +) + +type BGPStaticAnnounceResp struct { + BGPStaticAnnounce BGPStaticAnnounceResponse `json:"dc_bgp_static_announce"` +} + +type BGPStaticAnnounceResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCBGPID string `json:"dc_bgp_id"` + Network string `json:"network"` + Gateway string `json:"gateway"` + Enabled bool `json:"enabled"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*BGPStaticAnnounceResponse, error) { + var res *BGPStaticAnnounceResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.BGPStaticAnnounce, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/bgp_static_announces/urls.go b/vkcs/internal/services/dc/v2/bgp_static_announces/urls.go new file mode 100644 index 00000000..8aec3204 --- /dev/null +++ b/vkcs/internal/services/dc/v2/bgp_static_announces/urls.go @@ -0,0 +1,15 @@ +package bgp_static_announces + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_bgp_static_announces" +} + +func bgpstaticannouncesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func bgpstaticannounceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/interfaces/requests.go b/vkcs/internal/services/dc/v2/interfaces/requests.go new file mode 100644 index 00000000..0e6b42f5 --- /dev/null +++ b/vkcs/internal/services/dc/v2/interfaces/requests.go @@ -0,0 +1,86 @@ +package interfaces + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type InterfaceCreate struct { + Interface *CreateOpts `json:"dc_interface"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCRouterID string `json:"dc_router_id"` + NetworkID string `json:"network_id"` + SubnetID string `json:"subnet_id,omitempty"` + BGPAnnounceEnabled *bool `json:"bgp_announce_enabled,omitempty"` +} + +func (opts *InterfaceCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(interfacesURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(interfaceURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type InterfaceUpdate struct { + Interface *UpdateOpts `json:"dc_interface"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + BGPAnnounceEnabled *bool `json:"bgp_announce_enabled,omitempty"` +} + +func (opts *InterfaceUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(interfaceURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(interfaceURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/interfaces/results.go b/vkcs/internal/services/dc/v2/interfaces/results.go new file mode 100644 index 00000000..b3f68d3d --- /dev/null +++ b/vkcs/internal/services/dc/v2/interfaces/results.go @@ -0,0 +1,55 @@ +package interfaces + +import ( + "github.com/gophercloud/gophercloud" +) + +type InterfaceResp struct { + Interface InterfaceResponse `json:"dc_interface"` +} + +type InterfaceResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCRouterID string `json:"dc_router_id"` + NetworkID string `json:"network_id"` + SubnetID string `json:"subnet_id"` + BGPAnnounceEnabled bool `json:"bgp_announce_enabled"` + PortID string `json:"port_id"` + SDN string `json:"sdn"` + IPAddress string `json:"ip_address"` + IPNetmask int `json:"ip_netmask"` + MACAddress string `json:"mac_address"` + MTU int `json:"mtu"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_ad"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*InterfaceResponse, error) { + var res *InterfaceResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.Interface, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/interfaces/urls.go b/vkcs/internal/services/dc/v2/interfaces/urls.go new file mode 100644 index 00000000..1c02465b --- /dev/null +++ b/vkcs/internal/services/dc/v2/interfaces/urls.go @@ -0,0 +1,15 @@ +package interfaces + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_interfaces" +} + +func interfacesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func interfaceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/routers/requests.go b/vkcs/internal/services/dc/v2/routers/requests.go new file mode 100644 index 00000000..d5764d50 --- /dev/null +++ b/vkcs/internal/services/dc/v2/routers/requests.go @@ -0,0 +1,83 @@ +package routers + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type RouterCreate struct { + Router *CreateOpts `json:"dc_router"` +} + +type CreateOpts struct { + AvailabilityZone string `json:"availability_zone"` + Flavor string `json:"flavor"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +func (opts *RouterCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(routersURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(routerURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type RouterUpdate struct { + Router *UpdateOpts `json:"dc_router"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +func (opts *RouterUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(routerURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(routerURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/routers/results.go b/vkcs/internal/services/dc/v2/routers/results.go new file mode 100644 index 00000000..817c4b22 --- /dev/null +++ b/vkcs/internal/services/dc/v2/routers/results.go @@ -0,0 +1,47 @@ +package routers + +import ( + "github.com/gophercloud/gophercloud" +) + +type RouterResp struct { + Router RouterResponse `json:"dc_router"` +} + +type RouterResponse struct { + AvailabilityZone string `json:"availability_zone"` + Flavor string `json:"flavor"` + Name string `json:"name"` + Description string `json:"description"` + CreatedAt string `json:"created_at"` + ID string `json:"id"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*RouterResponse, error) { + var res *RouterResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.Router, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/routers/urls.go b/vkcs/internal/services/dc/v2/routers/urls.go new file mode 100644 index 00000000..210f059d --- /dev/null +++ b/vkcs/internal/services/dc/v2/routers/urls.go @@ -0,0 +1,15 @@ +package routers + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_routers" +} + +func routersURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func routerURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/static_routes/requests.go b/vkcs/internal/services/dc/v2/static_routes/requests.go new file mode 100644 index 00000000..e461e072 --- /dev/null +++ b/vkcs/internal/services/dc/v2/static_routes/requests.go @@ -0,0 +1,86 @@ +package static_routes + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type StaticRouteCreate struct { + StaticRoute *CreateOpts `json:"dc_static_route"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCRouterID string `json:"dc_router_id"` + Network string `json:"network"` + Gateway string `json:"gateway"` + Metric int `json:"metric"` +} + +func (opts *StaticRouteCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(staticroutesURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(staticrouteURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type StaticRouteUpdate struct { + StaticRoute *UpdateOpts `json:"dc_static_route"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Metric int `json:"metric,omitempty"` +} + +func (opts *StaticRouteUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(staticrouteURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(staticrouteURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/static_routes/results.go b/vkcs/internal/services/dc/v2/static_routes/results.go new file mode 100644 index 00000000..8553eb15 --- /dev/null +++ b/vkcs/internal/services/dc/v2/static_routes/results.go @@ -0,0 +1,49 @@ +package static_routes + +import ( + "github.com/gophercloud/gophercloud" +) + +type StaticRouteResp struct { + StaticRoute StaticRouteResponse `json:"dc_static_route"` +} + +type StaticRouteResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCRouterID string `json:"dc_router_id"` + Network string `json:"network"` + Gateway string `json:"gateway"` + Metric int `json:"metric"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*StaticRouteResponse, error) { + var res *StaticRouteResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.StaticRoute, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/static_routes/urls.go b/vkcs/internal/services/dc/v2/static_routes/urls.go new file mode 100644 index 00000000..ca5964a3 --- /dev/null +++ b/vkcs/internal/services/dc/v2/static_routes/urls.go @@ -0,0 +1,15 @@ +package static_routes + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_static_routes" +} + +func staticroutesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func staticrouteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/vrrp_addresses/requests.go b/vkcs/internal/services/dc/v2/vrrp_addresses/requests.go new file mode 100644 index 00000000..1a2957bf --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_addresses/requests.go @@ -0,0 +1,83 @@ +package vrrp_addresses + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type VRRPAddressCreate struct { + VRRPAddress *CreateOpts `json:"dc_vrrp_address"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCVRRPID string `json:"dc_vrrp_id"` + IPAddress string `json:"ip_address"` +} + +func (opts *VRRPAddressCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(vrrpaddresssURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(vrrpaddressURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type VRRPAddressUpdate struct { + VRRPAddress *UpdateOpts `json:"dc_vrrp_address"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} + +func (opts *VRRPAddressUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(vrrpaddressURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(vrrpaddressURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/vrrp_addresses/results.go b/vkcs/internal/services/dc/v2/vrrp_addresses/results.go new file mode 100644 index 00000000..56c1bf9b --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_addresses/results.go @@ -0,0 +1,48 @@ +package vrrp_addresses + +import ( + "github.com/gophercloud/gophercloud" +) + +type VRRPAddressResp struct { + VRRPAddress VRRPAddressResponse `json:"dc_vrrp_address"` +} + +type VRRPAddressResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCVRRPID string `json:"dc_vrrp_id"` + IPAddress string `json:"ip_address"` + PortID string `json:"port_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*VRRPAddressResponse, error) { + var res *VRRPAddressResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.VRRPAddress, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/vrrp_addresses/urls.go b/vkcs/internal/services/dc/v2/vrrp_addresses/urls.go new file mode 100644 index 00000000..5bdf4b05 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_addresses/urls.go @@ -0,0 +1,15 @@ +package vrrp_addresses + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_vrrp_addresses" +} + +func vrrpaddresssURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func vrrpaddressURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/vrrp_interfaces/requests.go b/vkcs/internal/services/dc/v2/vrrp_interfaces/requests.go new file mode 100644 index 00000000..d63d1ea3 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_interfaces/requests.go @@ -0,0 +1,89 @@ +package vrrp_interfaces + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type VRRPInterfaceCreate struct { + VRRPInterface *CreateOpts `json:"dc_vrrp_interface"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + DCVRRPID string `json:"dc_vrrp_id"` + DCInterfaceID string `json:"dc_interface_id"` + Priority int `json:"priority"` + Preempt *bool `json:"preempt,omitempty"` + Master *bool `json:"master,omitempty"` +} + +func (opts *VRRPInterfaceCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(vrrpinterfacesURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(vrrpinterfaceURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type VRRPInterfaceUpdate struct { + VRRPInterface *UpdateOpts `json:"dc_vrrp_interface"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Priority int `json:"priority,omitempty"` + Preempt *bool `json:"preempt,omitempty"` + Master *bool `json:"master,omitempty"` +} + +func (opts *VRRPInterfaceUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(vrrpinterfaceURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(vrrpinterfaceURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/vrrp_interfaces/results.go b/vkcs/internal/services/dc/v2/vrrp_interfaces/results.go new file mode 100644 index 00000000..75f833a2 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_interfaces/results.go @@ -0,0 +1,50 @@ +package vrrp_interfaces + +import ( + "github.com/gophercloud/gophercloud" +) + +type VRRPInterfaceResp struct { + VRRPInterface VRRPInterfaceResponse `json:"dc_vrrp_interface"` +} + +type VRRPInterfaceResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DCVRRPID string `json:"dc_vrrp_id"` + DCInterfaceID string `json:"dc_interface_id"` + Priority int `json:"priority"` + Preempt bool `json:"preempt"` + Master bool `json:"master"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*VRRPInterfaceResponse, error) { + var res *VRRPInterfaceResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.VRRPInterface, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/vrrp_interfaces/urls.go b/vkcs/internal/services/dc/v2/vrrp_interfaces/urls.go new file mode 100644 index 00000000..d5bd8e52 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrp_interfaces/urls.go @@ -0,0 +1,15 @@ +package vrrp_interfaces + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_vrrp_interfaces" +} + +func vrrpinterfacesURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func vrrpinterfaceURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/internal/services/dc/v2/vrrps/requests.go b/vkcs/internal/services/dc/v2/vrrps/requests.go new file mode 100644 index 00000000..cc3b8d23 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrps/requests.go @@ -0,0 +1,88 @@ +package vrrps + +import ( + "net/http" + + "github.com/gophercloud/gophercloud" +) + +type OptsBuilder interface { + Map() (map[string]interface{}, error) +} +type VRRPCreate struct { + VRRP *CreateOpts `json:"dc_vrrp"` +} + +type CreateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + GroupID int `json:"group_id"` + NetworkID string `json:"network_id"` + SubnetID string `json:"subnet_id,omitempty"` + AdvertInterval int `json:"advert_interval,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *VRRPCreate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Create(client *gophercloud.ServiceClient, opts OptsBuilder) (r CreateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(vrrpsURL(client), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{201}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + resp, err := client.Get(vrrpURL(client, id), &r.Body, nil) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +type VRRPUpdate struct { + VRRP *UpdateOpts `json:"dc_vrrp"` +} + +type UpdateOpts struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AdvertInterval int `json:"advert_interval,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func (opts *VRRPUpdate) Map() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(*opts, "") +} + +func Update(client *gophercloud.ServiceClient, id string, opts OptsBuilder) (r UpdateResult) { + b, err := opts.Map() + if err != nil { + r.Err = err + return + } + + resp, err := client.Put(vrrpURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { + var result *http.Response + result, r.Err = client.Delete(vrrpURL(client, id), &gophercloud.RequestOpts{ + OkCodes: []int{204}, + }) + if r.Err == nil { + r.Header = result.Header + } + return +} diff --git a/vkcs/internal/services/dc/v2/vrrps/results.go b/vkcs/internal/services/dc/v2/vrrps/results.go new file mode 100644 index 00000000..4dd3825e --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrps/results.go @@ -0,0 +1,51 @@ +package vrrps + +import ( + "github.com/gophercloud/gophercloud" +) + +type VRRPResp struct { + VRRP VRRPResponse `json:"dc_vrrp"` +} + +type VRRPResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + GroupID int `json:"group_id"` + NetworkID string `json:"network_id"` + SubnetID string `json:"subnet_id"` + AdvertInterval int `json:"advert_interval"` + Enabled bool `json:"enabled"` + SDN string `json:"sdn"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type commonResult struct { + gophercloud.Result +} + +func (r commonResult) Extract() (*VRRPResponse, error) { + var res *VRRPResp + if err := r.ExtractInto(&res); err != nil { + return nil, err + } + return &res.VRRP, nil +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + gophercloud.ErrResult +} diff --git a/vkcs/internal/services/dc/v2/vrrps/urls.go b/vkcs/internal/services/dc/v2/vrrps/urls.go new file mode 100644 index 00000000..12d8ae29 --- /dev/null +++ b/vkcs/internal/services/dc/v2/vrrps/urls.go @@ -0,0 +1,15 @@ +package vrrps + +import "github.com/gophercloud/gophercloud" + +func baseURL() string { + return "direct_connect/dc_vrrps" +} + +func vrrpsURL(c *gophercloud.ServiceClient) string { + return c.ServiceURL(baseURL()) +} + +func vrrpURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL(baseURL(), id) +} diff --git a/vkcs/provider/provider.go b/vkcs/provider/provider.go index d923d372..2881cd5d 100755 --- a/vkcs/provider/provider.go +++ b/vkcs/provider/provider.go @@ -10,6 +10,7 @@ import ( "github.com/vk-cs/terraform-provider-vkcs/vkcs/backup" "github.com/vk-cs/terraform-provider-vkcs/vkcs/db" + "github.com/vk-cs/terraform-provider-vkcs/vkcs/dc" "github.com/vk-cs/terraform-provider-vkcs/vkcs/images" "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/clients" wrapper "github.com/vk-cs/terraform-provider-vkcs/vkcs/internal/providerwrapper/framework" @@ -122,5 +123,14 @@ func (p *vkcsProvider) Resources(_ context.Context) []func() resource.Resource { db.NewBackupResource, kubernetes.NewAddonResource, backup.NewPlanResource, + dc.NewRouterResource, + dc.NewInterfaceResource, + dc.NewBGPInstanceResource, + dc.NewBGPNeighborResource, + dc.NewBGPStaticAnnounceResource, + dc.NewStaticRouteResource, + dc.NewVRRPResource, + dc.NewVRRPInterfaceResource, + dc.NewVRRPAddressResource, } }