-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 959e842
Showing
14 changed files
with
1,620 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Terraform Provider for AOS-CX | ||
|
||
The Terraform Provider for AOS-CX provides a set of configuration management modules and resources specifically designed to manage/configure AOS-CX switches using REST API. | ||
|
||
|
||
## Requirements | ||
|
||
- [Terraform](https://www.terraform.io/downloads.html) >= 0.13.x | ||
- [Go](https://golang.org/doc/install) >= 1.18 | ||
- Install AOS-CX Terraform Provider from Terraform Registry | ||
- Terraform 0.13 added support for automatically downloading providers from the terraform registry. Add the following to your terraform project | ||
|
||
``` | ||
terraform { | ||
required_providers { | ||
aoscx = { | ||
version = "=> 1.0.0" | ||
source = "aruba/aoscx" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
|
||
## Using the AOS-CX provider | ||
|
||
To use the AOS-CX Terraform provider you'll need to define the switch connection details inside a provider block with the following variables: | ||
- `hostname`: IP address of the switch | ||
- `username`: Username used to login to the switch using REST API | ||
- `password`: Password used to login to the switch using REST API | ||
- see Terraform's documentation on how to [Protect Sensitive Input Variables](https://developer.hashicorp.com/terraform/tutorials/configuration-language/sensitive-variables) | ||
``` | ||
provider "aoscx" { | ||
hostname = "10.6.7.16" | ||
username = "admin" | ||
password = "admin" | ||
} | ||
``` | ||
|
||
Once the provider is defined then you'll define the resources you want Terraform manage on your CX switch. To see all supported resources and their required/optional values see the [/docs](https://github.com/aruba/terraform-provider-aoscx/tree/master/docs) directory. | ||
|
||
Here's an example: | ||
``` | ||
resource "aoscx_vlan" "vlan42" { | ||
vlan_id = 42 | ||
name = "terraform vlan" | ||
} | ||
resource "aoscx_interface" "int_1_1_14" { | ||
name = "1/1/14" | ||
admin_state = "down" | ||
description = "terraform_uplink" | ||
} | ||
resource "aoscx_l2_interface" "int_1_1_15" { | ||
interface = "1/1/15" | ||
admin_state = "up" | ||
description = "terraform_downlink" | ||
vlan_mode = "access" | ||
vlan_tag = 42 | ||
} | ||
resource "aoscx_l2_interface" "int_1_1_16" { | ||
interface = "1/1/16" | ||
admin_state = "down" | ||
vlan_mode = "trunk" | ||
vlan_ids = [20, 42] | ||
native_vlan_tag = true | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package aoscx | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"net/http" | ||
|
||
"github.com/aruba/aoscxgo" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
) | ||
|
||
type Aoscx struct { | ||
hostname string | ||
username string | ||
password string | ||
rest_version string | ||
cookie *http.Cookie | ||
} | ||
|
||
func Provider() *schema.Provider { | ||
return &schema.Provider{ | ||
Schema: map[string]*schema.Schema{ | ||
"hostname": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
Optional: false, | ||
Description: "Hostname/IP address of the AOS-CX switch to connect to", | ||
}, | ||
"username": { | ||
Type: schema.TypeString, | ||
Optional: false, | ||
Required: true, | ||
Description: "Username used to authenticate", | ||
}, | ||
"password": { | ||
Type: schema.TypeString, | ||
Optional: false, | ||
Required: true, | ||
Description: "Password used to authenticate", | ||
}, | ||
}, | ||
ResourcesMap: map[string]*schema.Resource{ | ||
"aoscx_vlan": resourceVlan(), | ||
"aoscx_interface": resourceInterface(), | ||
"aoscx_l2_interface": resourceL2Interface(), | ||
}, | ||
ConfigureContextFunc: providerConfigure, | ||
} | ||
} | ||
|
||
func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { | ||
hostname := d.Get("hostname").(string) | ||
username := d.Get("username").(string) | ||
password := d.Get("password").(string) | ||
|
||
var diags diag.Diagnostics | ||
|
||
if (hostname != "") && (username != "") && (password != "") { | ||
tr := &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | ||
} | ||
|
||
sw, err := aoscxgo.Connect( | ||
&aoscxgo.Client{ | ||
Hostname: hostname, | ||
Username: username, | ||
Password: password, | ||
Transport: tr, | ||
}, | ||
) | ||
|
||
if (sw.Cookie == nil) || (err != nil) { | ||
return nil, diag.FromErr(err) | ||
} | ||
|
||
return sw, diags | ||
} | ||
|
||
diags = append(diags, diag.Diagnostic{ | ||
Severity: diag.Error, | ||
Summary: "Unable to create AOS-CX client", | ||
Detail: "Invalid or no values found for hostname, username, password", | ||
}) | ||
|
||
return nil, diags | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
package aoscx | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/aruba/aoscxgo" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
) | ||
|
||
func resourceInterface() *schema.Resource { | ||
return &schema.Resource{ | ||
Description: "Resource to configure interfaces physical attributes on AOS-CX switches.", | ||
CreateContext: resourceInterfaceCreate, | ||
ReadContext: resourceInterfaceRead, | ||
UpdateContext: resourceInterfaceUpdate, | ||
DeleteContext: resourceInterfaceDelete, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"description": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: false, | ||
Optional: true, | ||
}, | ||
"admin_state": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: false, | ||
Default: "up", | ||
Optional: true, | ||
ValidateFunc: validation.StringInSlice([]string{"up", "down"}, true), | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceInterfaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
// Warning or errors can be collected in a slice type | ||
var diags diag.Diagnostics | ||
var err error | ||
|
||
sw := m.(*aoscxgo.Client) | ||
|
||
tmp_int := aoscxgo.Interface{ | ||
Name: d.Get("name").(string), | ||
Description: d.Get("description").(string), | ||
AdminState: d.Get("admin_state").(string), | ||
} | ||
|
||
err = tmp_int.Create(sw) | ||
|
||
//defer logout(tr, cookie, url) | ||
|
||
if materialized := tmp_int.GetStatus(); !materialized { | ||
|
||
err = tmp_int.Get(sw) | ||
|
||
if err != nil { | ||
{ | ||
diags = append(diags, diag.Errorf("Error in Creating Interface ", err)...) | ||
return diags | ||
} | ||
} | ||
|
||
diags = append(diags, diag.Diagnostic{ | ||
Severity: diag.Warning, | ||
Summary: "Interface Already Existing", | ||
Detail: string(tmp_int.Name), | ||
}) | ||
|
||
err = tmp_int.Update(sw) | ||
|
||
if err != nil { | ||
if err.(*aoscxgo.RequestError).StatusCode == "404 Not Found" { | ||
diags = append(diags, diag.Errorf("Error Updating Interface does not exist: %s", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} else if err.(*aoscxgo.RequestError).StatusCode != "204 No Content" { | ||
diags = append(diags, diag.Errorf("Error in Updating Interface: %s", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} | ||
} | ||
|
||
} | ||
|
||
d.SetId(d.Get("name").(string)) | ||
d.Set("name", d.Get("name").(string)) | ||
|
||
resourceInterfaceRead(ctx, d, m) | ||
|
||
return diags | ||
} | ||
|
||
func resourceInterfaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
// Warning or errors can be collected in a slice type | ||
var diags diag.Diagnostics | ||
var err error | ||
|
||
sw := m.(*aoscxgo.Client) | ||
|
||
// Retrieve Interface from sw if existing | ||
tmp_int := aoscxgo.Interface{ | ||
Name: d.Get("name").(string), | ||
} | ||
//tmp_vlan.GetStatus() will return if existing | ||
err = tmp_int.Get(sw) | ||
|
||
if err != nil { | ||
//Failure in VLAN retrieval | ||
d.SetId("") | ||
diags = append(diags, diag.Diagnostic{ | ||
Severity: diag.Warning, | ||
Summary: "Interface Not Found", | ||
Detail: "Interface Not Found", | ||
}) | ||
return diags | ||
} | ||
|
||
d.Set("name", tmp_int.Name) | ||
d.Set("description", tmp_int.Description) | ||
d.Set("admin_state", tmp_int.AdminState) | ||
|
||
return diags | ||
} | ||
|
||
func resourceInterfaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
// Warning or errors can be collected in a slice type | ||
var diags diag.Diagnostics | ||
var err error | ||
|
||
sw := m.(*aoscxgo.Client) | ||
|
||
// Retrieve Interface from sw if existing | ||
tmp_int := aoscxgo.Interface{ | ||
Name: d.Get("name").(string), | ||
} | ||
//tmp_vlan.GetStatus() will return if existing | ||
err = tmp_int.Get(sw) | ||
|
||
if d.HasChange("description") { | ||
tmp_int.Description = d.Get("description").(string) | ||
} | ||
|
||
if d.HasChange("admin_state") { | ||
tmp_state := d.Get("admin_state").(string) | ||
if tmp_state == "" || (tmp_state != "down" && tmp_state != "up") { | ||
tmp_int.AdminState = "down" | ||
d.Set("admin_state", "down") | ||
// Should enforce value can only be up or down | ||
} | ||
tmp_int.AdminState = tmp_state | ||
} | ||
|
||
err = tmp_int.Update(sw) | ||
|
||
if err != nil { | ||
if err.(*aoscxgo.RequestError).StatusCode == "404 Not Found" { | ||
diags = append(diags, diag.Errorf("Error Updating Interface does not exist: %s", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} else if err.(*aoscxgo.RequestError).StatusCode != "204 No Content" { | ||
diags = append(diags, diag.Errorf("Error in Updating Interface: %s", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} | ||
} | ||
|
||
return resourceInterfaceRead(ctx, d, m) | ||
} | ||
|
||
func resourceInterfaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { | ||
// Warning or errors can be collected in a slice type | ||
var diags diag.Diagnostics | ||
var err error | ||
|
||
sw := m.(*aoscxgo.Client) | ||
|
||
// Create Interface Obj | ||
tmp_int := aoscxgo.Interface{ | ||
Name: d.Get("name").(string), | ||
} | ||
|
||
err = tmp_int.Delete(sw) | ||
|
||
if err != nil { | ||
if err.(*aoscxgo.RequestError).StatusCode == "404 Not Found" { | ||
diags = append(diags, diag.Errorf("Error Updating Interface does not exist: %s", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} else if err.(*aoscxgo.RequestError).StatusCode != "204 No Content" { | ||
diags = append(diags, diag.Errorf("Error in Updating Interface: %s ", err.(*aoscxgo.RequestError).StatusCode)...) | ||
return diags | ||
} | ||
} | ||
|
||
d.SetId("") | ||
return nil | ||
} |
Oops, something went wrong.