Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/thalassa-cloud/cli/cmd/kubernetes"
"github.com/thalassa-cloud/cli/cmd/me"
"github.com/thalassa-cloud/cli/cmd/objectstorage"
"github.com/thalassa-cloud/cli/cmd/observability"
"github.com/thalassa-cloud/cli/cmd/oidc"
"github.com/thalassa-cloud/cli/cmd/projects"
"github.com/thalassa-cloud/cli/cmd/quotas"
Expand Down Expand Up @@ -86,6 +87,7 @@ func init() {
RootCmd.AddCommand(dbaas.DbaasCmd)
RootCmd.AddCommand(me.MeCmd)
RootCmd.AddCommand(iam.IamCmd)
RootCmd.AddCommand(observability.ObservabilityCmd)
RootCmd.AddCommand(projects.ProjectsCmd)
RootCmd.AddCommand(audit.AuditCmd)
RootCmd.AddCommand(registry.RegistryCmd)
Expand Down
20 changes: 20 additions & 0 deletions cmd/observability/observability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package observability

import (
"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/cmd/observability/workspaces"
)

// ObservabilityCmd manages observability resources.
var ObservabilityCmd = &cobra.Command{
Use: "observability",
Aliases: []string{"obs"},
Short: "Manage observability workspaces (metrics and logs)",
Long: `Manage Thalassa observability workspaces that provide Prometheus (Cortex) metrics
and Loki log ingestion/query endpoints for the current organisation/project scope.`,
}

func init() {
ObservabilityCmd.AddCommand(workspaces.WorkspacesCmd)
}
102 changes: 102 additions & 0 deletions cmd/observability/workspaces/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package workspaces

import (
"context"
"fmt"
"time"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
iaasutil "github.com/thalassa-cloud/cli/internal/iaas"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
"github.com/thalassa-cloud/client-go/iaas"
clientobs "github.com/thalassa-cloud/client-go/observability"
)

var (
createName string
createDescription string
createRegion string
createRetention int
createLabels []string
createAnnotations []string
createWait bool
createWaitTimeout time.Duration
)

var createCmd = &cobra.Command{
Use: "create",
Short: "Create an observability workspace",
Example: ` tcloud observability workspaces create --name prod-metrics --region nl-ams
tcloud observability workspaces create --name prod-metrics --region nl-ams --retention-days 30 --wait`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if createName == "" {
return fmt.Errorf("--name is required")
}

client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

req := clientobs.CreateObservabilityWorkspaceRequest{
Name: createName,
Description: createDescription,
Labels: shared.KeyValuePairsToMap(createLabels),
Annotations: shared.KeyValuePairsToMap(createAnnotations),
}
if cmd.Flags().Changed("retention-days") {
days := createRetention
req.RetentionDays = &days
}
if createRegion != "" {
regions, err := client.IaaS().ListRegions(cmd.Context(), &iaas.ListRegionsRequest{})
if err != nil {
return fmt.Errorf("failed to list regions: %w", err)
}
region, err := iaasutil.FindRegionByIdentitySlugOrNameWithError(regions, createRegion)
if err != nil {
return err
}
req.RegionIdentity = region.Identity
}

ws, err := client.Observability().CreateObservabilityWorkspace(cmd.Context(), req)
if err != nil {
return fmt.Errorf("failed to create observability workspace: %w", err)
}

if createWait {
waitCtx := cmd.Context()
var cancel context.CancelFunc
if createWaitTimeout > 0 {
waitCtx, cancel = context.WithTimeout(cmd.Context(), createWaitTimeout)
defer cancel()
}
ws, err = client.Observability().WaitUntilObservabilityWorkspaceReady(waitCtx, ws.Identity)
if err != nil {
return fmt.Errorf("workspace created but wait for ready failed: %w", err)
}
}

printWorkspaceDetails(ws)
return nil
},
}

func init() {
WorkspacesCmd.AddCommand(createCmd)
createCmd.Flags().StringVar(&createName, "name", "", "Workspace name (required)")
createCmd.Flags().StringVar(&createDescription, "description", "", "Workspace description")
createCmd.Flags().StringVar(&createRegion, "region", "", "Region identity, slug, or name")
createCmd.Flags().IntVar(&createRetention, "retention-days", 0, "Retention in days for metrics and logs (1-1095)")
createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)")
createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)")
createCmd.Flags().BoolVar(&createWait, "wait", false, "Wait until the workspace is ready")
createCmd.Flags().DurationVar(&createWaitTimeout, "wait-timeout", 10*time.Minute, "Timeout when waiting with --wait")
_ = createCmd.MarkFlagRequired("name")
_ = createCmd.RegisterFlagCompletionFunc("region", completion.CompleteRegion)
}
46 changes: 46 additions & 0 deletions cmd/observability/workspaces/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package workspaces

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
)

var deleteForce bool

var deleteCmd = &cobra.Command{
Use: "delete <workspace>",
Aliases: []string{"rm", "remove"},
Short: "Delete an observability workspace",
Args: cobra.ExactArgs(1),
ValidArgsFunction: completion.CompleteObservabilityWorkspaceID,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this observability workspace?\n Workspace: %s\n", args[0]))
if err != nil {
return err
}
if !ok {
return nil
}

if err := client.Observability().DeleteObservabilityWorkspace(cmd.Context(), args[0]); err != nil {
return fmt.Errorf("failed to delete observability workspace: %w", err)
}
fmt.Printf("Deleted observability workspace %s\n", args[0])
return nil
},
}

func init() {
WorkspacesCmd.AddCommand(deleteCmd)
deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete")
}
42 changes: 42 additions & 0 deletions cmd/observability/workspaces/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package workspaces

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/formattime"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
)

var getExactTime bool

var getCmd = &cobra.Command{
Use: "get <workspace>",
Aliases: []string{"view", "show", "describe"},
Short: "Show an observability workspace",
Args: cobra.ExactArgs(1),
ValidArgsFunction: completion.CompleteObservabilityWorkspaceID,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

ws, err := client.Observability().GetObservabilityWorkspace(cmd.Context(), args[0])
if err != nil {
return fmt.Errorf("failed to get observability workspace: %w", err)
}

printWorkspaceDetails(ws)
fmt.Printf("Created: %s\n", formattime.FormatTime(ws.CreatedAt.Local(), getExactTime))
fmt.Printf("Updated: %s\n", formattime.FormatTime(ws.UpdatedAt.Local(), getExactTime))
return nil
},
}

func init() {
WorkspacesCmd.AddCommand(getCmd)
getCmd.Flags().BoolVar(&getExactTime, "exact-time", false, "Show full timestamps instead of relative time")
}
56 changes: 56 additions & 0 deletions cmd/observability/workspaces/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package workspaces

import (
"fmt"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/table"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientobs "github.com/thalassa-cloud/client-go/observability"
)

var noHeader bool

var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List observability workspaces",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

workspaces, err := client.Observability().ListObservabilityWorkspaces(cmd.Context(), &clientobs.ListObservabilityWorkspacesRequest{})
if err != nil {
return fmt.Errorf("failed to list observability workspaces: %w", err)
}

body := make([][]string, 0, len(workspaces))
for _, ws := range workspaces {
body = append(body, []string{
ws.Identity,
ws.Name,
string(ws.Status),
regionName(ws),
retentionString(ws.RetentionDays),
boolYesNo(ws.PrometheusEnabled),
boolYesNo(ws.LokiEnabled),
})
}
if noHeader {
table.Print(nil, body)
} else {
table.Print([]string{"ID", "Name", "Status", "Region", "Retention", "Prometheus", "Loki"}, body)
}
return nil
},
}

func init() {
WorkspacesCmd.AddCommand(listCmd)
listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers")
}
104 changes: 104 additions & 0 deletions cmd/observability/workspaces/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package workspaces

import (
"context"
"fmt"
"time"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientobs "github.com/thalassa-cloud/client-go/observability"
)

var (
updateName string
updateDescription string
updateRetention int
updateLabels []string
updateAnnotations []string
updateWait bool
updateWaitTimeout time.Duration
)

var updateCmd = &cobra.Command{
Use: "update <workspace>",
Short: "Update an observability workspace (only set flags are changed)",
Args: cobra.ExactArgs(1),
ValidArgsFunction: completion.CompleteObservabilityWorkspaceID,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

current, err := client.Observability().GetObservabilityWorkspace(cmd.Context(), args[0])
if err != nil {
return fmt.Errorf("failed to get observability workspace: %w", err)
}

req := clientobs.UpdateObservabilityWorkspaceRequest{
Name: current.Name,
Description: current.Description,
Labels: current.Labels,
Annotations: current.Annotations,
RetentionDays: current.RetentionDays,
}
if cmd.Flags().Changed("name") {
req.Name = updateName
}
if cmd.Flags().Changed("description") {
req.Description = updateDescription
}
if cmd.Flags().Changed("labels") {
req.Labels = shared.KeyValuePairsToMap(updateLabels)
}
if cmd.Flags().Changed("annotations") {
req.Annotations = shared.KeyValuePairsToMap(updateAnnotations)
}
if cmd.Flags().Changed("retention-days") {
days := updateRetention
req.RetentionDays = &days
}
if req.Labels == nil {
req.Labels = map[string]string{}
}
if req.Annotations == nil {
req.Annotations = map[string]string{}
}

ws, err := client.Observability().UpdateObservabilityWorkspace(cmd.Context(), current.Identity, req)
if err != nil {
return fmt.Errorf("failed to update observability workspace: %w", err)
}

if updateWait {
waitCtx := cmd.Context()
var cancel context.CancelFunc
if updateWaitTimeout > 0 {
waitCtx, cancel = context.WithTimeout(cmd.Context(), updateWaitTimeout)
defer cancel()
}
ws, err = client.Observability().WaitUntilObservabilityWorkspaceReady(waitCtx, ws.Identity)
if err != nil {
return fmt.Errorf("workspace updated but wait for ready failed: %w", err)
}
}

printWorkspaceDetails(ws)
return nil
},
}

func init() {
WorkspacesCmd.AddCommand(updateCmd)
updateCmd.Flags().StringVar(&updateName, "name", "", "Workspace name")
updateCmd.Flags().StringVar(&updateDescription, "description", "", "Workspace description")
updateCmd.Flags().IntVar(&updateRetention, "retention-days", 0, "Retention in days for metrics and logs (1-1095)")
updateCmd.Flags().StringSliceVar(&updateLabels, "labels", nil, "Replace labels (key=value, repeatable)")
updateCmd.Flags().StringSliceVar(&updateAnnotations, "annotations", nil, "Replace annotations (key=value, repeatable)")
updateCmd.Flags().BoolVar(&updateWait, "wait", false, "Wait until the workspace is ready")
updateCmd.Flags().DurationVar(&updateWaitTimeout, "wait-timeout", 10*time.Minute, "Timeout when waiting with --wait")
}
Loading
Loading