forked from mirror/coolify-cli
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cad3ba0f7 | |||
| 1bc4625ef8 | |||
| d9494301f6 | |||
| 85cf8f6981 | |||
| c98a8330f8 | |||
| 49e5870c86 | |||
| 705fed3560 | |||
| 88d0b25537 | |||
| 3470b04235 | |||
| eb876e4bda | |||
| 7210b8df76 | |||
| 0546fd1932 | |||
| e2c1c86194 | |||
| 84e7e4921b | |||
| 0acb1fc512 | |||
| eaf9614bcc |
+1
-1
@@ -1,7 +1,7 @@
|
||||
coolify-cli
|
||||
coolify
|
||||
cli
|
||||
config.json
|
||||
.claude
|
||||
|
||||
# Generated documentation (can be regenerated)
|
||||
man/
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ The Coolify CLI is a command-line interface for managing Coolify instances, serv
|
||||
- `root.go` - Root command, global flags, initialization
|
||||
- `servers.go` - Server management commands
|
||||
- `deploy.go` - Deployment commands
|
||||
- `instances.go` - Instance configuration commands
|
||||
- `context.go` - Context (instance) configuration commands
|
||||
- `projects.go` - Project listing and inspection
|
||||
- etc.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ The codebase follows Cobra's command pattern with a root command and subcommands
|
||||
- Entry point: `main.go` calls `cmd.Execute()`
|
||||
- Root command: `cmd/root.go` - contains core utilities (HTTP client, authentication, version checking, config management)
|
||||
- Subcommands: Each command is in its own file in `cmd/`:
|
||||
- `instances.go` - manage Coolify instances (add, remove, list, set default/token)
|
||||
- `context.go` - manage Coolify context (add, remove, list, set default/token)
|
||||
- `servers.go` - list and get server information
|
||||
- `projects.go` - list projects with environments and applications
|
||||
- `resources.go` - list resources
|
||||
|
||||
@@ -10,48 +10,63 @@ It will install the CLI in `/usr/local/bin/coolify` and the configuration file i
|
||||
|
||||
> If you are a windows or mac user, please test the installation script and let us know if it works for you.
|
||||
|
||||
## Configuration
|
||||
## Getting Started
|
||||
1. Get a `<token>` from your Coolify dashboard (Cloud or self-hosted) at `/security/api-tokens`
|
||||
|
||||
### Cloud
|
||||
|
||||
2. Add the token with `coolify instances set token cloud <token>`
|
||||
2. Add the token with `coolify context set-token cloud <token>`
|
||||
|
||||
### Self-hosted
|
||||
|
||||
2. Add the token with `coolify instances add -d <name> <fqdn> <token>`
|
||||
|
||||
> Replace `<name>` with the name you want to give to the instance.
|
||||
2. Add the token with `coolify context add -d <context_name> <url> <token>`
|
||||
|
||||
> Replace `<context_name>` with the name you want to give to the context.
|
||||
>
|
||||
> Replace `<fqdn>` with the fully qualified domain name of your Coolify instance.
|
||||
> Replace `<url>` with the fully qualified domain name of your Coolify instance.
|
||||
|
||||
Now you can use the CLI with the token you just added.
|
||||
|
||||
## Change default instance
|
||||
You can change the default instance with `coolify instances set default <name>`
|
||||
## Change default context
|
||||
You can change the default context with `coolify context use <context_name>` or `coolify context set-default <context_name>`
|
||||
## Currently Supported Commands
|
||||
|
||||
### Update
|
||||
- `coolify update` - Update the CLI to the latest version
|
||||
|
||||
### Instances
|
||||
- `coolify instances list` - List all instances
|
||||
- `coolify instances add` - Create a new instance configuration
|
||||
- `coolify instances remove` - Remove an instance configuration
|
||||
- `coolify instances get` - Get an instance configuration
|
||||
- `coolify instances set <default>|<token>` - Set an instance as default or set a token for an instance
|
||||
- `coolify instances version` - Get the version of the Coolify API for an instance
|
||||
### Context Management
|
||||
- `coolify context list` - List all configured contexts
|
||||
- `coolify context add <context_name> <url> <token>` - Add a new context
|
||||
- `-d, --default` - Set as default context
|
||||
- `-f, --force` - Force overwrite if context already exists
|
||||
- `coolify context delete <context_name>` - Delete a context
|
||||
- `coolify context get <context_name>` - Get details of a specific context
|
||||
- `coolify context set-token <context_name> <token>` - Update the API token for a context
|
||||
- `coolify context set-default <context_name>` - Set a context as the default
|
||||
- `coolify context update <context_name>` - Update a context's properties
|
||||
- `--name <new_name>` - Change the context name
|
||||
- `--url <new_url>` - Change the context URL
|
||||
- `--token <new_token>` - Change the context token
|
||||
- `coolify context use <context_name>` - Switch to a different context (set as default)
|
||||
- `coolify context version` - Get the Coolify API version of the current context
|
||||
|
||||
### Servers
|
||||
- `coolify servers list` - List all servers
|
||||
- `coolify servers get <uuid>` - Get a server by UUID
|
||||
- `--resources` - Get the resources and their status of a server
|
||||
- `coolify servers add <name> <ip> <private_key_uuid>` - Add a new server
|
||||
<<<<<<< HEAD
|
||||
- `coolify servers add <server_name> <ip_address> <private_key_uuid>` - Add a new server
|
||||
- `--port <port>` - SSH port (default: 22)
|
||||
- `--user <user>` - SSH user (default: root)
|
||||
=======
|
||||
- `coolify servers add <name> <ip> <private_key_uuid>` - Add a new server
|
||||
- `-p, --port <port>` - SSH port (default: 22)
|
||||
- `-u, --user <user>` - SSH user (default: root)
|
||||
>>>>>>> origin/v4.x
|
||||
- `--validate` - Validate server immediately after adding
|
||||
- `coolify servers remove <uuid>` - Remove a server
|
||||
- `coolify servers validate <uuid>` - Validate a server connection
|
||||
- `coolify servers domains <uuid>` - Get server domains by UUID
|
||||
|
||||
### Projects
|
||||
- `coolify projects list` - List all projects
|
||||
@@ -66,8 +81,23 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
- `coolify app update <uuid>` - Update application configuration
|
||||
- `--name <name>` - Application name
|
||||
- `--description <description>` - Application description
|
||||
- `--git-branch <branch>` - Git branch
|
||||
- `--git-repository <url>` - Git repository URL
|
||||
- `--domains <domains>` - Domains (comma-separated)
|
||||
- `--build-command <cmd>` - Build command
|
||||
- `--start-command <cmd>` - Start command
|
||||
- `--install-command <cmd>` - Install command
|
||||
- `--base-directory <path>` - Base directory
|
||||
- `--publish-directory <path>` - Publish directory
|
||||
- `--dockerfile <content>` - Dockerfile content
|
||||
- `--docker-image <image>` - Docker image name
|
||||
- `--docker-tag <tag>` - Docker image tag
|
||||
- `--ports-exposes <ports>` - Exposed ports
|
||||
- `--ports-mappings <mappings>` - Port mappings
|
||||
- `--health-check-enabled` - Enable health check
|
||||
- `--health-check-path <path>` - Health check path
|
||||
- `coolify app delete <uuid>` - Delete an application
|
||||
- `--force` - Skip confirmation prompt
|
||||
- `-f, --force` - Skip confirmation prompt
|
||||
- `coolify app start <uuid>` - Start an application
|
||||
- `coolify app stop <uuid>` - Stop an application
|
||||
- `coolify app restart <uuid>` - Restart an application
|
||||
@@ -79,11 +109,10 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
- `coolify app env create <app_uuid>` - Create a new environment variable
|
||||
- `--key <key>` - Variable key (required)
|
||||
- `--value <value>` - Variable value (required)
|
||||
- `--is-preview` - Set variable for preview environments
|
||||
- `--is-build-time` - Set variable as build-time variable
|
||||
- `--is-literal` - Treat value as literal (no variable expansion)
|
||||
- `--is-multiline` - Allow multiline values
|
||||
- `--is-shown-once` - Show value only once (for secrets)
|
||||
- `--preview` - Available in preview deployments
|
||||
- `--build-time` - Available at build time
|
||||
- `--is-literal` - Treat value as literal (don't interpolate variables)
|
||||
- `--is-multiline` - Value is multiline
|
||||
- `coolify app env update <app_uuid> <env_uuid>` - Update an environment variable
|
||||
- `coolify app env delete <app_uuid> <env_uuid>` - Delete an environment variable
|
||||
- `coolify app env sync <app_uuid>` - Sync environment variables from a .env file
|
||||
@@ -96,18 +125,24 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
- Supported types: `postgresql`, `mysql`, `mariadb`, `mongodb`, `redis`, `keydb`, `clickhouse`, `dragonfly`
|
||||
- `--server-uuid <uuid>` - Server UUID (required)
|
||||
- `--project-uuid <uuid>` - Project UUID (required)
|
||||
- `--environment-name <name>` - Environment name (required unless using --environment-uuid)
|
||||
- `--environment-uuid <uuid>` - Environment UUID (required unless using --environment-name)
|
||||
- `--destination-uuid <uuid>` - Destination UUID if server has multiple destinations
|
||||
- `--name <name>` - Database name
|
||||
- `--description <description>` - Database description
|
||||
- `--image <image>` - Docker image
|
||||
- `--instant-deploy` - Deploy immediately after creation
|
||||
- `--is-public` - Make database publicly accessible
|
||||
- `--public-port <port>` - Public port number
|
||||
- `--limits-memory <size>` - Memory limit (e.g., '512m', '2g')
|
||||
- `--limits-cpus <cpus>` - CPU limit (e.g., '0.5', '2')
|
||||
- Database-specific flags (postgres-user, mysql-root-password, etc.)
|
||||
- `coolify database update <uuid>` - Update database configuration
|
||||
- `coolify database delete <uuid>` - Delete a database
|
||||
- `--delete-configurations` - Delete configurations (default: true)
|
||||
- `--delete-volumes` - Delete volumes (default: true)
|
||||
- `--docker-cleanup` - Run docker cleanup (default: true)
|
||||
- `--delete-connected-networks` - Delete connected networks (default: true)
|
||||
- `coolify database start <uuid>` - Start a database
|
||||
- `coolify database stop <uuid>` - Stop a database
|
||||
- `coolify database restart <uuid>` - Restart a database
|
||||
@@ -119,9 +154,16 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
- `--enabled` - Enable backup schedule
|
||||
- `--save-s3` - Save backups to S3
|
||||
- `--s3-storage-uuid <uuid>` - S3 storage UUID
|
||||
- `--retention-amount-locally <n>` - Number of backups to retain locally
|
||||
- `--retention-days-locally <n>` - Days to retain backups locally
|
||||
- `--timeout <seconds>` - Backup timeout
|
||||
- `--databases-to-backup <list>` - Comma-separated list of databases to backup
|
||||
- `--dump-all` - Dump all databases
|
||||
- `--retention-amount-local <n>` - Number of backups to retain locally
|
||||
- `--retention-days-local <n>` - Days to retain backups locally
|
||||
- `--retention-storage-local <size>` - Max storage for local backups (e.g., '1GB', '500MB')
|
||||
- `--retention-amount-s3 <n>` - Number of backups to retain in S3
|
||||
- `--retention-days-s3 <n>` - Days to retain backups in S3
|
||||
- `--retention-storage-s3 <size>` - Max storage for S3 backups (e.g., '1GB', '500MB')
|
||||
- `--timeout <seconds>` - Backup timeout in seconds
|
||||
- `--disable-local` - Disable local backup storage
|
||||
- `coolify database backup update <database_uuid> <backup_uuid>` - Update a backup configuration
|
||||
- `coolify database backup delete <database_uuid> <backup_uuid>` - Delete a backup configuration
|
||||
- `coolify database backup trigger <database_uuid> <backup_uuid>` - Trigger an immediate backup
|
||||
@@ -148,51 +190,54 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
|
||||
### Deployments
|
||||
- `coolify deploy uuid <uuid>` - Deploy a resource by UUID
|
||||
<<<<<<< HEAD
|
||||
- `--force` - Force deployment
|
||||
- `coolify deploy name <resource_name>` - Deploy a resource by name
|
||||
- `--force` - Force deployment
|
||||
=======
|
||||
- `-f, --force` - Force deployment
|
||||
- `coolify deploy name <name>` - Deploy a resource by name
|
||||
- `--force` - Force deployment
|
||||
- `-f, --force` - Force deployment
|
||||
>>>>>>> origin/v4.x
|
||||
- `coolify deploy batch <name1,name2,...>` - Deploy multiple resources at once
|
||||
- `--force` - Force all deployments
|
||||
- `-f, --force` - Force all deployments
|
||||
- `coolify deploy list` - List all deployments
|
||||
- `coolify deploy get <uuid>` - Get deployment details
|
||||
- `coolify deploy cancel <uuid>` - Cancel a deployment
|
||||
- `--force` - Skip confirmation prompt
|
||||
- `-f, --force` - Skip confirmation prompt
|
||||
|
||||
### GitHub Apps
|
||||
- `coolify github list` - List all GitHub App integrations
|
||||
- `coolify github get <app_uuid>` - Get GitHub App details
|
||||
- `coolify github create` - Create a new GitHub App integration
|
||||
- `--name <name>` - GitHub App name (required)
|
||||
- `--api-url <url>` - GitHub API URL (required)
|
||||
- `--html-url <url>` - GitHub HTML URL (required)
|
||||
- `--api-url <url>` - GitHub API URL (required, e.g., https://api.github.com)
|
||||
- `--html-url <url>` - GitHub HTML URL (required, e.g., https://github.com)
|
||||
- `--app-id <id>` - GitHub App ID (required)
|
||||
- `--installation-id <id>` - Installation ID (required)
|
||||
- `--client-id <id>` - OAuth Client ID (required)
|
||||
- `--client-secret <secret>` - OAuth Client Secret (required)
|
||||
- `--private-key-uuid <uuid>` - Private key UUID (required)
|
||||
- `--installation-id <id>` - GitHub Installation ID (required)
|
||||
- `--client-id <id>` - GitHub OAuth Client ID (required)
|
||||
- `--client-secret <secret>` - GitHub OAuth Client Secret (required)
|
||||
- `--private-key-uuid <uuid>` - UUID of existing private key (required)
|
||||
- `--organization <org>` - GitHub organization
|
||||
- `--custom-user <user>` - Custom SSH user
|
||||
- `--custom-port <port>` - Custom SSH port
|
||||
- `--webhook-secret <secret>` - Webhook secret
|
||||
- `--system-wide` - System-wide installation
|
||||
- `--custom-user <user>` - Custom user for SSH (default: git)
|
||||
- `--custom-port <port>` - Custom port for SSH (default: 22)
|
||||
- `--webhook-secret <secret>` - GitHub Webhook Secret
|
||||
- `--system-wide` - Is this app system-wide (cloud only)
|
||||
- `coolify github update <app_uuid>` - Update a GitHub App
|
||||
- `coolify github delete <app_uuid>` - Delete a GitHub App
|
||||
- `--force` - Skip confirmation prompt
|
||||
- `-f, --force` - Skip confirmation prompt
|
||||
- `coolify github repos <app_uuid>` - List repositories accessible by a GitHub App
|
||||
- `coolify github branches <app_uuid> <owner/repo>` - List branches for a repository
|
||||
|
||||
### Teams
|
||||
- `coolify team list` - List all teams
|
||||
- `coolify team get <id>` - Get team details
|
||||
- `coolify team get <team_id>` - Get team details
|
||||
- `coolify team current` - Get current team
|
||||
- `coolify team members list [team_id]` - List team members
|
||||
|
||||
### Domains
|
||||
- `coolify domains list` - List all domains
|
||||
|
||||
### Private Keys
|
||||
- `coolify privatekeys list` - List all private keys
|
||||
- `coolify privatekeys create <name> <private-key>` - Create a new private key
|
||||
- `coolify privatekeys create <key_name> <private-key>` - Create a new private key
|
||||
- Use `@filename` to read from file: `coolify privatekeys create mykey @~/.ssh/id_rsa`
|
||||
- `coolify privatekeys delete <uuid>` - Delete a private key
|
||||
|
||||
@@ -200,12 +245,16 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
|
||||
All commands support these global flags:
|
||||
|
||||
- `--instance <name>` - Use a specific instance profile instead of default (NEW)
|
||||
<<<<<<< HEAD
|
||||
- `--context <name>` - Use a specific context instead of default
|
||||
=======
|
||||
- `--instance <name>` - Use a specific instance profile instead of default
|
||||
>>>>>>> origin/v4.x
|
||||
- `--host <fqdn>` - Override the Coolify instance hostname
|
||||
- `--token <token>` - Override the authentication token
|
||||
- `--format <format>` - Output format: `table` (default), `json`, or `pretty`
|
||||
- `--show-sensitive` / `-s` - Show sensitive information (tokens, IPs, etc.)
|
||||
- `--force` / `-f` - Force operation (skip confirmations)
|
||||
- `-s, --show-sensitive` - Show sensitive information (tokens, IPs, etc.)
|
||||
- `-f, --force` - Force operation (skip confirmations)
|
||||
- `--debug` - Enable debug mode
|
||||
|
||||
## Examples
|
||||
@@ -213,20 +262,20 @@ All commands support these global flags:
|
||||
### Multi-Environment Workflows
|
||||
|
||||
```bash
|
||||
# Add multiple instances
|
||||
coolify instances add prod https://prod.coolify.io <prod-token>
|
||||
coolify instances add staging https://staging.coolify.io <staging-token>
|
||||
coolify instances add dev https://dev.coolify.io <dev-token>
|
||||
# Add multiple contexts
|
||||
coolify context add prod https://prod.coolify.io <prod-token>
|
||||
coolify context add staging https://staging.coolify.io <staging-token>
|
||||
coolify context add dev https://dev.coolify.io <dev-token>
|
||||
|
||||
# Set default
|
||||
coolify instances set default prod
|
||||
coolify context use prod
|
||||
|
||||
# Use different profiles
|
||||
coolify --instance=staging servers list
|
||||
coolify --instance=prod deploy name api
|
||||
coolify --instance=dev resources list
|
||||
# Use different contexts
|
||||
coolify --context=staging servers list
|
||||
coolify --context=prod deploy name api
|
||||
coolify --context=dev resources list
|
||||
|
||||
# Default profile (prod in this case)
|
||||
# Default context (prod in this case)
|
||||
coolify servers list
|
||||
```
|
||||
|
||||
@@ -307,8 +356,8 @@ coolify deploy name my-application
|
||||
# Deploy multiple apps at once
|
||||
coolify deploy batch api,worker,frontend
|
||||
|
||||
# Force deploy with specific profile
|
||||
coolify --instance=prod deploy batch api,worker --force
|
||||
# Force deploy with specific context
|
||||
coolify --context=prod deploy batch api,worker --force
|
||||
|
||||
# Traditional UUID deployment still works
|
||||
coolify deploy uuid abc123-def456-...
|
||||
@@ -365,7 +414,7 @@ coolify team members list
|
||||
|
||||
```bash
|
||||
# List servers in production
|
||||
coolify --instance=prod servers list
|
||||
coolify --context=prod servers list
|
||||
|
||||
# Add a server with validation
|
||||
coolify servers add myserver 192.168.1.100 <key-uuid> --validate
|
||||
@@ -394,7 +443,7 @@ coolify servers list --format=pretty
|
||||
This CLI follows a clean architecture with:
|
||||
- **Service Layer**: Business logic and API interactions
|
||||
- **Output Layer**: Consistent formatting across all commands
|
||||
- **Config Layer**: Multi-instance configuration management
|
||||
- **Config Layer**: Multi-context configuration management
|
||||
- **Models Layer**: Type-safe data structures
|
||||
|
||||
## Development
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/cmd/application/env"
|
||||
)
|
||||
|
||||
// NewAppCommand creates the app parent command
|
||||
func NewAppCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "app",
|
||||
Aliases: []string{"apps", "application", "applications"},
|
||||
Short: "Application related commands",
|
||||
Long: `Manage Coolify applications - list, get, create, update, delete, and control application lifecycle.`,
|
||||
}
|
||||
|
||||
// Add main subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewUpdateCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
cmd.AddCommand(NewStartCommand())
|
||||
cmd.AddCommand(NewStopCommand())
|
||||
cmd.AddCommand(NewRestartCommand())
|
||||
cmd.AddCommand(NewLogsCommand())
|
||||
|
||||
// Add env subcommand with its children
|
||||
envCmd := &cobra.Command{
|
||||
Use: "env",
|
||||
Aliases: []string{"envs", "environment"},
|
||||
Short: "Manage application environment variables",
|
||||
Long: `List and manage environment variables for applications. All commands require the application UUID first to establish context.`,
|
||||
}
|
||||
envCmd.AddCommand(env.NewListEnvCommand())
|
||||
envCmd.AddCommand(env.NewGetEnvCommand())
|
||||
envCmd.AddCommand(env.NewCreateEnvCommand())
|
||||
envCmd.AddCommand(env.NewUpdateEnvCommand())
|
||||
envCmd.AddCommand(env.NewDeleteEnvCommand())
|
||||
envCmd.AddCommand(env.NewSyncEnvCommand())
|
||||
cmd.AddCommand(envCmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <uuid>",
|
||||
Short: "Delete an application",
|
||||
Long: `Delete an application. This action cannot be undone.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete application %s? This cannot be undone. (yes/no): ", uuid)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
err = appSvc.Delete(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Application %s deleted successfully.\n", uuid)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
return cmd
|
||||
}
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateEnvCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <app_uuid>",
|
||||
Short: "Create an environment variable for an application",
|
||||
Long: `Create a new environment variable for a specific application. Use --key and --value flags to specify the variable.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
|
||||
if key == "" {
|
||||
return fmt.Errorf("--key is required")
|
||||
}
|
||||
if value == "" {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableCreateRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.CreateEnv(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' created successfully.\n", env.Key)
|
||||
fmt.Printf("UUID: %s\n", env.UUID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("key", "", "Environment variable key (required)")
|
||||
cmd.Flags().String("value", "", "Environment variable value (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
|
||||
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
return cmd
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteEnvCommand() *cobra.Command {
|
||||
deleteEnvCmd := &cobra.Command{
|
||||
Use: "delete <app_uuid> <env_uuid>",
|
||||
Short: "Delete an environment variable",
|
||||
Long: `Delete an environment variable from an application. First UUID is the application, second is the specific environment variable to delete.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
err = appSvc.DeleteEnv(ctx, appUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Environment variable deleted successfully.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteEnvCmd.Flags().Bool("force", false, "Skip confirmation prompt")
|
||||
return deleteEnvCmd
|
||||
}
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetEnvCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <app_uuid> <env_uuid_or_key>",
|
||||
Short: "Get environment variable details",
|
||||
Long: `Get detailed information about a specific environment variable by UUID or key name.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.GetEnv(ctx, appUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get environment variable: %w", err)
|
||||
}
|
||||
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
if !showSensitive {
|
||||
env.Value = "********"
|
||||
if env.RealValue != nil {
|
||||
masked := "********"
|
||||
env.RealValue = &masked
|
||||
}
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(env)
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListEnvCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list <app_uuid>",
|
||||
Short: "List all environment variables for an application",
|
||||
Long: `List all environment variables for a specific application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
envs, err := appSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list environment variables: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
if !showSensitive {
|
||||
for i := range envs {
|
||||
envs[i].Value = "********"
|
||||
if envs[i].RealValue != nil {
|
||||
masked := "********"
|
||||
envs[i].RealValue = &masked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(envs)
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+154
@@ -0,0 +1,154 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/parser"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSyncEnvCommand() *cobra.Command {
|
||||
syncEnvCmd := &cobra.Command{
|
||||
Use: "sync <app_uuid>",
|
||||
Short: "Sync environment variables from a .env file",
|
||||
Long: `Sync environment variables from a .env file. This command intelligently:
|
||||
- Updates existing environment variables with new values
|
||||
- Creates new environment variables that don't exist yet
|
||||
- Uses efficient bulk operations where possible
|
||||
|
||||
Example: coolify app env sync abc123 --file .env.production`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
filePath, _ := cmd.Flags().GetString("file")
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("--file is required")
|
||||
}
|
||||
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
|
||||
// Parse the .env file
|
||||
envVars, err := parser.ParseEnvFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse .env file: %w", err)
|
||||
}
|
||||
|
||||
if len(envVars) == 0 {
|
||||
fmt.Println("No environment variables found in file.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d environment variables in file. Syncing...\n", len(envVars))
|
||||
|
||||
// Fetch existing environment variables
|
||||
appSvc := service.NewApplicationService(client)
|
||||
existingEnvs, err := appSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list existing environment variables: %w", err)
|
||||
}
|
||||
|
||||
// Build a map of existing env vars by key
|
||||
existingMap := make(map[string]models.EnvironmentVariable)
|
||||
for _, env := range existingEnvs {
|
||||
existingMap[env.Key] = env
|
||||
}
|
||||
|
||||
// Separate into updates and creates
|
||||
var toUpdate []models.EnvironmentVariableCreateRequest
|
||||
var toCreate []models.EnvironmentVariableCreateRequest
|
||||
|
||||
for _, envVar := range envVars {
|
||||
req := models.EnvironmentVariableCreateRequest{
|
||||
Key: envVar.Key,
|
||||
Value: envVar.Value,
|
||||
}
|
||||
|
||||
// Apply flags if explicitly provided
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
|
||||
// Auto-detect multiline values
|
||||
if strings.Contains(envVar.Value, "\n") {
|
||||
multiline := true
|
||||
req.IsMultiline = &multiline
|
||||
}
|
||||
|
||||
if _, exists := existingMap[envVar.Key]; exists {
|
||||
toUpdate = append(toUpdate, req)
|
||||
} else {
|
||||
toCreate = append(toCreate, req)
|
||||
}
|
||||
}
|
||||
|
||||
updateCount := 0
|
||||
createCount := 0
|
||||
failCount := 0
|
||||
|
||||
// Perform bulk update if there are vars to update
|
||||
if len(toUpdate) > 0 {
|
||||
fmt.Printf("Updating %d existing variables...\n", len(toUpdate))
|
||||
bulkReq := &service.BulkUpdateEnvsRequest{
|
||||
Data: toUpdate,
|
||||
}
|
||||
_, err := appSvc.BulkUpdateEnvs(ctx, uuid, bulkReq)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Bulk update failed: %v\n", err)
|
||||
failCount += len(toUpdate)
|
||||
} else {
|
||||
updateCount = len(toUpdate)
|
||||
fmt.Printf(" ✓ Successfully updated %d variables\n", updateCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new variables one by one
|
||||
if len(toCreate) > 0 {
|
||||
fmt.Printf("Creating %d new variables...\n", len(toCreate))
|
||||
for _, req := range toCreate {
|
||||
_, err := appSvc.CreateEnv(ctx, uuid, &req)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Failed to create '%s': %v\n", req.Key, err)
|
||||
failCount++
|
||||
} else {
|
||||
fmt.Printf(" ✓ Created '%s'\n", req.Key)
|
||||
createCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nSync complete: %d updated, %d created, %d failed\n", updateCount, createCount, failCount)
|
||||
|
||||
if failCount > 0 {
|
||||
return fmt.Errorf("some environment variables failed to sync")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
syncEnvCmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
|
||||
syncEnvCmd.Flags().Bool("build-time", false, "Make all variables available at build time")
|
||||
syncEnvCmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
|
||||
syncEnvCmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
|
||||
return syncEnvCmd
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateEnvCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <app_uuid> <env_uuid>",
|
||||
Short: "Update an environment variable",
|
||||
Long: `Update an existing environment variable. First UUID is the application, second is the specific environment variable to update.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableUpdateRequest{
|
||||
UUID: envUUID,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("key") {
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
req.Key = &key
|
||||
}
|
||||
if cmd.Flags().Changed("value") {
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
req.Value = &value
|
||||
}
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
if req.Key == nil && req.Value == nil && req.IsBuildTime == nil && req.IsPreview == nil && req.IsLiteral == nil && req.IsMultiline == nil {
|
||||
return fmt.Errorf("at least one field must be provided to update")
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.UpdateEnv(ctx, appUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' updated successfully.\n", env.Key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("key", "", "New environment variable key")
|
||||
cmd.Flags().String("value", "", "New environment variable value")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat value as literal")
|
||||
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get application details by UUID",
|
||||
Long: `Retrieve detailed information about a specific application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
app, err := appSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get application: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all applications",
|
||||
Long: `List all applications in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
apps, err := appSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list applications: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full application structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(apps)
|
||||
}
|
||||
|
||||
// For table format, convert to simplified rows
|
||||
var rows []models.ApplicationListItem
|
||||
for _, app := range apps {
|
||||
rows = append(rows, models.ApplicationListItem{
|
||||
UUID: app.UUID,
|
||||
Name: app.Name,
|
||||
Description: app.Description,
|
||||
Status: app.Status,
|
||||
GitBranch: app.GitBranch,
|
||||
FQDN: app.FQDN,
|
||||
})
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewLogsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "logs <uuid>",
|
||||
Short: "Get application logs",
|
||||
Long: `Retrieve logs for an application. Use --follow to continuously stream new logs.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
lines, _ := cmd.Flags().GetInt("lines")
|
||||
follow, _ := cmd.Flags().GetBool("follow")
|
||||
appSvc := service.NewApplicationService(client)
|
||||
|
||||
if !follow {
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
fmt.Print(resp.Logs)
|
||||
return nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
lastLogs := ""
|
||||
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
fmt.Print(resp.Logs)
|
||||
lastLogs = resp.Logs
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sigChan:
|
||||
fmt.Println("\nStopping log follow...")
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if resp.Logs != lastLogs {
|
||||
if len(resp.Logs) > len(lastLogs) && strings.HasPrefix(resp.Logs, lastLogs) {
|
||||
fmt.Print(resp.Logs[len(lastLogs):])
|
||||
} else {
|
||||
fmt.Print(resp.Logs)
|
||||
}
|
||||
lastLogs = resp.Logs
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntP("lines", "n", 100, "Number of log lines to retrieve")
|
||||
cmd.Flags().BoolP("follow", "f", false, "Follow log output (like tail -f)")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewRestartCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "restart <uuid>",
|
||||
Short: "Restart an application",
|
||||
Long: `Restart a running application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Restart(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewStartCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "start <uuid>",
|
||||
Aliases: []string{"deploy"},
|
||||
Short: "Start an application",
|
||||
Long: `Start an application (initiates a deployment).`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
instantDeploy, _ := cmd.Flags().GetBool("instant-deploy")
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Start(ctx, uuid, force, instantDeploy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
if resp.DeploymentUUID != nil && *resp.DeploymentUUID != "" {
|
||||
fmt.Printf("Deployment UUID: %s\n", *resp.DeploymentUUID)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("force", false, "Force rebuild")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Instant deploy (skip queuing)")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewStopCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "stop <uuid>",
|
||||
Short: "Stop an application",
|
||||
Long: `Stop a running application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Stop(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <uuid>",
|
||||
Short: "Update application configuration",
|
||||
Long: `Update configuration for a specific application. Only specified fields will be updated.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := models.ApplicationUpdateRequest{}
|
||||
hasUpdates := false
|
||||
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
desc, _ := cmd.Flags().GetString("description")
|
||||
req.Description = &desc
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("git-branch") {
|
||||
branch, _ := cmd.Flags().GetString("git-branch")
|
||||
req.GitBranch = &branch
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("git-repository") {
|
||||
repo, _ := cmd.Flags().GetString("git-repository")
|
||||
req.GitRepository = &repo
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("domains") {
|
||||
domains, _ := cmd.Flags().GetString("domains")
|
||||
req.Domains = &domains
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("build-command") {
|
||||
buildCmd, _ := cmd.Flags().GetString("build-command")
|
||||
req.BuildCommand = &buildCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("start-command") {
|
||||
startCmd, _ := cmd.Flags().GetString("start-command")
|
||||
req.StartCommand = &startCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("install-command") {
|
||||
installCmd, _ := cmd.Flags().GetString("install-command")
|
||||
req.InstallCommand = &installCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("base-directory") {
|
||||
baseDir, _ := cmd.Flags().GetString("base-directory")
|
||||
req.BaseDirectory = &baseDir
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("publish-directory") {
|
||||
publishDir, _ := cmd.Flags().GetString("publish-directory")
|
||||
req.PublishDirectory = &publishDir
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("dockerfile") {
|
||||
dockerfile, _ := cmd.Flags().GetString("dockerfile")
|
||||
req.Dockerfile = &dockerfile
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("docker-image") {
|
||||
image, _ := cmd.Flags().GetString("docker-image")
|
||||
req.DockerRegistryImageName = &image
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("docker-tag") {
|
||||
tag, _ := cmd.Flags().GetString("docker-tag")
|
||||
req.DockerRegistryImageTag = &tag
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("ports-exposes") {
|
||||
ports, _ := cmd.Flags().GetString("ports-exposes")
|
||||
req.PortsExposes = &ports
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("ports-mappings") {
|
||||
ports, _ := cmd.Flags().GetString("ports-mappings")
|
||||
req.PortsMappings = &ports
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("health-check-enabled") {
|
||||
enabled, _ := cmd.Flags().GetBool("health-check-enabled")
|
||||
req.HealthCheckEnabled = &enabled
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("health-check-path") {
|
||||
path, _ := cmd.Flags().GetString("health-check-path")
|
||||
req.HealthCheckPath = &path
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
if !hasUpdates {
|
||||
return fmt.Errorf("no fields to update. Use --help to see available flags")
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
app, err := appSvc.Update(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update application: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("git-branch", "", "Git branch")
|
||||
cmd.Flags().String("git-repository", "", "Git repository URL")
|
||||
cmd.Flags().String("domains", "", "Domains (comma-separated)")
|
||||
cmd.Flags().String("build-command", "", "Build command")
|
||||
cmd.Flags().String("start-command", "", "Start command")
|
||||
cmd.Flags().String("install-command", "", "Install command")
|
||||
cmd.Flags().String("base-directory", "", "Base directory")
|
||||
cmd.Flags().String("publish-directory", "", "Publish directory")
|
||||
cmd.Flags().String("dockerfile", "", "Dockerfile content")
|
||||
cmd.Flags().String("docker-image", "", "Docker image name")
|
||||
cmd.Flags().String("docker-tag", "", "Docker image tag")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health check")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,910 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/parser"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var applicationsCmd = &cobra.Command{
|
||||
Use: "app",
|
||||
Aliases: []string{"apps", "application", "applications"},
|
||||
Short: "Application related commands",
|
||||
Long: `Manage Coolify applications - list, get, create, update, delete, and control application lifecycle.`,
|
||||
}
|
||||
|
||||
var listApplicationsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all applications",
|
||||
Long: `List all applications in your Coolify instance.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
apps, err := appSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list applications: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full application structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(apps)
|
||||
}
|
||||
|
||||
// For table format, convert to simplified rows
|
||||
var rows []models.ApplicationListItem
|
||||
for _, app := range apps {
|
||||
rows = append(rows, models.ApplicationListItem{
|
||||
UUID: app.UUID,
|
||||
Name: app.Name,
|
||||
Description: app.Description,
|
||||
Status: app.Status,
|
||||
GitBranch: app.GitBranch,
|
||||
FQDN: app.FQDN,
|
||||
})
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
|
||||
var getApplicationCmd = &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get application details by UUID",
|
||||
Long: `Retrieve detailed information about a specific application.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
app, err := appSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get application: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
var updateApplicationCmd = &cobra.Command{
|
||||
Use: "update <uuid>",
|
||||
Short: "Update application configuration",
|
||||
Long: `Update configuration for a specific application. Only specified fields will be updated.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Build update request from flags
|
||||
req := models.ApplicationUpdateRequest{}
|
||||
hasUpdates := false
|
||||
|
||||
// Basic configuration
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
desc, _ := cmd.Flags().GetString("description")
|
||||
req.Description = &desc
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("git-branch") {
|
||||
branch, _ := cmd.Flags().GetString("git-branch")
|
||||
req.GitBranch = &branch
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("git-repository") {
|
||||
repo, _ := cmd.Flags().GetString("git-repository")
|
||||
req.GitRepository = &repo
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("domains") {
|
||||
domains, _ := cmd.Flags().GetString("domains")
|
||||
req.Domains = &domains
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Build configuration
|
||||
if cmd.Flags().Changed("build-command") {
|
||||
buildCmd, _ := cmd.Flags().GetString("build-command")
|
||||
req.BuildCommand = &buildCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("start-command") {
|
||||
startCmd, _ := cmd.Flags().GetString("start-command")
|
||||
req.StartCommand = &startCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("install-command") {
|
||||
installCmd, _ := cmd.Flags().GetString("install-command")
|
||||
req.InstallCommand = &installCmd
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("base-directory") {
|
||||
baseDir, _ := cmd.Flags().GetString("base-directory")
|
||||
req.BaseDirectory = &baseDir
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("publish-directory") {
|
||||
publishDir, _ := cmd.Flags().GetString("publish-directory")
|
||||
req.PublishDirectory = &publishDir
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Docker configuration
|
||||
if cmd.Flags().Changed("dockerfile") {
|
||||
dockerfile, _ := cmd.Flags().GetString("dockerfile")
|
||||
req.Dockerfile = &dockerfile
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("docker-image") {
|
||||
image, _ := cmd.Flags().GetString("docker-image")
|
||||
req.DockerRegistryImageName = &image
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("docker-tag") {
|
||||
tag, _ := cmd.Flags().GetString("docker-tag")
|
||||
req.DockerRegistryImageTag = &tag
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Ports
|
||||
if cmd.Flags().Changed("ports-exposes") {
|
||||
ports, _ := cmd.Flags().GetString("ports-exposes")
|
||||
req.PortsExposes = &ports
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("ports-mappings") {
|
||||
ports, _ := cmd.Flags().GetString("ports-mappings")
|
||||
req.PortsMappings = &ports
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Health check
|
||||
if cmd.Flags().Changed("health-check-enabled") {
|
||||
enabled, _ := cmd.Flags().GetBool("health-check-enabled")
|
||||
req.HealthCheckEnabled = &enabled
|
||||
hasUpdates = true
|
||||
}
|
||||
if cmd.Flags().Changed("health-check-path") {
|
||||
path, _ := cmd.Flags().GetString("health-check-path")
|
||||
req.HealthCheckPath = &path
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
if !hasUpdates {
|
||||
return fmt.Errorf("no fields to update. Use --help to see available flags")
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
app, err := appSvc.Update(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update application: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
var deleteApplicationCmd = &cobra.Command{
|
||||
Use: "delete <uuid>",
|
||||
Short: "Delete an application",
|
||||
Long: `Delete an application. This action cannot be undone.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete application %s? This cannot be undone. (yes/no): ", uuid)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
err = appSvc.Delete(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Application %s deleted successfully.\n", uuid)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Define update command flags (most common ones)
|
||||
updateApplicationCmd.Flags().String("name", "", "Application name")
|
||||
updateApplicationCmd.Flags().String("description", "", "Application description")
|
||||
updateApplicationCmd.Flags().String("git-branch", "", "Git branch")
|
||||
updateApplicationCmd.Flags().String("git-repository", "", "Git repository URL")
|
||||
updateApplicationCmd.Flags().String("domains", "", "Domains (comma-separated)")
|
||||
updateApplicationCmd.Flags().String("build-command", "", "Build command")
|
||||
updateApplicationCmd.Flags().String("start-command", "", "Start command")
|
||||
updateApplicationCmd.Flags().String("install-command", "", "Install command")
|
||||
updateApplicationCmd.Flags().String("base-directory", "", "Base directory")
|
||||
updateApplicationCmd.Flags().String("publish-directory", "", "Publish directory")
|
||||
updateApplicationCmd.Flags().String("dockerfile", "", "Dockerfile content")
|
||||
updateApplicationCmd.Flags().String("docker-image", "", "Docker image name")
|
||||
updateApplicationCmd.Flags().String("docker-tag", "", "Docker image tag")
|
||||
updateApplicationCmd.Flags().String("ports-exposes", "", "Exposed ports")
|
||||
updateApplicationCmd.Flags().String("ports-mappings", "", "Port mappings")
|
||||
updateApplicationCmd.Flags().Bool("health-check-enabled", false, "Enable health check")
|
||||
updateApplicationCmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
// Define delete command flags
|
||||
deleteApplicationCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
|
||||
// Define start command flags
|
||||
startApplicationCmd.Flags().Bool("force", false, "Force rebuild")
|
||||
startApplicationCmd.Flags().Bool("instant-deploy", false, "Instant deploy (skip queuing)")
|
||||
|
||||
// Define logs command flags
|
||||
logsApplicationCmd.Flags().IntP("lines", "n", 100, "Number of log lines to retrieve")
|
||||
logsApplicationCmd.Flags().BoolP("follow", "f", false, "Follow log output (like tail -f)")
|
||||
|
||||
// Define envs create command flags
|
||||
createEnvCmd.Flags().String("key", "", "Environment variable key (required)")
|
||||
createEnvCmd.Flags().String("value", "", "Environment variable value (required)")
|
||||
createEnvCmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
createEnvCmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
createEnvCmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
|
||||
createEnvCmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
|
||||
// Define envs update command flags
|
||||
updateEnvCmd.Flags().String("key", "", "New environment variable key")
|
||||
updateEnvCmd.Flags().String("value", "", "New environment variable value")
|
||||
updateEnvCmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
updateEnvCmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
updateEnvCmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
|
||||
updateEnvCmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
|
||||
// Define envs delete command flags
|
||||
deleteEnvCmd.Flags().Bool("force", false, "Skip confirmation prompt")
|
||||
|
||||
// Define envs sync command flags
|
||||
syncEnvCmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
|
||||
syncEnvCmd.Flags().Bool("build-time", false, "Make all variables available at build time")
|
||||
syncEnvCmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
|
||||
syncEnvCmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
|
||||
|
||||
rootCmd.AddCommand(applicationsCmd)
|
||||
applicationsCmd.AddCommand(listApplicationsCmd)
|
||||
applicationsCmd.AddCommand(getApplicationCmd)
|
||||
applicationsCmd.AddCommand(updateApplicationCmd)
|
||||
applicationsCmd.AddCommand(deleteApplicationCmd)
|
||||
applicationsCmd.AddCommand(startApplicationCmd)
|
||||
applicationsCmd.AddCommand(stopApplicationCmd)
|
||||
applicationsCmd.AddCommand(restartApplicationCmd)
|
||||
applicationsCmd.AddCommand(logsApplicationCmd)
|
||||
applicationsCmd.AddCommand(envsApplicationCmd)
|
||||
envsApplicationCmd.AddCommand(listEnvsCmd)
|
||||
envsApplicationCmd.AddCommand(getEnvCmd)
|
||||
envsApplicationCmd.AddCommand(createEnvCmd)
|
||||
envsApplicationCmd.AddCommand(updateEnvCmd)
|
||||
envsApplicationCmd.AddCommand(deleteEnvCmd)
|
||||
envsApplicationCmd.AddCommand(syncEnvCmd)
|
||||
}
|
||||
|
||||
var startApplicationCmd = &cobra.Command{
|
||||
Use: "start <uuid>",
|
||||
Aliases: []string{"deploy"},
|
||||
Short: "Start an application",
|
||||
Long: `Start an application (initiates a deployment).`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
instantDeploy, _ := cmd.Flags().GetBool("instant-deploy")
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Start(ctx, uuid, force, instantDeploy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
if resp.DeploymentUUID != nil && *resp.DeploymentUUID != "" {
|
||||
fmt.Printf("Deployment UUID: %s\n", *resp.DeploymentUUID)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var stopApplicationCmd = &cobra.Command{
|
||||
Use: "stop <uuid>",
|
||||
Short: "Stop an application",
|
||||
Long: `Stop a running application.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Stop(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var restartApplicationCmd = &cobra.Command{
|
||||
Use: "restart <uuid>",
|
||||
Short: "Restart an application",
|
||||
Long: `Restart a running application.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
resp, err := appSvc.Restart(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart application: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(resp.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var logsApplicationCmd = &cobra.Command{
|
||||
Use: "logs <uuid>",
|
||||
Short: "Get application logs",
|
||||
Long: `Retrieve logs for an application. Use --follow to continuously stream new logs.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
lines, _ := cmd.Flags().GetInt("lines")
|
||||
follow, _ := cmd.Flags().GetBool("follow")
|
||||
appSvc := service.NewApplicationService(client)
|
||||
|
||||
if !follow {
|
||||
// One-time fetch
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
fmt.Print(resp.Logs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Follow mode: poll for new logs
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Track the last log content to avoid duplicates
|
||||
lastLogs := ""
|
||||
|
||||
// Fetch initial logs
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
fmt.Print(resp.Logs)
|
||||
lastLogs = resp.Logs
|
||||
|
||||
// Poll for new logs
|
||||
for {
|
||||
select {
|
||||
case <-sigChan:
|
||||
fmt.Println("\nStopping log follow...")
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
resp, err := appSvc.Logs(ctx, uuid, lines)
|
||||
if err != nil {
|
||||
// Don't fail on transient errors in follow mode
|
||||
continue
|
||||
}
|
||||
// Only print if logs have changed
|
||||
if resp.Logs != lastLogs {
|
||||
// Print only the new content
|
||||
if len(resp.Logs) > len(lastLogs) && strings.HasPrefix(resp.Logs, lastLogs) {
|
||||
fmt.Print(resp.Logs[len(lastLogs):])
|
||||
} else {
|
||||
// Logs were truncated or changed, print all
|
||||
fmt.Print(resp.Logs)
|
||||
}
|
||||
lastLogs = resp.Logs
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var envsApplicationCmd = &cobra.Command{
|
||||
Use: "env",
|
||||
Aliases: []string{"envs", "environment"},
|
||||
Short: "Manage application environment variables",
|
||||
Long: `List and manage environment variables for applications. All commands require the application UUID first to establish context.`,
|
||||
}
|
||||
|
||||
var listEnvsCmd = &cobra.Command{
|
||||
Use: "list <app_uuid>",
|
||||
Short: "List all environment variables for an application",
|
||||
Long: `List all environment variables for a specific application.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
envs, err := appSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list environment variables: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Mask sensitive values unless --show-sensitive is used
|
||||
if !showSensitive {
|
||||
for i := range envs {
|
||||
envs[i].Value = "********"
|
||||
if envs[i].RealValue != nil {
|
||||
masked := "********"
|
||||
envs[i].RealValue = &masked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(envs)
|
||||
},
|
||||
}
|
||||
|
||||
var getEnvCmd = &cobra.Command{
|
||||
Use: "get <app_uuid> <env_uuid_or_key>",
|
||||
Short: "Get environment variable details",
|
||||
Long: `Get detailed information about a specific environment variable by UUID or key name.`,
|
||||
Args: exactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.GetEnv(ctx, appUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get environment variable: %w", err)
|
||||
}
|
||||
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Mask sensitive value unless --show-sensitive is used
|
||||
if !showSensitive {
|
||||
env.Value = "********"
|
||||
if env.RealValue != nil {
|
||||
masked := "********"
|
||||
env.RealValue = &masked
|
||||
}
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(env)
|
||||
},
|
||||
}
|
||||
|
||||
var createEnvCmd = &cobra.Command{
|
||||
Use: "create <app_uuid>",
|
||||
Short: "Create an environment variable for an application",
|
||||
Long: `Create a new environment variable for a specific application. Use --key and --value flags to specify the variable.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
|
||||
if key == "" {
|
||||
return fmt.Errorf("--key is required")
|
||||
}
|
||||
if value == "" {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableCreateRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
// Only set flags if they were explicitly provided
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.CreateEnv(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' created successfully.\n", env.Key)
|
||||
fmt.Printf("UUID: %s\n", env.UUID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var updateEnvCmd = &cobra.Command{
|
||||
Use: "update <app_uuid> <env_uuid>",
|
||||
Short: "Update an environment variable",
|
||||
Long: `Update an existing environment variable. First UUID is the application, second is the specific environment variable to update.`,
|
||||
Args: exactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error{
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableUpdateRequest{
|
||||
UUID: envUUID,
|
||||
}
|
||||
|
||||
// Only set fields that were provided
|
||||
if cmd.Flags().Changed("key") {
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
req.Key = &key
|
||||
}
|
||||
if cmd.Flags().Changed("value") {
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
req.Value = &value
|
||||
}
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
// Check if at least one field is being updated
|
||||
if req.Key == nil && req.Value == nil && req.IsBuildTime == nil && req.IsPreview == nil && req.IsLiteral == nil && req.IsMultiline == nil {
|
||||
return fmt.Errorf("at least one field must be provided to update (--key, --value, --build-time, --preview, --is-literal, or --is-multiline)")
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.UpdateEnv(ctx, appUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' updated successfully.\n", env.Key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var deleteEnvCmd = &cobra.Command{
|
||||
Use: "delete <app_uuid> <env_uuid>",
|
||||
Short: "Delete an environment variable",
|
||||
Long: `Delete an environment variable from an application. First UUID is the application, second is the specific environment variable to delete.`,
|
||||
Args: exactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
err = appSvc.DeleteEnv(ctx, appUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Environment variable deleted successfully.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var syncEnvCmd = &cobra.Command{
|
||||
Use: "sync <app_uuid>",
|
||||
Short: "Sync environment variables from a .env file",
|
||||
Long: `Sync environment variables from a .env file. This command intelligently:
|
||||
- Updates existing environment variables with new values
|
||||
- Creates new environment variables that don't exist yet
|
||||
- Uses efficient bulk operations where possible
|
||||
|
||||
Example: coolify app env sync abc123 --file .env.production`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
filePath, _ := cmd.Flags().GetString("file")
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("--file is required")
|
||||
}
|
||||
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
|
||||
// Parse the .env file
|
||||
envVars, err := parser.ParseEnvFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse .env file: %w", err)
|
||||
}
|
||||
|
||||
if len(envVars) == 0 {
|
||||
fmt.Println("No environment variables found in file.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d environment variables in file. Syncing...\n", len(envVars))
|
||||
|
||||
// Fetch existing environment variables
|
||||
appSvc := service.NewApplicationService(client)
|
||||
existingEnvs, err := appSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list existing environment variables: %w", err)
|
||||
}
|
||||
|
||||
// Build a map of existing env vars by key
|
||||
existingMap := make(map[string]models.EnvironmentVariable)
|
||||
for _, env := range existingEnvs {
|
||||
existingMap[env.Key] = env
|
||||
}
|
||||
|
||||
// Separate into updates and creates
|
||||
var toUpdate []models.EnvironmentVariableCreateRequest
|
||||
var toCreate []models.EnvironmentVariableCreateRequest
|
||||
|
||||
for _, envVar := range envVars {
|
||||
req := models.EnvironmentVariableCreateRequest{
|
||||
Key: envVar.Key,
|
||||
Value: envVar.Value,
|
||||
}
|
||||
|
||||
// Apply flags if explicitly provided
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
|
||||
// Auto-detect multiline values
|
||||
if strings.Contains(envVar.Value, "\n") {
|
||||
multiline := true
|
||||
req.IsMultiline = &multiline
|
||||
}
|
||||
|
||||
if _, exists := existingMap[envVar.Key]; exists {
|
||||
toUpdate = append(toUpdate, req)
|
||||
} else {
|
||||
toCreate = append(toCreate, req)
|
||||
}
|
||||
}
|
||||
|
||||
updateCount := 0
|
||||
createCount := 0
|
||||
failCount := 0
|
||||
|
||||
// Perform bulk update if there are vars to update
|
||||
if len(toUpdate) > 0 {
|
||||
fmt.Printf("Updating %d existing variables...\n", len(toUpdate))
|
||||
bulkReq := &service.BulkUpdateEnvsRequest{
|
||||
Data: toUpdate,
|
||||
}
|
||||
_, err := appSvc.BulkUpdateEnvs(ctx, uuid, bulkReq)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Bulk update failed: %v\n", err)
|
||||
failCount += len(toUpdate)
|
||||
} else {
|
||||
updateCount = len(toUpdate)
|
||||
fmt.Printf(" ✓ Successfully updated %d variables\n", updateCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new variables one by one
|
||||
if len(toCreate) > 0 {
|
||||
fmt.Printf("Creating %d new variables...\n", len(toCreate))
|
||||
for _, req := range toCreate {
|
||||
_, err := appSvc.CreateEnv(ctx, uuid, &req)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Failed to create '%s': %v\n", req.Key, err)
|
||||
failCount++
|
||||
} else {
|
||||
fmt.Printf(" ✓ Created '%s'\n", req.Key)
|
||||
createCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nSync complete: %d updated, %d created, %d failed\n", updateCount, createCount, failCount)
|
||||
|
||||
if failCount > 0 {
|
||||
return fmt.Errorf("some environment variables failed to sync")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,466 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApplicationsListCmd_Flags(t *testing.T) {
|
||||
cmd := listApplicationsCmd
|
||||
|
||||
// Verify command structure
|
||||
assert.Equal(t, "list", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
}
|
||||
|
||||
func TestApplicationsGetCmd_Args(t *testing.T) {
|
||||
cmd := getApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsGetCmd_Flags(t *testing.T) {
|
||||
cmd := getApplicationCmd
|
||||
|
||||
// Verify command structure
|
||||
assert.Equal(t, "get <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
}
|
||||
|
||||
func TestApplicationsUpdateCmd_Args(t *testing.T) {
|
||||
cmd := updateApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsUpdateCmd_Flags(t *testing.T) {
|
||||
cmd := updateApplicationCmd
|
||||
|
||||
// Verify command structure
|
||||
assert.Equal(t, "update <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify key flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("name"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("description"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("git-branch"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("domains"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("build-command"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("start-command"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("docker-image"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("health-check-enabled"))
|
||||
}
|
||||
|
||||
func TestApplicationsCmd_Structure(t *testing.T) {
|
||||
// Verify parent command exists
|
||||
assert.Equal(t, "applications", applicationsCmd.Use)
|
||||
assert.NotEmpty(t, applicationsCmd.Short)
|
||||
|
||||
// Verify subcommands are registered
|
||||
hasListCmd := false
|
||||
hasGetCmd := false
|
||||
hasUpdateCmd := false
|
||||
|
||||
for _, cmd := range applicationsCmd.Commands() {
|
||||
if cmd.Use == "list" {
|
||||
hasListCmd = true
|
||||
}
|
||||
if cmd.Use == "get <uuid>" {
|
||||
hasGetCmd = true
|
||||
}
|
||||
if cmd.Use == "update <uuid>" {
|
||||
hasUpdateCmd = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, hasListCmd, "list subcommand should be registered")
|
||||
assert.True(t, hasGetCmd, "get subcommand should be registered")
|
||||
assert.True(t, hasUpdateCmd, "update subcommand should be registered")
|
||||
}
|
||||
|
||||
func TestApplicationsDeleteCmd_Args(t *testing.T) {
|
||||
cmd := deleteApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsDeleteCmd_Flags(t *testing.T) {
|
||||
cmd := deleteApplicationCmd
|
||||
|
||||
// Verify command structure
|
||||
assert.Equal(t, "delete <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify force flag exists
|
||||
forceFlag := cmd.Flags().Lookup("force")
|
||||
assert.NotNil(t, forceFlag)
|
||||
assert.Equal(t, "false", forceFlag.DefValue)
|
||||
}
|
||||
|
||||
func TestApplicationsCmd_AllSubcommands(t *testing.T) {
|
||||
// Verify all subcommands are registered
|
||||
hasListCmd := false
|
||||
hasGetCmd := false
|
||||
hasUpdateCmd := false
|
||||
hasDeleteCmd := false
|
||||
|
||||
for _, cmd := range applicationsCmd.Commands() {
|
||||
switch cmd.Use {
|
||||
case "list":
|
||||
hasListCmd = true
|
||||
case "get <uuid>":
|
||||
hasGetCmd = true
|
||||
case "update <uuid>":
|
||||
hasUpdateCmd = true
|
||||
case "delete <uuid>":
|
||||
hasDeleteCmd = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, hasListCmd, "list subcommand should be registered")
|
||||
assert.True(t, hasGetCmd, "get subcommand should be registered")
|
||||
assert.True(t, hasUpdateCmd, "update subcommand should be registered")
|
||||
assert.True(t, hasDeleteCmd, "delete subcommand should be registered")
|
||||
}
|
||||
|
||||
func TestApplicationsStartCmd_Args(t *testing.T) {
|
||||
cmd := startApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsStartCmd_Structure(t *testing.T) {
|
||||
cmd := startApplicationCmd
|
||||
|
||||
assert.Equal(t, "start <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify aliases exist
|
||||
assert.Contains(t, cmd.Aliases, "deploy")
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("force"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("instant-deploy"))
|
||||
}
|
||||
|
||||
func TestApplicationsStopCmd_Args(t *testing.T) {
|
||||
cmd := stopApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsStopCmd_Structure(t *testing.T) {
|
||||
cmd := stopApplicationCmd
|
||||
|
||||
assert.Equal(t, "stop <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
}
|
||||
|
||||
func TestApplicationsRestartCmd_Args(t *testing.T) {
|
||||
cmd := restartApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsRestartCmd_Structure(t *testing.T) {
|
||||
cmd := restartApplicationCmd
|
||||
|
||||
assert.Equal(t, "restart <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
}
|
||||
|
||||
func TestApplicationsLogsCmd_Args(t *testing.T) {
|
||||
cmd := logsApplicationCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsLogsCmd_Structure(t *testing.T) {
|
||||
cmd := logsApplicationCmd
|
||||
|
||||
assert.Equal(t, "logs <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("lines"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("follow"))
|
||||
}
|
||||
|
||||
func TestApplicationsCmd_AllLifecycleCommands(t *testing.T) {
|
||||
// Verify all lifecycle subcommands are registered
|
||||
hasStartCmd := false
|
||||
hasStopCmd := false
|
||||
hasRestartCmd := false
|
||||
hasLogsCmd := false
|
||||
|
||||
for _, cmd := range applicationsCmd.Commands() {
|
||||
switch cmd.Use {
|
||||
case "start <uuid>":
|
||||
hasStartCmd = true
|
||||
case "stop <uuid>":
|
||||
hasStopCmd = true
|
||||
case "restart <uuid>":
|
||||
hasRestartCmd = true
|
||||
case "logs <uuid>":
|
||||
hasLogsCmd = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, hasStartCmd, "start subcommand should be registered")
|
||||
assert.True(t, hasStopCmd, "stop subcommand should be registered")
|
||||
assert.True(t, hasRestartCmd, "restart subcommand should be registered")
|
||||
assert.True(t, hasLogsCmd, "logs subcommand should be registered")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsCmd_Structure(t *testing.T) {
|
||||
cmd := envsApplicationCmd
|
||||
|
||||
assert.Equal(t, "envs", cmd.Use)
|
||||
assert.NotNil(t, cmd.Commands())
|
||||
assert.Greater(t, len(cmd.Commands()), 0, "envs should have subcommands")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsListCmd_Args(t *testing.T) {
|
||||
cmd := listEnvsCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsListCmd_Structure(t *testing.T) {
|
||||
cmd := listEnvsCmd
|
||||
|
||||
assert.Equal(t, "list <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
}
|
||||
|
||||
func TestApplicationsCmd_HasEnvsSubcommand(t *testing.T) {
|
||||
// Verify envs subcommand is registered
|
||||
hasEnvsCmd := false
|
||||
|
||||
for _, cmd := range applicationsCmd.Commands() {
|
||||
if cmd.Use == "envs" {
|
||||
hasEnvsCmd = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.True(t, hasEnvsCmd, "envs subcommand should be registered")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsCreateCmd_Args(t *testing.T) {
|
||||
cmd := createEnvCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsCreateCmd_Structure(t *testing.T) {
|
||||
cmd := createEnvCmd
|
||||
|
||||
assert.Equal(t, "create <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("key"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("value"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("build-time"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("preview"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("is-literal"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("is-multiline"))
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsUpdateCmd_Args(t *testing.T) {
|
||||
cmd := updateEnvCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 2 arguments")
|
||||
|
||||
// Test with 1 argument - should fail
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.Error(t, err, "should require exactly 2 arguments")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123", "env-uuid-456"})
|
||||
assert.NoError(t, err, "should accept 2 arguments")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2", "uuid3"})
|
||||
assert.Error(t, err, "should not accept more than 2 arguments")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsUpdateCmd_Structure(t *testing.T) {
|
||||
cmd := updateEnvCmd
|
||||
|
||||
assert.Equal(t, "update <app_uuid> <env_uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("key"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("value"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("build-time"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("preview"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("is-literal"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("is-multiline"))
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsDeleteCmd_Args(t *testing.T) {
|
||||
cmd := deleteEnvCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 2 arguments")
|
||||
|
||||
// Test with 1 argument - should fail
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.Error(t, err, "should require exactly 2 arguments")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123", "env-uuid-456"})
|
||||
assert.NoError(t, err, "should accept 2 arguments")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2", "uuid3"})
|
||||
assert.Error(t, err, "should not accept more than 2 arguments")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsDeleteCmd_Structure(t *testing.T) {
|
||||
cmd := deleteEnvCmd
|
||||
|
||||
assert.Equal(t, "delete <app_uuid> <env_uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("force"))
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsImportCmd_Args(t *testing.T) {
|
||||
cmd := importEnvCmd
|
||||
|
||||
// Test with no arguments - should fail
|
||||
err := cmd.Args(cmd, []string{})
|
||||
assert.Error(t, err, "should require exactly 1 argument")
|
||||
|
||||
// Test with correct number of arguments - should pass
|
||||
err = cmd.Args(cmd, []string{"app-uuid-123"})
|
||||
assert.NoError(t, err, "should accept 1 argument")
|
||||
|
||||
// Test with too many arguments - should fail
|
||||
err = cmd.Args(cmd, []string{"uuid1", "uuid2"})
|
||||
assert.Error(t, err, "should not accept more than 1 argument")
|
||||
}
|
||||
|
||||
func TestApplicationsEnvsImportCmd_Structure(t *testing.T) {
|
||||
cmd := importEnvCmd
|
||||
|
||||
assert.Equal(t, "import <uuid>", cmd.Use)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
assert.NotNil(t, cmd.Args)
|
||||
|
||||
// Verify flags exist
|
||||
assert.NotNil(t, cmd.Flags().Lookup("file"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("build-time"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("preview"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("is-literal"))
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCompletionsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "completion <shell>",
|
||||
Short: "Output shell completion code for the specified shell",
|
||||
Long: `To load completions:
|
||||
|
||||
### Bash
|
||||
|
||||
To load completions into the current shell execute:
|
||||
|
||||
source <(coolify completion bash)
|
||||
|
||||
In order to make the completions permanent, append the line above to
|
||||
your .bashrc.
|
||||
|
||||
### Zsh
|
||||
|
||||
If shell completions are not already enabled for your environment need
|
||||
to enable them. Add the following line to your ~/.zshrc file:
|
||||
|
||||
autoload -Uz compinit; compinit
|
||||
|
||||
To load completions for each session execute the following commands:
|
||||
|
||||
mkdir -p ~/.config/coolify/completion/zsh
|
||||
coolify completion zsh > ~/.config/coolify/completion/zsh/_coolify
|
||||
|
||||
Finally add the following line to your ~/.zshrc file, *before* you
|
||||
call the compinit function:
|
||||
|
||||
fpath+=(~/.config/coolify/completion/zsh)
|
||||
|
||||
In the end your ~/.zshrc file should contain the following two lines
|
||||
in the order given here.
|
||||
|
||||
fpath+=(~/.config/coolify/completion/zsh)
|
||||
# ... anything else that needs to be done before compinit
|
||||
autoload -Uz compinit; compinit
|
||||
# ...
|
||||
|
||||
You will need to start a new shell for this setup to take effect.
|
||||
|
||||
### Fish
|
||||
|
||||
To load completions into the current shell execute:
|
||||
|
||||
coolify completion fish | source
|
||||
|
||||
In order to make the completions permanent execute once:
|
||||
|
||||
coolify completion fish > ~/.config/fish/completions/coolify.fish
|
||||
|
||||
### PowerShell:
|
||||
|
||||
To load completions into the current shell execute:
|
||||
|
||||
PS> coolify completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
To load completions for every new session, run
|
||||
and source this file from your PowerShell profile.
|
||||
|
||||
PS> coolify completion powershell > coolify.ps1
|
||||
`,
|
||||
Args: cli.ExactArgs(1, "<shell>"),
|
||||
ValidArgs: []string{"bash", "fish", "zsh", "powershell"},
|
||||
DisableFlagsInUseLine: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
err = cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "fish":
|
||||
err = cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
case "zsh":
|
||||
err = cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "powershell":
|
||||
err = cmd.Root().GenPowerShellCompletion(os.Stdout)
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported shell: %s", args[0])
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewAddCommand creates the add command
|
||||
func NewAddCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add <context_name> <url> <token>",
|
||||
Example: `context add myserver https://coolify.example.com your-api-token`,
|
||||
Args: cli.ExactArgs(3, "<context_name> <url> <token>"),
|
||||
Short: "Add a new context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
host := args[1]
|
||||
token := args[2]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
setDefault, _ := cmd.Flags().GetBool("default")
|
||||
|
||||
instances := viper.Get("instances").([]any)
|
||||
|
||||
// Check if instance already exists
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]any)
|
||||
if instanceMap["name"] == name {
|
||||
if force {
|
||||
instanceMap["token"] = token
|
||||
if setDefault {
|
||||
// Remove default from all instances
|
||||
for _, inst := range instances {
|
||||
instMap := inst.(map[string]any)
|
||||
instMap["default"] = false
|
||||
}
|
||||
instanceMap["default"] = true
|
||||
fmt.Printf("%s already exists. Force overwriting. Setting it as default.\n", name)
|
||||
} else {
|
||||
fmt.Printf("%s already exists. Force overwriting.\n", name)
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s already exists.\n", name)
|
||||
fmt.Println("\nNote: Use --force to force overwrite.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new instance
|
||||
newInstance := config.Instance{
|
||||
Name: name,
|
||||
FQDN: host,
|
||||
Token: token,
|
||||
Default: false,
|
||||
}
|
||||
|
||||
if setDefault {
|
||||
// Remove default from all instances
|
||||
for _, inst := range instances {
|
||||
instMap := inst.(map[string]any)
|
||||
instMap["default"] = false
|
||||
}
|
||||
newInstance.Default = true
|
||||
fmt.Printf("Context '%s' added and set as default.\n", newInstance.Name)
|
||||
} else {
|
||||
fmt.Printf("Context '%s' added successfully.\n", newInstance.Name)
|
||||
}
|
||||
|
||||
instances = append(instances, newInstance)
|
||||
|
||||
viper.Set("instances", instances)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
fmt.Printf("failed to write config: %v\n", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolP("default", "d", false, "Set as default context")
|
||||
cmd.Flags().BoolP("force", "f", false, "Force overwrite if context already exists")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewContextCommand creates the context parent command
|
||||
func NewContextCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "context",
|
||||
Short: "Manage Coolify contexts",
|
||||
Long: `Manage Coolify contexts. A context contains the configuration (URL and token) for connecting to Coolify.`,
|
||||
}
|
||||
|
||||
// Add subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewAddCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
cmd.AddCommand(NewUseCommand())
|
||||
cmd.AddCommand(NewUpdateCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewSetTokenCommand())
|
||||
cmd.AddCommand(NewSetDefaultCommand())
|
||||
cmd.AddCommand(NewVersionCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// NewDeleteCommand creates the delete command
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "delete <context_name>",
|
||||
Example: `context delete myserver`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Delete a context",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for i, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
instances = slices.Delete(instances, i, i+1)
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
|
||||
if instanceMap["default"] == true {
|
||||
if len(instances) > 0 {
|
||||
instances[0].(map[string]interface{})["default"] = true
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
newDefaultName := instances[0].(map[string]interface{})["name"]
|
||||
fmt.Printf("Context '%s' deleted. '%s' is now the default context.\n", Name, newDefaultName)
|
||||
} else {
|
||||
fmt.Printf("Context '%s' deleted. No contexts remaining.\n", Name)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Context '%s' deleted.\n", Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("Context '%s' not found.\n", Name)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <context_name>",
|
||||
Example: `context get myserver`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Get details of a specific context",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
instancesRaw := viper.Get("instances")
|
||||
if instancesRaw == nil {
|
||||
instancesRaw = []any{}
|
||||
}
|
||||
instancesInterface := instancesRaw.([]any)
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Convert interface{} to config.Instance structs
|
||||
var instances []config.Instance
|
||||
for _, item := range instancesInterface {
|
||||
itemMap := item.(map[string]any)
|
||||
|
||||
instance := config.Instance{
|
||||
Name: getString(itemMap, "name"),
|
||||
FQDN: getString(itemMap, "fqdn"),
|
||||
Token: getString(itemMap, "token"),
|
||||
Default: getBool(itemMap, "default"),
|
||||
}
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
|
||||
// If a name was provided, filter to that single instance
|
||||
var results []config.Instance
|
||||
for _, inst := range instances {
|
||||
if inst.Name == name {
|
||||
results = append(results, inst)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(results)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all configured contexts",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get instances from viper (returns []interface{})
|
||||
instancesRaw := viper.Get("instances")
|
||||
if instancesRaw == nil {
|
||||
instancesRaw = []interface{}{}
|
||||
}
|
||||
instancesInterface := instancesRaw.([]interface{})
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Convert interface{} to config.Instance structs
|
||||
var instances []config.Instance
|
||||
for _, item := range instancesInterface {
|
||||
itemMap := item.(map[string]any)
|
||||
|
||||
instance := config.Instance{
|
||||
Name: getString(itemMap, "name"),
|
||||
FQDN: getString(itemMap, "fqdn"),
|
||||
Token: getString(itemMap, "token"),
|
||||
Default: getBool(itemMap, "default"),
|
||||
}
|
||||
instances = append(instances, instance)
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(instances)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions to safely extract values from map
|
||||
func getString(m map[string]interface{}, key string) string {
|
||||
if val, ok := m[key]; ok {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getBool(m map[string]interface{}, key string) bool {
|
||||
if val, ok := m[key]; ok {
|
||||
if b, ok := val.(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewSetTokenCommand creates the set-token command
|
||||
func NewSetDefaultCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "set-default <context_name>",
|
||||
Example: `context set-default myserver`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Set a context as the default",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
|
||||
// Check if instance exists
|
||||
var found bool
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
found = true
|
||||
instanceMap["default"] = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
} else {
|
||||
// Only unset other defaults if we found the target instance
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] != name {
|
||||
instanceMap["default"] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viper.Set("instances", instances)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
|
||||
// Show the list after updating
|
||||
return NewListCommand().RunE(cmd, args)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewSetTokenCommand creates the set-token command
|
||||
func NewSetTokenCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "set-token <context_name> <token>",
|
||||
Example: `context set-token myserver your-new-api-token`,
|
||||
Args: cli.ExactArgs(2, "<context_name> <token>"),
|
||||
Short: "Update the API token for a context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
token := args[1]
|
||||
var found interface{}
|
||||
for _, instance := range viper.Get("instances").([]interface{}) {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
found = instanceMap
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
fmt.Printf("Context '%s' not found.\n", name)
|
||||
return
|
||||
}
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
instanceMap["token"] = token
|
||||
}
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
fmt.Printf("Token updated for context '%s'.\n", name)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewUpdateCommand creates the update command
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <context_name>",
|
||||
Example: `context update myserver --name newname --url https://new.coolify.com --token newtoken`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Update a context's properties (name, URL, token)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
oldName := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
|
||||
// Get flags
|
||||
newName, _ := cmd.Flags().GetString("name")
|
||||
newURL, _ := cmd.Flags().GetString("url")
|
||||
newToken, _ := cmd.Flags().GetString("token")
|
||||
|
||||
// Check if at least one flag is provided
|
||||
if newName == "" && newURL == "" && newToken == "" {
|
||||
fmt.Println("Error: At least one of --name, --url, or --token must be provided")
|
||||
fmt.Println("\nUsage: coolify context update <context_name> [--name <new_name>] [--url <new_url>] [--token <new_token>]")
|
||||
return
|
||||
}
|
||||
|
||||
// Find the context
|
||||
var found bool
|
||||
var contextToUpdate map[string]interface{}
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == oldName {
|
||||
found = true
|
||||
contextToUpdate = instanceMap
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Printf("Context '%s' not found.\n", oldName)
|
||||
return
|
||||
}
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
if newName != "" && newName != oldName {
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == newName {
|
||||
fmt.Printf("Error: Context with name '%s' already exists.\n", newName)
|
||||
return
|
||||
}
|
||||
}
|
||||
contextToUpdate["name"] = newName
|
||||
}
|
||||
|
||||
// Update URL if provided
|
||||
if newURL != "" {
|
||||
contextToUpdate["fqdn"] = newURL
|
||||
}
|
||||
|
||||
// Update token if provided
|
||||
if newToken != "" {
|
||||
contextToUpdate["token"] = newToken
|
||||
}
|
||||
|
||||
// Save changes
|
||||
viper.Set("instances", instances)
|
||||
err := viper.WriteConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the new name if renamed, otherwise use old name
|
||||
finalName := oldName
|
||||
if newName != "" {
|
||||
finalName = newName
|
||||
}
|
||||
fmt.Printf("Context '%s' updated successfully.\n", finalName)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("name", "n", "", "New name for the context")
|
||||
cmd.Flags().StringP("url", "u", "", "New URL for the context")
|
||||
cmd.Flags().StringP("token", "t", "", "New token for the context")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewUseCommand creates the use command
|
||||
func NewUseCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "use <context_name>",
|
||||
Example: `context use myserver`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Switch to a different context (set as default)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
|
||||
// Check if instance exists
|
||||
var found bool
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
}
|
||||
|
||||
// Update default
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
instanceMap["default"] = true
|
||||
} else {
|
||||
delete(instanceMap, "default")
|
||||
}
|
||||
}
|
||||
|
||||
viper.Set("instances", instances)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Switched to context '%s'.\n", name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewVersionCommand creates the version command for contexts
|
||||
func NewVersionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current context's Coolify version",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Get version using API client
|
||||
version, err := client.GetVersion(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get version: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCreateCommand creates a new database
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
createBackupCmd := &cobra.Command{
|
||||
Use: "create <database_uuid>",
|
||||
Short: "Create a new scheduled backup configuration",
|
||||
Long: `Create a new scheduled backup configuration for a database. Configure frequency, retention, S3 storage, and other backup options.
|
||||
|
||||
Example: coolify database backup create abc123 --frequency "0 0 * * *" --enabled`,
|
||||
Args: cli.ExactArgs(1, "<database_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.DatabaseBackupCreateRequest{}
|
||||
|
||||
// Apply flags if provided
|
||||
if cmd.Flags().Changed("frequency") {
|
||||
frequency, _ := cmd.Flags().GetString("frequency")
|
||||
req.Frequency = &frequency
|
||||
}
|
||||
if cmd.Flags().Changed("enabled") {
|
||||
enabled, _ := cmd.Flags().GetBool("enabled")
|
||||
req.Enabled = &enabled
|
||||
}
|
||||
if cmd.Flags().Changed("save-s3") {
|
||||
saveS3, _ := cmd.Flags().GetBool("save-s3")
|
||||
req.SaveS3 = &saveS3
|
||||
}
|
||||
if cmd.Flags().Changed("s3-storage-uuid") {
|
||||
s3UUID, _ := cmd.Flags().GetString("s3-storage-uuid")
|
||||
req.S3StorageUUID = &s3UUID
|
||||
}
|
||||
if cmd.Flags().Changed("databases") {
|
||||
databases, _ := cmd.Flags().GetString("databases")
|
||||
req.DatabasesToBackup = &databases
|
||||
}
|
||||
if cmd.Flags().Changed("dump-all") {
|
||||
dumpAll, _ := cmd.Flags().GetBool("dump-all")
|
||||
req.DumpAll = &dumpAll
|
||||
}
|
||||
if cmd.Flags().Changed("retention-amount-locally") {
|
||||
amount, _ := cmd.Flags().GetInt("retention-amount-locally")
|
||||
req.DatabaseBackupRetentionAmountLocally = &amount
|
||||
}
|
||||
if cmd.Flags().Changed("retention-days-locally") {
|
||||
days, _ := cmd.Flags().GetInt("retention-days-locally")
|
||||
req.DatabaseBackupRetentionDaysLocally = &days
|
||||
}
|
||||
if cmd.Flags().Changed("retention-storage-locally") {
|
||||
storage, _ := cmd.Flags().GetString("retention-storage-locally")
|
||||
req.DatabaseBackupRetentionMaxStorageLocally = &storage
|
||||
}
|
||||
if cmd.Flags().Changed("retention-amount-s3") {
|
||||
amount, _ := cmd.Flags().GetInt("retention-amount-s3")
|
||||
req.DatabaseBackupRetentionAmountS3 = &amount
|
||||
}
|
||||
if cmd.Flags().Changed("retention-days-s3") {
|
||||
days, _ := cmd.Flags().GetInt("retention-days-s3")
|
||||
req.DatabaseBackupRetentionDaysS3 = &days
|
||||
}
|
||||
if cmd.Flags().Changed("retention-storage-s3") {
|
||||
storage, _ := cmd.Flags().GetString("retention-storage-s3")
|
||||
req.DatabaseBackupRetentionMaxStorageS3 = &storage
|
||||
}
|
||||
if cmd.Flags().Changed("timeout") {
|
||||
timeout, _ := cmd.Flags().GetInt("timeout")
|
||||
req.Timeout = &timeout
|
||||
}
|
||||
if cmd.Flags().Changed("disable-local") {
|
||||
disableLocal, _ := cmd.Flags().GetBool("disable-local")
|
||||
req.DisableLocalBackup = &disableLocal
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
backup, err := dbService.CreateBackup(ctx, dbUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create backup: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(backup)
|
||||
},
|
||||
}
|
||||
|
||||
createBackupCmd.Flags().String("frequency", "", "Backup frequency (cron expression, e.g., '0 0 * * *' for daily)")
|
||||
createBackupCmd.Flags().Bool("enabled", false, "Enable backup schedule")
|
||||
createBackupCmd.Flags().Bool("save-s3", false, "Save backups to S3")
|
||||
createBackupCmd.Flags().String("s3-storage-uuid", "", "S3 storage UUID")
|
||||
createBackupCmd.Flags().String("databases-to-backup", "", "Comma-separated list of databases to backup")
|
||||
createBackupCmd.Flags().Bool("dump-all", false, "Dump all databases")
|
||||
createBackupCmd.Flags().Int("retention-amount-locally", 0, "Number of backups to retain locally")
|
||||
createBackupCmd.Flags().Int("retention-days-locally", 0, "Days to retain backups locally")
|
||||
createBackupCmd.Flags().String("retention-max-storage-locally", "", "Max storage for local backups (e.g., '1GB', '500MB')")
|
||||
createBackupCmd.Flags().Int("retention-amount-s3", 0, "Number of backups to retain in S3")
|
||||
createBackupCmd.Flags().Int("retention-days-s3", 0, "Days to retain backups in S3")
|
||||
createBackupCmd.Flags().String("retention-max-storage-s3", "", "Max storage for S3 backups (e.g., '1GB', '500MB')")
|
||||
createBackupCmd.Flags().Int("timeout", 0, "Backup timeout in seconds")
|
||||
createBackupCmd.Flags().Bool("disable-local-backup", false, "Disable local backup storage")
|
||||
|
||||
return createBackupCmd
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteExecutionCommand lists all databases
|
||||
func NewDeleteExecutionCommand() *cobra.Command {
|
||||
deleteBackupExecutionCmd := &cobra.Command{
|
||||
Use: "delete-execution <database_uuid> <backup_uuid> <execution_uuid>",
|
||||
Short: "Delete backup execution",
|
||||
Long: `Delete a specific backup execution and optionally from S3. First UUID is the database, second is the backup configuration, third is the specific execution.`,
|
||||
Args: cli.ExactArgs(3, "<database_uuid> <backup_uuid> <execution_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
executionUUID := args[2]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deleteS3, _ := cmd.Flags().GetBool("delete-s3")
|
||||
|
||||
if !force {
|
||||
fmt.Printf("Are you sure you want to delete backup execution %s? (y/N): ", executionUUID)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading input: %w", err)
|
||||
}
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Delete cancelled")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
err = dbService.DeleteBackupExecution(ctx, dbUUID, backupUUID, executionUUID, deleteS3)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete backup execution: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Backup execution deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteBackupExecutionCmd.Flags().Bool("delete-s3", false, "Delete backup file from S3")
|
||||
return deleteBackupExecutionCmd
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a database
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
deleteBackupCmd := &cobra.Command{
|
||||
Use: "delete <database_uuid> <backup_uuid>",
|
||||
Short: "Delete backup configuration",
|
||||
Long: `Delete a backup configuration and optionally all its executions from S3. First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deleteS3, _ := cmd.Flags().GetBool("delete-s3")
|
||||
|
||||
if !force {
|
||||
fmt.Printf("Are you sure you want to delete backup configuration %s? (y/N): ", backupUUID)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading input: %w", err)
|
||||
}
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Delete cancelled")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
err = dbService.DeleteBackup(ctx, dbUUID, backupUUID, deleteS3)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete backup: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Backup configuration deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteBackupCmd.Flags().Bool("delete-s3", false, "Delete backup files from S3")
|
||||
return deleteBackupCmd
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewExecutionCommand lists all databases
|
||||
func NewExecutionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "executions <database_uuid> <backup_uuid>",
|
||||
Short: "List backup executions",
|
||||
Long: `List all executions for a backup configuration. First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
executions, err := dbService.ListBackupExecutions(ctx, dbUUID, backupUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list backup executions: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(executions)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all databases
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list <database_uuid>",
|
||||
Short: "List all backup configurations for a database",
|
||||
Long: `List all backup configurations for a specific database.`,
|
||||
Args: cli.ExactArgs(1, "<database_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
backups, err := dbService.ListBackups(ctx, dbUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list backups: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(backups)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewTriggerCommand triggers a database backup
|
||||
func NewTriggerCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "trigger <database_uuid> <backup_uuid>",
|
||||
Short: "Trigger immediate backup",
|
||||
Long: `Trigger an immediate backup for a specific backup configuration. First UUID is the database, second is the specific backup configuration to trigger.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
|
||||
// Trigger immediate backup by updating with backup_now flag
|
||||
req := &models.DatabaseBackupUpdateRequest{
|
||||
BackupNow: cli.BoolPtr(true),
|
||||
}
|
||||
|
||||
err = dbService.UpdateBackup(ctx, dbUUID, backupUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to trigger backup: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Immediate backup triggered successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUpdateCommand updates a database
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
updateBackupCmd := &cobra.Command{
|
||||
Use: "update <database_uuid> <backup_uuid>",
|
||||
Short: "Update backup configuration",
|
||||
Long: `Update a backup configuration settings (frequency, retention, S3, etc.). First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
req := &models.DatabaseBackupUpdateRequest{}
|
||||
hasChanges := false
|
||||
|
||||
if cmd.Flags().Changed("enabled") {
|
||||
enabled, _ := cmd.Flags().GetBool("enabled")
|
||||
req.Enabled = &enabled
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("frequency") {
|
||||
freq, _ := cmd.Flags().GetString("frequency")
|
||||
req.Frequency = &freq
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("save-s3") {
|
||||
saveS3, _ := cmd.Flags().GetBool("save-s3")
|
||||
req.SaveS3 = &saveS3
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("s3-storage-uuid") {
|
||||
s3UUID, _ := cmd.Flags().GetString("s3-storage-uuid")
|
||||
req.S3StorageUUID = &s3UUID
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("databases-to-backup") {
|
||||
dbs, _ := cmd.Flags().GetString("databases-to-backup")
|
||||
req.DatabasesToBackup = &dbs
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("dump-all") {
|
||||
dumpAll, _ := cmd.Flags().GetBool("dump-all")
|
||||
req.DumpAll = &dumpAll
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
// Retention settings
|
||||
if cmd.Flags().Changed("retention-amount-locally") {
|
||||
amount, _ := cmd.Flags().GetInt("retention-amount-locally")
|
||||
req.DatabaseBackupRetentionAmountLocally = &amount
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("retention-days-locally") {
|
||||
days, _ := cmd.Flags().GetInt("retention-days-locally")
|
||||
req.DatabaseBackupRetentionDaysLocally = &days
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("retention-max-storage-locally") {
|
||||
storage, _ := cmd.Flags().GetInt("retention-max-storage-locally")
|
||||
req.DatabaseBackupRetentionMaxStorageLocally = &storage
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("retention-amount-s3") {
|
||||
amount, _ := cmd.Flags().GetInt("retention-amount-s3")
|
||||
req.DatabaseBackupRetentionAmountS3 = &amount
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("retention-days-s3") {
|
||||
days, _ := cmd.Flags().GetInt("retention-days-s3")
|
||||
req.DatabaseBackupRetentionDaysS3 = &days
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("retention-max-storage-s3") {
|
||||
storage, _ := cmd.Flags().GetInt("retention-max-storage-s3")
|
||||
req.DatabaseBackupRetentionMaxStorageS3 = &storage
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if !hasChanges {
|
||||
return fmt.Errorf("no fields to update")
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
err = dbService.UpdateBackup(ctx, dbUUID, backupUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update backup: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Backup configuration updated successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
updateBackupCmd.Flags().Bool("enabled", false, "Enable or disable backup")
|
||||
updateBackupCmd.Flags().String("frequency", "", "Backup frequency (cron expression)")
|
||||
updateBackupCmd.Flags().Bool("save-s3", false, "Save backups to S3")
|
||||
updateBackupCmd.Flags().String("s3-storage-uuid", "", "S3 storage UUID")
|
||||
updateBackupCmd.Flags().String("databases-to-backup", "", "Comma-separated list of databases to backup")
|
||||
updateBackupCmd.Flags().Bool("dump-all", false, "Dump all databases")
|
||||
updateBackupCmd.Flags().Int("retention-amount-locally", 0, "Number of backups to retain locally")
|
||||
updateBackupCmd.Flags().Int("retention-days-locally", 0, "Days to retain backups locally")
|
||||
updateBackupCmd.Flags().Int("retention-max-storage-locally", 0, "Max storage for local backups (MB)")
|
||||
updateBackupCmd.Flags().Int("retention-amount-s3", 0, "Number of backups to retain in S3")
|
||||
updateBackupCmd.Flags().Int("retention-days-s3", 0, "Days to retain backups in S3")
|
||||
updateBackupCmd.Flags().Int("retention-max-storage-s3", 0, "Max storage for S3 backups (MB)")
|
||||
|
||||
return updateBackupCmd
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <type>",
|
||||
Short: "Create a new database",
|
||||
Long: `Create a new database of the specified type.
|
||||
|
||||
Supported types: postgresql, mysql, mariadb, mongodb, redis, keydb, clickhouse, dragonfly
|
||||
|
||||
Examples:
|
||||
coolify databases create postgresql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production
|
||||
coolify databases create mysql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --name="My MySQL"`,
|
||||
Args: cli.ExactArgs(1, "<type>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
dbType := args[0]
|
||||
validTypes := []string{"postgresql", "mysql", "mariadb", "mongodb", "redis", "keydb", "clickhouse", "dragonfly"}
|
||||
isValid := false
|
||||
for _, t := range validTypes {
|
||||
if t == dbType {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
return fmt.Errorf("invalid database type '%s'. Valid types: %s", dbType, strings.Join(validTypes, ", "))
|
||||
}
|
||||
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.DatabaseCreateRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Common flags
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
desc, _ := cmd.Flags().GetString("description")
|
||||
req.Description = &desc
|
||||
}
|
||||
if cmd.Flags().Changed("image") {
|
||||
image, _ := cmd.Flags().GetString("image")
|
||||
req.Image = &image
|
||||
}
|
||||
if cmd.Flags().Changed("destination-uuid") {
|
||||
dest, _ := cmd.Flags().GetString("destination-uuid")
|
||||
req.DestinationUUID = &dest
|
||||
}
|
||||
if cmd.Flags().Changed("instant-deploy") {
|
||||
instant, _ := cmd.Flags().GetBool("instant-deploy")
|
||||
req.InstantDeploy = &instant
|
||||
}
|
||||
if cmd.Flags().Changed("is-public") {
|
||||
isPublic, _ := cmd.Flags().GetBool("is-public")
|
||||
req.IsPublic = &isPublic
|
||||
}
|
||||
if cmd.Flags().Changed("public-port") {
|
||||
port, _ := cmd.Flags().GetInt("public-port")
|
||||
req.PublicPort = &port
|
||||
}
|
||||
|
||||
// Resource limits
|
||||
if cmd.Flags().Changed("limits-memory") {
|
||||
mem, _ := cmd.Flags().GetString("limits-memory")
|
||||
req.LimitsMemory = &mem
|
||||
}
|
||||
if cmd.Flags().Changed("limits-cpus") {
|
||||
cpus, _ := cmd.Flags().GetString("limits-cpus")
|
||||
req.LimitsCpus = &cpus
|
||||
}
|
||||
|
||||
// PostgreSQL specific
|
||||
if dbType == "postgresql" {
|
||||
if cmd.Flags().Changed("postgres-user") {
|
||||
user, _ := cmd.Flags().GetString("postgres-user")
|
||||
req.PostgresUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("postgres-password") {
|
||||
pass, _ := cmd.Flags().GetString("postgres-password")
|
||||
req.PostgresPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("postgres-db") {
|
||||
db, _ := cmd.Flags().GetString("postgres-db")
|
||||
req.PostgresDb = &db
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL specific
|
||||
if dbType == "mysql" {
|
||||
if cmd.Flags().Changed("mysql-root-password") {
|
||||
pass, _ := cmd.Flags().GetString("mysql-root-password")
|
||||
req.MysqlRootPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("mysql-user") {
|
||||
user, _ := cmd.Flags().GetString("mysql-user")
|
||||
req.MysqlUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("mysql-password") {
|
||||
pass, _ := cmd.Flags().GetString("mysql-password")
|
||||
req.MysqlPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("mysql-database") {
|
||||
db, _ := cmd.Flags().GetString("mysql-database")
|
||||
req.MysqlDatabase = &db
|
||||
}
|
||||
}
|
||||
|
||||
// MariaDB specific
|
||||
if dbType == "mariadb" {
|
||||
if cmd.Flags().Changed("mariadb-root-password") {
|
||||
pass, _ := cmd.Flags().GetString("mariadb-root-password")
|
||||
req.MariadbRootPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("mariadb-user") {
|
||||
user, _ := cmd.Flags().GetString("mariadb-user")
|
||||
req.MariadbUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("mariadb-password") {
|
||||
pass, _ := cmd.Flags().GetString("mariadb-password")
|
||||
req.MariadbPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("mariadb-database") {
|
||||
db, _ := cmd.Flags().GetString("mariadb-database")
|
||||
req.MariadbDatabase = &db
|
||||
}
|
||||
}
|
||||
|
||||
// MongoDB specific
|
||||
if dbType == "mongodb" {
|
||||
if cmd.Flags().Changed("mongo-root-username") {
|
||||
user, _ := cmd.Flags().GetString("mongo-root-username")
|
||||
req.MongoInitdbRootUsername = &user
|
||||
}
|
||||
if cmd.Flags().Changed("mongo-root-password") {
|
||||
pass, _ := cmd.Flags().GetString("mongo-root-password")
|
||||
req.MongoInitdbRootPassword = &pass
|
||||
}
|
||||
if cmd.Flags().Changed("mongo-database") {
|
||||
db, _ := cmd.Flags().GetString("mongo-database")
|
||||
req.MongoInitdbDatabase = &db
|
||||
}
|
||||
}
|
||||
|
||||
// Redis specific
|
||||
if dbType == "redis" {
|
||||
if cmd.Flags().Changed("redis-password") {
|
||||
pass, _ := cmd.Flags().GetString("redis-password")
|
||||
req.RedisPassword = &pass
|
||||
}
|
||||
}
|
||||
|
||||
// KeyDB specific
|
||||
if dbType == "keydb" {
|
||||
if cmd.Flags().Changed("keydb-password") {
|
||||
pass, _ := cmd.Flags().GetString("keydb-password")
|
||||
req.KeydbPassword = &pass
|
||||
}
|
||||
}
|
||||
|
||||
// Clickhouse specific
|
||||
if dbType == "clickhouse" {
|
||||
if cmd.Flags().Changed("clickhouse-admin-user") {
|
||||
user, _ := cmd.Flags().GetString("clickhouse-admin-user")
|
||||
req.ClickhouseAdminUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("clickhouse-admin-password") {
|
||||
pass, _ := cmd.Flags().GetString("clickhouse-admin-password")
|
||||
req.ClickhouseAdminPassword = &pass
|
||||
}
|
||||
}
|
||||
|
||||
// Dragonfly specific
|
||||
if dbType == "dragonfly" {
|
||||
if cmd.Flags().Changed("dragonfly-password") {
|
||||
pass, _ := cmd.Flags().GetString("dragonfly-password")
|
||||
req.DragonflyPassword = &pass
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
database, err := dbService.Create(ctx, dbType, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create database: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(database)
|
||||
},
|
||||
}
|
||||
|
||||
// Common flags
|
||||
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
|
||||
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
|
||||
cmd.Flags().String("environment-name", "", "Environment name")
|
||||
cmd.Flags().String("environment-uuid", "", "Environment UUID")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("name", "", "Database name")
|
||||
cmd.Flags().String("description", "", "Database description")
|
||||
cmd.Flags().String("image", "", "Docker image")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().Bool("is-public", false, "Make database publicly accessible")
|
||||
cmd.Flags().Int("public-port", 0, "Public port")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit (e.g., '512m', '2g')")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit (e.g., '0.5', '2')")
|
||||
|
||||
// PostgreSQL flags
|
||||
cmd.Flags().String("postgres-user", "", "PostgreSQL user")
|
||||
cmd.Flags().String("postgres-password", "", "PostgreSQL password")
|
||||
cmd.Flags().String("postgres-db", "", "PostgreSQL database name")
|
||||
|
||||
// MySQL flags
|
||||
cmd.Flags().String("mysql-root-password", "", "MySQL root password")
|
||||
cmd.Flags().String("mysql-user", "", "MySQL user")
|
||||
cmd.Flags().String("mysql-password", "", "MySQL password")
|
||||
cmd.Flags().String("mysql-database", "", "MySQL database name")
|
||||
|
||||
// MariaDB flags
|
||||
cmd.Flags().String("mariadb-root-password", "", "MariaDB root password")
|
||||
cmd.Flags().String("mariadb-user", "", "MariaDB user")
|
||||
cmd.Flags().String("mariadb-password", "", "MariaDB password")
|
||||
cmd.Flags().String("mariadb-database", "", "MariaDB database name")
|
||||
|
||||
// MongoDB flags
|
||||
cmd.Flags().String("mongo-root-username", "", "MongoDB root username")
|
||||
cmd.Flags().String("mongo-root-password", "", "MongoDB root password")
|
||||
cmd.Flags().String("mongo-database", "", "MongoDB database name")
|
||||
|
||||
// Redis flags
|
||||
cmd.Flags().String("redis-password", "", "Redis password")
|
||||
|
||||
// KeyDB flags
|
||||
cmd.Flags().String("keydb-password", "", "KeyDB password")
|
||||
|
||||
// Clickhouse flags
|
||||
cmd.Flags().String("clickhouse-admin-user", "", "Clickhouse admin user")
|
||||
cmd.Flags().String("clickhouse-admin-password", "", "Clickhouse admin password")
|
||||
|
||||
// Dragonfly flags
|
||||
cmd.Flags().String("dragonfly-password", "", "Dragonfly password")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/cmd/database/backup"
|
||||
)
|
||||
|
||||
// NewDatabaseCommand creates the database parent command with all subcommands
|
||||
func NewDatabaseCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "database",
|
||||
Aliases: []string{"databases", "db", "dbs"},
|
||||
Short: "Manage Coolify databases",
|
||||
Long: `Manage Coolify databases (PostgreSQL, MySQL, MongoDB, Redis, MariaDB, KeyDB, Clickhouse, Dragonfly).`,
|
||||
}
|
||||
|
||||
// Add main database commands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewStartCommand())
|
||||
cmd.AddCommand(NewStopCommand())
|
||||
cmd.AddCommand(NewRestartCommand())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
cmd.AddCommand(NewUpdateCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
|
||||
// Add backup subcommand
|
||||
backupCmd := &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Manage database backups",
|
||||
}
|
||||
backupCmd.AddCommand(backup.NewCreateCommand())
|
||||
backupCmd.AddCommand(backup.NewListCommand())
|
||||
backupCmd.AddCommand(backup.NewDeleteCommand())
|
||||
backupCmd.AddCommand(backup.NewUpdateCommand())
|
||||
backupCmd.AddCommand(backup.NewTriggerCommand())
|
||||
backupCmd.AddCommand(backup.NewExecutionCommand())
|
||||
backupCmd.AddCommand(backup.NewDeleteExecutionCommand())
|
||||
cmd.AddCommand(backupCmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a database
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
deleteDatabaseCmd := &cobra.Command{
|
||||
Use: "delete <uuid>",
|
||||
Short: "Delete a database",
|
||||
Long: `Delete a database and optionally clean up its configurations, volumes, and networks.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deleteConfigurations, _ := cmd.Flags().GetBool("delete-configurations")
|
||||
deleteVolumes, _ := cmd.Flags().GetBool("delete-volumes")
|
||||
dockerCleanup, _ := cmd.Flags().GetBool("docker-cleanup")
|
||||
deleteConnectedNetworks, _ := cmd.Flags().GetBool("delete-connected-networks")
|
||||
|
||||
if !force {
|
||||
fmt.Printf("Are you sure you want to delete database %s? (y/N): ", uuid)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
response, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading input: %w", err)
|
||||
}
|
||||
response = strings.TrimSpace(strings.ToLower(response))
|
||||
if response != "y" && response != "yes" {
|
||||
fmt.Println("Delete cancelled")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
err = dbService.Delete(ctx, uuid, deleteConfigurations, deleteVolumes, dockerCleanup, deleteConnectedNetworks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete database: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Database deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteDatabaseCmd.Flags().Bool("delete-configurations", true, "Delete configurations")
|
||||
deleteDatabaseCmd.Flags().Bool("delete-volumes", true, "Delete volumes")
|
||||
deleteDatabaseCmd.Flags().Bool("docker-cleanup", true, "Run docker cleanup")
|
||||
deleteDatabaseCmd.Flags().Bool("delete-connected-networks", true, "Delete connected networks")
|
||||
|
||||
return deleteDatabaseCmd
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand gets database details
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get database details",
|
||||
Long: `Get detailed information about a specific database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
database, err := dbService.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database: %w", err)
|
||||
}
|
||||
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
formatter, err := output.NewFormatter("table", output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(database)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all databases
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all databases",
|
||||
Long: `List all databases in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
databases, err := dbService.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list databases: %w", err)
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter("table", output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(databases)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewRestartCommand restarts a database
|
||||
func NewRestartCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "restart <uuid>",
|
||||
Short: "Restart a database",
|
||||
Long: `Restart a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
response, err := dbService.Restart(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart database: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(response.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStartCommand starts a database
|
||||
func NewStartCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "start <uuid>",
|
||||
Short: "Start a database",
|
||||
Long: `Start a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
response, err := dbService.Start(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start database: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(response.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStopCommand stops a database
|
||||
func NewStopCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "stop <uuid>",
|
||||
Short: "Stop a database",
|
||||
Long: `Stop a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
response, err := dbService.Stop(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop database: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(response.Message)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUpdateCommand updates a database
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
updateDatabaseCmd := &cobra.Command{
|
||||
Use: "update <uuid>",
|
||||
Short: "Update a database",
|
||||
Long: `Update a database's configuration by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
req := &models.DatabaseUpdateRequest{}
|
||||
hasChanges := false
|
||||
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("description") {
|
||||
desc, _ := cmd.Flags().GetString("description")
|
||||
req.Description = &desc
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("image") {
|
||||
image, _ := cmd.Flags().GetString("image")
|
||||
req.Image = &image
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("is-public") {
|
||||
isPublic, _ := cmd.Flags().GetBool("is-public")
|
||||
req.IsPublic = &isPublic
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("public-port") {
|
||||
port, _ := cmd.Flags().GetInt("public-port")
|
||||
req.PublicPort = &port
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
// Resource limits
|
||||
if cmd.Flags().Changed("limits-memory") {
|
||||
mem, _ := cmd.Flags().GetString("limits-memory")
|
||||
req.LimitsMemory = &mem
|
||||
hasChanges = true
|
||||
}
|
||||
if cmd.Flags().Changed("limits-cpus") {
|
||||
cpus, _ := cmd.Flags().GetString("limits-cpus")
|
||||
req.LimitsCpus = &cpus
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if !hasChanges {
|
||||
return fmt.Errorf("no fields to update")
|
||||
}
|
||||
|
||||
// Validate is-public requires public-port
|
||||
if req.IsPublic != nil && *req.IsPublic {
|
||||
// If setting to public, check if port is provided or fetch current database to check existing port
|
||||
if req.PublicPort == nil || *req.PublicPort == 0 {
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
currentDB, err := dbService.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current database: %w", err)
|
||||
}
|
||||
|
||||
// Check if database already has a public port
|
||||
if currentDB.PublicPort == nil || *currentDB.PublicPort == 0 {
|
||||
return fmt.Errorf("cannot set database as public without a public port. Please provide --public-port")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
dbService := service.NewDatabaseService(client)
|
||||
err = dbService.Update(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update database: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Database updated successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
updateDatabaseCmd.Flags().String("name", "", "Database name")
|
||||
updateDatabaseCmd.Flags().String("description", "", "Database description")
|
||||
updateDatabaseCmd.Flags().String("image", "", "Docker image")
|
||||
updateDatabaseCmd.Flags().Bool("is-public", false, "Make database publicly accessible")
|
||||
updateDatabaseCmd.Flags().Int("public-port", 0, "Public port")
|
||||
updateDatabaseCmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
updateDatabaseCmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
|
||||
return updateDatabaseCmd
|
||||
}
|
||||
-1012
File diff suppressed because it is too large
Load Diff
-357
@@ -1,357 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deployCmd = &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy related commands",
|
||||
}
|
||||
|
||||
// DeployResultDisplay represents a deploy result for table display
|
||||
type DeployResultDisplay struct {
|
||||
Message string `json:"message"`
|
||||
DeploymentUUID string `json:"deployment_uuid"`
|
||||
}
|
||||
|
||||
var deployByUuidCmd = &cobra.Command{
|
||||
Use: "uuid <uuid>",
|
||||
Short: "Deploy by uuid",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Deploy(ctx, uuid, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to deploy resource: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For table format, convert deployment info array to display format
|
||||
if format == output.FormatTable {
|
||||
displays := make([]DeployResultDisplay, len(result.Deployments))
|
||||
for i, dep := range result.Deployments {
|
||||
displays[i] = DeployResultDisplay{
|
||||
Message: dep.Message,
|
||||
DeploymentUUID: dep.DeploymentUUID,
|
||||
}
|
||||
}
|
||||
return formatter.Format(displays)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
var deployByNameCmd = &cobra.Command{
|
||||
Use: "name <name>",
|
||||
Short: "Deploy by resource name",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
name := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Find resource by name
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
var matchedUUID string
|
||||
for _, r := range resources {
|
||||
if r.Name == name {
|
||||
matchedUUID = r.UUID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchedUUID == "" {
|
||||
return fmt.Errorf("resource with name '%s' not found", name)
|
||||
}
|
||||
|
||||
// Deploy using the found UUID
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Deploy(ctx, matchedUUID, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to deploy resource: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For table format, convert deployment info array to display format
|
||||
if format == output.FormatTable {
|
||||
displays := make([]DeployResultDisplay, len(result.Deployments))
|
||||
for i, dep := range result.Deployments {
|
||||
displays[i] = DeployResultDisplay{
|
||||
Message: dep.Message,
|
||||
DeploymentUUID: dep.DeploymentUUID,
|
||||
}
|
||||
}
|
||||
return formatter.Format(displays)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
var deployBatchCmd = &cobra.Command{
|
||||
Use: "batch <name1,name2,...>",
|
||||
Short: "Deploy multiple resources by name",
|
||||
Long: `Deploy multiple resources at once.
|
||||
Provide resource names as comma-separated values.
|
||||
Example: coolify deploy batch app1,app2,app3`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
namesStr := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Parse comma-separated names
|
||||
names := make([]string, 0)
|
||||
for _, name := range strings.Split(namesStr, ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
if name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return fmt.Errorf("no resource names provided")
|
||||
}
|
||||
|
||||
// Find resources by name
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
// Build map of name -> UUID
|
||||
nameToUUID := make(map[string]string)
|
||||
for _, r := range resources {
|
||||
nameToUUID[r.Name] = r.UUID
|
||||
}
|
||||
|
||||
// Validate all names exist
|
||||
var notFound []string
|
||||
for _, name := range names {
|
||||
if _, exists := nameToUUID[name]; !exists {
|
||||
notFound = append(notFound, name)
|
||||
}
|
||||
}
|
||||
if len(notFound) > 0 {
|
||||
return fmt.Errorf("resources not found: %v", notFound)
|
||||
}
|
||||
|
||||
// Deploy all resources
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
|
||||
type result struct {
|
||||
Name string
|
||||
UUID string
|
||||
Success bool
|
||||
Message string
|
||||
Error string
|
||||
}
|
||||
|
||||
results := make([]result, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
uuid := nameToUUID[name]
|
||||
fmt.Printf("Deploying %s...\n", name)
|
||||
|
||||
res, err := deploySvc.Deploy(ctx, uuid, force)
|
||||
if err != nil {
|
||||
results = append(results, result{
|
||||
Name: name,
|
||||
UUID: uuid,
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
fmt.Printf(" ❌ Failed: %v\n", err)
|
||||
} else {
|
||||
// Get first deployment message from the array
|
||||
message := ""
|
||||
if len(res.Deployments) > 0 {
|
||||
message = res.Deployments[0].Message
|
||||
}
|
||||
results = append(results, result{
|
||||
Name: name,
|
||||
UUID: uuid,
|
||||
Success: true,
|
||||
Message: message,
|
||||
})
|
||||
fmt.Printf(" ✅ Success: %s\n", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
successCount := 0
|
||||
for _, r := range results {
|
||||
if r.Success {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nBatch deployment complete: %d/%d succeeded\n", successCount, len(results))
|
||||
|
||||
if successCount < len(results) {
|
||||
return fmt.Errorf("some deployments failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var listDeploymentsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all deployments",
|
||||
Long: `List all currently running deployments across all resources.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
deployments, err := deploySvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list deployments: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(deployments)
|
||||
},
|
||||
}
|
||||
|
||||
var getDeploymentCmd = &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get deployment details by UUID",
|
||||
Long: `Get detailed information about a specific deployment by its UUID.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
deployment, err := deploySvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get deployment: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(deployment)
|
||||
},
|
||||
}
|
||||
|
||||
var cancelDeploymentCmd = &cobra.Command{
|
||||
Use: "cancel <uuid>",
|
||||
Short: "Cancel a deployment by UUID",
|
||||
Long: `Cancel an in-progress deployment. This will stop the deployment process and clean up any temporary resources.`,
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to cancel deployment %s? (yes/no): ", uuid)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Cancel aborted.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Cancel(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to cancel deployment: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
deployByUuidCmd.Flags().Bool("force", false, "Force deployment")
|
||||
deployByNameCmd.Flags().Bool("force", false, "Force deployment")
|
||||
deployBatchCmd.Flags().Bool("force", false, "Force deployment")
|
||||
cancelDeploymentCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
|
||||
rootCmd.AddCommand(deployCmd)
|
||||
deployCmd.AddCommand(deployByUuidCmd)
|
||||
deployCmd.AddCommand(deployByNameCmd)
|
||||
deployCmd.AddCommand(deployBatchCmd)
|
||||
deployCmd.AddCommand(listDeploymentsCmd)
|
||||
deployCmd.AddCommand(getDeploymentCmd)
|
||||
deployCmd.AddCommand(cancelDeploymentCmd)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewBatchCommand deploys multiple resources by name
|
||||
func NewBatchCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "batch <name1,name2,...>",
|
||||
Short: "Deploy multiple resources by name",
|
||||
Long: `Deploy multiple resources at once.
|
||||
Provide resource names as comma-separated values.
|
||||
Example: coolify deploy batch app1,app2,app3`,
|
||||
Args: cli.ExactArgs(1, "<names>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
namesStr := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Parse comma-separated names
|
||||
names := make([]string, 0)
|
||||
for _, name := range strings.Split(namesStr, ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
if name != "" {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return fmt.Errorf("no resource names provided")
|
||||
}
|
||||
|
||||
// Find resources by name
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
// Build map of name -> UUID
|
||||
nameToUUID := make(map[string]string)
|
||||
for _, r := range resources {
|
||||
nameToUUID[r.Name] = r.UUID
|
||||
}
|
||||
|
||||
// Validate all names exist
|
||||
var notFound []string
|
||||
for _, name := range names {
|
||||
if _, exists := nameToUUID[name]; !exists {
|
||||
notFound = append(notFound, name)
|
||||
}
|
||||
}
|
||||
if len(notFound) > 0 {
|
||||
return fmt.Errorf("resources not found: %v", notFound)
|
||||
}
|
||||
|
||||
// Deploy all resources
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
|
||||
type result struct {
|
||||
Name string
|
||||
UUID string
|
||||
Success bool
|
||||
Message string
|
||||
Error string
|
||||
}
|
||||
|
||||
results := make([]result, 0, len(names))
|
||||
|
||||
for _, name := range names {
|
||||
uuid := nameToUUID[name]
|
||||
fmt.Printf("Deploying %s...\n", name)
|
||||
|
||||
res, err := deploySvc.Deploy(ctx, uuid, force)
|
||||
if err != nil {
|
||||
results = append(results, result{
|
||||
Name: name,
|
||||
UUID: uuid,
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
fmt.Printf(" ❌ Failed: %v\n", err)
|
||||
} else {
|
||||
// Get first deployment message from the array
|
||||
message := ""
|
||||
if len(res.Deployments) > 0 {
|
||||
message = res.Deployments[0].Message
|
||||
}
|
||||
results = append(results, result{
|
||||
Name: name,
|
||||
UUID: uuid,
|
||||
Success: true,
|
||||
Message: message,
|
||||
})
|
||||
fmt.Printf(" ✅ Success: %s\n", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
successCount := 0
|
||||
for _, r := range results {
|
||||
if r.Success {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nBatch deployment complete: %d/%d succeeded\n", successCount, len(results))
|
||||
|
||||
if successCount < len(results) {
|
||||
return fmt.Errorf("some deployments failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("force", false, "Force deployment")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCancelCommand cancels a deployment
|
||||
func NewCancelCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cancel <uuid>",
|
||||
Short: "Cancel a deployment by UUID",
|
||||
Long: `Cancel an in-progress deployment. This will stop the deployment process and clean up any temporary resources.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to cancel deployment %s? (yes/no): ", uuid)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Cancel aborted.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Cancel(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to cancel deployment: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package deployment
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewDeploymentCommand creates the deployment parent command with all subcommands
|
||||
func NewDeploymentCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy",
|
||||
Short: "Deploy related commands",
|
||||
}
|
||||
|
||||
// Add all deployment subcommands
|
||||
cmd.AddCommand(NewUUIDCommand())
|
||||
cmd.AddCommand(NewNameCommand())
|
||||
cmd.AddCommand(NewBatchCommand())
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewCancelCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand gets deployment details
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get deployment details by UUID",
|
||||
Long: `Get detailed information about a specific deployment by its UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
deployment, err := deploySvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get deployment: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(deployment)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all deployments
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all deployments",
|
||||
Long: `List all currently running deployments across all resources.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
deployments, err := deploySvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list deployments: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(deployments)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewNameCommand deploys a resource by name
|
||||
func NewNameCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "name <resource_name>",
|
||||
Short: "Deploy by resource name",
|
||||
Args: cli.ExactArgs(1, "<resource_name>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
name := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Find resource by name
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
var matchedUUID string
|
||||
for _, r := range resources {
|
||||
if r.Name == name {
|
||||
matchedUUID = r.UUID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matchedUUID == "" {
|
||||
return fmt.Errorf("resource with name '%s' not found", name)
|
||||
}
|
||||
|
||||
// Deploy using the found UUID
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Deploy(ctx, matchedUUID, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to deploy resource: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For table format, convert deployment info array to display format
|
||||
if format == output.FormatTable {
|
||||
displays := make([]ResultDisplay, len(result.Deployments))
|
||||
for i, dep := range result.Deployments {
|
||||
displays[i] = ResultDisplay{
|
||||
Message: dep.Message,
|
||||
DeploymentUUID: dep.DeploymentUUID,
|
||||
}
|
||||
}
|
||||
return formatter.Format(displays)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("force", false, "Force deployment")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ResultDisplay represents a deploy result for table display
|
||||
type ResultDisplay struct {
|
||||
Message string `json:"message"`
|
||||
DeploymentUUID string `json:"deployment_uuid"`
|
||||
}
|
||||
|
||||
// NewUUIDCommand deploys a resource by UUID
|
||||
func NewUUIDCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "uuid <uuid>",
|
||||
Short: "Deploy by uuid",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
result, err := deploySvc.Deploy(ctx, uuid, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to deploy resource: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For table format, convert deployment info array to display format
|
||||
if format == output.FormatTable {
|
||||
displays := make([]ResultDisplay, len(result.Deployments))
|
||||
for i, dep := range result.Deployments {
|
||||
displays[i] = ResultDisplay{
|
||||
Message: dep.Message,
|
||||
DeploymentUUID: dep.DeploymentUUID,
|
||||
}
|
||||
}
|
||||
return formatter.Format(displays)
|
||||
}
|
||||
|
||||
return formatter.Format(result)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("force", false, "Force deployment")
|
||||
return cmd
|
||||
}
|
||||
+7
-4
@@ -35,7 +35,7 @@ The man pages will be written to the specified directory (default: ./man).`,
|
||||
header := &doc.GenManHeader{
|
||||
Title: "COOLIFY",
|
||||
Section: "1",
|
||||
Source: "Coolify CLI " + Version,
|
||||
Source: "Coolify CLI",
|
||||
}
|
||||
|
||||
if err := doc.GenManTree(rootCmd, header, outputDir); err != nil {
|
||||
@@ -56,7 +56,9 @@ The man pages will be written to the specified directory (default: ./man).`,
|
||||
}
|
||||
|
||||
var markdownCmd = &cobra.Command{
|
||||
Use: "markdown",
|
||||
Use: "markdown",
|
||||
Aliases: []string{"md"},
|
||||
|
||||
Short: "Generate markdown documentation",
|
||||
Long: `Generate markdown documentation for all Coolify CLI commands.
|
||||
|
||||
@@ -83,11 +85,12 @@ The markdown files will be written to the specified directory (default: ./docs).
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(docsCmd)
|
||||
func NewDocsCommand() *cobra.Command {
|
||||
docsCmd.AddCommand(manCmd)
|
||||
docsCmd.AddCommand(markdownCmd)
|
||||
|
||||
manCmd.Flags().StringP("output-dir", "o", "./man", "Output directory for man pages")
|
||||
markdownCmd.Flags().StringP("output-dir", "o", "./docs", "Output directory for markdown files")
|
||||
|
||||
return docsCmd
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var domainsCmd = &cobra.Command{
|
||||
Use: "domain",
|
||||
Aliases: []string{"domains"},
|
||||
Short: "Domain related commands",
|
||||
Long: `List all domains configured across your Coolify resources.`,
|
||||
}
|
||||
|
||||
var listDomainsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all domains",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
domainSvc := service.NewDomainService(client)
|
||||
domains, err := domainSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list domains: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(domains)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(domainsCmd)
|
||||
domainsCmd.AddCommand(listDomainsCmd)
|
||||
}
|
||||
-393
@@ -1,393 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var githubCmd = &cobra.Command{
|
||||
Use: "github",
|
||||
Aliases: []string{"gh", "github-app", "github-apps"},
|
||||
Short: "Manage GitHub App integrations",
|
||||
Long: `Manage GitHub App integrations for private repository deployments.`,
|
||||
}
|
||||
|
||||
var listGitHubAppsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all GitHub App integrations",
|
||||
Long: `List all GitHub App integrations configured in your Coolify instance.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
apps, err := svc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list GitHub Apps: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(apps)
|
||||
},
|
||||
}
|
||||
|
||||
var getGitHubAppCmd = &cobra.Command{
|
||||
Use: "get <app_uuid>",
|
||||
Short: "Get GitHub App details by UUID",
|
||||
Long: `Get detailed information about a specific GitHub App integration.`,
|
||||
Args: exactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
app, err := svc.Get(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get GitHub App: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
var createGitHubAppCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a GitHub App integration",
|
||||
Long: `Create a new GitHub App integration. This allows you to deploy private repositories from GitHub.
|
||||
|
||||
Required flags: --name, --api-url, --html-url, --app-id, --installation-id, --client-id, --client-secret, --private-key-uuid
|
||||
|
||||
Example: coolify github create --name "My GitHub App" --api-url "https://api.github.com" --html-url "https://github.com" --app-id 123456 --installation-id 789012 --client-id "Iv1.abc123" --client-secret "secret123" --private-key-uuid "abc-123-def-456"`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
apiURL, _ := cmd.Flags().GetString("api-url")
|
||||
htmlURL, _ := cmd.Flags().GetString("html-url")
|
||||
appID, _ := cmd.Flags().GetInt("app-id")
|
||||
installationID, _ := cmd.Flags().GetInt("installation-id")
|
||||
clientID, _ := cmd.Flags().GetString("client-id")
|
||||
clientSecret, _ := cmd.Flags().GetString("client-secret")
|
||||
privateKeyUUID, _ := cmd.Flags().GetString("private-key-uuid")
|
||||
|
||||
req := &models.GitHubAppCreateRequest{
|
||||
Name: name,
|
||||
APIURL: apiURL,
|
||||
HTMLURL: htmlURL,
|
||||
AppID: appID,
|
||||
InstallationID: installationID,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
PrivateKeyUUID: privateKeyUUID,
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
if cmd.Flags().Changed("organization") {
|
||||
org, _ := cmd.Flags().GetString("organization")
|
||||
req.Organization = &org
|
||||
}
|
||||
if cmd.Flags().Changed("custom-user") {
|
||||
user, _ := cmd.Flags().GetString("custom-user")
|
||||
req.CustomUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("custom-port") {
|
||||
port, _ := cmd.Flags().GetInt("custom-port")
|
||||
req.CustomPort = &port
|
||||
}
|
||||
if cmd.Flags().Changed("webhook-secret") {
|
||||
secret, _ := cmd.Flags().GetString("webhook-secret")
|
||||
req.WebhookSecret = &secret
|
||||
}
|
||||
if cmd.Flags().Changed("system-wide") {
|
||||
systemWide, _ := cmd.Flags().GetBool("system-wide")
|
||||
req.IsSystemWide = &systemWide
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
app, err := svc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create GitHub App: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
var updateGitHubAppCmd = &cobra.Command{
|
||||
Use: "update <app_uuid>",
|
||||
Short: "Update a GitHub App integration",
|
||||
Long: `Update an existing GitHub App integration. Provide the app UUID and the fields you want to update.`,
|
||||
Args: exactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.GitHubAppUpdateRequest{}
|
||||
|
||||
// Update only fields that were explicitly provided
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
}
|
||||
if cmd.Flags().Changed("organization") {
|
||||
org, _ := cmd.Flags().GetString("organization")
|
||||
req.Organization = &org
|
||||
}
|
||||
if cmd.Flags().Changed("api-url") {
|
||||
apiURL, _ := cmd.Flags().GetString("api-url")
|
||||
req.APIURL = &apiURL
|
||||
}
|
||||
if cmd.Flags().Changed("html-url") {
|
||||
htmlURL, _ := cmd.Flags().GetString("html-url")
|
||||
req.HTMLURL = &htmlURL
|
||||
}
|
||||
if cmd.Flags().Changed("custom-user") {
|
||||
user, _ := cmd.Flags().GetString("custom-user")
|
||||
req.CustomUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("custom-port") {
|
||||
port, _ := cmd.Flags().GetInt("custom-port")
|
||||
req.CustomPort = &port
|
||||
}
|
||||
if cmd.Flags().Changed("app-id") {
|
||||
id, _ := cmd.Flags().GetInt("app-id")
|
||||
req.AppID = &id
|
||||
}
|
||||
if cmd.Flags().Changed("installation-id") {
|
||||
id, _ := cmd.Flags().GetInt("installation-id")
|
||||
req.InstallationID = &id
|
||||
}
|
||||
if cmd.Flags().Changed("client-id") {
|
||||
clientID, _ := cmd.Flags().GetString("client-id")
|
||||
req.ClientID = &clientID
|
||||
}
|
||||
if cmd.Flags().Changed("client-secret") {
|
||||
clientSecret, _ := cmd.Flags().GetString("client-secret")
|
||||
req.ClientSecret = &clientSecret
|
||||
}
|
||||
if cmd.Flags().Changed("webhook-secret") {
|
||||
secret, _ := cmd.Flags().GetString("webhook-secret")
|
||||
req.WebhookSecret = &secret
|
||||
}
|
||||
if cmd.Flags().Changed("private-key-uuid") {
|
||||
uuid, _ := cmd.Flags().GetString("private-key-uuid")
|
||||
req.PrivateKeyUUID = &uuid
|
||||
}
|
||||
if cmd.Flags().Changed("system-wide") {
|
||||
systemWide, _ := cmd.Flags().GetBool("system-wide")
|
||||
req.IsSystemWide = &systemWide
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
err = svc.Update(ctx, appUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update GitHub App: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("GitHub App updated successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var deleteGitHubAppCmd = &cobra.Command{
|
||||
Use: "delete <app_uuid>",
|
||||
Short: "Delete a GitHub App integration",
|
||||
Long: `Delete a GitHub App integration. The app must not be used by any applications.`,
|
||||
Args: exactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete GitHub App %s? This cannot be undone. (yes/no): ", appUUID)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
err = svc.Delete(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete GitHub App: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("GitHub App deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var listRepositoriesCmd = &cobra.Command{
|
||||
Use: "repos <app_uuid>",
|
||||
Short: "List repositories accessible by a GitHub App",
|
||||
Long: `List all repositories that are accessible by the specified GitHub App.`,
|
||||
Args: exactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
repos, err := svc.ListRepositories(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list repositories: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(repos)
|
||||
},
|
||||
}
|
||||
|
||||
var listBranchesCmd = &cobra.Command{
|
||||
Use: "branches <app_uuid> <owner/repo>",
|
||||
Short: "List branches for a repository",
|
||||
Long: `List all branches for a specific repository. Provide the app UUID and repository in owner/repo format.
|
||||
|
||||
Example: coolify github branches abc-123-def owner/repository`,
|
||||
Args: exactArgs(2, "<app_uuid> <owner/repo>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
// Parse owner/repo
|
||||
ownerRepo := args[1]
|
||||
parts := splitOwnerRepo(ownerRepo)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid repository format. Expected 'owner/repo', got '%s'", ownerRepo)
|
||||
}
|
||||
owner, repo := parts[0], parts[1]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
branches, err := svc.ListBranches(ctx, appUUID, owner, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list branches: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(branches)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Create command flags
|
||||
createGitHubAppCmd.Flags().String("name", "", "GitHub App name (required)")
|
||||
createGitHubAppCmd.Flags().String("organization", "", "GitHub organization")
|
||||
createGitHubAppCmd.Flags().String("api-url", "", "GitHub API URL (required, e.g., https://api.github.com)")
|
||||
createGitHubAppCmd.Flags().String("html-url", "", "GitHub HTML URL (required, e.g., https://github.com)")
|
||||
createGitHubAppCmd.Flags().String("custom-user", "", "Custom user for SSH (default: git)")
|
||||
createGitHubAppCmd.Flags().Int("custom-port", 0, "Custom port for SSH (default: 22)")
|
||||
createGitHubAppCmd.Flags().Int("app-id", 0, "GitHub App ID (required)")
|
||||
createGitHubAppCmd.Flags().Int("installation-id", 0, "GitHub Installation ID (required)")
|
||||
createGitHubAppCmd.Flags().String("client-id", "", "GitHub OAuth Client ID (required)")
|
||||
createGitHubAppCmd.Flags().String("client-secret", "", "GitHub OAuth Client Secret (required)")
|
||||
createGitHubAppCmd.Flags().String("webhook-secret", "", "GitHub Webhook Secret")
|
||||
createGitHubAppCmd.Flags().String("private-key-uuid", "", "UUID of existing private key (required)")
|
||||
createGitHubAppCmd.Flags().Bool("system-wide", false, "Is this app system-wide (cloud only)")
|
||||
|
||||
createGitHubAppCmd.MarkFlagRequired("name")
|
||||
createGitHubAppCmd.MarkFlagRequired("api-url")
|
||||
createGitHubAppCmd.MarkFlagRequired("html-url")
|
||||
createGitHubAppCmd.MarkFlagRequired("app-id")
|
||||
createGitHubAppCmd.MarkFlagRequired("installation-id")
|
||||
createGitHubAppCmd.MarkFlagRequired("client-id")
|
||||
createGitHubAppCmd.MarkFlagRequired("client-secret")
|
||||
createGitHubAppCmd.MarkFlagRequired("private-key-uuid")
|
||||
|
||||
// Update command flags (all optional)
|
||||
updateGitHubAppCmd.Flags().String("name", "", "GitHub App name")
|
||||
updateGitHubAppCmd.Flags().String("organization", "", "GitHub organization")
|
||||
updateGitHubAppCmd.Flags().String("api-url", "", "GitHub API URL")
|
||||
updateGitHubAppCmd.Flags().String("html-url", "", "GitHub HTML URL")
|
||||
updateGitHubAppCmd.Flags().String("custom-user", "", "Custom user for SSH")
|
||||
updateGitHubAppCmd.Flags().Int("custom-port", 0, "Custom port for SSH")
|
||||
updateGitHubAppCmd.Flags().Int("app-id", 0, "GitHub App ID")
|
||||
updateGitHubAppCmd.Flags().Int("installation-id", 0, "GitHub Installation ID")
|
||||
updateGitHubAppCmd.Flags().String("client-id", "", "GitHub OAuth Client ID")
|
||||
updateGitHubAppCmd.Flags().String("client-secret", "", "GitHub OAuth Client Secret")
|
||||
updateGitHubAppCmd.Flags().String("webhook-secret", "", "GitHub Webhook Secret")
|
||||
updateGitHubAppCmd.Flags().String("private-key-uuid", "", "UUID of private key")
|
||||
updateGitHubAppCmd.Flags().Bool("system-wide", false, "Is this app system-wide")
|
||||
|
||||
// Delete command flags
|
||||
deleteGitHubAppCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
|
||||
rootCmd.AddCommand(githubCmd)
|
||||
githubCmd.AddCommand(listGitHubAppsCmd)
|
||||
githubCmd.AddCommand(getGitHubAppCmd)
|
||||
githubCmd.AddCommand(createGitHubAppCmd)
|
||||
githubCmd.AddCommand(updateGitHubAppCmd)
|
||||
githubCmd.AddCommand(deleteGitHubAppCmd)
|
||||
githubCmd.AddCommand(listRepositoriesCmd)
|
||||
githubCmd.AddCommand(listBranchesCmd)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListBranchesCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "branches <app_uuid> <owner/repo>",
|
||||
Short: "List branches for a repository",
|
||||
Long: `List all branches for a specific repository. Provide the app UUID and repository in owner/repo format.
|
||||
|
||||
Example: coolify github branches abc-123-def owner/repository`,
|
||||
Args: cli.ExactArgs(2, "<app_uuid> <owner/repo>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
// Parse owner/repo
|
||||
ownerRepo := args[1]
|
||||
parts := cli.SplitOwnerRepo(ownerRepo)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid repository format. Expected 'owner/repo', got '%s'", ownerRepo)
|
||||
}
|
||||
owner, repo := parts[0], parts[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
branches, err := svc.ListBranches(ctx, appUUID, owner, repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list branches: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(branches)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
createCmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a GitHub App integration",
|
||||
Long: `Create a new GitHub App integration. This allows you to deploy private repositories from GitHub.
|
||||
|
||||
Required flags: --name, --api-url, --html-url, --app-id, --installation-id, --client-id, --client-secret, --private-key-uuid
|
||||
|
||||
Example: coolify github create --name "My GitHub App" --api-url "https://api.github.com" --html-url "https://github.com" --app-id 123456 --installation-id 789012 --client-id "Iv1.abc123" --client-secret "secret123" --private-key-uuid "abc-123-def-456"`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
apiURL, _ := cmd.Flags().GetString("api-url")
|
||||
htmlURL, _ := cmd.Flags().GetString("html-url")
|
||||
appID, _ := cmd.Flags().GetInt("app-id")
|
||||
installationID, _ := cmd.Flags().GetInt("installation-id")
|
||||
clientID, _ := cmd.Flags().GetString("client-id")
|
||||
clientSecret, _ := cmd.Flags().GetString("client-secret")
|
||||
privateKeyUUID, _ := cmd.Flags().GetString("private-key-uuid")
|
||||
|
||||
req := &models.GitHubAppCreateRequest{
|
||||
Name: name,
|
||||
APIURL: apiURL,
|
||||
HTMLURL: htmlURL,
|
||||
AppID: appID,
|
||||
InstallationID: installationID,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
PrivateKeyUUID: privateKeyUUID,
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
if cmd.Flags().Changed("organization") {
|
||||
org, _ := cmd.Flags().GetString("organization")
|
||||
req.Organization = &org
|
||||
}
|
||||
if cmd.Flags().Changed("custom-user") {
|
||||
user, _ := cmd.Flags().GetString("custom-user")
|
||||
req.CustomUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("custom-port") {
|
||||
port, _ := cmd.Flags().GetInt("custom-port")
|
||||
req.CustomPort = &port
|
||||
}
|
||||
if cmd.Flags().Changed("webhook-secret") {
|
||||
secret, _ := cmd.Flags().GetString("webhook-secret")
|
||||
req.WebhookSecret = &secret
|
||||
}
|
||||
if cmd.Flags().Changed("system-wide") {
|
||||
systemWide, _ := cmd.Flags().GetBool("system-wide")
|
||||
req.IsSystemWide = &systemWide
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
app, err := svc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create GitHub App: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
|
||||
createCmd.Flags().String("name", "", "GitHub App name (required)")
|
||||
createCmd.Flags().String("organization", "", "GitHub organization")
|
||||
createCmd.Flags().String("api-url", "", "GitHub API URL (required, e.g., https://api.github.com)")
|
||||
createCmd.Flags().String("html-url", "", "GitHub HTML URL (required, e.g., https://github.com)")
|
||||
createCmd.Flags().String("custom-user", "", "Custom user for SSH (default: git)")
|
||||
createCmd.Flags().Int("custom-port", 0, "Custom port for SSH (default: 22)")
|
||||
createCmd.Flags().Int("app-id", 0, "GitHub App ID (required)")
|
||||
createCmd.Flags().Int("installation-id", 0, "GitHub Installation ID (required)")
|
||||
createCmd.Flags().String("client-id", "", "GitHub OAuth Client ID (required)")
|
||||
createCmd.Flags().String("client-secret", "", "GitHub OAuth Client Secret (required)")
|
||||
createCmd.Flags().String("webhook-secret", "", "GitHub Webhook Secret")
|
||||
createCmd.Flags().String("private-key-uuid", "", "UUID of existing private key (required)")
|
||||
createCmd.Flags().Bool("system-wide", false, "Is this app system-wide (cloud only)")
|
||||
|
||||
createCmd.MarkFlagRequired("name")
|
||||
createCmd.MarkFlagRequired("api-url")
|
||||
createCmd.MarkFlagRequired("html-url")
|
||||
createCmd.MarkFlagRequired("app-id")
|
||||
createCmd.MarkFlagRequired("installation-id")
|
||||
createCmd.MarkFlagRequired("client-id")
|
||||
createCmd.MarkFlagRequired("client-secret")
|
||||
createCmd.MarkFlagRequired("private-key-uuid")
|
||||
|
||||
return createCmd
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
deleteCmd := &cobra.Command{
|
||||
Use: "delete <app_uuid>",
|
||||
Short: "Delete a GitHub App integration",
|
||||
Long: `Delete a GitHub App integration. The app must not be used by any applications.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete GitHub App %s? This cannot be undone. (yes/no): ", appUUID)
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
err = svc.Delete(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete GitHub App: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("GitHub App deleted successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
deleteCmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
|
||||
return deleteCmd
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <app_uuid>",
|
||||
Short: "Get GitHub App details by UUID",
|
||||
Long: `Get detailed information about a specific GitHub App integration.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
app, err := svc.Get(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get GitHub App: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(app)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGitHubCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "github",
|
||||
Aliases: []string{"gh", "github-app", "github-apps"},
|
||||
Short: "Manage GitHub App integrations",
|
||||
Long: `Manage GitHub App integrations for private repository deployments.`,
|
||||
}
|
||||
|
||||
// Add main database commands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
cmd.AddCommand(NewUpdateCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
cmd.AddCommand(NewListRepositoriesCommand())
|
||||
cmd.AddCommand(NewListBranchesCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all GitHub App integrations",
|
||||
Long: `List all GitHub App integrations configured in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
apps, err := svc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list GitHub Apps: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(apps)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListRepositoriesCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "repos <app_uuid>",
|
||||
Short: "List repositories accessible by a GitHub App",
|
||||
Long: `List all repositories that are accessible by the specified GitHub App.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
repos, err := svc.ListRepositories(ctx, appUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list repositories: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(repos)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
updateCmd := &cobra.Command{
|
||||
Use: "update <app_uuid>",
|
||||
Short: "Update a GitHub App integration",
|
||||
Long: `Update an existing GitHub App integration. Provide the app UUID and the fields you want to update.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.GitHubAppUpdateRequest{}
|
||||
|
||||
// Update only fields that were explicitly provided
|
||||
if cmd.Flags().Changed("name") {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
req.Name = &name
|
||||
}
|
||||
if cmd.Flags().Changed("organization") {
|
||||
org, _ := cmd.Flags().GetString("organization")
|
||||
req.Organization = &org
|
||||
}
|
||||
if cmd.Flags().Changed("api-url") {
|
||||
apiURL, _ := cmd.Flags().GetString("api-url")
|
||||
req.APIURL = &apiURL
|
||||
}
|
||||
if cmd.Flags().Changed("html-url") {
|
||||
htmlURL, _ := cmd.Flags().GetString("html-url")
|
||||
req.HTMLURL = &htmlURL
|
||||
}
|
||||
if cmd.Flags().Changed("custom-user") {
|
||||
user, _ := cmd.Flags().GetString("custom-user")
|
||||
req.CustomUser = &user
|
||||
}
|
||||
if cmd.Flags().Changed("custom-port") {
|
||||
port, _ := cmd.Flags().GetInt("custom-port")
|
||||
req.CustomPort = &port
|
||||
}
|
||||
if cmd.Flags().Changed("app-id") {
|
||||
id, _ := cmd.Flags().GetInt("app-id")
|
||||
req.AppID = &id
|
||||
}
|
||||
if cmd.Flags().Changed("installation-id") {
|
||||
id, _ := cmd.Flags().GetInt("installation-id")
|
||||
req.InstallationID = &id
|
||||
}
|
||||
if cmd.Flags().Changed("client-id") {
|
||||
clientID, _ := cmd.Flags().GetString("client-id")
|
||||
req.ClientID = &clientID
|
||||
}
|
||||
if cmd.Flags().Changed("client-secret") {
|
||||
clientSecret, _ := cmd.Flags().GetString("client-secret")
|
||||
req.ClientSecret = &clientSecret
|
||||
}
|
||||
if cmd.Flags().Changed("webhook-secret") {
|
||||
secret, _ := cmd.Flags().GetString("webhook-secret")
|
||||
req.WebhookSecret = &secret
|
||||
}
|
||||
if cmd.Flags().Changed("private-key-uuid") {
|
||||
uuid, _ := cmd.Flags().GetString("private-key-uuid")
|
||||
req.PrivateKeyUUID = &uuid
|
||||
}
|
||||
if cmd.Flags().Changed("system-wide") {
|
||||
systemWide, _ := cmd.Flags().GetBool("system-wide")
|
||||
req.IsSystemWide = &systemWide
|
||||
}
|
||||
|
||||
svc := service.NewGitHubAppService(client)
|
||||
err = svc.Update(ctx, appUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update GitHub App: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("GitHub App updated successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
updateCmd.Flags().String("name", "", "GitHub App name")
|
||||
updateCmd.Flags().String("organization", "", "GitHub organization")
|
||||
updateCmd.Flags().String("api-url", "", "GitHub API URL")
|
||||
updateCmd.Flags().String("html-url", "", "GitHub HTML URL")
|
||||
updateCmd.Flags().String("custom-user", "", "Custom user for SSH")
|
||||
updateCmd.Flags().Int("custom-port", 0, "Custom port for SSH")
|
||||
updateCmd.Flags().Int("app-id", 0, "GitHub App ID")
|
||||
updateCmd.Flags().Int("installation-id", 0, "GitHub Installation ID")
|
||||
updateCmd.Flags().String("client-id", "", "GitHub OAuth Client ID")
|
||||
updateCmd.Flags().String("client-secret", "", "GitHub OAuth Client Secret")
|
||||
updateCmd.Flags().String("webhook-secret", "", "GitHub Webhook Secret")
|
||||
updateCmd.Flags().String("private-key-uuid", "", "UUID of private key")
|
||||
updateCmd.Flags().Bool("system-wide", false, "Is this app system-wide")
|
||||
|
||||
return updateCmd
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var contextCmd = &cobra.Command{
|
||||
Use: "context",
|
||||
Short: "Manage Coolify contexts (instance configurations)",
|
||||
Long: `Manage Coolify contexts. A context contains the configuration (URL and token) for a Coolify instance.`,
|
||||
}
|
||||
|
||||
var contextVersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current context's Coolify version",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Get version using API client
|
||||
version, err := client.GetVersion(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get version: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(version)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
var listContextsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all configured contexts",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
|
||||
if PrettyMode {
|
||||
var prettyJSON bytes.Buffer
|
||||
instancesBytes, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = json.Indent(&prettyJSON, instancesBytes, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(prettyJSON.String())
|
||||
return
|
||||
}
|
||||
if JsonMode {
|
||||
instancesBytes, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(instancesBytes))
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w, "#\tName\tFqdn\tToken\tDefault")
|
||||
for index, entry := range instances {
|
||||
entryMap, ok := entry.(map[string]interface{})
|
||||
if !ok {
|
||||
fmt.Println("Error")
|
||||
return
|
||||
}
|
||||
if ShowSensitive {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", index+1, entryMap["name"], entryMap["fqdn"], entryMap["token"], map[bool]string{true: "true", false: ""}[entryMap["default"] == true])
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", index+1, entryMap["name"], entryMap["fqdn"], SensitiveInformationOverlay, map[bool]string{true: "true", false: ""}[entryMap["default"] == true])
|
||||
}
|
||||
|
||||
}
|
||||
w.Flush()
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
},
|
||||
}
|
||||
var addContextCmd = &cobra.Command{
|
||||
Use: "add <name> <url> <token>",
|
||||
Example: `context add myserver https://coolify.example.com your-api-token`,
|
||||
Args: exactArgs(3, "<name> <url> <token>"),
|
||||
Short: "Add a new context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name := args[0]
|
||||
Host := args[1]
|
||||
Token := args[2]
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
if force {
|
||||
instanceMap["token"] = Token
|
||||
if SetDefaultInstance {
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
delete(instanceMap, "default")
|
||||
}
|
||||
instanceMap["default"] = true
|
||||
fmt.Printf("%s already exists. Force overwriting. Setting it as default. \n", Name)
|
||||
} else {
|
||||
fmt.Printf("%s already exists. Force overwriting. \n", Name)
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s already exists. \n", Name)
|
||||
fmt.Println("\nNote: Use --force to force overwrite.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
instances = append(instances, map[string]interface{}{
|
||||
"name": Name,
|
||||
"fqdn": Host,
|
||||
"token": Token,
|
||||
})
|
||||
|
||||
if SetDefaultInstance {
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
delete(instanceMap, "default")
|
||||
}
|
||||
instances[len(instances)-1].(map[string]interface{})["default"] = true
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
listContextsCmd.Run(cmd, args)
|
||||
},
|
||||
}
|
||||
var deleteContextCmd = &cobra.Command{
|
||||
Use: "delete <name>",
|
||||
Example: `context delete myserver`,
|
||||
Args: exactArgs(1, "<name>"),
|
||||
Short: "Delete a context",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for i, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
instances = append(instances[:i], instances[i+1:]...)
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
fmt.Printf("%s removed. \n", Name)
|
||||
if instanceMap["default"] == true {
|
||||
fmt.Println("Note: The default instance has been removed.")
|
||||
if len(instances) > 0 {
|
||||
instances[0].(map[string]interface{})["default"] = true
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
fmt.Printf("%s set as default. \n", instances[0].(map[string]interface{})["fqdn"])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s not found. \n", Name)
|
||||
},
|
||||
}
|
||||
var setTokenCmd = &cobra.Command{
|
||||
Use: "set-token <name> <token>",
|
||||
Example: `context set-token myserver your-new-api-token`,
|
||||
Args: exactArgs(2, "<name> <token>"),
|
||||
Short: "Update the API token for a context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name = args[0]
|
||||
Token = args[1]
|
||||
var found interface{}
|
||||
for _, instance := range viper.Get("instances").([]interface{}) {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
found = instanceMap
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
fmt.Printf("%s instance is not found. \n", Name)
|
||||
return
|
||||
}
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
instanceMap["token"] = Token
|
||||
}
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
listContextsCmd.Run(cmd, args)
|
||||
},
|
||||
}
|
||||
var useContextCmd = &cobra.Command{
|
||||
Use: "use <name>",
|
||||
Example: `context use myserver`,
|
||||
Args: exactArgs(1, "<name>"),
|
||||
Short: "Switch to a different context (set as default)",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
var found interface{}
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
found = instanceMap
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
fmt.Printf("%s not found. \n", Name)
|
||||
return
|
||||
}
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
instanceMap["default"] = true
|
||||
} else {
|
||||
delete(instanceMap, "default")
|
||||
}
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
listContextsCmd.Run(cmd, args)
|
||||
},
|
||||
}
|
||||
var getContextCmd = &cobra.Command{
|
||||
Use: "get <name>",
|
||||
Example: `context get myserver`,
|
||||
Args: exactArgs(1, "<name>"),
|
||||
Short: "Get details of a specific context",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
if PrettyMode {
|
||||
var prettyJSON bytes.Buffer
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
instanceMap["token"] = SensitiveInformationOverlay
|
||||
}
|
||||
instancesBytes, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = json.Indent(&prettyJSON, instancesBytes, "", "\t")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(prettyJSON.String())
|
||||
return
|
||||
}
|
||||
if JsonMode {
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
instanceMap["token"] = SensitiveInformationOverlay
|
||||
}
|
||||
instancesBytes, err := json.Marshal(instances)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(instancesBytes))
|
||||
return
|
||||
}
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == Name {
|
||||
fmt.Fprintln(w, "Name\tHost\tToken")
|
||||
if ShowSensitive {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", Name, instanceMap["fqdn"], instanceMap["token"])
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", Name, instanceMap["fqdn"], SensitiveInformationOverlay)
|
||||
}
|
||||
w.Flush()
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s not found. \n", Name)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
addContextCmd.Flags().BoolVarP(&SetDefaultInstance, "default", "d", false, "Set as default context")
|
||||
addContextCmd.Flags().BoolP("force", "f", false, "Force overwrite if context already exists")
|
||||
|
||||
rootCmd.AddCommand(contextCmd)
|
||||
contextCmd.AddCommand(contextVersionCmd)
|
||||
contextCmd.AddCommand(listContextsCmd)
|
||||
contextCmd.AddCommand(addContextCmd)
|
||||
contextCmd.AddCommand(deleteContextCmd)
|
||||
contextCmd.AddCommand(setTokenCmd)
|
||||
contextCmd.AddCommand(useContextCmd)
|
||||
contextCmd.AddCommand(getContextCmd)
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var privateKeysCmd = &cobra.Command{
|
||||
Use: "private-key",
|
||||
Aliases: []string{"private-keys", "key", "keys"},
|
||||
Short: "Private key related commands",
|
||||
Long: `Manage SSH private keys for server authentication - list, add, and remove keys.`,
|
||||
}
|
||||
|
||||
var listPrivateKeysCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all private keys",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
keys, err := keySvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list private keys: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(keys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var addPrivateKeyCmd = &cobra.Command{
|
||||
Use: "add <name> <private_key_or_file>",
|
||||
Example: `add mykey ~/.ssh/id_rsa`,
|
||||
Args: exactArgs(2, "<uuid1> <uuid2>"),
|
||||
Short: "Add a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
name := args[0]
|
||||
privateKeyInput := args[1]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
var privateKey string
|
||||
// Check if input is a file path
|
||||
if _, err := os.Stat(privateKeyInput); err == nil {
|
||||
keyBytes, err := os.ReadFile(privateKeyInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading private key file: %w", err)
|
||||
}
|
||||
privateKey = string(keyBytes)
|
||||
} else {
|
||||
privateKey = privateKeyInput
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
req := models.PrivateKeyCreateRequest{
|
||||
Name: name,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
|
||||
key, err := keySvc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add private key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Private key '%s' added successfully (UUID: %s)\n", key.Name, key.UUID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var removePrivateKeyCmd = &cobra.Command{
|
||||
Use: "remove <uuid>",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
Short: "Remove a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
err = keySvc.Delete(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove private key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Private key removed successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(privateKeysCmd)
|
||||
privateKeysCmd.AddCommand(listPrivateKeysCmd)
|
||||
privateKeysCmd.AddCommand(addPrivateKeyCmd)
|
||||
privateKeysCmd.AddCommand(removePrivateKeyCmd)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCreateCommand creates the create command
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "add <key_name> <private_key_or_file>",
|
||||
Example: `add mykey ~/.ssh/id_rsa`,
|
||||
Args: cli.ExactArgs(2, "<key_name> <private_key_or_file>"),
|
||||
Short: "Add a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
name := args[0]
|
||||
privateKeyInput := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
var privateKey string
|
||||
// Check if input is a file path
|
||||
if _, err := os.Stat(privateKeyInput); err == nil {
|
||||
keyBytes, err := os.ReadFile(privateKeyInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading private key file: %w", err)
|
||||
}
|
||||
privateKey = string(keyBytes)
|
||||
} else {
|
||||
privateKey = privateKeyInput
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
req := models.PrivateKeyCreateRequest{
|
||||
Name: name,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
|
||||
key, err := keySvc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add private key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Private key '%s' added successfully (UUID: %s)\n", key.Name, key.UUID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand creates the delete command
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "remove <uuid>",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Remove a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
err = keySvc.Delete(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove private key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Private key removed successfully")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all private keys",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
keySvc := service.NewPrivateKeyService(client)
|
||||
keys, err := keySvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list private keys: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(keys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDomainsCommand creates the domains parent command
|
||||
func NewPrivateKeysCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "private-key",
|
||||
Aliases: []string{"private-keys", "key", "keys"},
|
||||
Short: "Private key related commands",
|
||||
Long: `Manage SSH private keys for server authentication - list, add, and remove keys.`,
|
||||
}
|
||||
|
||||
// Add subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// EnvironmentRow represents an environment for display
|
||||
type EnvironmentRow struct {
|
||||
UUID string `json:"environment_uuid"`
|
||||
EnvironmentName string `json:"environment_name"`
|
||||
}
|
||||
|
||||
// NewGetCommand returns the get project command
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get a project by uuid",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
projectSvc := service.NewProjectService(client)
|
||||
project, err := projectSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get project: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full project structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(project)
|
||||
}
|
||||
|
||||
// For table format, expand environments into separate rows
|
||||
var rows []EnvironmentRow
|
||||
|
||||
// If the project has environments, expand them
|
||||
if project.Environments != nil && len(project.Environments) > 0 {
|
||||
for _, env := range project.Environments {
|
||||
rows = append(rows, EnvironmentRow{
|
||||
UUID: env.UUID,
|
||||
EnvironmentName: env.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ListRow represents a project for list display (without environments)
|
||||
type ListRow struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// NewListCommand returns the list projects command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all projects",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
projectSvc := service.NewProjectService(client)
|
||||
projects, err := projectSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list projects: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full project structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(projects)
|
||||
}
|
||||
|
||||
// For table format, convert to simplified rows without environments
|
||||
var rows []ListRow
|
||||
for _, p := range projects {
|
||||
desc := ""
|
||||
if p.Description != nil {
|
||||
desc = *p.Description
|
||||
}
|
||||
rows = append(rows, ListRow{
|
||||
UUID: p.UUID,
|
||||
Name: p.Name,
|
||||
Description: desc,
|
||||
})
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package project
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewProjectCommand creates the project parent command with all subcommands
|
||||
func NewProjectCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "project",
|
||||
Aliases: []string{"projects"},
|
||||
Short: "Project related commands",
|
||||
Long: `Manage Coolify projects - list all projects or get details about a specific project.`,
|
||||
}
|
||||
|
||||
// Add all project subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// EnvironmentRow represents an environment for display
|
||||
type EnvironmentRow struct {
|
||||
UUID string `json:"environment_uuid"`
|
||||
EnvironmentName string `json:"environment_name"`
|
||||
}
|
||||
|
||||
// ProjectListRow represents a project for list display (without environments)
|
||||
type ProjectListRow struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var projectsCmd = &cobra.Command{
|
||||
Use: "project",
|
||||
Aliases: []string{"projects"},
|
||||
Short: "Project related commands",
|
||||
Long: `Manage Coolify projects - list all projects or get details about a specific project.`,
|
||||
}
|
||||
|
||||
var listProjectsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all projects",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
projectSvc := service.NewProjectService(client)
|
||||
projects, err := projectSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list projects: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full project structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(projects)
|
||||
}
|
||||
|
||||
// For table format, convert to simplified rows without environments
|
||||
var rows []ProjectListRow
|
||||
for _, p := range projects {
|
||||
desc := ""
|
||||
if p.Description != nil {
|
||||
desc = *p.Description
|
||||
}
|
||||
rows = append(rows, ProjectListRow{
|
||||
UUID: p.UUID,
|
||||
Name: p.Name,
|
||||
Description: desc,
|
||||
})
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
|
||||
var oneProjectCmd = &cobra.Command{
|
||||
Use: "get [uuid]",
|
||||
Short: "Get a project by uuid",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
projectSvc := service.NewProjectService(client)
|
||||
project, err := projectSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get project: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// For JSON/pretty formats, return the full project structure
|
||||
if format != output.FormatTable {
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatter.Format(project)
|
||||
}
|
||||
|
||||
// For table format, expand environments into separate rows
|
||||
rows := expandProjectEnvironments(project)
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(rows)
|
||||
},
|
||||
}
|
||||
|
||||
// expandProjectEnvironments creates environment rows for display
|
||||
func expandProjectEnvironments(project *models.Project) []EnvironmentRow {
|
||||
var rows []EnvironmentRow
|
||||
|
||||
// If no environments, return empty list
|
||||
if len(project.Environments) == 0 {
|
||||
return rows
|
||||
}
|
||||
|
||||
// Create one row per environment with just UUID and Name
|
||||
for _, env := range project.Environments {
|
||||
rows = append(rows, EnvironmentRow{
|
||||
UUID: env.UUID,
|
||||
EnvironmentName: env.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(projectsCmd)
|
||||
projectsCmd.AddCommand(listProjectsCmd)
|
||||
projectsCmd.AddCommand(oneProjectCmd)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var resourcesCmd = &cobra.Command{
|
||||
Use: "resource",
|
||||
Aliases: []string{"resources"},
|
||||
Short: "Resource related commands",
|
||||
Long: `List all resources (applications, services, databases) across your Coolify instance.`,
|
||||
}
|
||||
|
||||
var listResourcesCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all resources",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(resources)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(resourcesCmd)
|
||||
resourcesCmd.AddCommand(listResourcesCmd)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand returns the list projects command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all resources",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
resourceSvc := service.NewResourceService(client)
|
||||
resources, err := resourceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Format(resources)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewResourceCommand creates the resource parent command with all subcommands
|
||||
func NewResourceCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "resource",
|
||||
Aliases: []string{"resources"},
|
||||
Short: "Resource related commands",
|
||||
Long: `List all resources (applications, services, databases) in Coolify.`,
|
||||
}
|
||||
|
||||
// Add all resource subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
+60
-211
@@ -1,239 +1,60 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/api"
|
||||
"github.com/coollabsio/coolify-cli/cmd/application"
|
||||
"github.com/coollabsio/coolify-cli/cmd/completion"
|
||||
"github.com/coollabsio/coolify-cli/cmd/context"
|
||||
"github.com/coollabsio/coolify-cli/cmd/database"
|
||||
"github.com/coollabsio/coolify-cli/cmd/deployment"
|
||||
"github.com/coollabsio/coolify-cli/cmd/github"
|
||||
"github.com/coollabsio/coolify-cli/cmd/privatekeys"
|
||||
"github.com/coollabsio/coolify-cli/cmd/project"
|
||||
"github.com/coollabsio/coolify-cli/cmd/resources"
|
||||
"github.com/coollabsio/coolify-cli/cmd/server"
|
||||
"github.com/coollabsio/coolify-cli/cmd/service"
|
||||
"github.com/coollabsio/coolify-cli/cmd/teams"
|
||||
"github.com/coollabsio/coolify-cli/cmd/update"
|
||||
cliversion "github.com/coollabsio/coolify-cli/cmd/version"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/version"
|
||||
compareVersion "github.com/hashicorp/go-version"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// CliVersion is the CLI version
|
||||
var CliVersion = "1.0.0"
|
||||
|
||||
// CheckInterval for version checking
|
||||
var CheckInterval = 10 * time.Minute
|
||||
|
||||
// SensitiveInformationOverlay is the string used to hide sensitive data
|
||||
var SensitiveInformationOverlay = "********"
|
||||
|
||||
// Legacy global variables - kept for backward compatibility during migration
|
||||
// TODO: Remove these once all commands are refactored
|
||||
var (
|
||||
Version string
|
||||
Name string
|
||||
Fqdn string
|
||||
Token string
|
||||
ContextName string
|
||||
Debug bool
|
||||
ShowSensitive bool
|
||||
Format string
|
||||
JsonMode bool
|
||||
PrettyMode bool
|
||||
Version string
|
||||
Name string
|
||||
Fqdn string
|
||||
Token string
|
||||
ContextName string
|
||||
Debug bool
|
||||
ShowSensitive bool
|
||||
Format string
|
||||
JsonMode bool
|
||||
PrettyMode bool
|
||||
SetDefaultInstance bool
|
||||
w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)
|
||||
Instance http.Client
|
||||
w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)
|
||||
)
|
||||
|
||||
// Tag represents a git tag for version checking
|
||||
type Tag struct {
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "coolify",
|
||||
Short: "Coolify CLI",
|
||||
Long: `A CLI tool to interact with Coolify API.`,
|
||||
SilenceUsage: true, // Don't show usage on errors
|
||||
Use: "coolify",
|
||||
Short: "Coolify CLI",
|
||||
Long: `A CLI tool to interact with Coolify API.`,
|
||||
SilenceUsage: true, // Don't show usage on errors
|
||||
SilenceErrors: false, // Still print errors
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// getAPIClient creates an API client from command flags or config
|
||||
func getAPIClient(cmd *cobra.Command) (*api.Client, error) {
|
||||
// Get flags
|
||||
token, _ := cmd.Flags().GetString("token")
|
||||
contextName, _ := cmd.Flags().GetString("context")
|
||||
debug, _ := cmd.Flags().GetBool("debug")
|
||||
|
||||
// Load config to get instance details
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
var instance *config.Instance
|
||||
// Use context if specified, otherwise use default
|
||||
if contextName != "" {
|
||||
instance, err = cfg.GetInstance(contextName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("context '%s' not found: %w", contextName, err)
|
||||
}
|
||||
} else {
|
||||
instance, err = cfg.GetDefault()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no default instance configured: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get FQDN from instance
|
||||
fqdn := instance.FQDN
|
||||
|
||||
// Use token from flag if provided, otherwise use instance token
|
||||
if token == "" {
|
||||
token = instance.Token
|
||||
}
|
||||
|
||||
// Create client
|
||||
client := api.NewClient(fqdn, token, api.WithDebug(debug))
|
||||
|
||||
// Set legacy global variables for backward compatibility
|
||||
Fqdn = fqdn
|
||||
Token = token
|
||||
Debug = debug
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// exactArgs returns a validator that ensures exactly n arguments are provided with a helpful error message
|
||||
func exactArgs(n int, usage string) cobra.PositionalArgs {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != n {
|
||||
if n == 1 {
|
||||
return fmt.Errorf("missing required argument: %s\n\nUsage: %s", usage, cmd.UseLine())
|
||||
}
|
||||
return fmt.Errorf("expected %d argument(s), got %d\n\nUsage: %s", n, len(args), cmd.UseLine())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// minArgs returns a validator that ensures at least n arguments are provided with a helpful error message
|
||||
func minArgs(n int, usage string) cobra.PositionalArgs {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < n {
|
||||
return fmt.Errorf("missing required arguments: %s\n\nUsage: %s", usage, cmd.UseLine())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseInt parses a string to int with better error message
|
||||
func parseInt(s string) (int, error) {
|
||||
var result int
|
||||
_, err := fmt.Sscanf(s, "%d", &result)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("'%s' is not a valid integer", s)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// splitOwnerRepo splits owner/repo string into parts
|
||||
func splitOwnerRepo(s string) []string {
|
||||
parts := make([]string, 0, 2)
|
||||
var current string
|
||||
for _, char := range s {
|
||||
if char == '/' {
|
||||
if current != "" {
|
||||
parts = append(parts, current)
|
||||
current = ""
|
||||
}
|
||||
} else {
|
||||
current += string(char)
|
||||
}
|
||||
}
|
||||
if current != "" {
|
||||
parts = append(parts, current)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
// CheckLatestVersionOfCli checks for CLI updates
|
||||
func CheckLatestVersionOfCli() (string, error) {
|
||||
lastCheck := viper.GetString("lastupdatechecktime")
|
||||
if lastCheck != "" {
|
||||
lastCheckTime, err := time.Parse(time.RFC3339, lastCheck)
|
||||
if err == nil && lastCheckTime.Add(CheckInterval).After(time.Now()) {
|
||||
if Debug {
|
||||
log.Println("Skipping update check. Last check was less than 10 minutes ago.")
|
||||
}
|
||||
return CliVersion, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Update check time
|
||||
viper.Set("lastupdatechecktime", time.Now().Format(time.RFC3339))
|
||||
viper.WriteConfig()
|
||||
|
||||
url := "https://api.github.com/repos/coollabsio/coolify-cli/git/refs/tags"
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("%d - Failed to fetch data from %s. Error: %s", resp.StatusCode, url, string(body))
|
||||
}
|
||||
|
||||
var tags []Tag
|
||||
if err := json.Unmarshal(body, &tags); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
versionsRaw := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
versionStr := tag.Ref[10:]
|
||||
versionsRaw = append(versionsRaw, versionStr)
|
||||
}
|
||||
|
||||
versions := make([]*compareVersion.Version, len(versionsRaw))
|
||||
for i, raw := range versionsRaw {
|
||||
v, err := compareVersion.NewVersion(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
versions[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(compareVersion.Collection(versions))
|
||||
latestVersion := versions[len(versions)-1]
|
||||
|
||||
// Compare versions properly using semantic versioning
|
||||
currentVersion, err := compareVersion.NewVersion(CliVersion)
|
||||
if err != nil {
|
||||
return latestVersion.String(), err
|
||||
}
|
||||
|
||||
if latestVersion.GreaterThan(currentVersion) {
|
||||
fmt.Printf("There is a new version of Coolify CLI available.\nPlease update with 'coolify update'.\n\n")
|
||||
}
|
||||
return latestVersion.String(), nil
|
||||
}
|
||||
|
||||
// Execute runs the root command
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
@@ -243,6 +64,17 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "coolify",
|
||||
Short: "Coolify CLI",
|
||||
Long: fmt.Sprintf("A CLI tool to interact with Coolify API.\nVersion: %s", version.CliVersion),
|
||||
SilenceUsage: true, // Don't show usage on errors
|
||||
SilenceErrors: false, // Still print errors
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&Token, "token", "", "", "Token for authentication (override context token)")
|
||||
@@ -251,6 +83,23 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&Format, "format", "", "table", "Format output (table|json|pretty)")
|
||||
rootCmd.PersistentFlags().BoolVarP(&ShowSensitive, "show-sensitive", "s", false, "Show sensitive information")
|
||||
rootCmd.PersistentFlags().BoolVarP(&Debug, "debug", "", false, "Debug mode")
|
||||
|
||||
// Register all subcommands
|
||||
rootCmd.AddCommand(application.NewAppCommand())
|
||||
rootCmd.AddCommand(context.NewContextCommand())
|
||||
rootCmd.AddCommand(completion.NewCompletionsCommand())
|
||||
rootCmd.AddCommand(database.NewDatabaseCommand())
|
||||
rootCmd.AddCommand(deployment.NewDeploymentCommand())
|
||||
rootCmd.AddCommand(github.NewGitHubCommand())
|
||||
rootCmd.AddCommand(privatekeys.NewPrivateKeysCommand())
|
||||
rootCmd.AddCommand(project.NewProjectCommand())
|
||||
rootCmd.AddCommand(resources.NewResourceCommand())
|
||||
rootCmd.AddCommand(server.NewServerCommand())
|
||||
rootCmd.AddCommand(service.NewServiceCommand())
|
||||
rootCmd.AddCommand(teams.NewTeamsCommand())
|
||||
rootCmd.AddCommand(update.NewUpdateCommand())
|
||||
rootCmd.AddCommand(cliversion.NewVersionCommand())
|
||||
rootCmd.AddCommand(NewDocsCommand())
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
@@ -291,7 +140,7 @@ func initConfig() {
|
||||
// This allows --instance flag to work correctly
|
||||
|
||||
// Check for updates
|
||||
latestVersionStr, err := CheckLatestVersionOfCli()
|
||||
latestVersionStr, err := version.CheckLatestVersionOfCli(Debug)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
log.Println("Failed to check for updates:", err)
|
||||
@@ -302,7 +151,7 @@ func initConfig() {
|
||||
if latestVersionStr != "" {
|
||||
latestVersion, err := compareVersion.NewVersion(latestVersionStr)
|
||||
if err == nil {
|
||||
currentVersion, err := compareVersion.NewVersion(CliVersion)
|
||||
currentVersion, err := compareVersion.NewVersion(version.CliVersion)
|
||||
if err == nil && latestVersion.GreaterThan(currentVersion) {
|
||||
if Debug {
|
||||
log.Printf("New version of Coolify CLI is available: %s\n", latestVersionStr)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewAddCommand creates the add command
|
||||
func NewAddCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add <server_name> <ip_address> <private_key_uuid>",
|
||||
Args: cli.ExactArgs(3, "<server_name> <ip_address> <private_key_uuid>"),
|
||||
Short: "Add a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Parse arguments and flags
|
||||
name := args[0]
|
||||
ip := args[1]
|
||||
privateKeyUuid := args[2]
|
||||
port, _ := cmd.Flags().GetInt("port")
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
validate, _ := cmd.Flags().GetBool("validate")
|
||||
|
||||
// Create request
|
||||
req := models.ServerCreateRequest{
|
||||
Name: name,
|
||||
IP: ip,
|
||||
Port: port,
|
||||
User: user,
|
||||
PrivateKeyUUID: privateKeyUuid,
|
||||
InstantValidate: validate,
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
response, err := serverSvc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server: %w", err)
|
||||
}
|
||||
|
||||
if validate {
|
||||
fmt.Printf("Server added successfully with uuid %s\n", response.UUID)
|
||||
} else {
|
||||
fmt.Printf("Server added successfully with uuid %s. Server is not validated. Use 'servers validate %s' to validate the server.\n", response.UUID, response.UUID)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntP("port", "p", 22, "Port")
|
||||
cmd.Flags().StringP("user", "u", "root", "User")
|
||||
cmd.Flags().Bool("validate", false, "Validate the server")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
func NewGetDomainsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "domains <uuid>",
|
||||
Aliases: []string{"domain"},
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Get server domains by uuid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
// Get format flags
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
domains, err := serverSvc.GetDomains(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server domains: %w", err)
|
||||
}
|
||||
|
||||
// Use output formatter
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(domains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
func NewGetCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Get server details by uuid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
// Get format flags
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
withResources, _ := cmd.Flags().GetBool("resources")
|
||||
|
||||
var data interface{}
|
||||
if withResources {
|
||||
resources, err := serverSvc.GetResources(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server resources: %w", err)
|
||||
}
|
||||
data = resources.Resources
|
||||
} else {
|
||||
server, err := serverSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server: %w", err)
|
||||
}
|
||||
data = server
|
||||
}
|
||||
|
||||
// Use output formatter
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable && !withResources {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("resources", false, "With resources")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all servers",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
servers, err := serverSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list servers: %w", err)
|
||||
}
|
||||
|
||||
// Use output formatter
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(servers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewRemoveCommand creates the remove command
|
||||
func NewRemoveCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "remove <uuid>",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Remove a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
if err := serverSvc.Delete(ctx, uuid); err != nil {
|
||||
return fmt.Errorf("failed to delete server: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Server %s deleted successfully\n", uuid)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewServerCommand creates the server parent command
|
||||
func NewServerCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "server",
|
||||
Aliases: []string{"servers"},
|
||||
Short: "Server related commands",
|
||||
Long: `Manage Coolify servers - list, get details, add new servers, validate connections, and remove servers.`,
|
||||
}
|
||||
|
||||
// Add subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewGetDomainsCommand())
|
||||
cmd.AddCommand(NewAddCommand())
|
||||
cmd.AddCommand(NewRemoveCommand())
|
||||
cmd.AddCommand(NewValidateCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewValidateCommand creates the validate command
|
||||
func NewValidateCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "validate <uuid>",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Validate a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
response, err := serverSvc.Validate(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate server: %w", err)
|
||||
}
|
||||
|
||||
if response.Message != "" {
|
||||
fmt.Println(response.Message)
|
||||
} else {
|
||||
fmt.Printf("Server %s validated successfully\n", uuid)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var WithResources bool
|
||||
|
||||
var serversCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Aliases: []string{"servers"},
|
||||
Short: "Server related commands",
|
||||
Long: `Manage Coolify servers - list, get details, add new servers, validate connections, and remove servers.`,
|
||||
}
|
||||
|
||||
var listServersCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all servers",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Check API version
|
||||
version, err := client.GetVersion(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API version: %w", err)
|
||||
}
|
||||
Version = version
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
servers, err := serverSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list servers: %w", err)
|
||||
}
|
||||
|
||||
// Use output formatter
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(servers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var oneServerCmd = &cobra.Command{
|
||||
Use: "get [uuid]",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
Short: "Get server details by uuid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
// Get format flags
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
var data interface{}
|
||||
if WithResources {
|
||||
resources, err := serverSvc.GetResources(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server resources: %w", err)
|
||||
}
|
||||
data = resources.Resources
|
||||
} else {
|
||||
server, err := serverSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server: %w", err)
|
||||
}
|
||||
data = server
|
||||
}
|
||||
|
||||
// Use output formatter
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := formatter.Format(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !showSensitive && format == output.FormatTable && !WithResources {
|
||||
fmt.Println("\nNote: Use -s to show sensitive information.")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var removeServerCmd = &cobra.Command{
|
||||
Use: "remove [uuid]",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
Short: "Remove a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
if err := serverSvc.Delete(ctx, uuid); err != nil {
|
||||
return fmt.Errorf("failed to delete server: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Server %s deleted successfully\n", uuid)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var addServerCmd = &cobra.Command{
|
||||
Use: "add [name] [ip] [private_key_uuid]",
|
||||
Args: exactArgs(3, "<uuid1> <uuid2> <uuid3>"),
|
||||
Short: "Add a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Parse arguments and flags
|
||||
name := args[0]
|
||||
ip := args[1]
|
||||
privateKeyUuid := args[2]
|
||||
port, _ := cmd.Flags().GetInt("port")
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
validate, _ := cmd.Flags().GetBool("validate")
|
||||
|
||||
// Create request
|
||||
req := models.ServerCreateRequest{
|
||||
Name: name,
|
||||
IP: ip,
|
||||
Port: port,
|
||||
User: user,
|
||||
PrivateKeyUUID: privateKeyUuid,
|
||||
InstantValidate: validate,
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
response, err := serverSvc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create server: %w", err)
|
||||
}
|
||||
|
||||
if validate {
|
||||
fmt.Printf("Server added successfully with uuid %s\n", response.UUID)
|
||||
} else {
|
||||
fmt.Printf("Server added successfully with uuid %s. Server is not validated. Use 'servers validate %s' to validate the server.\n", response.UUID, response.UUID)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var validateServerCmd = &cobra.Command{
|
||||
Use: "validate [uuid]",
|
||||
Args: exactArgs(1, "<uuid>"),
|
||||
Short: "Validate a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get API client
|
||||
client, err := getAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
serverSvc := service.NewServerService(client)
|
||||
uuid := args[0]
|
||||
|
||||
response, err := serverSvc.Validate(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate server: %w", err)
|
||||
}
|
||||
|
||||
if response.Message != "" {
|
||||
fmt.Println(response.Message)
|
||||
} else {
|
||||
fmt.Printf("Server %s validated successfully\n", uuid)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Note: format and show-sensitive flags are inherited from rootCmd.PersistentFlags()
|
||||
|
||||
oneServerCmd.Flags().BoolVarP(&WithResources, "resources", "", false, "With resources")
|
||||
rootCmd.AddCommand(serversCmd)
|
||||
serversCmd.AddCommand(listServersCmd)
|
||||
serversCmd.AddCommand(oneServerCmd)
|
||||
|
||||
addServerCmd.Flags().IntP("port", "p", 22, "Port")
|
||||
addServerCmd.Flags().StringP("user", "u", "root", "User")
|
||||
addServerCmd.Flags().BoolP("validate", "", false, "Validate the server")
|
||||
serversCmd.AddCommand(addServerCmd)
|
||||
serversCmd.AddCommand(validateServerCmd)
|
||||
serversCmd.AddCommand(removeServerCmd)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a service
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <uuid>",
|
||||
Short: "Delete a service",
|
||||
Long: `Delete a service and optionally clean up its configurations, volumes, and networks.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
deleteConfigurations, _ := cmd.Flags().GetBool("delete-configurations")
|
||||
deleteVolumes, _ := cmd.Flags().GetBool("delete-volumes")
|
||||
dockerCleanup, _ := cmd.Flags().GetBool("docker-cleanup")
|
||||
deleteConnectedNetworks, _ := cmd.Flags().GetBool("delete-connected-networks")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this service? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
err = serviceSvc.Delete(ctx, uuid, deleteConfigurations, deleteVolumes, dockerCleanup, deleteConnectedNetworks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete service: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Service deletion request queued.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolP("force", "f", false, "Skip confirmation prompt")
|
||||
cmd.Flags().Bool("delete-configurations", true, "Delete configurations")
|
||||
cmd.Flags().Bool("delete-volumes", true, "Delete volumes")
|
||||
cmd.Flags().Bool("docker-cleanup", true, "Run docker cleanup")
|
||||
cmd.Flags().Bool("delete-connected-networks", true, "Delete connected networks")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <service_uuid>",
|
||||
Short: "Create an environment variable for a service",
|
||||
Long: `Create a new environment variable for a specific service. Use --key and --value flags to specify the variable.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
|
||||
if key == "" {
|
||||
return fmt.Errorf("--key is required")
|
||||
}
|
||||
if value == "" {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableCreateRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
// Only set flags if they were explicitly provided
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
env, err := serviceSvc.CreateEnv(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' created successfully.\n", env.Key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("key", "", "Environment variable key (required)")
|
||||
cmd.Flags().String("value", "", "Environment variable value (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
|
||||
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <service_uuid> <env_uuid>",
|
||||
Short: "Delete an environment variable",
|
||||
Long: `Delete an environment variable from a service. First UUID is the service, second is the specific environment variable to delete.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
serviceUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
err = serviceSvc.DeleteEnv(ctx, serviceUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Environment variable deleted successfully.")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("force", false, "Skip confirmation prompt")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
// Package env contains service environment variable commands
|
||||
package env
|
||||
|
||||
// This package handles environment variable management for services
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <service_uuid> <env_uuid_or_key>",
|
||||
Short: "Get environment variable details",
|
||||
Long: `Get detailed information about a specific environment variable. First UUID is the service, second is the environment variable UUID or key name.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
serviceUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
env, err := serviceSvc.GetEnv(ctx, serviceUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get environment variable: %w", err)
|
||||
}
|
||||
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Mask sensitive value unless --show-sensitive is used
|
||||
if !showSensitive {
|
||||
env.Value = "********"
|
||||
if env.RealValue != nil {
|
||||
masked := "********"
|
||||
env.RealValue = &masked
|
||||
}
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(env)
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list <service_uuid>",
|
||||
Short: "List all environment variables for a service",
|
||||
Long: `List all environment variables for a specific service.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
envs, err := serviceSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list environment variables: %w", err)
|
||||
}
|
||||
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
|
||||
// Mask sensitive values unless --show-sensitive is used
|
||||
if !showSensitive {
|
||||
for i := range envs {
|
||||
envs[i].Value = "********"
|
||||
if envs[i].RealValue != nil {
|
||||
masked := "********"
|
||||
envs[i].RealValue = &masked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
formatter, err := output.NewFormatter(format, output.Options{
|
||||
ShowSensitive: showSensitive,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(envs)
|
||||
},
|
||||
}
|
||||
}
|
||||
Vendored
+155
@@ -0,0 +1,155 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/parser"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSyncCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "sync <service_uuid>",
|
||||
Short: "Sync environment variables from a .env file",
|
||||
Long: `Sync environment variables from a .env file. This command intelligently:
|
||||
- Updates existing environment variables with new values
|
||||
- Creates new environment variables that don't exist yet
|
||||
- Uses efficient bulk operations where possible
|
||||
|
||||
Example: coolify service env sync abc123 --file .env.production`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
filePath, _ := cmd.Flags().GetString("file")
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("--file is required")
|
||||
}
|
||||
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
|
||||
// Parse the .env file
|
||||
envVars, err := parser.ParseEnvFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse .env file: %w", err)
|
||||
}
|
||||
|
||||
if len(envVars) == 0 {
|
||||
fmt.Println("No environment variables found in file.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d environment variables in file. Syncing...\n", len(envVars))
|
||||
|
||||
// Fetch existing environment variables
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
existingEnvs, err := serviceSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list existing environment variables: %w", err)
|
||||
}
|
||||
|
||||
// Build a map of existing env vars by key
|
||||
existingMap := make(map[string]models.EnvironmentVariable)
|
||||
for _, env := range existingEnvs {
|
||||
existingMap[env.Key] = env
|
||||
}
|
||||
|
||||
// Separate into updates and creates
|
||||
var toUpdate []models.EnvironmentVariableCreateRequest
|
||||
var toCreate []models.EnvironmentVariableCreateRequest
|
||||
|
||||
for _, envVar := range envVars {
|
||||
req := models.EnvironmentVariableCreateRequest{
|
||||
Key: envVar.Key,
|
||||
Value: envVar.Value,
|
||||
}
|
||||
|
||||
// Apply flags if explicitly provided
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
|
||||
// Auto-detect multiline values
|
||||
if strings.Contains(envVar.Value, "\n") {
|
||||
multiline := true
|
||||
req.IsMultiline = &multiline
|
||||
}
|
||||
|
||||
if _, exists := existingMap[envVar.Key]; exists {
|
||||
toUpdate = append(toUpdate, req)
|
||||
} else {
|
||||
toCreate = append(toCreate, req)
|
||||
}
|
||||
}
|
||||
|
||||
updateCount := 0
|
||||
createCount := 0
|
||||
failCount := 0
|
||||
|
||||
// Perform bulk update if there are vars to update
|
||||
if len(toUpdate) > 0 {
|
||||
fmt.Printf("Updating %d existing variables...\n", len(toUpdate))
|
||||
bulkReq := &service.BulkUpdateEnvsRequest{
|
||||
Data: toUpdate,
|
||||
}
|
||||
_, err := serviceSvc.BulkUpdateEnvs(ctx, uuid, bulkReq)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Bulk update failed: %v\n", err)
|
||||
failCount += len(toUpdate)
|
||||
} else {
|
||||
updateCount = len(toUpdate)
|
||||
fmt.Printf(" ✓ Successfully updated %d variables\n", updateCount)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new variables one by one
|
||||
if len(toCreate) > 0 {
|
||||
fmt.Printf("Creating %d new variables...\n", len(toCreate))
|
||||
for _, req := range toCreate {
|
||||
_, err := serviceSvc.CreateEnv(ctx, uuid, &req)
|
||||
if err != nil {
|
||||
fmt.Printf(" ✗ Failed to create '%s': %v\n", req.Key, err)
|
||||
failCount++
|
||||
} else {
|
||||
fmt.Printf(" ✓ Created '%s'\n", req.Key)
|
||||
createCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nSync complete: %d updated, %d created, %d failed\n", updateCount, createCount, failCount)
|
||||
|
||||
if failCount > 0 {
|
||||
return fmt.Errorf("some environment variables failed to sync")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Make all variables available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <service_uuid> <env_uuid>",
|
||||
Short: "Update an environment variable",
|
||||
Long: `Update an existing environment variable. First UUID is the service, second is the specific environment variable to update.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
serviceUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableUpdateRequest{
|
||||
UUID: envUUID,
|
||||
}
|
||||
|
||||
// Only set fields that were provided
|
||||
if cmd.Flags().Changed("key") {
|
||||
key, _ := cmd.Flags().GetString("key")
|
||||
req.Key = &key
|
||||
}
|
||||
if cmd.Flags().Changed("value") {
|
||||
value, _ := cmd.Flags().GetString("value")
|
||||
req.Value = &value
|
||||
}
|
||||
if cmd.Flags().Changed("build-time") {
|
||||
isBuildTime, _ := cmd.Flags().GetBool("build-time")
|
||||
req.IsBuildTime = &isBuildTime
|
||||
}
|
||||
if cmd.Flags().Changed("preview") {
|
||||
isPreview, _ := cmd.Flags().GetBool("preview")
|
||||
req.IsPreview = &isPreview
|
||||
}
|
||||
if cmd.Flags().Changed("is-literal") {
|
||||
isLiteral, _ := cmd.Flags().GetBool("is-literal")
|
||||
req.IsLiteral = &isLiteral
|
||||
}
|
||||
if cmd.Flags().Changed("is-multiline") {
|
||||
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
|
||||
req.IsMultiline = &isMultiline
|
||||
}
|
||||
|
||||
// Check if at least one field is being updated
|
||||
if req.Key == nil && req.Value == nil && req.IsBuildTime == nil && req.IsPreview == nil && req.IsLiteral == nil && req.IsMultiline == nil {
|
||||
return fmt.Errorf("at least one field must be provided to update (--key, --value, --build-time, --preview, --is-literal, or --is-multiline)")
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
env, err := serviceSvc.UpdateEnv(ctx, serviceUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update environment variable: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Environment variable '%s' updated successfully.\n", env.Key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("key", "", "New environment variable key")
|
||||
cmd.Flags().String("value", "", "New environment variable value")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
|
||||
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand gets service details
|
||||
func NewGetCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "get <uuid>",
|
||||
Short: "Get service details",
|
||||
Long: `Get detailed information about a specific service.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
svc, err := serviceSvc.Get(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get service: %w", err)
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter("table", output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(svc)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all services
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all services",
|
||||
Long: `List all services in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
services, err := serviceSvc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list services: %w", err)
|
||||
}
|
||||
|
||||
formatter, err := output.NewFormatter("table", output.Options{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create formatter: %w", err)
|
||||
}
|
||||
|
||||
return formatter.Format(services)
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user