Skip to content

Commit

Permalink
network_integration: add network_integration resource
Browse files Browse the repository at this point in the history
Signed-off-by: Jonatas Ferreira <[email protected]>
  • Loading branch information
jonatas-lima committed Nov 25, 2024
1 parent 5118735 commit 89a73a2
Show file tree
Hide file tree
Showing 4 changed files with 504 additions and 0 deletions.
58 changes: 58 additions & 0 deletions docs/resources/network_integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# incus_network_integration

Manage integrations between the local Incus deployment and remote networks hosted on Incus or other platforms. Currently available only for [OVN networks](https://linuxcontainers.org/incus/docs/main/reference/network_ovn/#network-ovn).

## Basic Example

```hcl
resource "incus_network_integration" "this" {
name = "ovn-region"
config = {
"ovn.northbound_connection" = "tcp:[192.0.2.12]:6645,tcp:[192.0.3.13]:6645,tcp:[192.0.3.14]:6645"
"ovn.southbound_connection" = "tcp:[192.0.2.12]:6646,tcp:[192.0.3.13]:6646,tcp:[192.0.3.14]:6646"
}
}
```

## Peer Example

```hcl
resource "incus_network" "default" {
name = "default"
type = "ovn"
config = {
"ipv4.address" = "192.168.2.0/24"
"ipv4.nat" = "true"
}
}
resource "incus_network_integration" "this" {
name = "ovn-region"
config = {
"ovn.northbound_connection" = "tcp:[192.0.2.12]:6645,tcp:[192.0.3.13]:6645,tcp:[192.0.3.14]:6645"
"ovn.southbound_connection" = "tcp:[192.0.2.12]:6646,tcp:[192.0.3.13]:6646,tcp:[192.0.3.14]:6646"
}
}
resource "incus_network_peer" "this" {
name = "ovn-peer"
network = incus_network.default.name
target_integration = incus_network_integration.this.name
}
```

## Argument Reference

* `name` - **Required** - Name of the network integration.

* `description` *Optional* - Description of the network integration.

* `type` *Optional* - The type of the network integration. Currently, only supports **ovn** type. If empty, an OVN integration will be created.

* `project` - *Optional* - Name of the project where the network will be created.

* `remote` - *Optional* - The remote in which the resource will be created. If
not provided, the provider's default remote will be used.

* `config` - *Optional* - Map of key/value pairs of [network integration config settings](https://linuxcontainers.org/incus/docs/main/howto/network_integrations/)
280 changes: 280 additions & 0 deletions internal/network/resource_network_integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package network

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/terraform-provider-incus/internal/common"
"github.com/lxc/terraform-provider-incus/internal/errors"
provider_config "github.com/lxc/terraform-provider-incus/internal/provider-config"
)

// NetworkIntegrationModel resource data model that matches the schema.
type NetworkIntegrationModel struct {
Name types.String `tfsdk:"name"`
Type types.String `tfsdk:"type"`
Description types.String `tfsdk:"description"`
Project types.String `tfsdk:"project"`
Remote types.String `tfsdk:"remote"`
Config types.Map `tfsdk:"config"`
}

// NetworkIntegrationResource represent network integration resource.
type NetworkIntegrationResource struct {
provider *provider_config.IncusProviderConfig
}

// NewNetworkIntegrationResource returns a new network integration resource.
func NewNetworkIntegrationResource() resource.Resource {
return &NetworkIntegrationResource{}
}

// Metadata for NetworkIntegrationResource.
func (r NetworkIntegrationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = fmt.Sprintf("%s_network_integration", req.ProviderTypeName)
}

// Schema for NetworkIntegrationResource.
func (r NetworkIntegrationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},

"description": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},

"remote": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},

"project": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},

"type": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString("ovn"),
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.OneOf("ovn"),
},
},

"config": schema.MapAttribute{
Optional: true,
Computed: true,
ElementType: types.StringType,
Default: mapdefault.StaticValue(types.MapValueMust(types.StringType, map[string]attr.Value{})),
},
},
}
}

func (r *NetworkIntegrationResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
data := req.ProviderData
if data == nil {
return
}

provider, ok := data.(*provider_config.IncusProviderConfig)
if !ok {
resp.Diagnostics.Append(errors.NewProviderDataTypeError(req.ProviderData))
}

r.provider = provider
}

func (r NetworkIntegrationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan NetworkIntegrationModel

diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

remote := plan.Remote.ValueString()
project := plan.Project.ValueString()
server, err := r.provider.InstanceServer(remote, project, "")
if err != nil {
resp.Diagnostics.Append(errors.NewInstanceServerError(err))
return
}

config, diag := common.ToConfigMap(ctx, plan.Config)
resp.Diagnostics.Append(diag...)

networkIntegration := api.NetworkIntegrationsPost{
Name: plan.Name.ValueString(),
Type: plan.Type.ValueString(),
NetworkIntegrationPut: api.NetworkIntegrationPut{
Description: plan.Description.ValueString(),
Config: config,
},
}

err = server.CreateNetworkIntegration(networkIntegration)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to create network integration %q", networkIntegration.Name), err.Error())
return
}

diags = r.SyncState(ctx, &resp.State, server, plan)
resp.Diagnostics.Append(diags...)
}

func (r NetworkIntegrationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state NetworkIntegrationModel

diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

remote := state.Remote.ValueString()
project := state.Project.ValueString()
server, err := r.provider.InstanceServer(remote, project, "")
if err != nil {
resp.Diagnostics.Append(errors.NewInstanceServerError(err))
}

diags = r.SyncState(ctx, &resp.State, server, state)
resp.Diagnostics.Append(diags...)
}

func (r NetworkIntegrationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan NetworkIntegrationModel

diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

remote := plan.Remote.ValueString()
project := plan.Project.ValueString()
server, err := r.provider.InstanceServer(remote, project, "")
if err != nil {
resp.Diagnostics.Append(errors.NewInstanceServerError(err))
return
}

name := plan.Name.ValueString()

config, diag := common.ToConfigMap(ctx, plan.Config)
resp.Diagnostics.Append(diag...)
if resp.Diagnostics.HasError() {
return
}

_, etag, err := server.GetNetworkIntegration(name)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to retrieve existing network integration %q", name), err.Error())
return
}

networkIntegrationRequest := api.NetworkIntegrationPut{
Description: plan.Description.ValueString(),
Config: config,
}

err = server.UpdateNetworkIntegration(name, networkIntegrationRequest, etag)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to update network integration %q", name), err.Error())
}

diags = r.SyncState(ctx, &resp.State, server, plan)
resp.Diagnostics.Append(diags...)
}

func (r NetworkIntegrationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state NetworkIntegrationModel

diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

remote := state.Remote.ValueString()
project := state.Project.ValueString()
server, err := r.provider.InstanceServer(remote, project, "")
if err != nil {
resp.Diagnostics.Append(errors.NewInstanceServerError(err))
return
}

name := state.Name.ValueString()

err = server.DeleteNetworkIntegration(name)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Failed to remove network integration %q", name), err.Error())
}
}

// SyncState fetches the server's current state for a network integration and updates
// the provided model. It then applies this updated model as the new state
// in Terraform.
func (r NetworkIntegrationResource) SyncState(ctx context.Context, tfState *tfsdk.State, server incus.InstanceServer, m NetworkIntegrationModel) diag.Diagnostics {
var respDiags diag.Diagnostics

networkIntegrationName := m.Name.ValueString()
networkIntegration, _, err := server.GetNetworkIntegration(networkIntegrationName)
if err != nil {
if errors.IsNotFoundError(err) {
tfState.RemoveResource(ctx)
return nil
}

respDiags.AddError(fmt.Sprintf("Failed to retrieve network integration %q", networkIntegrationName), err.Error())
return respDiags
}

config, diags := common.ToConfigMapType(ctx, common.ToNullableConfig(networkIntegration.Config), m.Config)
respDiags.Append(diags...)

m.Description = types.StringValue(networkIntegration.Description)
m.Type = types.StringValue(networkIntegration.Type)
m.Config = config

if respDiags.HasError() {
return respDiags
}

return tfState.Set(ctx, &m)
}
Loading

0 comments on commit 89a73a2

Please sign in to comment.