Skip to content

Commit

Permalink
Introduce Archer resources and data sources
Browse files Browse the repository at this point in the history
  • Loading branch information
kayrus committed Jul 9, 2024
1 parent f1990ae commit 1eb9a69
Show file tree
Hide file tree
Showing 21 changed files with 2,308 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ additional services:
* Lyra for Automation management
* Billing for Billing management
* Andromeda for GSLB / GTM (Global Server Load Balancing / Global Traffic Management)
* Archer for Endpoint Services

The provider needs to be configured with the proper OpenStack credentials
before it can be used. For details see the OpenStack provider.
Expand Down
72 changes: 72 additions & 0 deletions ccloud/archer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ccloud

import (
"fmt"
"log"
"net/url"
"reflect"

"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/sapcc/archer/client"

"github.com/gophercloud/gophercloud"
osClient "github.com/gophercloud/utils/client"
)

type archer struct {
client.Archer
provider *gophercloud.ProviderClient
}

func newArcherV1(c *Config, eo gophercloud.EndpointOpts) (*archer, error) {
var err error
var endpoint string
var aurl *url.URL

if v, ok := c.EndpointOverrides["endpoint-services"]; ok {
if e, ok := v.(string); ok && e != "" {
endpoint = e
}
}

if endpoint == "" && !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("endpoint-services")
endpoint, err = c.OsClient.EndpointLocator(eo)
if err != nil {
return nil, err
}
}

if aurl, err = url.Parse(endpoint); err != nil {
return nil, fmt.Errorf("parsing the Archer URL failed: %s", err)
}

transport := httptransport.New(aurl.Host, aurl.EscapedPath(), []string{aurl.Scheme})

if v, ok := c.OsClient.HTTPClient.Transport.(*osClient.RoundTripper); ok && v.Logger != nil {
// enable JSON debug for Archer
transport.SetLogger(logger{"Archer"})
transport.Debug = true
}

operations := client.New(transport, strfmt.Default)

return &archer{*operations, c.OsClient}, nil
}

func (a *archer) authFunc() runtime.ClientAuthInfoWriterFunc {
return runtime.ClientAuthInfoWriterFunc(
func(req runtime.ClientRequest, reg strfmt.Registry) error {
err := req.SetHeaderParam("X-Auth-Token", a.provider.Token())
if err != nil {
log.Printf("[DEBUG] Kubernikus auth func cannot set X-Auth-Token header value: %v", err)
}
err = req.SetHeaderParam("User-Agent", a.provider.UserAgent.Join())
if err != nil {
log.Printf("[DEBUG] Kubernikus auth func cannot set User-Agent header value: %v", err)
}
return nil
})
}
12 changes: 12 additions & 0 deletions ccloud/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ func (c *Config) andromedaV1Client(region string) (*client.Andromeda, error) {
})
}

func (c *Config) archerV1Client(region string) (*archer, error) {
if err := c.Authenticate(); err != nil {
return nil, err
}

return newArcherV1(c, gophercloud.EndpointOpts{
Type: "endpoint-services",
Region: c.DetermineRegion(region),
Availability: clientconfig.GetEndpointType(c.EndpointType),
})
}

func (c *Config) arcV1Client(region string) (*gophercloud.ServiceClient, error) {
return c.CommonServiceClientInit(clients.NewArcV1, region, "arc")
}
Expand Down
294 changes: 294 additions & 0 deletions ccloud/data_source_ccloud_endpoint_service_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
package ccloud

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/sapcc/archer/client/service"
"github.com/sapcc/archer/models"
)

func dataSourceCCloudEndpointServiceV1() *schema.Resource {
return &schema.Resource{
ReadContext: dataSourceCCloudEndpointServiceV1Read,

Schema: map[string]*schema.Schema{
"region": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"availability_zone": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"ip_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
},
Optional: true,
},
"all_ip_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
},
Computed: true,
},
"port": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"network_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"project_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"service_provider": {
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
"tenant", "cp",
}, false),
Optional: true,
Computed: true,
},
"proxy_protocol": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"require_approval": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"visibility": {
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
"private", "public",
}, false),
Optional: true,
Computed: true,
},
"tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"all_tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"host": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"status": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceCCloudEndpointServiceV1Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
config := meta.(*Config)
c, err := config.archerV1Client(GetRegion(d, config))
if err != nil {
return diag.Errorf("error creating Archer client: %s", err)
}
client := c.Service

// List the services
listOpts := &service.GetServiceParams{
Tags: expandToStringSlice(d.Get("tags").([]interface{})),
}
if v, ok := d.GetOk("project_id"); ok {
v := v.(string)
listOpts.ProjectID = &v
}

services, err := client.GetService(listOpts, c.authFunc())
if err != nil {
return diag.Errorf("error listing Archer services: %s", err)
}

if services.Payload == nil || len(services.Payload.Items) == 0 {
return diag.Errorf("Archer services not found")
}

filteredServices := make([]models.Service, 0, len(services.Payload.Items))

// define filter values
var name, description, availabilityZone, networkID, provider, visibility, host, status *string
var enabled, proxyProtocol, requireApproval *bool
var port *int32
var ipAddresses []string

if v, ok := d.GetOk("name"); ok {
name = ptr(v.(string))
}
if v, ok := d.GetOk("description"); ok {
description = ptr(v.(string))
}
if v, ok := d.GetOk("availability_zone"); ok {
availabilityZone = ptr(v.(string))
}
if v, ok := d.GetOk("network_id"); ok {
networkID = ptr(v.(string))
}
if v, ok := d.GetOk("service_provider"); ok {
provider = ptr(v.(string))
}
if v, ok := d.GetOk("visibility"); ok {
visibility = ptr(v.(string))
}
if v, ok := d.GetOk("host"); ok {
host = ptr(v.(string))
}
if v, ok := d.GetOk("status"); ok {
status = ptr(v.(string))
}

if v, ok := d.GetOk("enabled"); ok {
enabled = ptr(v.(bool))
}
if v, ok := d.GetOk("proxy_protocol"); ok {
proxyProtocol = ptr(v.(bool))
}
if v, ok := d.GetOk("require_approval"); ok {
requireApproval = ptr(v.(bool))
}

if v, ok := d.GetOk("port"); ok {
port = ptr(int32(v.(int)))
}

if v, ok := d.GetOk("ip_addresses"); ok {
ipAddresses = expandToStringSlice(v.([]interface{}))
}

ItemsLoop:
for _, svc := range services.Payload.Items {
if svc == nil {
continue
}
if name != nil && *name != svc.Name {
continue
}
if description != nil && *description != svc.Description {
continue
}
if availabilityZone != nil && *availabilityZone != ptrValue(svc.AvailabilityZone) {
continue
}
if networkID != nil && *networkID != string(ptrValue(svc.NetworkID)) {
continue
}
if provider != nil && *provider != ptrValue(svc.Provider) {
continue
}
if visibility != nil && *visibility != ptrValue(svc.Visibility) {
continue
}
if enabled != nil && *enabled != ptrValue(svc.Enabled) {
continue
}
if proxyProtocol != nil && *proxyProtocol != ptrValue(svc.ProxyProtocol) {
continue
}
if requireApproval != nil && *requireApproval != ptrValue(svc.RequireApproval) {
continue
}
if port != nil && *port != svc.Port {
continue
}
if host != nil && *host != ptrValue(svc.Host) {
continue
}
if status != nil && *status != svc.Status {
continue
}
svcIPAddresses := flattenToStrFmtIPv4Slice(svc.IPAddresses)
for _, ip := range ipAddresses {
if !sliceContains(svcIPAddresses, ip) {
continue ItemsLoop
}
}
filteredServices = append(filteredServices, *svc)
}

if len(filteredServices) == 0 {
return diag.Errorf("Archer services not found")
}

if len(filteredServices) > 1 {
return diag.Errorf("found more than one Archer services: %v", filteredServices)
}

svc := services.Payload.Items[0]

d.SetId(string(svc.ID))

d.Set("enabled", ptrValue(svc.Enabled))
d.Set("all_ip_addresses", flattenToStrFmtIPv4Slice(svc.IPAddresses))
d.Set("name", svc.Name)
d.Set("description", svc.Description)
d.Set("port", svc.Port)
d.Set("network_id", ptrValue(svc.NetworkID))
d.Set("project_id", svc.ProjectID)
d.Set("all_tags", svc.Tags)
d.Set("service_provider", ptrValue(svc.Provider))
d.Set("proxy_protocol", ptrValue(svc.ProxyProtocol))
d.Set("require_approval", ptrValue(svc.RequireApproval))
d.Set("visibility", ptrValue(svc.Visibility))

// computed
d.Set("host", ptrValue(svc.Host))
d.Set("status", svc.Status)
d.Set("created_at", svc.CreatedAt.String())
d.Set("updated_at", svc.UpdatedAt.String())

d.Set("region", GetRegion(d, config))

return nil
}
Loading

0 comments on commit 1eb9a69

Please sign in to comment.