Skip to content

Commit

Permalink
Merge pull request #74 from cloud-ca/vpn
Browse files Browse the repository at this point in the history
Added Remote Access VPN functionality
  • Loading branch information
pdube authored Apr 15, 2019
2 parents 5a92e49 + bd31cec commit 21885fd
Show file tree
Hide file tree
Showing 19 changed files with 841 additions and 86 deletions.
4 changes: 3 additions & 1 deletion cloudca/resource_cloudca.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform/helper/schema"
)

// GetCloudCAResourceMap resutrn the available Resource map
// GetCloudCAResourceMap return the available Resource map
func GetCloudCAResourceMap() map[string]*schema.Resource {
return map[string]*schema.Resource{
"cloudca_instance": resourceCloudcaInstance(),
Expand All @@ -27,6 +27,8 @@ func GetCloudCAResourceMap() map[string]*schema.Resource {
"cloudca_network_acl_rule": resourceCloudcaNetworkACLRule(),
"cloudca_static_nat": resourceCloudcaStaticNAT(),
"cloudca_ssh_key": resourceCloudcaSSHKey(),
"cloudca_vpn": resourceCloudcaVpn(),
"cloudca_vpn_user": resourceCloudcaVpnUser(),
}
}

Expand Down
10 changes: 1 addition & 9 deletions cloudca/resource_cloudca_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func resourceCloudcaInstance() *schema.Resource {
Required: true,
Description: "Name of instance",
},

"template": {
Type: schema.TypeString,
Required: true,
Expand All @@ -43,7 +42,6 @@ func resourceCloudcaInstance() *schema.Resource {
return strings.ToLower(val.(string))
},
},

"compute_offering": {
Type: schema.TypeString,
Required: true,
Expand All @@ -52,39 +50,33 @@ func resourceCloudcaInstance() *schema.Resource {
return strings.ToLower(val.(string))
},
},

"network_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Id of the network into which the new instance will be created",
},

"ssh_key_name": {
Type: schema.TypeString,
Optional: true,
Description: "SSH key name to attach to the new instance. Note: Cannot be used with public key.",
},

"public_key": {
Type: schema.TypeString,
Optional: true,
Description: "Public key to attach to the new instance. Note: Cannot be used with SSH key name.",
},

"user_data": {
Type: schema.TypeString,
Optional: true,
Description: "Additional data passed to the new instance during its initialization",
},

"cpu_count": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Description: "The instances CPU count. If the compute offering is custom, this value is required",
},

"memory_in_mb": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -353,7 +345,7 @@ func retrieveTemplateID(ccaRes *cloudca.Resources, name string) (id string, err

if strings.EqualFold(template.Name, name) {
log.Printf("Found template: %+v", template)
return template.Id, nil
return template.ID, nil
}
}

Expand Down
165 changes: 165 additions & 0 deletions cloudca/resource_cloudca_vpn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package cloudca

import (
"fmt"
"log"

"github.com/cloud-ca/go-cloudca"
"github.com/cloud-ca/go-cloudca/api"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceCloudcaVpn() *schema.Resource {
return &schema.Resource{
Create: resourceCloudcaVpnCreate,
Read: resourceCloudcaVpnRead,
Delete: resourceCloudcaVpnDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"environment_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "ID of the environment where the vpn should be created",
},
"vpc_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Id of the VPC for the vpn",
},
"certificate": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "Certificate to use when using IKEV2 vpn type",
},
"preshared_key": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "Preshared key to use when using L2TP vpn type",
},
"public_ip": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "Public IP address associated with the vpn",
},
"public_ip_id": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "ID of the public IP address associated with the vpn",
},
"state": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "State of the vpn",
},
"type": {
Type: schema.TypeString,
ForceNew: true,
Computed: true,
Description: "Type of vpn connection",
},
},
}
}

func resourceCloudcaVpnCreate(d *schema.ResourceData, meta interface{}) error {
vpnIPPurpose := "SOURCE_NAT"
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}

var vpnPubIPID string
pubIps, _ := ccaResources.PublicIps.List()
for _, ip := range pubIps {
if ip.VpcId == d.Get("vpc_id").(string) {
for _, purpose := range ip.Purposes {
if purpose == vpnIPPurpose {
vpnPubIPID = ip.Id
break
}
}
}
if vpnPubIPID != "" {
break
}
}

if vpnPubIPID == "" {
return fmt.Errorf("Error enabling the VPN because no Source NAT IP was found for the VPC")
}

_, err := ccaResources.RemoteAccessVpn.Enable(vpnPubIPID)
if err != nil {
return fmt.Errorf("Error enabling the VPN: %s", err)
}
d.SetId(vpnPubIPID)
return resourceCloudcaVpnRead(d, meta)
}

func resourceCloudcaVpnRead(d *schema.ResourceData, meta interface{}) error {
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}

vpn, err := ccaResources.RemoteAccessVpn.Get(d.Id())
if err != nil {
return handleNotFoundError("VPN", false, err, d)
}

if vpn.State == "Disabled" {
// If the VPN is disabled, it means the VPN is not active
// so this entity is "missing" (at least as far as terraform is concerned).
d.SetId("")
return handleNotFoundError("VPN Disabled", false, err, d)
}
if err := d.Set("state", vpn.State); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
if err := d.Set("certificate", vpn.Certificate); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
if err := d.Set("preshared_key", vpn.PresharedKey); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
if err := d.Set("public_ip", vpn.PublicIpAddress); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
if err := d.Set("public_ip_id", vpn.PublicIpAddressId); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
if err := d.Set("type", vpn.Type); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
return nil
}

func resourceCloudcaVpnDelete(d *schema.ResourceData, meta interface{}) error {
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}
if _, err := ccaResources.RemoteAccessVpn.Disable(d.Id()); err != nil {
if ccaError, ok := err.(api.CcaErrorResponse); ok {
if ccaError.StatusCode == 404 {
log.Printf("VPN with id=%s no longer exists", d.Id())
d.SetId("")
return nil
}
return handleNotFoundError("VPN Delete", true, err, d)
}
return handleNotFoundError("VPN Delete", true, err, d)
}
return nil
}
126 changes: 126 additions & 0 deletions cloudca/resource_cloudca_vpn_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package cloudca

import (
"fmt"
"log"

"github.com/cloud-ca/go-cloudca"
"github.com/cloud-ca/go-cloudca/api"
"github.com/cloud-ca/go-cloudca/services/cloudca"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceCloudcaVpnUser() *schema.Resource {
return &schema.Resource{
Create: resourceCloudcaVpnUserCreate,
Read: resourceCloudcaVpnUserRead,
Delete: resourceCloudcaVpnUserDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"environment_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "ID of the environment where the vpn should be created",
},
"username": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Username of the VPN user",
},
"password": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Password of the VPN user",
},
},
}
}

func resourceCloudcaVpnUserCreate(d *schema.ResourceData, meta interface{}) error {
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}

remoteAccessVpnUser := cloudca.RemoteAccessVpnUser{
Username: d.Get("username").(string),
Password: d.Get("password").(string),
}
_, err := ccaResources.RemoteAccessVpnUser.Create(remoteAccessVpnUser)
if err != nil {
return fmt.Errorf("Error adding VPN user: %s", err)
}

// TODO: When the CMC API actually returns the ID of the created user, use it.
// Currently there is no way to do a 'Get' based on the username, and we don't have the ID, so
// we have to list all users and then loop through to match the username in order to find the ID.
vpnUsers, err := ccaResources.RemoteAccessVpnUser.List()
if err != nil {
return fmt.Errorf("Error getting the created VPN user ID: %s", err)
}
var userID string
for _, user := range vpnUsers {
if user.Username == d.Get("username").(string) {
userID = user.Id
break
}
}
if userID != "" {
d.SetId(userID)
} else {
return fmt.Errorf("Error finding the created VPN user ID: %s", err)
}
return resourceCloudcaVpnUserRead(d, meta)
}

func resourceCloudcaVpnUserRead(d *schema.ResourceData, meta interface{}) error {
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}

// Get the user based on the ID
vpnUser, err := ccaResources.RemoteAccessVpnUser.Get(d.Id())
if err != nil {
d.SetId("")
// If we return an error instead of nil, then if a VPN user is removed via the web UI
// it will break the ability for terraform to plan or apply any changes, so terraform
// will be in a broken state which can not be recovered from.
return nil
}

if err := d.Set("username", vpnUser.Username); err != nil {
return fmt.Errorf("Error reading Trigger: %s", err)
}
return nil
}

func resourceCloudcaVpnUserDelete(d *schema.ResourceData, meta interface{}) error {
ccaResources, rerr := getResourcesForEnvironmentID(meta.(*cca.CcaClient), d.Get("environment_id").(string))
if rerr != nil {
return rerr
}
remoteAccessVpnUser := cloudca.RemoteAccessVpnUser{
Id: d.Id(),
Username: d.Get("username").(string),
}
if _, err := ccaResources.RemoteAccessVpnUser.Delete(remoteAccessVpnUser); err != nil {
if ccaError, ok := err.(api.CcaErrorResponse); ok {
if ccaError.StatusCode == 404 {
log.Printf("VPN User with id=%s no longer exists", d.Id())
d.SetId("")
return nil
}
return handleNotFoundError("VPN User Delete", true, err, d)
}
return handleNotFoundError("VPN User Delete", true, err, d)
}
return nil
}
Loading

0 comments on commit 21885fd

Please sign in to comment.