From 5bd87a705a9d036d301dd66e30175ad7a3e6dbdd Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Fri, 13 Dec 2024 13:57:27 +0200 Subject: [PATCH 1/8] Add access groups support --- cloudconnexa/data_source_access_group.go | 56 +++++ cloudconnexa/provider.go | 2 + cloudconnexa/resource_access_group.go | 281 +++++++++++++++++++++++ docs/data-sources/access_group.md | 45 ++++ docs/data-sources/user_group.md | 2 +- docs/index.md | 15 +- docs/resources/access_group.md | 54 +++++ docs/resources/user_group.md | 2 +- examples/access_groups.tf | 108 +++++++++ go.mod | 6 +- go.sum | 12 +- 11 files changed, 559 insertions(+), 24 deletions(-) create mode 100644 cloudconnexa/data_source_access_group.go create mode 100644 cloudconnexa/resource_access_group.go create mode 100644 docs/data-sources/access_group.md create mode 100644 docs/resources/access_group.md create mode 100644 examples/access_groups.tf diff --git a/cloudconnexa/data_source_access_group.go b/cloudconnexa/data_source_access_group.go new file mode 100644 index 0000000..7cb5a9f --- /dev/null +++ b/cloudconnexa/data_source_access_group.go @@ -0,0 +1,56 @@ +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 dataSourceAccessGroup() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceAccessGroupRead, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The Access group name.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The Access group description.", + }, + "source": { + Type: schema.TypeSet, + Computed: true, + Elem: resourceSource(), + }, + "destination": { + Type: schema.TypeSet, + Computed: true, + Elem: resourceDestination(), + }, + }, + } +} + +func dataSourceAccessGroupRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { + c := i.(*cloudconnexa.Client) + var diags diag.Diagnostics + var id = data.Get("id").(string) + group, err := c.AccessGroups.Get(id) + + if err != nil { + return diag.FromErr(err) + } + if group == nil { + return append(diags, diag.Errorf("Access Group with id %s was not found", id)...) + } + setAccessGroupData(data, group) + return nil +} diff --git a/cloudconnexa/provider.go b/cloudconnexa/provider.go index 1d722f6..d565919 100644 --- a/cloudconnexa/provider.go +++ b/cloudconnexa/provider.go @@ -55,6 +55,7 @@ func Provider() *schema.Provider { "cloudconnexa_ip_service": resourceIPService(), "cloudconnexa_application": resourceApplication(), "cloudconnexa_location_context": resourceLocationContext(), + "cloudconnexa_access_group": resourceAccessGroup(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -68,6 +69,7 @@ func Provider() *schema.Provider { "cloudconnexa_ip_service": dataSourceIPService(), "cloudconnexa_application": dataSourceApplication(), "cloudconnexa_location_context": dataSourceLocationContext(), + "cloudconnexa_access_group": dataSourceAccessGroup(), }, ConfigureContextFunc: providerConfigure, } diff --git a/cloudconnexa/resource_access_group.go b/cloudconnexa/resource_access_group.go new file mode 100644 index 0000000..e5940dc --- /dev/null +++ b/cloudconnexa/resource_access_group.go @@ -0,0 +1,281 @@ +package cloudconnexa + +import ( + "context" + "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" +) + +var ( + validSource = []string{"USER_GROUP", "NETWORK", "HOST"} + validDestination = []string{"USER_GROUP", "NETWORK", "HOST", "PUBLISHED_APPLICATION"} + sourceRequestConversions = map[string]string{ + "NETWORK": "NETWORK_SERVICE", + } + destinationRequestConversions = map[string]string{ + "NETWORK": "NETWORK_SERVICE", + "HOST": "HOST_SERVICE", + "PUBLISHED_APPLICATION": "PUBLISHED_SERVICE", + } + sourceResponseConversions = map[string]string{ + "NETWORK_SERVICE": "NETWORK", + } + destinationResponseConversions = map[string]string{ + "NETWORK_SERVICE": "NETWORK", + "HOST_SERVICE": "HOST", + "PUBLISHED_SERVICE": "PUBLISHED_APPLICATION", + } +) + +func resourceAccessGroup() *schema.Resource { + return &schema.Resource{ + Description: "Use `cloudconnexa_access_group` to create an Access group.", + CreateContext: resourceAccessGroupCreate, + ReadContext: resourceAccessGroupRead, + DeleteContext: resourceAccessGroupDelete, + UpdateContext: resourceAccessGroupUpdate, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The Access group name.", + }, + "description": { + Type: schema.TypeString, + Required: true, + Description: "The Access group description.", + }, + "source": { + Type: schema.TypeSet, + MinItems: 1, + Required: true, + ForceNew: true, + Elem: resourceSource(), + }, + "destination": { + Type: schema.TypeSet, + MinItems: 1, + Required: true, + ForceNew: true, + Elem: resourceDestination(), + }, + }, + } +} + +func resourceSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: "Source type.", + ValidateFunc: validation.StringInSlice(validSource, false), + }, + "all_covered": { + Type: schema.TypeBool, + Required: true, + Description: "Allows to select all items of specific type or all children under specific parent", + }, + "parent": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the entity assigned to access group source.", + }, + "children": { + Type: schema.TypeList, + Optional: true, + Description: "ID of child entities assigned to access group source.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceDestination() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: "Destination type.", + ValidateFunc: validation.StringInSlice(validDestination, false), + }, + "all_covered": { + Type: schema.TypeBool, + Required: true, + Description: "Allows to select all items of specific type or all children under specific parent", + }, + "parent": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the entity assigned to access group destination.", + }, + "children": { + Type: schema.TypeList, + Optional: true, + Description: "ID of child entities assigned to access group destination.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceAccessGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + request := resourceDataToAccessGroup(d) + accessGroup, err := c.AccessGroups.Create(request) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + d.SetId(accessGroup.Id) + return diags +} + +func resourceAccessGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + id := d.Id() + ag, err := c.AccessGroups.Get(id) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + if ag == nil { + d.SetId("") + } else { + setAccessGroupData(d, ag) + } + return diags +} + +func resourceAccessGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + ag := resourceDataToAccessGroup(d) + savedAccessGroup, err := c.AccessGroups.Update(d.Id(), ag) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + setAccessGroupData(d, savedAccessGroup) + return diags +} + +func resourceAccessGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + id := d.Id() + err := c.AccessGroups.Delete(id) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + return diags +} + +func setAccessGroupData(d *schema.ResourceData, ag *cloudconnexa.AccessGroupResponse) { + d.SetId(ag.Id) + d.Set("name", ag.Name) + d.Set("description", ag.Description) + + var sources []interface{} + for _, source := range ag.Source { + var parent = "" + if source.Parent != nil { + parent = source.Parent.Id + } + var children []interface{} + if source.Type == "USER_GROUP" || source.AllCovered == false { + for _, child := range source.Children { + children = append(children, child.Id) + } + } + + sources = append(sources, map[string]interface{}{ + "type": convert(source.Type, sourceResponseConversions), + "all_covered": source.AllCovered, + "parent": parent, + "children": children, + }) + } + d.Set("source", sources) + + var destinations []interface{} + for _, destination := range ag.Destination { + var parent = "" + if destination.Parent != nil { + parent = destination.Parent.Id + } + var children []interface{} + if destination.Type == "USER_GROUP" || destination.AllCovered == false { + for _, child := range destination.Children { + children = append(children, child.Id) + } + } + + destinations = append(destinations, map[string]interface{}{ + "type": convert(destination.Type, destinationResponseConversions), + "all_covered": destination.AllCovered, + "parent": parent, + "children": children, + }) + } + d.Set("destination", destinations) +} + +func resourceDataToAccessGroup(data *schema.ResourceData) *cloudconnexa.AccessGroupRequest { + name := data.Get("name").(string) + description := data.Get("description").(string) + + request := &cloudconnexa.AccessGroupRequest{ + Name: name, + Description: description, + } + + sources := data.Get("source").(*schema.Set).List() + + for _, source := range sources { + var convertedSource = source.(map[string]interface{}) + newSource := cloudconnexa.AccessItemRequest{ + Type: convert(convertedSource["type"].(string), sourceRequestConversions), + AllCovered: convertedSource["all_covered"].(bool), + Parent: convertedSource["parent"].(string), + } + for _, child := range convertedSource["children"].([]interface{}) { + newSource.Children = append(newSource.Children, child.(string)) + } + request.Source = append(request.Source, newSource) + } + + destinations := data.Get("destination").(*schema.Set).List() + + for _, destination := range destinations { + var mappedDestination = destination.(map[string]interface{}) + newDestination := cloudconnexa.AccessItemRequest{ + Type: convert(mappedDestination["type"].(string), destinationRequestConversions), + AllCovered: mappedDestination["all_covered"].(bool), + Parent: mappedDestination["parent"].(string), + } + for _, child := range mappedDestination["children"].([]interface{}) { + newDestination.Children = append(newDestination.Children, child.(string)) + } + request.Destination = append(request.Destination, newDestination) + } + + return request +} + +func convert(input string, conversions map[string]string) string { + if output, exists := conversions[input]; exists { + return output + } + return input +} diff --git a/docs/data-sources/access_group.md b/docs/data-sources/access_group.md new file mode 100644 index 0000000..e9c4b75 --- /dev/null +++ b/docs/data-sources/access_group.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cloudconnexa_access_group Data Source - terraform-provider-cloudconnexa" +subcategory: "" +description: |- + +--- + +# cloudconnexa_access_group (Data Source) + + + + + + +## Schema + +### Read-Only + +- `description` (String) The Access group description. +- `destination` (Set of Object) (see [below for nested schema](#nestedatt--destination)) +- `id` (String) The ID of this resource. +- `name` (String) The Access group name. +- `source` (Set of Object) (see [below for nested schema](#nestedatt--source)) + + +### Nested Schema for `destination` + +Read-Only: + +- `all_covered` (Boolean) +- `children` (List of String) +- `parent` (String) +- `type` (String) + + + +### Nested Schema for `source` + +Read-Only: + +- `all_covered` (Boolean) +- `children` (List of String) +- `parent` (String) +- `type` (String) diff --git a/docs/data-sources/user_group.md b/docs/data-sources/user_group.md index 5f9cdfa..cb2fb53 100644 --- a/docs/data-sources/user_group.md +++ b/docs/data-sources/user_group.md @@ -21,10 +21,10 @@ Use an `cloudconnexa_user_group` data source to read an CloudConnexa user group. ### Read-Only +- `all_regions_included` (Boolean) If true all regions will be available for this user group. - `connect_auth` (String) The type of connection authentication. Valid values are `AUTH`, `AUTO`, or `STRICT_AUTH`. - `id` (String) The user group ID. - `internet_access` (String) The type of internet access provided. Valid values are `BLOCKED`, `GLOBAL_INTERNET`, or `LOCAL`. Defaults to `LOCAL`. - `max_device` (Number) The maximum number of devices per user. - `system_subnets` (List of String) The IPV4 and IPV6 addresses of the subnets associated with this user group. - `vpn_region_ids` (List of String) The list of region IDs this user group is associated with. -- `all_regions_included` (Boolean) If true all regions will be available for this user group. diff --git a/docs/index.md b/docs/index.md index 97e6381..af39271 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,11 +6,11 @@ description: |- --- -# CloudConnexa Provider +# cloudconnexa Provider !> **WARNING:** This provider is experimental and support for it is on a best-effort basis. Additionally, the underlying API for CloudConnexa is on Beta, which means that future versions of the provider may introduce breaking changes. Should that happen, migration documentation and support will also be provided on a best-effort basis. -Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/developer/index.html). +Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/api-guide/). ## Schema @@ -23,14 +23,3 @@ Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cl - **client_id** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_ID` environment variable. - **client_secret** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_SECRET` environment variable. - -### Credentials - -To authenticate with the CloudConnexa API, you'll need the client_id and client_secret. -These credentials can be found in the CloudConnexa Portal. -Go to the Settings page and click on the API tab. -From there, you can enable the API and generate new authentication credentials. -Additionally, you'll find Swagger documentation for the API in the same location. - -More documentation on the OpenVPN API can be found here: -[CloudConnexa API Documentation](https://openvpn.net/cloud-docs/developer/cloudconnexa-api.html) diff --git a/docs/resources/access_group.md b/docs/resources/access_group.md new file mode 100644 index 0000000..b730d8f --- /dev/null +++ b/docs/resources/access_group.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cloudconnexa_access_group Resource - terraform-provider-cloudconnexa" +subcategory: "" +description: |- + Use cloudconnexa_access_group to create an Access group. +--- + +# cloudconnexa_access_group (Resource) + +Use `cloudconnexa_access_group` to create an Access group. + + + + +## Schema + +### Required + +- `description` (String) The Access group description. +- `destination` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--destination)) +- `name` (String) The Access group name. +- `source` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--source)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `destination` + +Required: + +- `all_covered` (Boolean) Allows to select all items of specific type or all children under specific parent +- `type` (String) Destination type. + +Optional: + +- `children` (List of String) ID of child entities assigned to access group destination. +- `parent` (String) ID of the entity assigned to access group destination. + + + +### Nested Schema for `source` + +Required: + +- `all_covered` (Boolean) Allows to select all items of specific type or all children under specific parent +- `type` (String) Source type. + +Optional: + +- `children` (List of String) ID of child entities assigned to access group source. +- `parent` (String) ID of the entity assigned to access group source. diff --git a/docs/resources/user_group.md b/docs/resources/user_group.md index 9eb5d45..4f14570 100644 --- a/docs/resources/user_group.md +++ b/docs/resources/user_group.md @@ -21,12 +21,12 @@ Use `cloudconnexa_user_group` to create an CloudConnexa user group. ### Optional +- `all_regions_included` (Boolean) If true all regions will be available for this user group. - `connect_auth` (String) - `internet_access` (String) - `max_device` (Number) The maximum number of devices that can be connected to the user group. - `system_subnets` (List of String) A list of subnets that are accessible to the user group. - `vpn_region_ids` (List of String) A list of regions that are accessible to the user group. -- `all_regions_included` (Boolean) If true all regions will be available for this user group. ### Read-Only diff --git a/examples/access_groups.tf b/examples/access_groups.tf new file mode 100644 index 0000000..cdca840 --- /dev/null +++ b/examples/access_groups.tf @@ -0,0 +1,108 @@ +#To select all resources in source and destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = true + } + source { + type = "HOST" + all_covered = true + } + source { + type = "USER_GROUP" + all_covered = true + } + destination { + type = "NETWORK" + all_covered = true + } + destination { + type = "HOST" + all_covered = true + } + destination { + type = "USER_GROUP" + all_covered = true + } +} +#To select specific network in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + } +} +#To select specific network ip service or application +#Note: only network IP services with selected type: IP_SOURCE can be used in an access group source +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = false + parent = "00000000-0000-0000-0000-000000000001" + children = ["00000000-0000-0000-0000-000000000002"] + } + destination { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000003" + children = ["00000000-0000-0000-0000-000000000004"] + } +} +#To select specific host in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + } +} +#To select specific host ip service or application +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + children = ["00000000-0000-0000-0000-000000000003"] + } +} +#To select specific user group in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "USER_GROUP" + all_covered = false + children = ["00000000-0000-0000-0000-000000000001"] + } + destination { + type = "USER_GROUP" + all_covered = false + children = ["00000000-0000-0000-0000-000000000002"] + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index eb12f07..07a31f8 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/gruntwork-io/terratest v0.47.2 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 - github.com/openvpn/cloudconnexa-go-client/v2 v2.0.17 - github.com/stretchr/testify v1.9.0 + github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18 + github.com/stretchr/testify v1.10.0 ) require ( @@ -89,7 +89,7 @@ require ( golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.20.0 // indirect google.golang.org/api v0.162.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 2320566..0163687 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.17 h1:sWTmQu/7smhJDgoF3on9dexwwB1jQE/0hNzxjGCcVPA= -github.com/openvpn/cloudconnexa-go-client/v2 v2.0.17/go.mod h1:aky93nehxFyRZ/rE6sjhQ7aLW9IU7XHOpAMCFbTgYIw= +github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18 h1:1Z1j1xMx9wj0DTLsAMfX+Q25tHgSyDxXba5vgC62YOY= +github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18/go.mod h1:bnKiUSuEp7J2K/o+gOxotF1m1Cx5dfVUE0GbWN5AXRY= 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= @@ -514,8 +514,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ= github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= @@ -797,8 +797,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From ed7b62a34ea3310fa1474d1737abb283b429bdba Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Fri, 13 Dec 2024 14:01:37 +0200 Subject: [PATCH 2/8] Revert index.md docs changes --- docs/index.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index af39271..380fec4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,14 +3,14 @@ page_title: "cloudconnexa Provider" subcategory: "" description: |- - + --- -# cloudconnexa Provider +# CloudConnexa Provider !> **WARNING:** This provider is experimental and support for it is on a best-effort basis. Additionally, the underlying API for CloudConnexa is on Beta, which means that future versions of the provider may introduce breaking changes. Should that happen, migration documentation and support will also be provided on a best-effort basis. -Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/api-guide/). +Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/developer/index.html). ## Schema @@ -23,3 +23,14 @@ Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cl - **client_id** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_ID` environment variable. - **client_secret** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_SECRET` environment variable. + +### Credentials + +To authenticate with the CloudConnexa API, you'll need the client_id and client_secret. +These credentials can be found in the CloudConnexa Portal. +Go to the Settings page and click on the API tab. +From there, you can enable the API and generate new authentication credentials. +Additionally, you'll find Swagger documentation for the API in the same location. + +More documentation on the OpenVPN API can be found here: +[CloudConnexa API Documentation](https://openvpn.net/cloud-docs/developer/cloudconnexa-api.html) From 1b2d49a6621e11b50c542af07376c7b64127f947 Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Fri, 13 Dec 2024 13:57:27 +0200 Subject: [PATCH 3/8] Add access groups support --- cloudconnexa/data_source_access_group.go | 56 +++++ cloudconnexa/provider.go | 2 + cloudconnexa/resource_access_group.go | 281 +++++++++++++++++++++++ docs/data-sources/access_group.md | 45 ++++ docs/data-sources/user_group.md | 2 +- docs/index.md | 15 +- docs/resources/access_group.md | 54 +++++ docs/resources/user_group.md | 2 +- examples/access_groups.tf | 108 +++++++++ go.mod | 6 +- go.sum | 12 +- 11 files changed, 559 insertions(+), 24 deletions(-) create mode 100644 cloudconnexa/data_source_access_group.go create mode 100644 cloudconnexa/resource_access_group.go create mode 100644 docs/data-sources/access_group.md create mode 100644 docs/resources/access_group.md create mode 100644 examples/access_groups.tf diff --git a/cloudconnexa/data_source_access_group.go b/cloudconnexa/data_source_access_group.go new file mode 100644 index 0000000..7cb5a9f --- /dev/null +++ b/cloudconnexa/data_source_access_group.go @@ -0,0 +1,56 @@ +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 dataSourceAccessGroup() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceAccessGroupRead, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The Access group name.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The Access group description.", + }, + "source": { + Type: schema.TypeSet, + Computed: true, + Elem: resourceSource(), + }, + "destination": { + Type: schema.TypeSet, + Computed: true, + Elem: resourceDestination(), + }, + }, + } +} + +func dataSourceAccessGroupRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { + c := i.(*cloudconnexa.Client) + var diags diag.Diagnostics + var id = data.Get("id").(string) + group, err := c.AccessGroups.Get(id) + + if err != nil { + return diag.FromErr(err) + } + if group == nil { + return append(diags, diag.Errorf("Access Group with id %s was not found", id)...) + } + setAccessGroupData(data, group) + return nil +} diff --git a/cloudconnexa/provider.go b/cloudconnexa/provider.go index 1d722f6..d565919 100644 --- a/cloudconnexa/provider.go +++ b/cloudconnexa/provider.go @@ -55,6 +55,7 @@ func Provider() *schema.Provider { "cloudconnexa_ip_service": resourceIPService(), "cloudconnexa_application": resourceApplication(), "cloudconnexa_location_context": resourceLocationContext(), + "cloudconnexa_access_group": resourceAccessGroup(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -68,6 +69,7 @@ func Provider() *schema.Provider { "cloudconnexa_ip_service": dataSourceIPService(), "cloudconnexa_application": dataSourceApplication(), "cloudconnexa_location_context": dataSourceLocationContext(), + "cloudconnexa_access_group": dataSourceAccessGroup(), }, ConfigureContextFunc: providerConfigure, } diff --git a/cloudconnexa/resource_access_group.go b/cloudconnexa/resource_access_group.go new file mode 100644 index 0000000..e5940dc --- /dev/null +++ b/cloudconnexa/resource_access_group.go @@ -0,0 +1,281 @@ +package cloudconnexa + +import ( + "context" + "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" +) + +var ( + validSource = []string{"USER_GROUP", "NETWORK", "HOST"} + validDestination = []string{"USER_GROUP", "NETWORK", "HOST", "PUBLISHED_APPLICATION"} + sourceRequestConversions = map[string]string{ + "NETWORK": "NETWORK_SERVICE", + } + destinationRequestConversions = map[string]string{ + "NETWORK": "NETWORK_SERVICE", + "HOST": "HOST_SERVICE", + "PUBLISHED_APPLICATION": "PUBLISHED_SERVICE", + } + sourceResponseConversions = map[string]string{ + "NETWORK_SERVICE": "NETWORK", + } + destinationResponseConversions = map[string]string{ + "NETWORK_SERVICE": "NETWORK", + "HOST_SERVICE": "HOST", + "PUBLISHED_SERVICE": "PUBLISHED_APPLICATION", + } +) + +func resourceAccessGroup() *schema.Resource { + return &schema.Resource{ + Description: "Use `cloudconnexa_access_group` to create an Access group.", + CreateContext: resourceAccessGroupCreate, + ReadContext: resourceAccessGroupRead, + DeleteContext: resourceAccessGroupDelete, + UpdateContext: resourceAccessGroupUpdate, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The Access group name.", + }, + "description": { + Type: schema.TypeString, + Required: true, + Description: "The Access group description.", + }, + "source": { + Type: schema.TypeSet, + MinItems: 1, + Required: true, + ForceNew: true, + Elem: resourceSource(), + }, + "destination": { + Type: schema.TypeSet, + MinItems: 1, + Required: true, + ForceNew: true, + Elem: resourceDestination(), + }, + }, + } +} + +func resourceSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: "Source type.", + ValidateFunc: validation.StringInSlice(validSource, false), + }, + "all_covered": { + Type: schema.TypeBool, + Required: true, + Description: "Allows to select all items of specific type or all children under specific parent", + }, + "parent": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the entity assigned to access group source.", + }, + "children": { + Type: schema.TypeList, + Optional: true, + Description: "ID of child entities assigned to access group source.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceDestination() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + Description: "Destination type.", + ValidateFunc: validation.StringInSlice(validDestination, false), + }, + "all_covered": { + Type: schema.TypeBool, + Required: true, + Description: "Allows to select all items of specific type or all children under specific parent", + }, + "parent": { + Type: schema.TypeString, + Optional: true, + Description: "ID of the entity assigned to access group destination.", + }, + "children": { + Type: schema.TypeList, + Optional: true, + Description: "ID of child entities assigned to access group destination.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceAccessGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + request := resourceDataToAccessGroup(d) + accessGroup, err := c.AccessGroups.Create(request) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + d.SetId(accessGroup.Id) + return diags +} + +func resourceAccessGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + id := d.Id() + ag, err := c.AccessGroups.Get(id) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + if ag == nil { + d.SetId("") + } else { + setAccessGroupData(d, ag) + } + return diags +} + +func resourceAccessGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + ag := resourceDataToAccessGroup(d) + savedAccessGroup, err := c.AccessGroups.Update(d.Id(), ag) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + setAccessGroupData(d, savedAccessGroup) + return diags +} + +func resourceAccessGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*cloudconnexa.Client) + var diags diag.Diagnostics + id := d.Id() + err := c.AccessGroups.Delete(id) + if err != nil { + return append(diags, diag.FromErr(err)...) + } + return diags +} + +func setAccessGroupData(d *schema.ResourceData, ag *cloudconnexa.AccessGroupResponse) { + d.SetId(ag.Id) + d.Set("name", ag.Name) + d.Set("description", ag.Description) + + var sources []interface{} + for _, source := range ag.Source { + var parent = "" + if source.Parent != nil { + parent = source.Parent.Id + } + var children []interface{} + if source.Type == "USER_GROUP" || source.AllCovered == false { + for _, child := range source.Children { + children = append(children, child.Id) + } + } + + sources = append(sources, map[string]interface{}{ + "type": convert(source.Type, sourceResponseConversions), + "all_covered": source.AllCovered, + "parent": parent, + "children": children, + }) + } + d.Set("source", sources) + + var destinations []interface{} + for _, destination := range ag.Destination { + var parent = "" + if destination.Parent != nil { + parent = destination.Parent.Id + } + var children []interface{} + if destination.Type == "USER_GROUP" || destination.AllCovered == false { + for _, child := range destination.Children { + children = append(children, child.Id) + } + } + + destinations = append(destinations, map[string]interface{}{ + "type": convert(destination.Type, destinationResponseConversions), + "all_covered": destination.AllCovered, + "parent": parent, + "children": children, + }) + } + d.Set("destination", destinations) +} + +func resourceDataToAccessGroup(data *schema.ResourceData) *cloudconnexa.AccessGroupRequest { + name := data.Get("name").(string) + description := data.Get("description").(string) + + request := &cloudconnexa.AccessGroupRequest{ + Name: name, + Description: description, + } + + sources := data.Get("source").(*schema.Set).List() + + for _, source := range sources { + var convertedSource = source.(map[string]interface{}) + newSource := cloudconnexa.AccessItemRequest{ + Type: convert(convertedSource["type"].(string), sourceRequestConversions), + AllCovered: convertedSource["all_covered"].(bool), + Parent: convertedSource["parent"].(string), + } + for _, child := range convertedSource["children"].([]interface{}) { + newSource.Children = append(newSource.Children, child.(string)) + } + request.Source = append(request.Source, newSource) + } + + destinations := data.Get("destination").(*schema.Set).List() + + for _, destination := range destinations { + var mappedDestination = destination.(map[string]interface{}) + newDestination := cloudconnexa.AccessItemRequest{ + Type: convert(mappedDestination["type"].(string), destinationRequestConversions), + AllCovered: mappedDestination["all_covered"].(bool), + Parent: mappedDestination["parent"].(string), + } + for _, child := range mappedDestination["children"].([]interface{}) { + newDestination.Children = append(newDestination.Children, child.(string)) + } + request.Destination = append(request.Destination, newDestination) + } + + return request +} + +func convert(input string, conversions map[string]string) string { + if output, exists := conversions[input]; exists { + return output + } + return input +} diff --git a/docs/data-sources/access_group.md b/docs/data-sources/access_group.md new file mode 100644 index 0000000..e9c4b75 --- /dev/null +++ b/docs/data-sources/access_group.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cloudconnexa_access_group Data Source - terraform-provider-cloudconnexa" +subcategory: "" +description: |- + +--- + +# cloudconnexa_access_group (Data Source) + + + + + + +## Schema + +### Read-Only + +- `description` (String) The Access group description. +- `destination` (Set of Object) (see [below for nested schema](#nestedatt--destination)) +- `id` (String) The ID of this resource. +- `name` (String) The Access group name. +- `source` (Set of Object) (see [below for nested schema](#nestedatt--source)) + + +### Nested Schema for `destination` + +Read-Only: + +- `all_covered` (Boolean) +- `children` (List of String) +- `parent` (String) +- `type` (String) + + + +### Nested Schema for `source` + +Read-Only: + +- `all_covered` (Boolean) +- `children` (List of String) +- `parent` (String) +- `type` (String) diff --git a/docs/data-sources/user_group.md b/docs/data-sources/user_group.md index 5f9cdfa..cb2fb53 100644 --- a/docs/data-sources/user_group.md +++ b/docs/data-sources/user_group.md @@ -21,10 +21,10 @@ Use an `cloudconnexa_user_group` data source to read an CloudConnexa user group. ### Read-Only +- `all_regions_included` (Boolean) If true all regions will be available for this user group. - `connect_auth` (String) The type of connection authentication. Valid values are `AUTH`, `AUTO`, or `STRICT_AUTH`. - `id` (String) The user group ID. - `internet_access` (String) The type of internet access provided. Valid values are `BLOCKED`, `GLOBAL_INTERNET`, or `LOCAL`. Defaults to `LOCAL`. - `max_device` (Number) The maximum number of devices per user. - `system_subnets` (List of String) The IPV4 and IPV6 addresses of the subnets associated with this user group. - `vpn_region_ids` (List of String) The list of region IDs this user group is associated with. -- `all_regions_included` (Boolean) If true all regions will be available for this user group. diff --git a/docs/index.md b/docs/index.md index 97e6381..af39271 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,11 +6,11 @@ description: |- --- -# CloudConnexa Provider +# cloudconnexa Provider !> **WARNING:** This provider is experimental and support for it is on a best-effort basis. Additionally, the underlying API for CloudConnexa is on Beta, which means that future versions of the provider may introduce breaking changes. Should that happen, migration documentation and support will also be provided on a best-effort basis. -Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/developer/index.html). +Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/api-guide/). ## Schema @@ -23,14 +23,3 @@ Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cl - **client_id** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_ID` environment variable. - **client_secret** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_SECRET` environment variable. - -### Credentials - -To authenticate with the CloudConnexa API, you'll need the client_id and client_secret. -These credentials can be found in the CloudConnexa Portal. -Go to the Settings page and click on the API tab. -From there, you can enable the API and generate new authentication credentials. -Additionally, you'll find Swagger documentation for the API in the same location. - -More documentation on the OpenVPN API can be found here: -[CloudConnexa API Documentation](https://openvpn.net/cloud-docs/developer/cloudconnexa-api.html) diff --git a/docs/resources/access_group.md b/docs/resources/access_group.md new file mode 100644 index 0000000..b730d8f --- /dev/null +++ b/docs/resources/access_group.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cloudconnexa_access_group Resource - terraform-provider-cloudconnexa" +subcategory: "" +description: |- + Use cloudconnexa_access_group to create an Access group. +--- + +# cloudconnexa_access_group (Resource) + +Use `cloudconnexa_access_group` to create an Access group. + + + + +## Schema + +### Required + +- `description` (String) The Access group description. +- `destination` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--destination)) +- `name` (String) The Access group name. +- `source` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--source)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `destination` + +Required: + +- `all_covered` (Boolean) Allows to select all items of specific type or all children under specific parent +- `type` (String) Destination type. + +Optional: + +- `children` (List of String) ID of child entities assigned to access group destination. +- `parent` (String) ID of the entity assigned to access group destination. + + + +### Nested Schema for `source` + +Required: + +- `all_covered` (Boolean) Allows to select all items of specific type or all children under specific parent +- `type` (String) Source type. + +Optional: + +- `children` (List of String) ID of child entities assigned to access group source. +- `parent` (String) ID of the entity assigned to access group source. diff --git a/docs/resources/user_group.md b/docs/resources/user_group.md index 9eb5d45..4f14570 100644 --- a/docs/resources/user_group.md +++ b/docs/resources/user_group.md @@ -21,12 +21,12 @@ Use `cloudconnexa_user_group` to create an CloudConnexa user group. ### Optional +- `all_regions_included` (Boolean) If true all regions will be available for this user group. - `connect_auth` (String) - `internet_access` (String) - `max_device` (Number) The maximum number of devices that can be connected to the user group. - `system_subnets` (List of String) A list of subnets that are accessible to the user group. - `vpn_region_ids` (List of String) A list of regions that are accessible to the user group. -- `all_regions_included` (Boolean) If true all regions will be available for this user group. ### Read-Only diff --git a/examples/access_groups.tf b/examples/access_groups.tf new file mode 100644 index 0000000..cdca840 --- /dev/null +++ b/examples/access_groups.tf @@ -0,0 +1,108 @@ +#To select all resources in source and destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = true + } + source { + type = "HOST" + all_covered = true + } + source { + type = "USER_GROUP" + all_covered = true + } + destination { + type = "NETWORK" + all_covered = true + } + destination { + type = "HOST" + all_covered = true + } + destination { + type = "USER_GROUP" + all_covered = true + } +} +#To select specific network in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + } +} +#To select specific network ip service or application +#Note: only network IP services with selected type: IP_SOURCE can be used in an access group source +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "NETWORK" + all_covered = false + parent = "00000000-0000-0000-0000-000000000001" + children = ["00000000-0000-0000-0000-000000000002"] + } + destination { + type = "NETWORK" + all_covered = true + parent = "00000000-0000-0000-0000-000000000003" + children = ["00000000-0000-0000-0000-000000000004"] + } +} +#To select specific host in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + } +} +#To select specific host ip service or application +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000001" + } + destination { + type = "HOST" + all_covered = true + parent = "00000000-0000-0000-0000-000000000002" + children = ["00000000-0000-0000-0000-000000000003"] + } +} +#To select specific user group in source or destination +resource "cloudconnexa_access_group" "full_mesh" { + name = "Access Group name" + description = "Add your description here" + source { + type = "USER_GROUP" + all_covered = false + children = ["00000000-0000-0000-0000-000000000001"] + } + destination { + type = "USER_GROUP" + all_covered = false + children = ["00000000-0000-0000-0000-000000000002"] + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index eb12f07..07a31f8 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/gruntwork-io/terratest v0.47.2 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 - github.com/openvpn/cloudconnexa-go-client/v2 v2.0.17 - github.com/stretchr/testify v1.9.0 + github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18 + github.com/stretchr/testify v1.10.0 ) require ( @@ -89,7 +89,7 @@ require ( golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.20.0 // indirect google.golang.org/api v0.162.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 2320566..0163687 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.17 h1:sWTmQu/7smhJDgoF3on9dexwwB1jQE/0hNzxjGCcVPA= -github.com/openvpn/cloudconnexa-go-client/v2 v2.0.17/go.mod h1:aky93nehxFyRZ/rE6sjhQ7aLW9IU7XHOpAMCFbTgYIw= +github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18 h1:1Z1j1xMx9wj0DTLsAMfX+Q25tHgSyDxXba5vgC62YOY= +github.com/openvpn/cloudconnexa-go-client/v2 v2.0.18/go.mod h1:bnKiUSuEp7J2K/o+gOxotF1m1Cx5dfVUE0GbWN5AXRY= 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= @@ -514,8 +514,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ= github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= @@ -797,8 +797,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= From 78d627973f4f13840fe2e29f67adec5dcb0e6cfd Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Fri, 13 Dec 2024 14:01:37 +0200 Subject: [PATCH 4/8] Revert index.md docs changes --- docs/index.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index af39271..380fec4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,14 +3,14 @@ page_title: "cloudconnexa Provider" subcategory: "" description: |- - + --- -# cloudconnexa Provider +# CloudConnexa Provider !> **WARNING:** This provider is experimental and support for it is on a best-effort basis. Additionally, the underlying API for CloudConnexa is on Beta, which means that future versions of the provider may introduce breaking changes. Should that happen, migration documentation and support will also be provided on a best-effort basis. -Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/api-guide/). +Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cloud-docs/developer/index.html). ## Schema @@ -23,3 +23,14 @@ Use this provider to interact with the [CloudConnexa API](https://openvpn.net/cl - **client_id** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_ID` environment variable. - **client_secret** (String, Sensitive) If not provided, it will default to the value of the `CLOUDCONNEXA_CLIENT_SECRET` environment variable. + +### Credentials + +To authenticate with the CloudConnexa API, you'll need the client_id and client_secret. +These credentials can be found in the CloudConnexa Portal. +Go to the Settings page and click on the API tab. +From there, you can enable the API and generate new authentication credentials. +Additionally, you'll find Swagger documentation for the API in the same location. + +More documentation on the OpenVPN API can be found here: +[CloudConnexa API Documentation](https://openvpn.net/cloud-docs/developer/cloudconnexa-api.html) From 582747a4bf636a1104bad8c41ec938c3cf0e9ddd Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Fri, 13 Dec 2024 15:54:13 +0200 Subject: [PATCH 5/8] Fix lint issues --- cloudconnexa/resource_access_group.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudconnexa/resource_access_group.go b/cloudconnexa/resource_access_group.go index e5940dc..f5716f3 100644 --- a/cloudconnexa/resource_access_group.go +++ b/cloudconnexa/resource_access_group.go @@ -193,7 +193,7 @@ func setAccessGroupData(d *schema.ResourceData, ag *cloudconnexa.AccessGroupResp parent = source.Parent.Id } var children []interface{} - if source.Type == "USER_GROUP" || source.AllCovered == false { + if source.Type == "USER_GROUP" || !source.AllCovered { for _, child := range source.Children { children = append(children, child.Id) } @@ -215,7 +215,7 @@ func setAccessGroupData(d *schema.ResourceData, ag *cloudconnexa.AccessGroupResp parent = destination.Parent.Id } var children []interface{} - if destination.Type == "USER_GROUP" || destination.AllCovered == false { + if destination.Type == "USER_GROUP" || !destination.AllCovered { for _, child := range destination.Children { children = append(children, child.Id) } From 62aa2ed27d388612c827d124314b8ecfe48b2f28 Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Sun, 22 Dec 2024 15:54:34 +0200 Subject: [PATCH 6/8] Make children TypeSet instead of TypeList --- cloudconnexa/resource_access_group.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cloudconnexa/resource_access_group.go b/cloudconnexa/resource_access_group.go index f5716f3..10c505f 100644 --- a/cloudconnexa/resource_access_group.go +++ b/cloudconnexa/resource_access_group.go @@ -47,7 +47,7 @@ func resourceAccessGroup() *schema.Resource { }, "description": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "The Access group description.", }, "source": { @@ -88,7 +88,7 @@ func resourceSource() *schema.Resource { Description: "ID of the entity assigned to access group source.", }, "children": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Description: "ID of child entities assigned to access group source.", Elem: &schema.Schema{ @@ -119,7 +119,7 @@ func resourceDestination() *schema.Resource { Description: "ID of the entity assigned to access group destination.", }, "children": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Description: "ID of child entities assigned to access group destination.", Elem: &schema.Schema{ @@ -249,7 +249,7 @@ func resourceDataToAccessGroup(data *schema.ResourceData) *cloudconnexa.AccessGr AllCovered: convertedSource["all_covered"].(bool), Parent: convertedSource["parent"].(string), } - for _, child := range convertedSource["children"].([]interface{}) { + for _, child := range convertedSource["children"].(*schema.Set).List() { newSource.Children = append(newSource.Children, child.(string)) } request.Source = append(request.Source, newSource) @@ -264,7 +264,7 @@ func resourceDataToAccessGroup(data *schema.ResourceData) *cloudconnexa.AccessGr AllCovered: mappedDestination["all_covered"].(bool), Parent: mappedDestination["parent"].(string), } - for _, child := range mappedDestination["children"].([]interface{}) { + for _, child := range mappedDestination["children"].(*schema.Set).List() { newDestination.Children = append(newDestination.Children, child.(string)) } request.Destination = append(request.Destination, newDestination) From b68964135b3fa6bc4a769c67fd4cbf5536fbc046 Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Tue, 24 Dec 2024 16:09:15 +0200 Subject: [PATCH 7/8] Remove ForceNew:true paramer from source and destination --- cloudconnexa/resource_access_group.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cloudconnexa/resource_access_group.go b/cloudconnexa/resource_access_group.go index 10c505f..165bf97 100644 --- a/cloudconnexa/resource_access_group.go +++ b/cloudconnexa/resource_access_group.go @@ -54,14 +54,12 @@ func resourceAccessGroup() *schema.Resource { Type: schema.TypeSet, MinItems: 1, Required: true, - ForceNew: true, Elem: resourceSource(), }, "destination": { Type: schema.TypeSet, MinItems: 1, Required: true, - ForceNew: true, Elem: resourceDestination(), }, }, From 394eae05c504caeb1d99921eead41d829f5ac8ad Mon Sep 17 00:00:00 2001 From: vladhanzha Date: Tue, 24 Dec 2024 16:27:35 +0200 Subject: [PATCH 8/8] Update docs --- docs/data-sources/access_group.md | 4 ++-- docs/resources/access_group.md | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/data-sources/access_group.md b/docs/data-sources/access_group.md index e9c4b75..2d12833 100644 --- a/docs/data-sources/access_group.md +++ b/docs/data-sources/access_group.md @@ -29,7 +29,7 @@ description: |- Read-Only: - `all_covered` (Boolean) -- `children` (List of String) +- `children` (Set of String) - `parent` (String) - `type` (String) @@ -40,6 +40,6 @@ Read-Only: Read-Only: - `all_covered` (Boolean) -- `children` (List of String) +- `children` (Set of String) - `parent` (String) - `type` (String) diff --git a/docs/resources/access_group.md b/docs/resources/access_group.md index b730d8f..485e6d0 100644 --- a/docs/resources/access_group.md +++ b/docs/resources/access_group.md @@ -17,11 +17,14 @@ Use `cloudconnexa_access_group` to create an Access group. ### Required -- `description` (String) The Access group description. - `destination` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--destination)) - `name` (String) The Access group name. - `source` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--source)) +### Optional + +- `description` (String) The Access group description. + ### Read-Only - `id` (String) The ID of this resource. @@ -36,7 +39,7 @@ Required: Optional: -- `children` (List of String) ID of child entities assigned to access group destination. +- `children` (Set of String) ID of child entities assigned to access group destination. - `parent` (String) ID of the entity assigned to access group destination. @@ -50,5 +53,5 @@ Required: Optional: -- `children` (List of String) ID of child entities assigned to access group source. +- `children` (Set of String) ID of child entities assigned to access group source. - `parent` (String) ID of the entity assigned to access group source.