From a5daedee04a18f98c1e1b23e50cb6e4c371879f6 Mon Sep 17 00:00:00 2001 From: Thomas Kooi Date: Fri, 12 Dec 2025 11:59:03 +0100 Subject: [PATCH] feat(tfs): implement TFS client and instance management - Added TFS client structure and initialization function. - Implemented methods for listing, retrieving, creating, updating, and deleting TFS instances. - Defined TFS instance structure and associated request types for creation and updates. - Introduced Labels and Annotations types for resource labeling and annotation. Signed-off-by: Thomas Kooi --- tfs/client.go | 14 ++++ tfs/constants.go | 7 ++ tfs/metadata.go | 14 ++++ tfs/tfs.go | 167 +++++++++++++++++++++++++++++++++++++++++++++ tfs/types.go | 123 +++++++++++++++++++++++++++++++++ thalassa/client.go | 17 +++-- 6 files changed, 338 insertions(+), 4 deletions(-) create mode 100644 tfs/client.go create mode 100644 tfs/constants.go create mode 100644 tfs/metadata.go create mode 100644 tfs/tfs.go create mode 100644 tfs/types.go diff --git a/tfs/client.go b/tfs/client.go new file mode 100644 index 0000000..63f6db5 --- /dev/null +++ b/tfs/client.go @@ -0,0 +1,14 @@ +package tfs + +import ( + "github.com/thalassa-cloud/client-go/pkg/client" +) + +type Client struct { + client.Client +} + +func New(c client.Client, opts ...client.Option) (*Client, error) { + c.WithOptions(opts...) + return &Client{c}, nil +} diff --git a/tfs/constants.go b/tfs/constants.go new file mode 100644 index 0000000..794527e --- /dev/null +++ b/tfs/constants.go @@ -0,0 +1,7 @@ +package tfs + +import "time" + +var ( + DefaultPollIntervalForWaiting = 1 * time.Second +) diff --git a/tfs/metadata.go b/tfs/metadata.go new file mode 100644 index 0000000..7a46a5f --- /dev/null +++ b/tfs/metadata.go @@ -0,0 +1,14 @@ +package tfs + +// Labels are key-value pairs that can be used to label resources. +// Keys and values must be RFC 1123 compliant. +// Label keys and values must be 1-63 characters long, and must conform to the following +// - contain at most 63 characters +// - contain only lowercase alphanumeric characters or '-' +// - start with an alphanumeric character +// - end with an alphanumeric character +type Labels map[string]string + +// Annotations are key-value pairs that can be used to annotate resources. +// Keys must be RFC 1123 compliant, but the values may contain any ascii characters. +type Annotations map[string]string diff --git a/tfs/tfs.go b/tfs/tfs.go new file mode 100644 index 0000000..192bf1a --- /dev/null +++ b/tfs/tfs.go @@ -0,0 +1,167 @@ +package tfs + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/thalassa-cloud/client-go/pkg/client" +) + +const ( + TfsEndpoint = "/v1/tfs" +) + +// ListTfsInstances lists all TFS instances for a given organisation. +func (c *Client) ListTfsInstances(ctx context.Context, request *ListTfsInstancesRequest) ([]TfsInstance, error) { + tfsInstances := []TfsInstance{} + req := c.R().SetResult(&tfsInstances) + if request != nil { + for _, filter := range request.Filters { + for k, v := range filter.ToParams() { + req.SetQueryParam(k, v) + } + } + } + + resp, err := c.Do(ctx, req, client.GET, TfsEndpoint) + if err != nil { + return nil, err + } + + if err := c.Check(resp); err != nil { + return tfsInstances, err + } + return tfsInstances, nil +} + +// GetTfsInstance retrieves a specific TFS instance by its identity. +func (c *Client) GetTfsInstance(ctx context.Context, identity string) (*TfsInstance, error) { + var tfsInstance *TfsInstance + req := c.R().SetResult(&tfsInstance) + resp, err := c.Do(ctx, req, client.GET, fmt.Sprintf("%s/%s", TfsEndpoint, identity)) + if err != nil { + return nil, err + } + if err := c.Check(resp); err != nil { + return tfsInstance, err + } + return tfsInstance, nil +} + +// CreateTfsInstance creates a new TFS instance. +func (c *Client) CreateTfsInstance(ctx context.Context, create CreateTfsInstanceRequest) (*TfsInstance, error) { + var tfsInstance *TfsInstance + req := c.R(). + SetBody(create).SetResult(&tfsInstance) + + resp, err := c.Do(ctx, req, client.POST, TfsEndpoint) + if err != nil { + return nil, err + } + if err := c.Check(resp); err != nil { + return tfsInstance, err + } + return tfsInstance, nil +} + +// UpdateTfsInstance updates an existing TFS instance. +func (c *Client) UpdateTfsInstance(ctx context.Context, identity string, update UpdateTfsInstanceRequest) (*TfsInstance, error) { + var tfsInstance *TfsInstance + req := c.R(). + SetBody(update).SetResult(&tfsInstance) + + resp, err := c.Do(ctx, req, client.PUT, fmt.Sprintf("%s/%s", TfsEndpoint, identity)) + if err != nil { + return nil, err + } + if err := c.Check(resp); err != nil { + return tfsInstance, err + } + return tfsInstance, nil +} + +// DeleteTfsInstance deletes a specific TFS instance by its identity. +func (c *Client) DeleteTfsInstance(ctx context.Context, identity string) error { + req := c.R() + + resp, err := c.Do(ctx, req, client.DELETE, fmt.Sprintf("%s/%s", TfsEndpoint, identity)) + if err != nil { + return err + } + if err := c.Check(resp); err != nil { + return err + } + return nil +} + +// WaitUntilTfsInstanceIsAvailable waits until a TFS instance is available. +// The user is expected to provide a timeout context. +func (c *Client) WaitUntilTfsInstanceIsAvailable(ctx context.Context, tfsIdentity string) error { + return c.WaitUntilTfsInstanceIsStatus(ctx, tfsIdentity, TfsStatusAvailable) +} + +// WaitUntilTfsInstanceIsStatus waits until a TFS instance is in a specific status. +// The user is expected to provide a timeout context. +func (c *Client) WaitUntilTfsInstanceIsStatus(ctx context.Context, tfsIdentity string, status TfsStatus) error { + tfsInstance, err := c.GetTfsInstance(ctx, tfsIdentity) + if err != nil { + return err + } + if strings.EqualFold(string(tfsInstance.Status), string(status)) { + return nil + } + // wait until the TFS instance is in the desired status + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(DefaultPollIntervalForWaiting): + } + + tfsInstance, err = c.GetTfsInstance(ctx, tfsIdentity) + if err != nil { + return err + } + if strings.EqualFold(string(tfsInstance.Status), string(status)) { + return nil + } + } +} + +// WaitUntilTfsInstanceIsDeleted waits until a TFS instance is deleted. +// The user is expected to provide a timeout context. +func (c *Client) WaitUntilTfsInstanceIsDeleted(ctx context.Context, tfsIdentity string) error { + tfsInstance, err := c.GetTfsInstance(ctx, tfsIdentity) + if err != nil { + if errors.Is(err, client.ErrNotFound) { + return nil + } + return err + } + if strings.EqualFold(string(tfsInstance.Status), string(TfsStatusDeleted)) { + return nil + } + if !strings.EqualFold(string(tfsInstance.Status), string(TfsStatusDeleting)) { + return fmt.Errorf("TFS instance %s is not being deleted (status: %s)", tfsIdentity, tfsInstance.Status) + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(DefaultPollIntervalForWaiting): + tfsInstance, err := c.GetTfsInstance(ctx, tfsIdentity) + if err != nil { + if errors.Is(err, client.ErrNotFound) { + return nil + } + return err + } + if strings.EqualFold(string(tfsInstance.Status), string(TfsStatusDeleted)) { + return nil + } + } + } +} diff --git a/tfs/types.go b/tfs/types.go new file mode 100644 index 0000000..93fb499 --- /dev/null +++ b/tfs/types.go @@ -0,0 +1,123 @@ +package tfs + +import ( + "time" + + "github.com/thalassa-cloud/client-go/filters" + "github.com/thalassa-cloud/client-go/iaas" + "github.com/thalassa-cloud/client-go/pkg/base" +) + +// Tfs represents a Thalassa Filesystem Service (TFS) instance +// TFS provides a high-availability, multi-availability zone Network File System (NFS) service +// for shared storage across your infrastructure. TFS supports NFSv4 and NFSv4.1 protocols. +type TfsInstance struct { + Identity string `json:"identity"` + Name string `json:"name"` + Slug string `json:"slug"` + Description *string `json:"description,omitempty"` + Labels Labels `json:"labels,omitempty"` + Annotations Annotations `json:"annotations,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` + ObjectVersion int `json:"objectVersion"` + + Region *iaas.Region `json:"region,omitempty"` + + Organisation *base.Organisation `json:"organisation,omitempty"` + + Vpc *iaas.Vpc `json:"vpc,omitempty"` + + // SubnetId is the subnet where the TFS instance is deployed + Subnet *iaas.Subnet `json:"subnet,omitempty"` + + // Endpoints is a list of endpoints that are associated with the TFS instance + Endpoints []iaas.Endpoint `json:"endpoints,omitempty"` + + // SecurityGroups is a list of security groups attached to the TFS instance + SecurityGroups []iaas.SecurityGroup `json:"securityGroups,omitempty"` + + // SizeGB is the size of the TFS instance in GB + SizeGB int `json:"size"` + + // DeleteProtection is a flag that prevents the TFS instance from being deleted + DeleteProtection bool `json:"deleteProtection"` + + // Status is the status of the TFS instance + Status TfsStatus `json:"status"` + + // LastStatusChangedAt is the time the status of the TFS instance was last changed + LastStatusChangedAt *time.Time `json:"lastStatusChangedAt,omitempty"` +} + +type TfsStatus string + +const ( + // TfsStatusCreating is the status of the TFS instance that is being created + TfsStatusCreating TfsStatus = "Creating" + TfsStatusProvisioning TfsStatus = "Provisioning" + TfsStatusAvailable TfsStatus = "Available" + TfsStatusDeleting TfsStatus = "Deleting" + TfsStatusDeleted TfsStatus = "Deleted" + TfsStatusError TfsStatus = "Error" + TfsStatusUnknown TfsStatus = "Unknown" +) + +type CreateTfsInstanceRequest struct { + // Name is the name of the TFS instance + Name string `json:"name"` + + // Description is a human-readable description of the object + Description string `json:"description,omitempty"` + // Annotations is a map of key-value pairs used for storing additional information + Annotations Annotations `json:"annotations,omitempty"` + + // Labels is a map of key-value pairs used for filtering and grouping objects + Labels Labels `json:"labels,omitempty"` + + // CloudRegionIdentity is the identity of the cloud region to create the TFS instance in + CloudRegionIdentity string `json:"cloudRegionIdentity"` + + // VpcIdentity is the identity of the VPC to create the TFS instance in + VpcIdentity string `json:"vpcIdentity"` + + // SubnetIdentity is the identity of the subnet to create the TFS instance in + SubnetIdentity string `json:"subnetIdentity"` + + // SizeGB is the size of the TFS instance in GB + SizeGB int `json:"size"` + + // SecurityGroupAttachments is a list of security group identities to attach to the TFS instance + SecurityGroupAttachments []string `json:"securityGroupAttachments,omitempty"` + + // DeleteProtection is a flag that prevents the TFS instance from being deleted + DeleteProtection bool `json:"deleteProtection"` +} + +type UpdateTfsInstanceRequest struct { + // Name is the name of the TFS instance + Name string `json:"name"` + + // Description is a human-readable description of the object + Description string `json:"description,omitempty"` + // Annotations is a map of key-value pairs used for storing additional information + Annotations Annotations `json:"annotations,omitempty"` + + // Labels is a map of key-value pairs used for filtering and grouping objects + Labels Labels `json:"labels,omitempty"` + + // SizeGB is the size of the TFS instance in GB + SizeGB int `json:"size"` + + // SecurityGroupAttachments is a list of security group identities to attach to the TFS instance + SecurityGroupAttachments []string `json:"securityGroupAttachments,omitempty"` + + // DeleteProtection is a flag that prevents the TFS instance from being deleted + DeleteProtection bool `json:"deleteProtection"` +} + +type ListTfsInstancesRequest struct { + // Filters is a list of filters to apply to the list of TFS instances + Filters []filters.Filter +} diff --git a/thalassa/client.go b/thalassa/client.go index 41db240..d8fcd8b 100644 --- a/thalassa/client.go +++ b/thalassa/client.go @@ -10,18 +10,19 @@ import ( "github.com/thalassa-cloud/client-go/objectstorage" "github.com/thalassa-cloud/client-go/pkg/client" "github.com/thalassa-cloud/client-go/quotas" + "github.com/thalassa-cloud/client-go/tfs" ) type Client interface { + Audit() *audit.Client + DbaaSAlphaV1() *dbaasalphav1.Client IaaS() *iaas.Client + IAM() *iam.Client Kubernetes() *kubernetes.Client Me() *me.Client - DbaaSAlphaV1() *dbaasalphav1.Client - IAM() *iam.Client ObjectStorage() *objectstorage.Client Quotas() *quotas.Client - Audit() *audit.Client - + Tfs() *tfs.Client // SetOrganisation sets the organisation for the client SetOrganisation(organisation string) } @@ -111,3 +112,11 @@ func (c *thalassaCloudClient) Audit() *audit.Client { } return auditClient } + +func (c *thalassaCloudClient) Tfs() *tfs.Client { + tfsClient, err := tfs.New(c.client) + if err != nil { + panic(err) + } + return tfsClient +}