Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
49e6afa
feat(completion): add completers for DNS, KMS, and networking resources
thojkooi Sep 10, 2026
ca0799b
feat(dns): add zones and records management commands
thojkooi Sep 10, 2026
585f051
feat(kms): add key management and crypto commands
thojkooi Sep 10, 2026
a2d2662
feat(secrets): add secrets manager commands
thojkooi Sep 10, 2026
21124ca
feat(networking): add reserved IP management
thojkooi Sep 10, 2026
17c82ca
feat(storage): add snapshot policy commands
thojkooi Sep 10, 2026
f684e14
feat(networking): add NAT gateway create and update
thojkooi Sep 10, 2026
66d8e9d
feat(networking): expand route tables with routes management
thojkooi Sep 10, 2026
011e08b
feat(networking): add security group update and batch rules
thojkooi Sep 10, 2026
8ba1a40
feat(projects): add create, view, update, and delete commands
thojkooi Sep 10, 2026
a731796
feat(kubernetes): add kubeconfig session list and delete
thojkooi Sep 10, 2026
adf7d87
chore(cli): mark dns, kms, secrets, and projects as beta
thojkooi Sep 10, 2026
046a7af
feat(kms): add file input and output for crypto commands
thojkooi Sep 10, 2026
f12ab26
feat(kms): complete --key and --key-version from the API
thojkooi Sep 10, 2026
25e60e5
feat(secrets): add interactive fzf browse navigation
thojkooi Sep 10, 2026
e9033ff
feat(secrets): complete --path and --version from the API
thojkooi Sep 10, 2026
2d108c5
fix(secrets): validate generate-bytes and tighten create UX
thojkooi Sep 10, 2026
305448a
chore(docs): generate docs
thojkooi Sep 10, 2026
b56a1a5
chore(docs): update README to remove development notice
thojkooi Sep 10, 2026
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
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Thalassa Cloud CLI (tcloud)
# Thalassa Cloud CLI (`tcloud`)

A command-line interface for managing your Thalassa Cloud Installation.
`tcloud` is the official CLI for the Thalassa Cloud Platform. Use it to authenticate against your organisation, select a project context, and manage platform resources from the terminal — including compute, networking, storage, Kubernetes, DBaaS, IAM, DNS, KMS, secrets, and more.

> This project is still in beta. Commands and UX may change while the project is in initial development.
Run `tcloud --help` for the full command list, or see the generated docs under [`docs/tcloud`](docs/tcloud).

## Installation

Expand Down
6 changes: 6 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ import (
"github.com/thalassa-cloud/cli/cmd/audit"
"github.com/thalassa-cloud/cli/cmd/context"
"github.com/thalassa-cloud/cli/cmd/dbaas"
"github.com/thalassa-cloud/cli/cmd/dns"
"github.com/thalassa-cloud/cli/cmd/iaas/compute"
"github.com/thalassa-cloud/cli/cmd/iaas/networking"
"github.com/thalassa-cloud/cli/cmd/iaas/regions"
"github.com/thalassa-cloud/cli/cmd/iaas/storage"
"github.com/thalassa-cloud/cli/cmd/iam"
"github.com/thalassa-cloud/cli/cmd/kms"
"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/oidc"
"github.com/thalassa-cloud/cli/cmd/projects"
"github.com/thalassa-cloud/cli/cmd/quotas"
"github.com/thalassa-cloud/cli/cmd/registry"
"github.com/thalassa-cloud/cli/cmd/secrets"
"github.com/thalassa-cloud/cli/cmd/version"
"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/config/contextstate"
Expand Down Expand Up @@ -71,6 +74,9 @@ func init() {
RootCmd.AddCommand(storage.StorageCmd)
RootCmd.AddCommand(compute.ComputeCmd)
RootCmd.AddCommand(objectstorage.ObjectStorageCmd)
RootCmd.AddCommand(dns.DnsCmd)
RootCmd.AddCommand(kms.KmsCmd)
RootCmd.AddCommand(secrets.SecretsCmd)

RootCmd.AddCommand(kubernetes.KubernetesCmd)
RootCmd.AddCommand(dbaas.DbaasCmd)
Expand Down
22 changes: 22 additions & 0 deletions cmd/dns/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dns

import (
"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/cmd/dns/records"
"github.com/thalassa-cloud/cli/cmd/dns/zones"
)

// DnsCmd manages DNS zones and records.
var DnsCmd = &cobra.Command{
Use: "dns",
Short: "Manage DNS zones and records (beta)",
Long: `Manage DNS zones and records within the Thalassa Cloud Platform.

Note: This command is in beta.`,
}

func init() {
DnsCmd.AddCommand(zones.ZonesCmd)
DnsCmd.AddCommand(records.RecordsCmd)
}
96 changes: 96 additions & 0 deletions cmd/dns/records/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package records

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/formattime"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/table"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientdns "github.com/thalassa-cloud/client-go/dns"
)

var (
createName string
createType string
createTTL int
createValues []string
)

var createCmd = &cobra.Command{
Use: "create",
Short: "Create a DNS record",
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)
}

recordType := clientdns.DnsRecordType(strings.ToUpper(createType))
switch recordType {
case clientdns.DnsRecordTypeTXT,
clientdns.DnsRecordTypeA,
clientdns.DnsRecordTypeCNAME,
clientdns.DnsRecordTypeCAA,
clientdns.DnsRecordTypeAAAA,
clientdns.DnsRecordTypeMX,
clientdns.DnsRecordTypeNS,
clientdns.DnsRecordTypeSRV:
default:
return fmt.Errorf("unsupported record type %q (supported: TXT, A, CNAME, CAA, AAAA, MX, NS, SRV)", createType)
}

if len(createValues) == 0 {
return fmt.Errorf("--value is required")
}

record, err := client.DNS().CreateRecord(cmd.Context(), zoneIdentity, clientdns.CreateDnsRecordRequest{
Name: createName,
Type: recordType,
TTL: createTTL,
Values: createValues,
})
if err != nil {
return fmt.Errorf("failed to create DNS record: %w", err)
}

body := [][]string{{
record.Identity,
record.Name,
string(record.Type),
fmt.Sprintf("%d", record.TTL),
strings.Join(record.Values, ","),
formattime.FormatTime(record.CreatedAt.Local(), showExactTime),
}}
if noHeader {
table.Print(nil, body)
} else {
table.Print([]string{"ID", "Name", "Type", "TTL", "Values", "Created"}, body)
}
return nil
},
}

func init() {
RecordsCmd.AddCommand(createCmd)
createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers")
createCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time")
createCmd.Flags().StringVar(&zoneIdentity, "zone", "", "DNS zone identity")
createCmd.Flags().StringVar(&createName, "name", "", "Record name")
createCmd.Flags().StringVar(&createType, "type", "", "Record type (TXT, A, CNAME, CAA, AAAA, MX, NS, SRV)")
createCmd.Flags().IntVar(&createTTL, "ttl", 300, "Record TTL in seconds")
createCmd.Flags().StringSliceVar(&createValues, "value", nil, "Record value (repeatable)")
_ = createCmd.MarkFlagRequired("zone")
_ = createCmd.MarkFlagRequired("name")
_ = createCmd.MarkFlagRequired("type")
_ = createCmd.MarkFlagRequired("value")
_ = createCmd.RegisterFlagCompletionFunc("zone", completion.CompleteDnsZoneIdentity)
_ = createCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"TXT", "A", "CNAME", "CAA", "AAAA", "MX", "NS", "SRV"}, cobra.ShellCompDirectiveNoFileComp
})
}
57 changes: 57 additions & 0 deletions cmd/dns/records/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package records

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 <record>",
Aliases: []string{"rm", "remove"},
Short: "Delete a DNS record",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

recordIdentity := args[0]
record, err := client.DNS().GetRecord(cmd.Context(), zoneIdentity, recordIdentity)
if err != nil {
return fmt.Errorf("failed to get DNS record: %w", err)
}

ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf(
"Are you sure you want to delete this DNS record?\n ID: %s\n Name: %s\n Type: %s\n",
record.Identity, record.Name, record.Type,
))
if err != nil {
return err
}
if !ok {
return nil
}

if err := client.DNS().DeleteRecord(cmd.Context(), zoneIdentity, record.Identity); err != nil {
return fmt.Errorf("failed to delete DNS record: %w", err)
}
fmt.Printf("Deleted DNS record %s\n", record.Name)
return nil
},
}

func init() {
RecordsCmd.AddCommand(deleteCmd)
deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete")
deleteCmd.Flags().StringVar(&zoneIdentity, "zone", "", "DNS zone identity")
_ = deleteCmd.MarkFlagRequired("zone")
_ = deleteCmd.RegisterFlagCompletionFunc("zone", completion.CompleteDnsZoneIdentity)
}
61 changes: 61 additions & 0 deletions cmd/dns/records/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package records

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/formattime"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/table"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientdns "github.com/thalassa-cloud/client-go/dns"
)

var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List DNS records in a zone",
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)
}

records, err := client.DNS().ListRecords(cmd.Context(), zoneIdentity, &clientdns.ListRecordsRequest{})
if err != nil {
return fmt.Errorf("failed to list DNS records: %w", err)
}

body := make([][]string, 0, len(records))
for _, r := range records {
body = append(body, []string{
r.Identity,
r.Name,
string(r.Type),
fmt.Sprintf("%d", r.TTL),
strings.Join(r.Values, ","),
formattime.FormatTime(r.CreatedAt.Local(), showExactTime),
})
}

if noHeader {
table.Print(nil, body)
} else {
table.Print([]string{"ID", "Name", "Type", "TTL", "Values", "Created"}, body)
}
return nil
},
}

func init() {
RecordsCmd.AddCommand(listCmd)
listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers")
listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time")
listCmd.Flags().StringVar(&zoneIdentity, "zone", "", "DNS zone identity")
_ = listCmd.MarkFlagRequired("zone")
_ = listCmd.RegisterFlagCompletionFunc("zone", completion.CompleteDnsZoneIdentity)
}
18 changes: 18 additions & 0 deletions cmd/dns/records/records.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package records

import (
"github.com/spf13/cobra"
)

// RecordsCmd manages DNS records.
var RecordsCmd = &cobra.Command{
Use: "records",
Aliases: []string{"record", "rr"},
Short: "Manage DNS records",
}

var (
noHeader bool
showExactTime bool
zoneIdentity string
)
75 changes: 75 additions & 0 deletions cmd/dns/records/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package records

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"github.com/thalassa-cloud/cli/internal/completion"
"github.com/thalassa-cloud/cli/internal/formattime"
"github.com/thalassa-cloud/cli/internal/shared"
"github.com/thalassa-cloud/cli/internal/table"
"github.com/thalassa-cloud/cli/internal/thalassaclient"
clientdns "github.com/thalassa-cloud/client-go/dns"
)

var (
updateTTL int
updateValues []string
)

var updateCmd = &cobra.Command{
Use: "update <record>",
Short: "Update a DNS record",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client, err := thalassaclient.GetThalassaClient()
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}

if !cmd.Flags().Changed("value") {
return fmt.Errorf("--value is required")
}

req := clientdns.UpdateDnsRecordRequest{
Values: updateValues,
}
if cmd.Flags().Changed("ttl") {
req.TTL = updateTTL
}

record, err := client.DNS().UpdateRecord(cmd.Context(), zoneIdentity, args[0], req)
if err != nil {
return fmt.Errorf("failed to update DNS record: %w", err)
}

body := [][]string{{
record.Identity,
record.Name,
string(record.Type),
fmt.Sprintf("%d", record.TTL),
strings.Join(record.Values, ","),
formattime.FormatTime(record.CreatedAt.Local(), showExactTime),
}}
if noHeader {
table.Print(nil, body)
} else {
table.Print([]string{"ID", "Name", "Type", "TTL", "Values", "Created"}, body)
}
return nil
},
}

func init() {
RecordsCmd.AddCommand(updateCmd)
updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers")
updateCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time")
updateCmd.Flags().StringVar(&zoneIdentity, "zone", "", "DNS zone identity")
updateCmd.Flags().IntVar(&updateTTL, "ttl", 0, "Record TTL in seconds")
updateCmd.Flags().StringSliceVar(&updateValues, "value", nil, "Record value (repeatable)")
_ = updateCmd.MarkFlagRequired("zone")
_ = updateCmd.MarkFlagRequired("value")
_ = updateCmd.RegisterFlagCompletionFunc("zone", completion.CompleteDnsZoneIdentity)
}
Loading
Loading