From 262851f8baed573a33a7dbef12a1a8ae650ba3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Mon, 16 Oct 2023 18:04:02 +0300 Subject: [PATCH 1/7] feat: managed object storage support --- CHANGELOG.md | 1 + .../data-source.tf | 1 + .../resource.tf | 36 ++ .../resource.tf | 12 + go.mod | 2 +- go.sum | 4 +- .../managedobjectstorage/data_sources.go | 82 ++++ .../managed_object_storage.go | 360 ++++++++++++++++++ .../managedobjectstorage/user_access_key.go | 212 +++++++++++ ...oud_managed_object_storage_regions_test.go | 28 ++ upcloud/provider.go | 4 + ...rce_upcloud_managed_object_storage_test.go | 87 +++++ ...ource_managed_object_storage_regions_s1.tf | 1 + .../managed_object_storage_e.tf | 24 ++ .../managed_object_storage_s1.tf | 49 +++ .../managed_object_storage_s2.tf | 49 +++ 16 files changed, 949 insertions(+), 3 deletions(-) create mode 100644 examples/data-sources/upcloud_managed_object_storage_regions/data-source.tf create mode 100644 examples/resources/upcloud_managed_object_storage/resource.tf create mode 100644 examples/resources/upcloud_managed_object_storage_user_access_key/resource.tf create mode 100644 internal/service/managedobjectstorage/data_sources.go create mode 100644 internal/service/managedobjectstorage/managed_object_storage.go create mode 100644 internal/service/managedobjectstorage/user_access_key.go create mode 100644 upcloud/datasource_upcloud_managed_object_storage_regions_test.go create mode 100644 upcloud/resource_upcloud_managed_object_storage_test.go create mode 100644 upcloud/testdata/upcloud_managed_object_storage/data_source_managed_object_storage_regions_s1.tf create mode 100644 upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_e.tf create mode 100644 upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s1.tf create mode 100644 upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s2.tf diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a870992..cbd8b3ad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) - kubernetes: `control_plane_ip_filter` field to `upcloud_kubernetes_cluster` resource - dbaas: `upcloud_managed_database_mysql_sessions`, `upcloud_managed_database_postgresql_sessions` and `upcloud_managed_database_redis_sessions` data sources - network: `dhcp_routes` field to `ip_network` block in `upcloud_network` resource +- managed_object_storage: `managed_object_storage` & `managed_object_storage_user_access_key` resources and `managed_object_storage_regions` data source ### Changed - kubernetes: remove node group maximum value validation. The maximum number of nodes (in the cluster) is determined by the cluster plan and the validation is done on the API side. diff --git a/examples/data-sources/upcloud_managed_object_storage_regions/data-source.tf b/examples/data-sources/upcloud_managed_object_storage_regions/data-source.tf new file mode 100644 index 000000000..891dc3626 --- /dev/null +++ b/examples/data-sources/upcloud_managed_object_storage_regions/data-source.tf @@ -0,0 +1 @@ +data "upcloud_Managed_object_storage" "this" {} diff --git a/examples/resources/upcloud_managed_object_storage/resource.tf b/examples/resources/upcloud_managed_object_storage/resource.tf new file mode 100644 index 000000000..be1d04585 --- /dev/null +++ b/examples/resources/upcloud_managed_object_storage/resource.tf @@ -0,0 +1,36 @@ +// Create router for the network +resource "upcloud_router" "this" { + name = "object-storage-example-router" +} + +// Create network for the Managed Object Storage +resource "upcloud_network" "this" { + name = "object-storage-example-net" + zone = "fi-hel1" + + ip_network { + address = "172.16.2.0/24" + dhcp = true + family = "IPv4" + } + + router = upcloud_router.this.id +} + +resource "upcloud_managed_object_storage" "this" { + region = "europe-1" + configured_status = "started" + + network { + family = "IPv4" + name = "example-private-net" + type = "private" + uuid = upcloud_network.this.id + } + + users = ["example"] + + labels = { + managed-by = "terraform" + } +} diff --git a/examples/resources/upcloud_managed_object_storage_user_access_key/resource.tf b/examples/resources/upcloud_managed_object_storage_user_access_key/resource.tf new file mode 100644 index 000000000..c3a8f54f4 --- /dev/null +++ b/examples/resources/upcloud_managed_object_storage_user_access_key/resource.tf @@ -0,0 +1,12 @@ +resource "upcloud_managed_object_storage" "this" { + region = "europe-1" + configured_status = "started" + users = ["example"] +} + +resource "upcloud_managed_object_storage_user_access_key" "this" { + name = "accesskey" + enabled = true + username = "example" + service_uuid = upcloud_managed_object_storage.this.id +} diff --git a/go.mod b/go.mod index bd16e022d..65a6c220f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/UpCloudLtd/terraform-provider-upcloud go 1.20 require ( - github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.1 + github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-retryablehttp v0.6.8 github.com/hashicorp/go-uuid v1.0.3 diff --git a/go.sum b/go.sum index 1735a0336..6a45cd583 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.1 h1:5LWCVMt59kOKlNA6XafC0qIfDv5aUQ1BIGv40YCL2fM= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.1/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb h1:jZvvNXP25BbxQCGyGn1D2LasVACwui83Aco6Y9fhO40= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= diff --git a/internal/service/managedobjectstorage/data_sources.go b/internal/service/managedobjectstorage/data_sources.go new file mode 100644 index 000000000..d7f82440c --- /dev/null +++ b/internal/service/managedobjectstorage/data_sources.go @@ -0,0 +1,82 @@ +package managedobjectstorage + +import ( + "context" + "time" + + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/service" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceManagedObjectStorageRegions() *schema.Resource { + return &schema.Resource{ + Description: `Returns a list of available Managed Object Storage regions.`, + ReadContext: dataSourceHostsRead, + Schema: map[string]*schema.Schema{ + "regions": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "Name of the region.", + Computed: true, + }, + "primary_zone": { + Type: schema.TypeString, + Description: "Primary zone of the region.", + Computed: true, + }, + "zones": { + Type: schema.TypeSet, + Description: "List of zones in the region.", + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceHostsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { + svc := meta.(*service.Service) + + regions, err := svc.GetManagedObjectStorageRegions(ctx, &request.GetManagedObjectStorageRegionsRequest{}) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(time.Now().UTC().String()) + + err = d.Set("regions", buildManagedObjectStorageRegions(regions)) + if err != nil { + return diag.FromErr(err) + } + + return diags +} + +func buildManagedObjectStorageRegions(regions []upcloud.ManagedObjectStorageRegion) []map[string]interface{} { + maps := make([]map[string]interface{}, 0) + for _, region := range regions { + zones := make([]string, 0) + for _, zone := range region.Zones { + zones = append(zones, zone.Name) + } + maps = append(maps, map[string]interface{}{ + "name": region.Name, + "primary_zone": region.PrimaryZone, + "zones": zones, + }) + } + + return maps +} diff --git a/internal/service/managedobjectstorage/managed_object_storage.go b/internal/service/managedobjectstorage/managed_object_storage.go new file mode 100644 index 000000000..6bb29f820 --- /dev/null +++ b/internal/service/managedobjectstorage/managed_object_storage.go @@ -0,0 +1,360 @@ +package managedobjectstorage + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" + + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/service" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceManagedObjectStorage() *schema.Resource { + return &schema.Resource{ + Description: "This resource represents an UpCloud Managed Object Storage instance, which provides S3 compatible storage.", + CreateContext: resourceManagedObjectStorageCreate, + ReadContext: resourceManagedObjectStorageRead, + UpdateContext: resourceManagedObjectStorageUpdate, + DeleteContext: resourceManagedObjectStorageDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "configured_status": { + Description: "Service status managed by the end user.", + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice( + []string{ + string(upcloud.ManagedObjectStorageConfiguredStatusStarted), + string(upcloud.ManagedObjectStorageConfiguredStatusStopped), + }, + false, + ), + }, + "created_at": { + Description: "Creation time.", + Computed: true, + Type: schema.TypeString, + }, + "endpoint": { + Description: "Endpoints for accessing the Managed Object Storage service.", + Computed: true, + Type: schema.TypeSet, + Elem: schemaEndpoint(), + }, + "labels": utils.LabelsSchema("network gateway"), + "network": { + Description: "Attached networks from where object storage can be used. Private networks must reside in object storage region.", + Optional: true, + Type: schema.TypeSet, + Elem: schemaNetwork(), + }, + "operational_state": { + Description: "Operational state of the Managed Object Storage service.", + Computed: true, + Type: schema.TypeString, + }, + "region": { + Description: "Region in which the service will be hosted, see `upcloud_object_storage2_regions` data source.", + Required: true, + Type: schema.TypeString, + }, + "updated_at": { + Description: "Creation time.", + Computed: true, + Type: schema.TypeString, + }, + "users": { + Description: "List of cloud Users allowed to use object storage. See `upcloud_managed_object_storage_user_access_key` for managing access keys.", + Optional: true, + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func schemaEndpoint() *schema.Resource { + return &schema.Resource{ + Description: "OpenSearch index", + Schema: map[string]*schema.Schema{ + "domain_name": { + Description: "Domain name of the endpoint.", + Computed: true, + Type: schema.TypeString, + }, + "type": { + Description: "Type of the endpoint (`private` / `public`).", + Computed: true, + Type: schema.TypeString, + }, + }, + } +} + +func schemaNetwork() *schema.Resource { + return &schema.Resource{ + Description: "Managed Object Storage network", + Schema: map[string]*schema.Schema{ + "family": { + Description: "Network family. IPv6 currently not supported.", + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice( + []string{ + upcloud.IPAddressFamilyIPv4, + // IPv6 currently not supported + }, + false, + ), + }, + "name": { + Description: "Network name. Must be unique within the service.", + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), ""), + ), + }, + "type": { + Description: "Network type.", + Required: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice( + []string{ + "private", + "public", + }, + false, + ), + }, + "uuid": { + Description: "Private network uuid. For public networks the field should be omitted.", + Optional: true, + Type: schema.TypeString, + }, + }, + } +} + +func resourceManagedObjectStorageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { + svc := meta.(*service.Service) + + req := &request.CreateManagedObjectStorageRequest{ + ConfiguredStatus: upcloud.ManagedObjectStorageConfiguredStatus(d.Get("configured_status").(string)), + Region: d.Get("region").(string), + } + + if v, ok := d.GetOk("labels"); ok { + req.Labels = utils.LabelsMapToSlice(v.(map[string]interface{})) + } + + networks, err := networksFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + req.Networks = networks + + userReqs := make([]request.ManagedObjectStorageUser, 0) + users, ok := d.GetOk("users") + if ok { + for _, user := range users.(*schema.Set).List() { + userReqs = append(req.Users, request.ManagedObjectStorageUser{ + Username: user.(string), + }) + } + } + req.Users = userReqs + + storage, err := svc.CreateManagedObjectStorage(ctx, req) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(storage.UUID) + + waitReq := &request.WaitForManagedObjectStorageOperationalStateRequest{ + DesiredState: upcloud.ManagedObjectStorageOperationalStateRunning, + Timeout: time.Minute * 20, + UUID: storage.UUID, + } + + if storage.ConfiguredStatus == upcloud.ManagedObjectStorageConfiguredStatusStopped { + waitReq.DesiredState = upcloud.ManagedObjectStorageOperationalStateStopped + } + + storage, err = svc.WaitForManagedObjectStorageOperationalState(ctx, waitReq) + if err != nil { + return diag.FromErr(err) + } + + return append(diags, setManagedObjectStorageData(d, storage)...) +} + +func resourceManagedObjectStorageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var err error + svc := meta.(*service.Service) + + storage, err := svc.GetManagedObjectStorage(ctx, &request.GetManagedObjectStorageRequest{UUID: d.Id()}) + if err != nil { + return diag.FromErr(err) + } + + return setManagedObjectStorageData(d, storage) +} + +func resourceManagedObjectStorageUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { + svc := meta.(*service.Service) + + req := &request.ModifyManagedObjectStorageRequest{ + UUID: d.Id(), + } + + if d.HasChange("configured_status") { + configuredStatus := upcloud.ManagedObjectStorageConfiguredStatus(d.Get("configured_status").(string)) + req.ConfiguredStatus = &configuredStatus + } + + if d.HasChange("labels") { + labels := utils.LabelsMapToSlice(d.Get("labels").(map[string]interface{})) + req.Labels = &labels + } + + if d.HasChange("network") { + networks, err := networksFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + req.Networks = &networks + } + + userReqs := make([]request.ManagedObjectStorageUser, 0) + if d.HasChange("users") { + users := d.Get("users") + for _, user := range users.(*schema.Set).List() { + r := request.ManagedObjectStorageUser{ + Username: user.(string), + } + userReqs = append(userReqs, r) + } + } + req.Users = &userReqs + storage, err := svc.ModifyManagedObjectStorage(ctx, req) + if err != nil { + return diag.FromErr(err) + } + + return setManagedObjectStorageData(d, storage) +} + +func resourceManagedObjectStorageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + svc := meta.(*service.Service) + err := svc.DeleteManagedObjectStorage(ctx, &request.DeleteManagedObjectStorageRequest{UUID: d.Id()}) + if err != nil { + return diag.FromErr(err) + } + + err = svc.WaitForManagedObjectStorageDeletion(ctx, &request.WaitForManagedObjectStorageDeletionRequest{ + Timeout: time.Minute * 20, + UUID: d.Id(), + }) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func networksFromResourceData(d *schema.ResourceData) ([]upcloud.ManagedObjectStorageNetwork, error) { + req := make([]upcloud.ManagedObjectStorageNetwork, 0) + if networks, ok := d.GetOk("network"); ok { + for i, network := range networks.(*schema.Set).List() { + n := network.(map[string]interface{}) + r := upcloud.ManagedObjectStorageNetwork{ + Name: n["name"].(string), + Type: n["type"].(string), + Family: n["family"].(string), + UUID: upcloud.StringPtr(n["uuid"].(string)), + } + if r.Type == "private" && *r.UUID == "" { + return req, fmt.Errorf("private network (#%d) UUID is required", i) + } + if r.Type == "public" && *r.UUID != "" { + return req, fmt.Errorf("setting UUID for a public network (#%d) is not supported", i) + } + req = append(req, r) + } + } + + return req, nil +} + +func setManagedObjectStorageData(d *schema.ResourceData, storage *upcloud.ManagedObjectStorage) (diags diag.Diagnostics) { + if err := d.Set("configured_status", storage.ConfiguredStatus); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("created_at", storage.CreatedAt.String()); err != nil { + return diag.FromErr(err) + } + + endpoints := make([]map[string]interface{}, 0) + for _, endpoint := range storage.Endpoints { + endpoints = append(endpoints, map[string]interface{}{ + "domain_name": endpoint.DomainName, + "type": endpoint.Type, + }) + } + if err := d.Set("endpoint", endpoints); err != nil { + return diag.FromErr(err) + } + + networks := make([]map[string]interface{}, 0) + for _, network := range storage.Networks { + networks = append(networks, map[string]interface{}{ + "family": network.Family, + "name": network.Name, + "type": network.Type, + "uuid": network.UUID, + }) + } + + if err := d.Set("network", networks); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("operational_state", storage.OperationalState); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("region", storage.Region); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("updated_at", storage.UpdatedAt.String()); err != nil { + return diag.FromErr(err) + } + + users := make([]interface{}, 0) + for _, user := range storage.Users { + users = append(users, user.Username) + } + if err := d.Set("users", users); err != nil { + return diag.FromErr(err) + } + + return diags +} diff --git a/internal/service/managedobjectstorage/user_access_key.go b/internal/service/managedobjectstorage/user_access_key.go new file mode 100644 index 000000000..422dcf023 --- /dev/null +++ b/internal/service/managedobjectstorage/user_access_key.go @@ -0,0 +1,212 @@ +package managedobjectstorage + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/service" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceManagedObjectStorageUserAccessKey() *schema.Resource { + return &schema.Resource{ + Description: "This resource represents an UpCloud Managed Object Storage user access key.", + CreateContext: resourceManagedObjectStorageUserAccessKeyCreate, + ReadContext: resourceManagedObjectStorageUserAccessKeyRead, + UpdateContext: resourceManagedObjectStorageUserAccessKeyUpdate, + DeleteContext: resourceManagedObjectStorageUserAccessKeyDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "access_key_id": { + Description: "Access key id.", + Computed: true, + Type: schema.TypeString, + }, + "created_at": { + Description: "Creation time.", + Computed: true, + Type: schema.TypeString, + }, + "enabled": { + Description: "Enabled or not.", + Required: true, + Type: schema.TypeBool, + }, + "last_used_at": { + Description: "Last used.", + Computed: true, + Type: schema.TypeString, + }, + "name": { + Description: "Access key name. Must be unique within the user.", + Required: true, + ForceNew: true, + Type: schema.TypeString, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), ""), + ), + }, + "secret_access_key": { + Description: "Secret access key.", + Computed: true, + Sensitive: true, + Type: schema.TypeString, + }, + "service_uuid": { + Description: "Managed Object Storage service UUID.", + Required: true, + ForceNew: true, + Type: schema.TypeString, + }, + "updated_at": { + Description: "Update time.", + Computed: true, + Type: schema.TypeString, + }, + "username": { + Description: "Username.", + Required: true, + ForceNew: true, + Type: schema.TypeString, + }, + }, + } +} + +func resourceManagedObjectStorageUserAccessKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { + svc := meta.(*service.Service) + + req := &request.CreateManagedObjectStorageUserAccessKeyRequest{ + Username: d.Get("username").(string), + ServiceUUID: d.Get("service_uuid").(string), + Name: d.Get("name").(string), + Enabled: d.Get("enabled").(bool), + } + + accessKey, err := svc.CreateManagedObjectStorageUserAccessKey(ctx, req) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(marshalID(req.ServiceUUID, req.Username, req.Name)) + + return setManagedObjectStorageUserAccessKeyData(d, accessKey) +} + +func resourceManagedObjectStorageUserAccessKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + svc := meta.(*service.Service) + + var serviceUUID, username, name string + if err := unmarshalID(d.Id(), &serviceUUID, &username, &name); err != nil { + return diag.FromErr(err) + } + + accessKey, err := svc.GetManagedObjectStorageUserAccessKey(ctx, &request.GetManagedObjectStorageUserAccessKeyRequest{ + ServiceUUID: serviceUUID, + Username: username, + Name: name, + }) + if err != nil { + return diag.FromErr(err) + } + + return setManagedObjectStorageUserAccessKeyData(d, accessKey) +} + +func resourceManagedObjectStorageUserAccessKeyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { + if !d.HasChange("enabled") { + return nil + } + + svc := meta.(*service.Service) + + var serviceUUID, username, name string + if err := unmarshalID(d.Id(), &serviceUUID, &username, &name); err != nil { + return diag.FromErr(err) + } + + req := &request.ModifyManagedObjectStorageUserAccessKeyRequest{ + Enabled: d.Get("enabled").(bool), + ServiceUUID: serviceUUID, + Username: username, + Name: name, + } + + accessKey, err := svc.ModifyManagedObjectStorageUserAccessKey(ctx, req) + if err != nil { + return diag.FromErr(err) + } + + return setManagedObjectStorageUserAccessKeyData(d, accessKey) +} + +func resourceManagedObjectStorageUserAccessKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + svc := meta.(*service.Service) + + var serviceUUID, username, name string + if err := unmarshalID(d.Id(), &serviceUUID, &username, &name); err != nil { + return diag.FromErr(err) + } + + req := &request.DeleteManagedObjectStorageUserAccessKeyRequest{ + ServiceUUID: serviceUUID, + Username: username, + Name: name, + } + + if err := svc.DeleteManagedObjectStorageUserAccessKey(ctx, req); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func setManagedObjectStorageUserAccessKeyData(d *schema.ResourceData, accessKey *upcloud.ManagedObjectStorageUserAccessKey) (diags diag.Diagnostics) { + if err := d.Set("access_key_id", accessKey.AccessKeyId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("created_at", accessKey.CreatedAt.String()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("enabled", accessKey.Enabled); err != nil { + return diag.FromErr(err) + } + if err := d.Set("last_used_at", accessKey.LastUsedAt.String()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("name", accessKey.Name); err != nil { + return diag.FromErr(err) + } + if err := d.Set("updated_at", accessKey.UpdatedAt.String()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("secret_access_key", accessKey.SecretAccessKey); err != nil { + return diag.FromErr(err) + } + + return diags +} + +func marshalID(components ...string) string { + return strings.Join(components, "/") +} + +func unmarshalID(id string, components ...*string) error { + parts := strings.Split(id, "/") + if len(parts) > len(components) { + return fmt.Errorf("not enough components (%d) to unmarshal id '%s'", len(components), id) + } + for i, c := range parts { + *components[i] = c + } + return nil +} diff --git a/upcloud/datasource_upcloud_managed_object_storage_regions_test.go b/upcloud/datasource_upcloud_managed_object_storage_regions_test.go new file mode 100644 index 000000000..728e752e6 --- /dev/null +++ b/upcloud/datasource_upcloud_managed_object_storage_regions_test.go @@ -0,0 +1,28 @@ +package upcloud + +import ( + "testing" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccDataSourceUpcloudManagedObjectStorageRegions(t *testing.T) { + testDataS1 := utils.ReadTestDataFile(t, "testdata/upcloud_managed_object_storage/data_source_managed_object_storage_regions_s1.tf") + + var providers []*schema.Provider + name := "data.upcloud_managed_object_storage_regions.this" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: testDataS1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(name, "regions.#"), + ), + }, + }, + }) +} diff --git a/upcloud/provider.go b/upcloud/provider.go index 231b6aa5d..d8008218e 100644 --- a/upcloud/provider.go +++ b/upcloud/provider.go @@ -20,6 +20,7 @@ import ( "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/ip" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/kubernetes" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/loadbalancer" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/managedobjectstorage" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/network" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/objectstorage" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/service/router" @@ -85,6 +86,8 @@ func Provider() *schema.Provider { "upcloud_managed_database_opensearch": database.ResourceOpenSearch(), "upcloud_managed_database_user": database.ResourceUser(), "upcloud_managed_database_logical_database": database.ResourceLogicalDatabase(), + "upcloud_managed_object_storage": managedobjectstorage.ResourceManagedObjectStorage(), + "upcloud_managed_object_storage_user_access_key": managedobjectstorage.ResourceManagedObjectStorageUserAccessKey(), "upcloud_loadbalancer": loadbalancer.ResourceLoadBalancer(), "upcloud_loadbalancer_resolver": loadbalancer.ResourceResolver(), "upcloud_loadbalancer_backend": loadbalancer.ResourceBackend(), @@ -112,6 +115,7 @@ func Provider() *schema.Provider { "upcloud_managed_database_mysql_sessions": database.DataSourceSessionsMySQL(), "upcloud_managed_database_postgresql_sessions": database.DataSourceSessionsPostgreSQL(), "upcloud_managed_database_redis_sessions": database.DataSourceSessionsRedis(), + "upcloud_managed_object_storage_regions": managedobjectstorage.DataSourceManagedObjectStorageRegions(), }, ConfigureContextFunc: providerConfigure, diff --git a/upcloud/resource_upcloud_managed_object_storage_test.go b/upcloud/resource_upcloud_managed_object_storage_test.go new file mode 100644 index 000000000..e0f4544ad --- /dev/null +++ b/upcloud/resource_upcloud_managed_object_storage_test.go @@ -0,0 +1,87 @@ +package upcloud + +import ( + "regexp" + "strings" + "testing" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccUpcloudManagedObjectStorage(t *testing.T) { + testDataS1 := utils.ReadTestDataFile(t, "testdata/upcloud_managed_object_storage/managed_object_storage_s1.tf") + testDataS2 := utils.ReadTestDataFile(t, "testdata/upcloud_managed_object_storage/managed_object_storage_s2.tf") + + var providers []*schema.Provider + name := "upcloud_managed_object_storage.this" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: testDataS1, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "region", "europe-1"), + resource.TestCheckResourceAttr(name, "configured_status", "stopped"), + resource.TestCheckResourceAttr(name, "labels.%", "2"), + resource.TestCheckResourceAttr(name, "labels.test", "objsto2-tf"), + resource.TestCheckResourceAttr(name, "network.#", "1"), + ), + }, + { + Config: testDataS2, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "configured_status", "started"), + resource.TestCheckResourceAttr(name, "labels.owned-by", "team-devex"), + ), + }, + }, + }) +} + +func TestAccUpcloudManagedObjectStorage_LabelsValidation(t *testing.T) { + testDataE := utils.ReadTestDataFile(t, "testdata/upcloud_managed_object_storage/managed_object_storage_e.tf") + + labelsPlaceholder := `TEST_KEY = "TEST_VALUE"` + stepsData := []struct { + labels string + errorRe *regexp.Regexp + }{ + { + labels: `t = "too-short-key"`, + errorRe: regexp.MustCompile(`Map key lengths should be in the range \(2 - 32\)`), + }, + { + labels: `test-validation-fails-if-label-name-too-long = ""`, + errorRe: regexp.MustCompile(`Map key lengths should be in the range \(2 - 32\)`), + }, + { + labels: `test-validation-fails-åäö = "invalid-characters-in-key"`, + errorRe: regexp.MustCompile(`Map key expected to match regular expression`), + }, + { + labels: `test-validation-fails = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam egestas dolor vitae erat egestas, vel malesuada nisi ullamcorper. Aenean suscipit turpis quam, ut interdum lorem varius dignissim. Morbi eu erat bibendum, tincidunt turpis id, porta enim. Pellentesque..."`, + errorRe: regexp.MustCompile(`Map value lengths should be in the range \(0 - 255\)`), + }, + } + var steps []resource.TestStep + for _, step := range stepsData { + steps = append(steps, resource.TestStep{ + Config: strings.Replace(testDataE, labelsPlaceholder, step.labels, 1), + ExpectError: step.errorRe, + PlanOnly: true, + }) + } + + var providers []*schema.Provider + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: steps, + }) +} diff --git a/upcloud/testdata/upcloud_managed_object_storage/data_source_managed_object_storage_regions_s1.tf b/upcloud/testdata/upcloud_managed_object_storage/data_source_managed_object_storage_regions_s1.tf new file mode 100644 index 000000000..9558cd3cd --- /dev/null +++ b/upcloud/testdata/upcloud_managed_object_storage/data_source_managed_object_storage_regions_s1.tf @@ -0,0 +1 @@ +data "upcloud_managed_object_storage_regions" "this" {} diff --git a/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_e.tf b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_e.tf new file mode 100644 index 000000000..1583e9fc8 --- /dev/null +++ b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_e.tf @@ -0,0 +1,24 @@ +variable "prefix" { + default = "tf-acc-test-objsto2-" + type = string +} + +variable "zone" { + default = "fi-hel1" + type = string +} + +variable "region" { + default = "europe-1" + type = string +} + +resource "upcloud_managed_object_storage" "this" { + region = var.region + configured_status = "started" + + labels = { + // This is replaced during tests to test labels validation + TEST_KEY = "TEST_VALUE" + } +} diff --git a/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s1.tf b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s1.tf new file mode 100644 index 000000000..e51e906e2 --- /dev/null +++ b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s1.tf @@ -0,0 +1,49 @@ +variable "prefix" { + default = "tf-acc-test-objsto2-" + type = string +} + +variable "zone" { + default = "fi-hel1" + type = string +} + +variable "region" { + default = "europe-1" + type = string +} + +resource "upcloud_router" "this" { + name = "${var.prefix}router" +} + +resource "upcloud_network" "this" { + name = "${var.prefix}net" + zone = "fi-hel1" + + ip_network { + address = "172.18.1.0/24" + dhcp = false + family = "IPv4" + } + + router = upcloud_router.this.id +} + +resource "upcloud_managed_object_storage" "this" { + region = var.region + + configured_status = "stopped" + + network { + family = "IPv4" + name = "${var.prefix}net" + type = "private" + uuid = upcloud_network.this.id + } + + labels = { + test = "objsto2-tf" + owned-by = "team-services" + } +} diff --git a/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s2.tf b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s2.tf new file mode 100644 index 000000000..b5f64a31f --- /dev/null +++ b/upcloud/testdata/upcloud_managed_object_storage/managed_object_storage_s2.tf @@ -0,0 +1,49 @@ +variable "prefix" { + default = "tf-acc-test-objsto2-" + type = string +} + +variable "zone" { + default = "fi-hel1" + type = string +} + +variable "region" { + default = "europe-1" + type = string +} + +resource "upcloud_router" "this" { + name = "${var.prefix}router" +} + +resource "upcloud_network" "this" { + name = "${var.prefix}net" + zone = "fi-hel1" + + ip_network { + address = "172.18.1.0/24" + dhcp = false + family = "IPv4" + } + + router = upcloud_router.this.id +} + +resource "upcloud_managed_object_storage" "this" { + region = var.region + + configured_status = "started" + + network { + family = "IPv4" + name = "${var.prefix}net" + type = "private" + uuid = upcloud_network.this.id + } + + labels = { + test = "objsto2-tf" + owned-by = "team-devex" + } +} From 913432c6665ef634456a62aaaab7c5099f9fb876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Wed, 18 Oct 2023 11:43:32 +0300 Subject: [PATCH 2/7] chore(go.mod): update to upcloud-go-api v6.8.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 65a6c220f..c206bd85d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/UpCloudLtd/terraform-provider-upcloud go 1.20 require ( - github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb + github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-retryablehttp v0.6.8 github.com/hashicorp/go-uuid v1.0.3 diff --git a/go.sum b/go.sum index 6a45cd583..c25656e86 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb h1:jZvvNXP25BbxQCGyGn1D2LasVACwui83Aco6Y9fhO40= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.7.2-0.20231010080325-a30d7e17d1eb/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0 h1:Xfg4BfarI2KqKprsBOS4+XBQGkcgcsoQ3CHXdrRAo9Q= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= From 68804382a38ac77f619467fa462fe1dbd0ea8a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Wed, 18 Oct 2023 11:44:59 +0300 Subject: [PATCH 3/7] fix(user_access_key): only set secret_access_key if not empty --- internal/service/managedobjectstorage/user_access_key.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/service/managedobjectstorage/user_access_key.go b/internal/service/managedobjectstorage/user_access_key.go index 422dcf023..3ab658b4d 100644 --- a/internal/service/managedobjectstorage/user_access_key.go +++ b/internal/service/managedobjectstorage/user_access_key.go @@ -189,8 +189,10 @@ func setManagedObjectStorageUserAccessKeyData(d *schema.ResourceData, accessKey if err := d.Set("updated_at", accessKey.UpdatedAt.String()); err != nil { return diag.FromErr(err) } - if err := d.Set("secret_access_key", accessKey.SecretAccessKey); err != nil { - return diag.FromErr(err) + if accessKey.SecretAccessKey != nil { + if err := d.Set("secret_access_key", accessKey.SecretAccessKey); err != nil { + return diag.FromErr(err) + } } return diags From 230275d757404e79133fa785286cdce42f672624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Wed, 18 Oct 2023 12:45:10 +0300 Subject: [PATCH 4/7] fix: editorial changes --- .../managedobjectstorage/managed_object_storage.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/service/managedobjectstorage/managed_object_storage.go b/internal/service/managedobjectstorage/managed_object_storage.go index 6bb29f820..67c873ac5 100644 --- a/internal/service/managedobjectstorage/managed_object_storage.go +++ b/internal/service/managedobjectstorage/managed_object_storage.go @@ -50,7 +50,7 @@ func ResourceManagedObjectStorage() *schema.Resource { Type: schema.TypeSet, Elem: schemaEndpoint(), }, - "labels": utils.LabelsSchema("network gateway"), + "labels": utils.LabelsSchema("managed object storage"), "network": { Description: "Attached networks from where object storage can be used. Private networks must reside in object storage region.", Optional: true, @@ -63,7 +63,7 @@ func ResourceManagedObjectStorage() *schema.Resource { Type: schema.TypeString, }, "region": { - Description: "Region in which the service will be hosted, see `upcloud_object_storage2_regions` data source.", + Description: "Region in which the service will be hosted, see `upcloud_managed_object_storage_regions` data source.", Required: true, Type: schema.TypeString, }, @@ -73,7 +73,7 @@ func ResourceManagedObjectStorage() *schema.Resource { Type: schema.TypeString, }, "users": { - Description: "List of cloud Users allowed to use object storage. See `upcloud_managed_object_storage_user_access_key` for managing access keys.", + Description: "List of UpCloud API users allowed to use object storage. Valid values include current account and it's sub-accounts. See `upcloud_managed_object_storage_user_access_key` for managing access keys.", Optional: true, Type: schema.TypeSet, Elem: &schema.Schema{ @@ -86,7 +86,7 @@ func ResourceManagedObjectStorage() *schema.Resource { func schemaEndpoint() *schema.Resource { return &schema.Resource{ - Description: "OpenSearch index", + Description: "Endpoint", Schema: map[string]*schema.Schema{ "domain_name": { Description: "Domain name of the endpoint.", @@ -104,7 +104,7 @@ func schemaEndpoint() *schema.Resource { func schemaNetwork() *schema.Resource { return &schema.Resource{ - Description: "Managed Object Storage network", + Description: "Network", Schema: map[string]*schema.Schema{ "family": { Description: "Network family. IPv6 currently not supported.", From f2caf4010087a1e466c6900002e5bc5dc7402ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Wed, 18 Oct 2023 12:53:53 +0300 Subject: [PATCH 5/7] fix(endpoint): add validation to type --- .../service/managedobjectstorage/managed_object_storage.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/service/managedobjectstorage/managed_object_storage.go b/internal/service/managedobjectstorage/managed_object_storage.go index 67c873ac5..1de0f22d9 100644 --- a/internal/service/managedobjectstorage/managed_object_storage.go +++ b/internal/service/managedobjectstorage/managed_object_storage.go @@ -97,6 +97,13 @@ func schemaEndpoint() *schema.Resource { Description: "Type of the endpoint (`private` / `public`).", Computed: true, Type: schema.TypeString, + ValidateFunc: validation.StringInSlice( + []string{ + "private", + "public", + }, + false, + ), }, }, } From a9c1217eb046947f7aa36a2dc58726ddbaf1cc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Thu, 19 Oct 2023 16:47:46 +0300 Subject: [PATCH 6/7] fix(endpoint): remove validation from computed field --- .../service/managedobjectstorage/managed_object_storage.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/service/managedobjectstorage/managed_object_storage.go b/internal/service/managedobjectstorage/managed_object_storage.go index 1de0f22d9..67c873ac5 100644 --- a/internal/service/managedobjectstorage/managed_object_storage.go +++ b/internal/service/managedobjectstorage/managed_object_storage.go @@ -97,13 +97,6 @@ func schemaEndpoint() *schema.Resource { Description: "Type of the endpoint (`private` / `public`).", Computed: true, Type: schema.TypeString, - ValidateFunc: validation.StringInSlice( - []string{ - "private", - "public", - }, - false, - ), }, }, } From 857148e7af955e2422106ebf5e0235840691fc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20V=C3=A4lim=C3=A4ki?= Date: Fri, 20 Oct 2023 10:58:16 +0300 Subject: [PATCH 7/7] chore(go.mod): update to upcloud-go-api v6.8.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c206bd85d..d959b3a00 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/UpCloudLtd/terraform-provider-upcloud go 1.20 require ( - github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0 + github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.1 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-retryablehttp v0.6.8 github.com/hashicorp/go-uuid v1.0.3 diff --git a/go.sum b/go.sum index c25656e86..0b13045ea 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0 h1:Xfg4BfarI2KqKprsBOS4+XBQGkcgcsoQ3CHXdrRAo9Q= -github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.0/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.1 h1:w/LgBDHRMSM9wLTsfqbN3ulqZIp0sUEUGH+g9LGLm0Q= +github.com/UpCloudLtd/upcloud-go-api/v6 v6.8.1/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=