diff --git a/cloudconnexa/data_source_application.go b/cloudconnexa/data_source_application.go
new file mode 100644
index 0000000..3614589
--- /dev/null
+++ b/cloudconnexa/data_source_application.go
@@ -0,0 +1,58 @@
+package cloudconnexa
+
+import (
+ "context"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/openvpn/cloudconnexa-go-client/v2/cloudconnexa"
+)
+
+func dataSourceApplication() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceApplicationRead,
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "description": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "routes": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: resourceApplicationRoute(),
+ },
+ "config": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: resourceApplicationConfig(),
+ },
+ "network_item_type": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "network_item_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceApplicationRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
+ c := i.(*cloudconnexa.Client)
+ var diags diag.Diagnostics
+ var name = data.Get("name").(string)
+ application, err := c.Applications.GetByName(name)
+
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ if application == nil {
+ return append(diags, diag.Errorf("Application with name %s was not found", name)...)
+ }
+ setApplicationData(data, application)
+ return nil
+}
diff --git a/cloudconnexa/data_source_host.go b/cloudconnexa/data_source_host.go
index 54d2d65..f095c89 100644
--- a/cloudconnexa/data_source_host.go
+++ b/cloudconnexa/data_source_host.go
@@ -15,6 +15,11 @@ func dataSourceHost() *schema.Resource {
Description: "Use an `cloudconnexa_host` data source to read an existing CloudConnexa connector.",
ReadContext: dataSourceHostRead,
Schema: map[string]*schema.Schema{
+ "host_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The host ID.",
+ },
"name": {
Type: schema.TypeString,
Required: true,
@@ -88,6 +93,7 @@ func dataSourceHostRead(ctx context.Context, d *schema.ResourceData, m interface
if err != nil {
return append(diags, diag.FromErr(err)...)
}
+ d.Set("host_id", host.Id)
d.Set("name", host.Name)
d.Set("internet_access", host.InternetAccess)
d.Set("system_subnets", host.SystemSubnets)
diff --git a/cloudconnexa/provider.go b/cloudconnexa/provider.go
index 4f9237b..2f80469 100644
--- a/cloudconnexa/provider.go
+++ b/cloudconnexa/provider.go
@@ -45,14 +45,15 @@ func Provider() *schema.Provider {
},
},
ResourcesMap: map[string]*schema.Resource{
- "cloudconnexa_network": resourceNetwork(),
- "cloudconnexa_connector": resourceConnector(),
- "cloudconnexa_route": resourceRoute(),
- "cloudconnexa_dns_record": resourceDnsRecord(),
- "cloudconnexa_user": resourceUser(),
- "cloudconnexa_host": resourceHost(),
- "cloudconnexa_user_group": resourceUserGroup(),
- "cloudconnexa_ip_service": resourceIPService(),
+ "cloudconnexa_network": resourceNetwork(),
+ "cloudconnexa_connector": resourceConnector(),
+ "cloudconnexa_route": resourceRoute(),
+ "cloudconnexa_dns_record": resourceDnsRecord(),
+ "cloudconnexa_user": resourceUser(),
+ "cloudconnexa_host": resourceHost(),
+ "cloudconnexa_user_group": resourceUserGroup(),
+ "cloudconnexa_ip_service": resourceIPService(),
+ "cloudconnexa_application": resourceApplication(),
},
DataSourcesMap: map[string]*schema.Resource{
@@ -64,6 +65,7 @@ func Provider() *schema.Provider {
"cloudconnexa_network_routes": dataSourceNetworkRoutes(),
"cloudconnexa_host": dataSourceHost(),
"cloudconnexa_ip_service": dataSourceIPService(),
+ "cloudconnexa_application": dataSourceApplication(),
},
ConfigureContextFunc: providerConfigure,
}
diff --git a/cloudconnexa/resource_application.go b/cloudconnexa/resource_application.go
new file mode 100644
index 0000000..4e36d05
--- /dev/null
+++ b/cloudconnexa/resource_application.go
@@ -0,0 +1,299 @@
+package cloudconnexa
+
+import (
+ "context"
+ "github.com/hashicorp/go-cty/cty"
+ "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"
+ "github.com/openvpn/cloudconnexa-go-client/v2/cloudconnexa"
+)
+
+func resourceApplication() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceApplicationCreate,
+ ReadContext: resourceApplicationRead,
+ DeleteContext: resourceApplicationDelete,
+ UpdateContext: resourceApplicationUpdate,
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringLenBetween(1, 40),
+ },
+ "description": {
+ Type: schema.TypeString,
+ Default: "Managed by Terraform",
+ Optional: true,
+ ValidateFunc: validation.StringLenBetween(1, 120),
+ },
+ "routes": {
+ Type: schema.TypeList,
+ Required: true,
+ MinItems: 1,
+ Elem: resourceApplicationRoute(),
+ },
+ "config": {
+ Type: schema.TypeList,
+ MaxItems: 1,
+ Optional: true,
+ Elem: resourceApplicationConfig(),
+ },
+ "network_item_type": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringInSlice([]string{"NETWORK", "HOST"}, false),
+ },
+ "network_item_id": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ },
+ }
+}
+
+func resourceApplicationUpdate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
+ c := i.(*cloudconnexa.Client)
+
+ s, err := c.Applications.Update(data.Id(), resourceDataToApplication(data))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ setApplicationData(data, s)
+ return nil
+}
+
+func resourceApplicationRoute() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "domain": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "allow_embedded_ip": {
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+ },
+ }
+}
+
+func resourceApplicationConfig() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "custom_service_types": {
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: customServiceTypesConfig(),
+ },
+ },
+ "service_types": {
+ Type: schema.TypeList,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics {
+
+ val := i.(string)
+ for _, validValue := range validValues {
+ if val == validValue {
+ return nil
+ }
+ }
+ return diag.Errorf("service type must be one of %s", validValues)
+ },
+ },
+ },
+ },
+ }
+}
+
+func customServiceTypesConfig() map[string]*schema.Schema {
+ return map[string]*schema.Schema{
+ "protocol": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringInSlice([]string{"TCP", "UDP", "ICMP"}, false),
+ },
+ "from_port": {
+ Type: schema.TypeInt,
+ Optional: true,
+ },
+ "to_port": {
+ Type: schema.TypeInt,
+ Optional: true,
+ },
+ }
+}
+
+func resourceApplicationRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
+ c := i.(*cloudconnexa.Client)
+ var diags diag.Diagnostics
+ application, err := c.Applications.Get(data.Id())
+ if err != nil {
+ return append(diags, diag.FromErr(err)...)
+ }
+ if application == nil {
+ data.SetId("")
+ return diags
+ }
+ setApplicationData(data, application)
+ return diags
+}
+
+func setApplicationData(data *schema.ResourceData, application *cloudconnexa.ApplicationResponse) {
+ data.SetId(application.Id)
+ _ = data.Set("name", application.Name)
+ _ = data.Set("description", application.Description)
+ _ = data.Set("routes", flattenApplicationRoutes(application.Routes))
+ _ = data.Set("config", flattenApplicationConfig(application.Config))
+ _ = data.Set("network_item_type", application.NetworkItemType)
+ _ = data.Set("network_item_id", application.NetworkItemId)
+}
+
+func resourceApplicationDelete(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics {
+ c := i.(*cloudconnexa.Client)
+ var diags diag.Diagnostics
+ err := c.Applications.Delete(data.Id())
+ if err != nil {
+ return append(diags, diag.FromErr(err)...)
+ }
+ return diags
+}
+
+func flattenApplicationConfig(config *cloudconnexa.ApplicationConfig) interface{} {
+ var data = map[string]interface{}{
+ "custom_service_types": flattenApplicationCustomTypes(config.CustomServiceTypes),
+ "service_types": config.ServiceTypes,
+ }
+ return []interface{}{data}
+}
+
+func flattenApplicationCustomTypes(types []*cloudconnexa.CustomApplicationType) interface{} {
+ var cst []interface{}
+ for _, t := range types {
+ var ports = append(t.Port, t.IcmpType...)
+ if len(ports) > 0 {
+ for _, port := range ports {
+ cst = append(cst, map[string]interface{}{
+ "protocol": t.Protocol,
+ "from_port": port.LowerValue,
+ "to_port": port.UpperValue,
+ })
+ }
+ } else {
+ cst = append(cst, map[string]interface{}{
+ "protocol": t.Protocol,
+ })
+ }
+ }
+ return cst
+}
+
+func flattenApplicationRoutes(routes []*cloudconnexa.Route) []map[string]interface{} {
+ var data []map[string]interface{}
+ for _, route := range routes {
+ data = append(data, map[string]interface{}{
+ "domain": route.Domain,
+ "allow_embedded_ip": route.AllowEmbeddedIp,
+ })
+ }
+ return data
+}
+
+func resourceApplicationCreate(ctx context.Context, data *schema.ResourceData, m interface{}) diag.Diagnostics {
+ client := m.(*cloudconnexa.Client)
+
+ application := resourceDataToApplication(data)
+ createdApplication, err := client.Applications.Create(application)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ setApplicationData(data, createdApplication)
+ return nil
+}
+
+func resourceDataToApplication(data *schema.ResourceData) *cloudconnexa.Application {
+ routes := data.Get("routes").([]interface{})
+ var configRoutes []*cloudconnexa.ApplicationRoute
+ for _, r := range routes {
+ var route = r.(map[string]interface{})
+ configRoutes = append(
+ configRoutes,
+ &cloudconnexa.ApplicationRoute{
+ Value: route["domain"].(string),
+ AllowEmbeddedIp: route["allow_embedded_ip"].(bool),
+ },
+ )
+ }
+
+ config := cloudconnexa.ApplicationConfig{}
+ configList := data.Get("config").([]interface{})
+ if len(configList) > 0 && configList[0] != nil {
+
+ config.CustomServiceTypes = []*cloudconnexa.CustomApplicationType{}
+ config.ServiceTypes = []string{}
+
+ mainConfig := configList[0].(map[string]interface{})
+ var cst = mainConfig["custom_service_types"].(*schema.Set)
+ var groupedCst = make(map[string][]cloudconnexa.Range)
+ for _, item := range cst.List() {
+ var cstItem = item.(map[string]interface{})
+ var protocol = cstItem["protocol"].(string)
+ var fromPort = cstItem["from_port"].(int)
+ var toPort = cstItem["to_port"].(int)
+
+ if groupedCst[protocol] == nil {
+ groupedCst[protocol] = make([]cloudconnexa.Range, 0)
+ }
+ if fromPort > 0 || toPort > 0 {
+ groupedCst[protocol] = append(groupedCst[protocol], cloudconnexa.Range{
+ LowerValue: fromPort,
+ UpperValue: toPort,
+ })
+ }
+ }
+
+ for protocol, ports := range groupedCst {
+ if protocol == "ICMP" {
+ config.CustomServiceTypes = append(
+ config.CustomServiceTypes,
+ &cloudconnexa.CustomApplicationType{
+ Protocol: protocol,
+ IcmpType: ports,
+ },
+ )
+ } else {
+ config.CustomServiceTypes = append(
+ config.CustomServiceTypes,
+ &cloudconnexa.CustomApplicationType{
+ Protocol: protocol,
+ Port: ports,
+ },
+ )
+ }
+ }
+
+ for _, r := range mainConfig["service_types"].([]interface{}) {
+ config.ServiceTypes = append(config.ServiceTypes, r.(string))
+ }
+ }
+
+ s := &cloudconnexa.Application{
+ Name: data.Get("name").(string),
+ Description: data.Get("description").(string),
+ NetworkItemId: data.Get("network_item_id").(string),
+ NetworkItemType: data.Get("network_item_type").(string),
+ Routes: configRoutes,
+ Config: &config,
+ }
+ return s
+}
diff --git a/docs/data-sources/application.md b/docs/data-sources/application.md
new file mode 100644
index 0000000..d9094d1
--- /dev/null
+++ b/docs/data-sources/application.md
@@ -0,0 +1,46 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "cloudconnexa_application Data Source - terraform-provider-cloudconnexa"
+subcategory: ""
+description: |-
+
+---
+
+# cloudconnexa_application (Data Source)
+
+
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String)
+
+### Read-Only
+
+- `config` (List of Object) (see [below for nested schema](#nestedatt--config))
+- `description` (String)
+- `id` (String) The ID of this resource.
+- `network_item_id` (String)
+- `network_item_type` (String)
+- `routes` (List of String)
+
+
+### Nested Schema for `config`
+
+Read-Only:
+
+- `custom_service_types` (Set of Object) (see [below for nested schema](#nestedobjatt--config--custom_service_types))
+- `service_types` (List of String)
+
+
+### Nested Schema for `config.custom_service_types`
+
+Read-Only:
+
+- `from_port` (Number)
+- `protocol` (String)
+- `to_port` (Number)
diff --git a/docs/resources/application.md b/docs/resources/application.md
new file mode 100644
index 0000000..2696dda
--- /dev/null
+++ b/docs/resources/application.md
@@ -0,0 +1,64 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "cloudconnexa_application Resource - terraform-provider-cloudconnexa"
+subcategory: ""
+description: |-
+
+---
+
+# cloudconnexa_application (Resource)
+
+
+
+
+
+
+## Schema
+
+### Required
+
+- `name` (String)
+- `network_item_id` (String)
+- `network_item_type` (String)
+- `routes` (Block List, Min: 1) (see [below for nested schema](#nestedblock--routes))
+
+### Optional
+
+- `config` (Block List, Max: 1) (see [below for nested schema](#nestedblock--config))
+- `description` (String)
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `routes`
+
+Required:
+
+- `domain` (String)
+
+Optional:
+
+- `allow_embedded_ip` (Boolean)
+
+
+
+### Nested Schema for `config`
+
+Optional:
+
+- `custom_service_types` (Block Set) (see [below for nested schema](#nestedblock--config--custom_service_types))
+- `service_types` (List of String)
+
+
+### Nested Schema for `config.custom_service_types`
+
+Required:
+
+- `protocol` (String)
+
+Optional:
+
+- `from_port` (Number)
+- `to_port` (Number)
diff --git a/example/applications.tf b/example/applications.tf
new file mode 100644
index 0000000..ea4bf03
--- /dev/null
+++ b/example/applications.tf
@@ -0,0 +1,88 @@
+data "cloudconnexa_network" "test-net" {
+ name = "test-net"
+}
+
+resource "cloudconnexa_application" "application_full_access" {
+ name = "example-application-1"
+ network_item_type = "NETWORK"
+ network_item_id = data.cloudconnexa_network.test-net.network_id
+ routes {
+ domain = "example-application-1.com"
+ allow_embedded_ip = false
+ }
+
+ config {
+ service_types = ["ANY"]
+ }
+}
+
+resource "cloudconnexa_application" "application_custom_access" {
+ name = "example-application-2"
+ network_item_type = "NETWORK"
+ network_item_id = data.cloudconnexa_network.test-net.network_id
+
+ routes {
+ domain = "example-application-2.com"
+ allow_embedded_ip = false
+ }
+
+ config {
+ service_types = ["HTTP", "HTTPS", "CUSTOM"]
+ custom_service_types {
+ protocol = "TCP" //all tcp ports
+ }
+ custom_service_types {
+ protocol = "UDP"
+ from_port = 1194
+ to_port = 1194
+ }
+ custom_service_types {
+ protocol = "UDP"
+ from_port = 5000
+ to_port = 5010
+ }
+ custom_service_types {
+ protocol = "ICMP"
+ from_port = 8
+ to_port = 8
+ }
+ custom_service_types {
+ protocol = "ICMP"
+ from_port = 20
+ to_port = 22
+ }
+ }
+}
+
+locals {
+ created_by = "managed by terraform"
+}
+
+variable "application_custom_access_advanced" {
+ description = "xxx"
+ type = any
+ default = {
+ "example-application-3" = { route = [{ domain = "example-application-3.com", allow_embedded_ip = true }, { domain = "example-application-33.com", allow_embedded_ip = false }] }
+ "example-application-4" = { route = [{ domain = "example-application-4.com", allow_embedded_ip = false }] }
+ }
+}
+
+resource "cloudconnexa_application" "application_custom_access_advanced" {
+ for_each = var.application_custom_access_advanced
+ name = each.key
+ description = try(each.value.description, local.created_by)
+ network_item_type = "NETWORK"
+ network_item_id = data.cloudconnexa_network.test-net.network_id
+ config {
+ service_types = ["ANY"]
+ }
+
+ dynamic "routes" {
+ for_each = each.value.route
+
+ content {
+ domain = routes.value.domain
+ allow_embedded_ip = routes.value.allow_embedded_ip
+ }
+ }
+}
diff --git a/go.mod b/go.mod
index 44cb4b0..3b33588 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
github.com/gruntwork-io/terratest v0.46.1
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
- github.com/openvpn/cloudconnexa-go-client/v2 v2.0.4
+ github.com/openvpn/cloudconnexa-go-client/v2 v2.0.8
github.com/stretchr/testify v1.9.0
)
diff --git a/go.sum b/go.sum
index 0dba60f..546fc20 100644
--- a/go.sum
+++ b/go.sum
@@ -485,8 +485,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
-github.com/openvpn/cloudconnexa-go-client/v2 v2.0.4 h1:0Z8eTHPDYUFH3kCA1DTemiUnlPK9FhEuJhZB3bG1usE=
-github.com/openvpn/cloudconnexa-go-client/v2 v2.0.4/go.mod h1:udq5IDkgXvMO6mQUEFsLHzEyGGAduhO0jJvlb9f4JkE=
+github.com/openvpn/cloudconnexa-go-client/v2 v2.0.8 h1:67NXu2WqNnE05fhrq1HXbQKFMidhnd7ts6SFeuZSkLo=
+github.com/openvpn/cloudconnexa-go-client/v2 v2.0.8/go.mod h1:udq5IDkgXvMO6mQUEFsLHzEyGGAduhO0jJvlb9f4JkE=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=