mirror of
https://github.com/coollabsio/coolify-cli.git
synced 2026-06-20 16:15:04 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd65345df8 | |||
| c94e147639 | |||
| 0ea34284ef | |||
| a93872ee16 | |||
| 1bc1a601a8 | |||
| 4ad94e2d65 | |||
| faa8186301 | |||
| 1eba511544 | |||
| 541f633edc | |||
| 0f23b029f0 | |||
| 780b3674c7 | |||
| 77adbfaebc | |||
| 9215fd537e | |||
| 26c0925854 | |||
| 1f1b187ed2 | |||
| 4af598c213 | |||
| 6ca3b700ce | |||
| 8cf0b71ebf | |||
| 99a40bfa1d | |||
| 188834fd6d | |||
| f9c3b9869a | |||
| 6044a2107e | |||
| 2f9dc6e8d7 | |||
| 1e741309cb | |||
| 51f759c38f | |||
| 51e9ec5ec8 | |||
| e071fd81d4 | |||
| cb0bbfc5cb | |||
| 87b6b8fdf7 | |||
| 3dbe2507f4 | |||
| 1e82217a50 | |||
| 6f33fa00f1 | |||
| d7841b3b5a | |||
| 234f6e9ed6 | |||
| fa86ceb5cc | |||
| 646bf9de36 | |||
| be29a6e05d | |||
| 63a882107a | |||
| 08cd3b8ac7 | |||
| 06f191e9ba | |||
| 7a19a02c02 | |||
| 806a6b9716 | |||
| a18d751ad4 | |||
| 9283717821 | |||
| a44c712163 | |||
| 7c42af6203 | |||
| ed2dbd4947 | |||
| f1301c1dbd | |||
| 8a58cdd72f | |||
| 5ca4c3a8e3 | |||
| 89cd744696 | |||
| 694b4f8e32 | |||
| ef15d013da | |||
| 9f5a44be04 | |||
| 1078a9d3ca | |||
| 9cef9ebee7 | |||
| 51f33cfc5e | |||
| 559d4e2709 | |||
| 94e237e2b1 | |||
| 3dd0dfdce1 | |||
| aac44a0ddb | |||
| a4c96d2803 | |||
| dc9670992a | |||
| c369689131 | |||
| dcf0b39c1b | |||
| 14a3f00c57 | |||
| 86f77716ee | |||
| ef91ed987e | |||
| d8cf7a5986 | |||
| d30b0b90de | |||
| 4a8a659090 | |||
| aa1bda4063 | |||
| 69f2a7ac1f | |||
| ad185a42ee | |||
| 22e34fb72e | |||
| c5b0ad4218 | |||
| 1a6fa9e397 | |||
| b767468b29 | |||
| 6b742d37dd | |||
| 0b2d277e41 | |||
| ac9a486d46 | |||
| ba8f05769f | |||
| 5c799410e5 | |||
| d5dd1b5bdf | |||
| 0d6a2bb1e9 | |||
| 9eba5b97f7 | |||
| 76c1434711 | |||
| f35d299f6e | |||
| 680264ab3f | |||
| 794025aee1 | |||
| 8d6da93aa1 | |||
| 22516fe51e | |||
| fe63c3a3b6 | |||
| 6cad3ba0f7 | |||
| 1bc4625ef8 | |||
| d9494301f6 | |||
| 85cf8f6981 | |||
| c98a8330f8 | |||
| 49e5870c86 | |||
| 705fed3560 | |||
| 88d0b25537 | |||
| 3470b04235 | |||
| eb876e4bda | |||
| 7210b8df76 | |||
| 0546fd1932 | |||
| e2c1c86194 | |||
| 84e7e4921b | |||
| 0acb1fc512 | |||
| eaf9614bcc |
@@ -5,14 +5,14 @@ tmp_dir = "tmp"
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./coolify"
|
||||
cmd = "go build -o ./coolify ."
|
||||
cmd = "go build -o ./coolify ./coolify"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", ".git", ".conductor"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = "echo 'Build complete. Binary: ./coolify'"
|
||||
full_bin = "echo 'Build complete. Binary: ./coolify/coolify'"
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
## Changes
|
||||
|
||||
-
|
||||
|
||||
## Issues & Discussions
|
||||
|
||||
- fix #
|
||||
@@ -10,18 +10,47 @@ jobs:
|
||||
release-cli:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: ${{ env.GITHUB_REF_NAME }}
|
||||
args: release --clean
|
||||
workdir: ./
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
update-version:
|
||||
needs: [release-cli]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: v4.x
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update version file
|
||||
run: |
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
echo "Updating version to $TAG"
|
||||
sed -i "s/^\tversion = \".*\"/\tversion = \"$TAG\"/" internal/version/checker.go
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add internal/version/checker.go
|
||||
git commit -m "chore: bump version to $TAG"
|
||||
git push origin v4.x
|
||||
|
||||
# Move the tag to point to the new commit with updated version
|
||||
git tag -d "$TAG" || true
|
||||
git tag "$TAG"
|
||||
git push origin "refs/tags/$TAG" --force
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Testing CLI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["v4.x"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Run gofmt
|
||||
run: diff -u <(echo -n) <(gofmt -d -s .)
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.5.0 # pin version for consistency
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v -race -cover ./...
|
||||
|
||||
go-mod-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Run go mod tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: Check uncommitted changes
|
||||
run: git diff --exit-code
|
||||
|
||||
- if: failure()
|
||||
run: echo "::error::Check failed, please run 'go mod tidy' and commit the changes."
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
coolify-cli
|
||||
coolify
|
||||
cli
|
||||
/coolify
|
||||
config.json
|
||||
.claude
|
||||
|
||||
# Generated documentation (can be regenerated)
|
||||
man/
|
||||
docs/cli/
|
||||
dist/
|
||||
|
||||
# Test coverage
|
||||
coverage.out
|
||||
@@ -0,0 +1,75 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- contextcheck
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- gocheckcompilerdirectives
|
||||
- gochecksumtype
|
||||
- gocritic
|
||||
- gomoddirectives
|
||||
- gomodguard
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- loggercheck
|
||||
- makezero
|
||||
- musttag
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- noctx
|
||||
- protogetter
|
||||
- reassign
|
||||
- recvcheck
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- testifylint
|
||||
- unparam
|
||||
- zerologlint
|
||||
|
||||
settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
|
||||
staticcheck:
|
||||
checks: ["all", "-ST1005", "-S1016"]
|
||||
|
||||
gosec:
|
||||
excludes:
|
||||
- G115
|
||||
|
||||
gosmopolitan:
|
||||
allow-time-local: true
|
||||
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- std-error-handling
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- goimports
|
||||
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/coollabsio)
|
||||
|
||||
exclusions:
|
||||
generated: lax
|
||||
+27
-2
@@ -1,8 +1,19 @@
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- binary: coolify
|
||||
- id: coolify
|
||||
binary: coolify
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s
|
||||
- -w
|
||||
- -X github.com/coollabsio/coolify-cli/internal/version.version={{ .Version }}
|
||||
main: ./coolify/main.go
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
@@ -11,4 +22,18 @@ builds:
|
||||
- amd64
|
||||
- arm64
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- CGO_ENABLED=0
|
||||
|
||||
checksum:
|
||||
name_template: checksums.txt
|
||||
algorithm: sha256
|
||||
|
||||
archives:
|
||||
- id: coolify-archive
|
||||
ids:
|
||||
- coolify
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
+5
-2
@@ -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.
|
||||
|
||||
@@ -550,7 +550,10 @@ c.httpClient = &http.Client{
|
||||
|
||||
```bash
|
||||
# Local build
|
||||
go build -o coolify .
|
||||
go build -o coolify ./coolify
|
||||
|
||||
# Install locally
|
||||
go install ./coolify
|
||||
|
||||
# Multi-platform release
|
||||
goreleaser release --clean
|
||||
|
||||
@@ -9,6 +9,7 @@ This is a CLI tool for interacting with the Coolify API, built with Go using the
|
||||
### API Specification
|
||||
This CLI is a client for the Coolify API. The API specification is defined in the OpenAPI schema:
|
||||
- **Source**: https://github.com/coollabsio/coolify/blob/v4.x/openapi.json
|
||||
- **Raw JSON**: https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/v4.x/openapi.json
|
||||
- **Base Path**: `/api/v1/`
|
||||
- **Authentication**: Bearer token (API tokens from Coolify dashboard at `/security/api-tokens`)
|
||||
|
||||
@@ -21,10 +22,10 @@ All commands in this CLI are wrappers around API endpoints defined in the OpenAP
|
||||
|
||||
### Command Structure
|
||||
The codebase follows Cobra's command pattern with a root command and subcommands:
|
||||
- Entry point: `main.go` calls `cmd.Execute()`
|
||||
- Entry point: `coolify/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
|
||||
@@ -62,23 +63,23 @@ Three output modes supported via `--format` flag:
|
||||
|
||||
### Build
|
||||
```bash
|
||||
go build -o coolify .
|
||||
go build -o coolify ./coolify
|
||||
```
|
||||
|
||||
### Run locally
|
||||
```bash
|
||||
go run main.go [command]
|
||||
go run ./coolify [command]
|
||||
```
|
||||
|
||||
### Test a command
|
||||
```bash
|
||||
go run main.go instances list
|
||||
go run main.go servers list --debug
|
||||
go run ./coolify context list
|
||||
go run ./coolify servers list --debug
|
||||
```
|
||||
|
||||
### Install locally
|
||||
```bash
|
||||
go install
|
||||
go install ./coolify
|
||||
```
|
||||
|
||||
### Run tests
|
||||
|
||||
+620
@@ -0,0 +1,620 @@
|
||||
# Contributing to Coolify CLI
|
||||
|
||||
Thank you for your interest in contributing to the Coolify CLI! This document provides guidelines and instructions for contributing to the project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Project Architecture](#project-architecture)
|
||||
- [Adding a New Command](#adding-a-new-command)
|
||||
- [Testing Requirements](#testing-requirements)
|
||||
- [Code Style & Conventions](#code-style--conventions)
|
||||
- [Submitting Changes](#submitting-changes)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Before you start contributing:
|
||||
|
||||
1. **Read the [ARCHITECTURE.md](ARCHITECTURE.md)** for detailed architectural guidance
|
||||
2. **Review the [OpenAPI specification](https://github.com/coollabsio/coolify/blob/v4.x/openapi.json)** to understand available API endpoints
|
||||
3. **Check existing issues** to see if your feature/bug is already being worked on
|
||||
4. **Open an issue** to discuss your proposed changes (for large features)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.24 or higher
|
||||
- Git
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Clone and Build
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub
|
||||
# Clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/coolify-cli.git
|
||||
cd coolify-cli
|
||||
|
||||
# Build the CLI
|
||||
go build -o coolify ./coolify
|
||||
|
||||
# Install locally
|
||||
go install
|
||||
```
|
||||
|
||||
### Running the CLI
|
||||
|
||||
```bash
|
||||
# Run without installing
|
||||
go run ./coolify [command]
|
||||
|
||||
# Example commands
|
||||
go run ./coolify context list
|
||||
go run ./coolify server list --debug
|
||||
|
||||
# With flags
|
||||
go run ./coolify server list --format json --debug
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
cmd/ # CLI commands (organized by feature)
|
||||
├── root.go # Root command and global flags
|
||||
├── application/ # Application management commands
|
||||
├── context/ # Manage Coolify instances
|
||||
├── server/ # Server management
|
||||
├── project/ # Project management
|
||||
├── database/ # Database management
|
||||
├── deployment/ # Deployment operations
|
||||
├── service/ # Service management
|
||||
└── ...
|
||||
|
||||
internal/ # Internal packages
|
||||
├── api/ # API client (HTTP communication)
|
||||
├── cli/ # CLI utilities (GetAPIClient helper)
|
||||
├── config/ # Configuration management
|
||||
├── models/ # Data models and structs
|
||||
├── output/ # Output formatters (table, json, pretty)
|
||||
├── parser/ # Input parsing utilities
|
||||
├── service/ # Business logic layer
|
||||
└── version/ # Version management
|
||||
|
||||
test/ # Test utilities and fixtures
|
||||
└── fixtures/ # Mock API response data
|
||||
```
|
||||
|
||||
## Project Architecture
|
||||
|
||||
The Coolify CLI follows a **layered architecture**:
|
||||
|
||||
```
|
||||
User → Commands (cmd/) → Services (internal/service/) → API Client (internal/api/) → Coolify API
|
||||
```
|
||||
|
||||
### Layer Responsibilities
|
||||
|
||||
1. **Command Layer** (`cmd/`)
|
||||
- Parse CLI arguments and flags
|
||||
- Call service layer methods
|
||||
- Format output using output formatters
|
||||
|
||||
2. **Service Layer** (`internal/service/`)
|
||||
- Business logic
|
||||
- Coordinate API calls
|
||||
- Transform data
|
||||
|
||||
3. **API Client Layer** (`internal/api/`)
|
||||
- HTTP communication
|
||||
- Retry logic with exponential backoff
|
||||
- Authentication (Bearer tokens)
|
||||
- Error handling
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
- **cobra**: CLI framework
|
||||
- **viper**: Configuration management
|
||||
- **stretchr/testify**: Testing assertions
|
||||
|
||||
## Adding a New Command
|
||||
|
||||
Follow these steps to add a new command:
|
||||
|
||||
### 1. Create Command Directory Structure
|
||||
|
||||
```bash
|
||||
# Create directory for your command
|
||||
mkdir -p cmd/myfeature
|
||||
```
|
||||
|
||||
### 2. Create Parent Command
|
||||
|
||||
Create `cmd/myfeature/myfeature.go`:
|
||||
|
||||
```go
|
||||
package myfeature
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewMyFeatureCommand creates the myfeature parent command
|
||||
func NewMyFeatureCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "myfeature",
|
||||
Aliases: []string{"mf"},
|
||||
Short: "MyFeature related commands",
|
||||
Long: `Manage MyFeature resources.`,
|
||||
}
|
||||
|
||||
// Add subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
// ... more subcommands
|
||||
|
||||
return cmd
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create Subcommand
|
||||
|
||||
Create `cmd/myfeature/list.go`:
|
||||
|
||||
```go
|
||||
package myfeature
|
||||
|
||||
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 myfeature resources",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Use service layer
|
||||
svc := service.NewMyFeatureService(client)
|
||||
items, err := svc.List(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list items: %w", err)
|
||||
}
|
||||
|
||||
// Format output
|
||||
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(items)
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Service Layer
|
||||
|
||||
Create `internal/service/myfeature.go`:
|
||||
|
||||
```go
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/api"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
)
|
||||
|
||||
type MyFeatureService struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
func NewMyFeatureService(client *api.Client) *MyFeatureService {
|
||||
return &MyFeatureService{client: client}
|
||||
}
|
||||
|
||||
func (s *MyFeatureService) List(ctx context.Context) ([]models.MyFeature, error) {
|
||||
var items []models.MyFeature
|
||||
err := s.client.Get(ctx, "myfeature", &items)
|
||||
return items, err
|
||||
}
|
||||
|
||||
func (s *MyFeatureService) Get(ctx context.Context, uuid string) (*models.MyFeature, error) {
|
||||
var item models.MyFeature
|
||||
err := s.client.Get(ctx, "myfeature/"+uuid, &item)
|
||||
return &item, err
|
||||
}
|
||||
|
||||
func (s *MyFeatureService) Create(ctx context.Context, req models.MyFeatureCreateRequest) (*models.Response, error) {
|
||||
var response models.Response
|
||||
err := s.client.Post(ctx, "myfeature", req, &response)
|
||||
return &response, err
|
||||
}
|
||||
|
||||
func (s *MyFeatureService) Delete(ctx context.Context, uuid string) error {
|
||||
return s.client.Delete(ctx, "myfeature/"+uuid)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Models
|
||||
|
||||
Create `internal/models/myfeature.go`:
|
||||
|
||||
```go
|
||||
package models
|
||||
|
||||
type MyFeature struct {
|
||||
ID int `json:"id" table:"-"` // Hidden from table output
|
||||
UUID string `json:"uuid"` // Shown to users
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
// Add more fields...
|
||||
}
|
||||
|
||||
type MyFeatureCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: Always use `UUID` for user-facing identifiers, not database `ID`. Hide `ID` field from table output using `table:"-"` tag.
|
||||
|
||||
### 6. Register Command
|
||||
|
||||
Add your command to `cmd/root.go`:
|
||||
|
||||
```go
|
||||
import (
|
||||
// ... existing imports
|
||||
"github.com/coollabsio/coolify-cli/cmd/myfeature"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// ... existing code
|
||||
rootCmd.AddCommand(myfeature.NewMyFeatureCommand())
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Create Tests
|
||||
|
||||
Create `internal/service/myfeature_test.go`:
|
||||
|
||||
```go
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/api"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMyFeatureService_List(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v1/myfeature", r.URL.Path)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
|
||||
items := []models.MyFeature{
|
||||
{UUID: "uuid-1", Name: "item-1"},
|
||||
{UUID: "uuid-2", Name: "item-2"},
|
||||
}
|
||||
json.NewEncoder(w).Encode(items)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := api.NewClient(server.URL, "test-token")
|
||||
svc := NewMyFeatureService(client)
|
||||
|
||||
items, err := svc.List(cmd.Context())
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, items, 2)
|
||||
assert.Equal(t, "uuid-1", items[0].UUID)
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Update Documentation
|
||||
|
||||
- Add command documentation to `README.md`
|
||||
- Include usage examples and flag descriptions
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
**All code changes MUST include tests.** This is non-negotiable.
|
||||
|
||||
### Coverage Requirements
|
||||
|
||||
- **Minimum coverage**: 70% for all packages
|
||||
- **New features**: 80%+ coverage required
|
||||
- **Bug fixes**: Must include regression tests
|
||||
- **Refactoring**: Must maintain or improve existing coverage
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
go test ./internal/...
|
||||
|
||||
# Run with coverage
|
||||
go test ./internal/... -cover
|
||||
|
||||
# Run specific package
|
||||
go test ./internal/service/... -v
|
||||
|
||||
# Run specific test
|
||||
go test ./internal/service -run TestServerService_List -v
|
||||
|
||||
# Generate coverage report
|
||||
go test ./internal/... -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
#### Use Table-Driven Tests
|
||||
|
||||
```go
|
||||
func TestMyFunction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "successful case",
|
||||
input: "test",
|
||||
want: "expected",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error case",
|
||||
input: "",
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := MyFunction(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("MyFunction() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("MyFunction() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Mock HTTP Requests
|
||||
|
||||
**IMPORTANT**: Never call real APIs in tests. Use `httptest.NewServer()`:
|
||||
|
||||
```go
|
||||
func TestServiceMethod(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request
|
||||
assert.Equal(t, "/api/v1/endpoint", r.URL.Path)
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
|
||||
// Return mock response
|
||||
response := models.MyResponse{Data: "test"}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := api.NewClient(server.URL, "test-token")
|
||||
// ... test your service
|
||||
}
|
||||
```
|
||||
|
||||
### Test Guidelines
|
||||
|
||||
- **Test naming**: `TestFunctionName_Scenario_ExpectedBehavior`
|
||||
- **Use subtests**: `t.Run()` for related test cases
|
||||
- **Use testify**: `require.NoError()` for must-pass assertions, `assert.Equal()` for comparisons
|
||||
- **Mock HTTP**: Use `httptest.NewServer()` for all API tests
|
||||
- **Test contexts**: Always pass `context.Background()` in tests
|
||||
- **Test errors**: Verify error messages and types
|
||||
|
||||
## Code Style & Conventions
|
||||
|
||||
### Go Standards
|
||||
|
||||
- Follow standard Go idioms and conventions
|
||||
- Use `gofmt` for code formatting
|
||||
- Run `go vet` to catch common issues
|
||||
- Prefer standard library over external dependencies
|
||||
|
||||
### Project Conventions
|
||||
|
||||
#### API Client Usage
|
||||
|
||||
```go
|
||||
// Create client (usually done via cli.GetAPIClient())
|
||||
client := api.NewClient(baseURL, token, api.WithDebug(true))
|
||||
|
||||
// GET request
|
||||
var result MyStruct
|
||||
err := client.Get(ctx, "endpoint", &result)
|
||||
|
||||
// POST request
|
||||
err := client.Post(ctx, "endpoint", requestBody, &result)
|
||||
|
||||
// DELETE request
|
||||
err := client.Delete(ctx, "endpoint")
|
||||
|
||||
// PATCH request
|
||||
err := client.Patch(ctx, "endpoint", requestBody, &result)
|
||||
```
|
||||
|
||||
#### Service Layer Pattern
|
||||
|
||||
```go
|
||||
type MyService struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
func NewMyService(client *api.Client) *MyService {
|
||||
return &MyService{client: client}
|
||||
}
|
||||
|
||||
func (s *MyService) List(ctx context.Context) ([]models.Item, error) {
|
||||
var items []models.Item
|
||||
err := s.client.Get(ctx, "items", &items)
|
||||
return items, err
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
|
||||
```go
|
||||
// Wrap errors with context
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch data: %w", err)
|
||||
}
|
||||
|
||||
// Check and handle specific error types
|
||||
if apiErr, ok := err.(*api.Error); ok {
|
||||
if apiErr.StatusCode == 404 {
|
||||
return fmt.Errorf("resource not found")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Global Flags
|
||||
|
||||
All commands automatically inherit these global flags:
|
||||
|
||||
- `--format` (table|json|pretty) - Output format
|
||||
- `--show-sensitive` - Show sensitive information
|
||||
- `--debug` - Enable debug mode
|
||||
- `--context` - Use specific context by name
|
||||
- `--token` - Override context token
|
||||
|
||||
Access flags in commands:
|
||||
|
||||
```go
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
|
||||
debug, _ := cmd.Flags().GetBool("debug")
|
||||
```
|
||||
|
||||
## Submitting Changes
|
||||
|
||||
### Before Committing
|
||||
|
||||
```bash
|
||||
# 1. Format code
|
||||
go fmt ./...
|
||||
|
||||
# 2. Run tests
|
||||
go test ./internal/...
|
||||
|
||||
# 3. Check coverage
|
||||
go test ./internal/... -cover
|
||||
|
||||
# 4. Run vet
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Write clear, descriptive commit messages following conventional commits format:
|
||||
|
||||
```
|
||||
<type>: <short summary>
|
||||
|
||||
<detailed description>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
feat: add server domains list command
|
||||
|
||||
- Implement GET /servers/{uuid}/domains endpoint
|
||||
- Add server domains subcommand
|
||||
- Include tests for domain listing
|
||||
- Update README with new command documentation
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
||||
1. **Fork** the repository
|
||||
2. **Create a branch** from `v4.x`: `git checkout -b feature/my-feature v4.x`
|
||||
3. **Make your changes** with tests
|
||||
4. **Push** to your fork: `git push origin feature/my-feature`
|
||||
5. **Open a pull request** against the `v4.x` branch
|
||||
6. **Describe your changes** clearly in the PR description
|
||||
7. **Link related issues** using "Fixes #123" or "Closes #123"
|
||||
|
||||
### PR Checklist
|
||||
|
||||
- [ ] Tests pass locally (`go test ./internal/...`)
|
||||
- [ ] Code coverage meets requirements (70%+ minimum)
|
||||
- [ ] Code is formatted (`go fmt ./...`)
|
||||
- [ ] README.md updated (if adding new commands)
|
||||
- [ ] CLAUDE.md updated (if changing architecture)
|
||||
- [ ] Commit messages are descriptive
|
||||
- [ ] PR description explains the changes
|
||||
- [ ] All global flags are supported (format, show-sensitive, debug)
|
||||
- [ ] Used UUIDs (not IDs) for resource identifiers
|
||||
|
||||
## Release Process (not for contributors :) )
|
||||
|
||||
Releases are automated using GoReleaser:
|
||||
|
||||
1. Tag a new version: `git tag v1.2.3`
|
||||
2. Push the tag: `git push origin v1.2.3`
|
||||
3. Create a GitHub release
|
||||
4. GoReleaser builds binaries for all platforms automatically
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Discord**: https://coolify.io/discord
|
||||
- **Issues**: [Open an issue](https://github.com/coollabsio/coolify-cli/issues) for bugs or feature requests
|
||||
- **Architecture**: Read [ARCHITECTURE.md](ARCHITECTURE.md) for detailed design documentation
|
||||
- **API Reference**: See the [OpenAPI specification](https://github.com/coollabsio/coolify/blob/v4.x/openapi.json)
|
||||
- **Code Guidance**: See [CLAUDE.md](CLAUDE.md) for AI assistant guidance
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the same license as the project.
|
||||
|
||||
---
|
||||
|
||||
Thank you for contributing to Coolify CLI! 🚀
|
||||
+10
-28
@@ -10,28 +10,7 @@ This guide explains the release process for the Coolify CLI.
|
||||
|
||||
## Release Process
|
||||
|
||||
### 1. Update Version Number
|
||||
|
||||
Edit `cmd/root.go` and update the `CliVersion` variable:
|
||||
|
||||
```go
|
||||
var CliVersion = "1.x.x" // Change to your new version
|
||||
```
|
||||
|
||||
**Version Format:** Use semantic versioning: `MAJOR.MINOR.PATCH` (e.g., `1.2.3`)
|
||||
- **MAJOR**: Breaking changes
|
||||
- **MINOR**: New features (backwards compatible)
|
||||
- **PATCH**: Bug fixes (backwards compatible)
|
||||
|
||||
### 2. Commit and Push Version Change
|
||||
|
||||
```bash
|
||||
git add cmd/root.go
|
||||
git commit -m "chore: bump version to 1.x.x"
|
||||
git push origin v4.x
|
||||
```
|
||||
|
||||
### 3. Create a GitHub Release
|
||||
### 1. Create a GitHub Release
|
||||
|
||||
1. Go to https://github.com/coollabsio/coolify-cli/releases/new
|
||||
2. Click "Choose a tag" and create a new tag:
|
||||
@@ -56,7 +35,7 @@ git push origin v4.x
|
||||
```
|
||||
5. Click "Publish release"
|
||||
|
||||
### 4. Automated Build Process
|
||||
### 2. Automated Build Process
|
||||
|
||||
Once you publish the release:
|
||||
|
||||
@@ -65,12 +44,14 @@ Once you publish the release:
|
||||
- **Linux**: amd64, arm64
|
||||
- **macOS (Darwin)**: amd64, arm64
|
||||
- **Windows**: amd64, arm64
|
||||
3. Binaries are automatically uploaded to the release
|
||||
4. The release becomes available at:
|
||||
3. Goreleaser injects the version from the tag into the binaries
|
||||
4. Binaries are automatically uploaded to the release
|
||||
5. The release becomes available at:
|
||||
- GitHub: `https://github.com/coollabsio/coolify-cli/releases/tag/v1.x.x`
|
||||
- Install script: `curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash`
|
||||
- `go install`: `go install github.com/coollabsio/coolify-cli/coolify@v1.x.x`
|
||||
|
||||
### 5. Verify the Release
|
||||
### 3. Verify the Release
|
||||
|
||||
After the workflow completes (usually 2-5 minutes):
|
||||
|
||||
@@ -128,10 +109,11 @@ After creating a release:
|
||||
|
||||
The release process uses these configuration files:
|
||||
|
||||
- `.goreleaser.yml` - GoReleaser configuration (build matrix, archives, etc.)
|
||||
- `.goreleaser.yml` - GoReleaser configuration (build matrix, archives, etc.) - points to `/coolify` as entry point
|
||||
- `.github/workflows/release-cli.yml` - GitHub Actions workflow
|
||||
- `scripts/install.sh` - User-facing install script
|
||||
- `cmd/root.go` - Contains `CliVersion` variable (line 22)
|
||||
- `internal/version/checker.go` - Contains `GetVersion()` function that returns the current version
|
||||
- `coolify/main.go` - Binary entry point for `go install` support
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -2,56 +2,107 @@
|
||||
|
||||
## Installation
|
||||
|
||||
### Install script (recommended)
|
||||
|
||||
#### Linux/macOS
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
It will install the CLI in `/usr/local/bin/coolify` and the configuration file in `~/.config/coolify/config.json`
|
||||
|
||||
> If you are a windows or mac user, please test the installation script and let us know if it works for you.
|
||||
#### Windows (PowerShell)
|
||||
|
||||
## Configuration
|
||||
```powershell
|
||||
irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
|
||||
```
|
||||
|
||||
It will install the CLI in `%ProgramFiles%\Coolify\coolify.exe` and the configuration file in `%USERPROFILE%\.config\coolify\config.json`
|
||||
|
||||
For user installation (no admin rights required):
|
||||
```powershell
|
||||
$env:COOLIFY_USER_INSTALL=1; irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
|
||||
```
|
||||
|
||||
For a specific version:
|
||||
```powershell
|
||||
$env:COOLIFY_VERSION='v1.0.0'; irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
|
||||
```
|
||||
|
||||
### Using `go install`
|
||||
|
||||
```bash
|
||||
go install github.com/coollabsio/coolify-cli/coolify@latest
|
||||
```
|
||||
|
||||
This will install the `coolify` binary in your `$GOPATH/bin` directory (usually `~/go/bin`). Make sure this directory is in your `$PATH`.
|
||||
|
||||
### Using the install script
|
||||
|
||||
|
||||
## 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
|
||||
### Configuration
|
||||
- `coolify config` - Show configuration file location
|
||||
|
||||
### Shell Completion
|
||||
- `coolify completion <shell>` - Generate shell completion script
|
||||
- Supported shells: `bash`, `zsh`, `fish`, `powershell`
|
||||
|
||||
### 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 verify` - Verify current context connection and authentication
|
||||
- `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
|
||||
|
||||
Commands can use `server` or `servers` interchangeably.
|
||||
|
||||
- `coolify server list` - List all servers
|
||||
- `coolify server 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
|
||||
- `--port <port>` - SSH port (default: 22)
|
||||
- `--user <user>` - SSH user (default: root)
|
||||
- `coolify server add <name> <ip> <private_key_uuid>` - Add a new server
|
||||
- `-p, --port <port>` - SSH port (default: 22)
|
||||
- `-u, --user <user>` - SSH user (default: root)
|
||||
- `--validate` - Validate server immediately after adding
|
||||
- `coolify servers remove <uuid>` - Remove a server
|
||||
- `coolify servers validate <uuid>` - Validate a server connection
|
||||
- `coolify server remove <uuid>` - Remove a server
|
||||
- `coolify server validate <uuid>` - Validate a server connection
|
||||
- `coolify server domains <uuid>` - Get server domains by UUID
|
||||
|
||||
### Projects
|
||||
- `coolify projects list` - List all projects
|
||||
@@ -66,8 +117,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,15 +145,34 @@ 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)
|
||||
- `coolify app env update <app_uuid> <env_uuid>` - Update an environment variable
|
||||
- `--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>` - Update an environment variable
|
||||
- `--key <key>` - Variable key (required)
|
||||
- `--value <value>` - Variable value (required)
|
||||
- `--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
|
||||
- `--runtime` - Available at runtime
|
||||
- `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
|
||||
- `--file <path>` - Path to .env file (required)
|
||||
- `--build-time` - Make all variables available at build time
|
||||
- `--preview` - Make all variables available in preview deployments
|
||||
- `--is-literal` - Treat all values as literal (don't interpolate variables)
|
||||
- **Behavior**: Updates existing variables, creates missing ones. Does NOT delete variables not in the file.
|
||||
|
||||
#### Application Deployments
|
||||
- `coolify app deployments list <app-uuid>` - List all deployments for an application
|
||||
- `coolify app deployments logs <app-uuid> [deployment-uuid]` - Get deployment logs (formatted as human-readable text)
|
||||
- If only `app-uuid` is provided: retrieves logs from the **latest/most recent deployment only**
|
||||
- If `deployment-uuid` is also provided: retrieves logs for that **specific deployment**
|
||||
- `-n, --lines <n>` - Number of log lines to display (default: 0 = all lines)
|
||||
- `-f, --follow` - Follow log output in real-time (like tail -f)
|
||||
- `--debuglogs` - Show debug logs (includes hidden commands and internal operations)
|
||||
|
||||
### Databases
|
||||
- `coolify database list` - List all databases
|
||||
@@ -96,18 +181,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 +210,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
|
||||
@@ -141,71 +239,81 @@ You can change the default instance with `coolify instances set default <name>`
|
||||
- `coolify service env get <service_uuid> <env_uuid_or_key>` - Get a specific environment variable
|
||||
- `coolify service env create <service_uuid>` - Create a new environment variable
|
||||
- Same flags as application environment variables
|
||||
- `coolify service env update <service_uuid> <env_uuid>` - Update an environment variable
|
||||
- `coolify service env update <service_uuid>` - Update an environment variable
|
||||
- `--key <key>` - Variable key (required)
|
||||
- `--value <value>` - Variable value (required)
|
||||
- `--build-time` - Available at build time
|
||||
- `--is-literal` - Treat value as literal (don't interpolate variables)
|
||||
- `--is-multiline` - Value is multiline
|
||||
- `--runtime` - Available at runtime
|
||||
- `coolify service env delete <service_uuid> <env_uuid>` - Delete an environment variable
|
||||
- `coolify service env sync <service_uuid>` - Sync environment variables from a .env file
|
||||
- `--file <path>` - Path to .env file (required)
|
||||
- `--build-time` - Make all variables available at build time
|
||||
- `--preview` - Make all variables available in preview deployments
|
||||
- `--is-literal` - Treat all values as literal (don't interpolate variables)
|
||||
- **Behavior**: Updates existing variables, creates missing ones. Does NOT delete variables not in the file.
|
||||
|
||||
### Deployments
|
||||
- `coolify deploy uuid <uuid>` - Deploy a resource by UUID
|
||||
- `--force` - Force deployment
|
||||
- `-f, --force` - Force deployment
|
||||
- `coolify deploy name <name>` - Deploy a resource by name
|
||||
- `--force` - Force deployment
|
||||
- `-f, --force` - Force deployment
|
||||
- `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
|
||||
- Use `@filename` to read from file: `coolify privatekeys create mykey @~/.ssh/id_rsa`
|
||||
- `coolify privatekeys delete <uuid>` - Delete a private key
|
||||
|
||||
Commands can use `private-key`, `private-keys`, `key`, or `keys` interchangeably.
|
||||
|
||||
- `coolify private-key list` - List all private keys
|
||||
- `coolify private-key add <key_name> <private-key>` - Add a new private key
|
||||
- Use `@filename` to read from file: `coolify private-key add mykey @~/.ssh/id_rsa`
|
||||
- `coolify private-key remove <uuid>` - Remove a private key
|
||||
|
||||
## Global Flags
|
||||
|
||||
All commands support these global flags:
|
||||
|
||||
- `--instance <name>` - Use a specific instance profile instead of default (NEW)
|
||||
- `--context <name>` - Use a specific context instead of default
|
||||
- `--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 +321,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
|
||||
```
|
||||
|
||||
@@ -250,7 +358,10 @@ coolify app logs <uuid>
|
||||
# Environment variables
|
||||
coolify app env list <uuid>
|
||||
coolify app env create <uuid> --key API_KEY --value secret123
|
||||
|
||||
# Sync from .env file (updates existing, creates new, keeps others unchanged)
|
||||
coolify app env sync <uuid> --file .env
|
||||
coolify app env sync <uuid> --file .env.production --build-time --preview
|
||||
```
|
||||
|
||||
### Database Management
|
||||
@@ -307,8 +418,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,13 +476,13 @@ coolify team members list
|
||||
|
||||
```bash
|
||||
# List servers in production
|
||||
coolify --instance=prod servers list
|
||||
coolify --context=prod server list
|
||||
|
||||
# Add a server with validation
|
||||
coolify servers add myserver 192.168.1.100 <key-uuid> --validate
|
||||
coolify server add myserver 192.168.1.100 <key-uuid> --validate
|
||||
|
||||
# Get server details with resources
|
||||
coolify servers get <uuid> --resources
|
||||
coolify server get <uuid> --resources
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
@@ -380,13 +491,13 @@ The CLI supports three output formats:
|
||||
|
||||
```bash
|
||||
# Table format (default, human-readable)
|
||||
coolify servers list
|
||||
coolify server list
|
||||
|
||||
# JSON format (for scripts)
|
||||
coolify servers list --format=json
|
||||
coolify server list --format=json
|
||||
|
||||
# Pretty JSON (for debugging)
|
||||
coolify servers list --format=pretty
|
||||
coolify server list --format=pretty
|
||||
```
|
||||
|
||||
## Architecture
|
||||
@@ -394,14 +505,14 @@ 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
|
||||
|
||||
```bash
|
||||
# Build
|
||||
go build -o coolify .
|
||||
go build -o coolify ./coolify
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
@@ -410,12 +521,12 @@ go test ./...
|
||||
go test -cover ./...
|
||||
|
||||
# Install locally
|
||||
go install
|
||||
go install ./coolify
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please check the [restructure documentation](RESTRUCTURE_PLAN.md) for architecture guidelines.
|
||||
Contributions are welcome!
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/cmd/application/create"
|
||||
"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(create.NewCreateCommand())
|
||||
cmd.AddCommand(NewUpdateCommand())
|
||||
cmd.AddCommand(NewDeleteCommand())
|
||||
cmd.AddCommand(NewStartCommand())
|
||||
cmd.AddCommand(NewStopCommand())
|
||||
cmd.AddCommand(NewRestartCommand())
|
||||
cmd.AddCommand(NewLogsCommand())
|
||||
cmd.AddCommand(NewDeploymentsCommand())
|
||||
|
||||
// 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,38 @@
|
||||
package create
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// NewCreateCommand creates the create parent command with all subcommands
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new application",
|
||||
Long: `Create a new application from various sources.
|
||||
|
||||
Available source types:
|
||||
public Create from a public git repository
|
||||
github Create from a private repository using GitHub App
|
||||
deploy-key Create from a private repository using SSH deploy key
|
||||
dockerfile Create from a custom Dockerfile
|
||||
dockerimage Create from a pre-built Docker image
|
||||
|
||||
Examples:
|
||||
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--git-repository "https://github.com/user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
|
||||
|
||||
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--github-app-uuid <uuid> --git-repository "user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
|
||||
|
||||
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--docker-registry-image-name "nginx:latest" --ports-exposes 80`,
|
||||
}
|
||||
|
||||
// Add all create subcommands
|
||||
cmd.AddCommand(NewPublicCommand())
|
||||
cmd.AddCommand(NewGitHubCommand())
|
||||
cmd.AddCommand(NewDeployKeyCommand())
|
||||
cmd.AddCommand(NewDockerfileCommand())
|
||||
cmd.AddCommand(NewDockerImageCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewDeployKeyCommand returns the create deploy-key application command
|
||||
func NewDeployKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy-key",
|
||||
Short: "Create an application from a private repository using SSH deploy key",
|
||||
Long: `Create a new application from a private git repository using SSH deploy key authentication.
|
||||
|
||||
Use 'coolify privatekeys list' to find your private key UUID.
|
||||
|
||||
Examples:
|
||||
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--private-key-uuid <uuid> --git-repository "git@github.com:owner/repo.git" --git-branch main \
|
||||
--build-pack nixpacks --ports-exposes 3000
|
||||
|
||||
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--private-key-uuid <uuid> --git-repository "git@gitlab.com:owner/repo.git" --git-branch main \
|
||||
--build-pack dockerfile --ports-exposes 8080 --instant-deploy`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get required flags
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
privateKeyUUID, _ := cmd.Flags().GetString("private-key-uuid")
|
||||
gitRepository, _ := cmd.Flags().GetString("git-repository")
|
||||
gitBranch, _ := cmd.Flags().GetString("git-branch")
|
||||
buildPack, _ := cmd.Flags().GetString("build-pack")
|
||||
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
// Validate required fields
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
if privateKeyUUID == "" {
|
||||
return fmt.Errorf("--private-key-uuid is required")
|
||||
}
|
||||
if gitRepository == "" || gitBranch == "" {
|
||||
return fmt.Errorf("--git-repository and --git-branch are required")
|
||||
}
|
||||
if buildPack == "" || portsExposes == "" {
|
||||
return fmt.Errorf("--build-pack and --ports-exposes are required")
|
||||
}
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.ApplicationCreateDeployKeyRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
PrivateKeyUUID: privateKeyUUID,
|
||||
GitRepository: gitRepository,
|
||||
GitBranch: gitBranch,
|
||||
BuildPack: buildPack,
|
||||
PortsExposes: portsExposes,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
setOptionalStringFlag(cmd, "name", &req.Name)
|
||||
setOptionalStringFlag(cmd, "description", &req.Description)
|
||||
setOptionalStringFlag(cmd, "domains", &req.Domains)
|
||||
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
|
||||
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
|
||||
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
|
||||
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
|
||||
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
|
||||
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
|
||||
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
|
||||
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
|
||||
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
|
||||
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
|
||||
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
|
||||
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
|
||||
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
|
||||
|
||||
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.CreateDeployKey(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create 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)
|
||||
},
|
||||
}
|
||||
|
||||
// Required 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("private-key-uuid", "", "Private key UUID (required)")
|
||||
cmd.Flags().String("git-repository", "", "Git repository SSH URL, e.g., 'git@github.com:owner/repo.git' (required)")
|
||||
cmd.Flags().String("git-branch", "", "Git branch (required)")
|
||||
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("domains", "", "Domain(s) for the application")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("build-command", "", "Custom build command")
|
||||
cmd.Flags().String("start-command", "", "Custom start command")
|
||||
cmd.Flags().String("install-command", "", "Custom install command")
|
||||
cmd.Flags().String("base-directory", "", "Base directory for the application")
|
||||
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewDockerfileCommand returns the create dockerfile application command
|
||||
func NewDockerfileCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dockerfile",
|
||||
Short: "Create an application from a custom Dockerfile",
|
||||
Long: `Create a new application from a custom Dockerfile content.
|
||||
|
||||
Examples:
|
||||
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--dockerfile "FROM node:18\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"npm\", \"start\"]"
|
||||
|
||||
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--dockerfile "$(cat Dockerfile)" --ports-exposes 3000 --instant-deploy`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get required flags
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
dockerfile, _ := cmd.Flags().GetString("dockerfile")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
// Validate required fields
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
if dockerfile == "" {
|
||||
return fmt.Errorf("--dockerfile is required")
|
||||
}
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.ApplicationCreateDockerfileRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
Dockerfile: dockerfile,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
setOptionalStringFlag(cmd, "name", &req.Name)
|
||||
setOptionalStringFlag(cmd, "description", &req.Description)
|
||||
setOptionalStringFlag(cmd, "domains", &req.Domains)
|
||||
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
|
||||
setOptionalStringFlag(cmd, "ports-exposes", &req.PortsExposes)
|
||||
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
|
||||
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
|
||||
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
|
||||
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
|
||||
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
|
||||
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
|
||||
|
||||
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.CreateDockerfile(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create 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)
|
||||
},
|
||||
}
|
||||
|
||||
// Required 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("dockerfile", "", "Dockerfile content (required)")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("domains", "", "Domain(s) for the application")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080'")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewDockerImageCommand returns the create dockerimage application command
|
||||
func NewDockerImageCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dockerimage",
|
||||
Short: "Create an application from a pre-built Docker image",
|
||||
Long: `Create a new application from a pre-built Docker image from a registry.
|
||||
|
||||
Examples:
|
||||
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--docker-registry-image-name "nginx:latest" --ports-exposes 80
|
||||
|
||||
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--docker-registry-image-name "ghcr.io/myorg/myapp" --docker-registry-image-tag "v1.0.0" \
|
||||
--ports-exposes 3000 --domains "myapp.example.com" --instant-deploy`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get required flags
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
dockerRegistryImageName, _ := cmd.Flags().GetString("docker-registry-image-name")
|
||||
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
// Validate required fields
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
if dockerRegistryImageName == "" {
|
||||
return fmt.Errorf("--docker-registry-image-name is required")
|
||||
}
|
||||
if portsExposes == "" {
|
||||
return fmt.Errorf("--ports-exposes is required")
|
||||
}
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.ApplicationCreateDockerImageRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
DockerRegistryImageName: dockerRegistryImageName,
|
||||
PortsExposes: portsExposes,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
setOptionalStringFlag(cmd, "name", &req.Name)
|
||||
setOptionalStringFlag(cmd, "description", &req.Description)
|
||||
setOptionalStringFlag(cmd, "domains", &req.Domains)
|
||||
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
|
||||
setOptionalStringFlag(cmd, "docker-registry-image-tag", &req.DockerRegistryImageTag)
|
||||
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
|
||||
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
|
||||
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
|
||||
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
|
||||
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
|
||||
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
|
||||
|
||||
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.CreateDockerImage(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create 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)
|
||||
},
|
||||
}
|
||||
|
||||
// Required 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("docker-registry-image-name", "", "Docker image name from registry (required)")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '80' or '80,443' (required)")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("domains", "", "Domain(s) for the application")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("docker-registry-image-tag", "", "Docker image tag (defaults to 'latest')")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewGitHubCommand returns the create github application command
|
||||
func NewGitHubCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "github",
|
||||
Short: "Create an application from a private repository using GitHub App",
|
||||
Long: `Create a new application from a private git repository using GitHub App authentication.
|
||||
|
||||
Use 'coolify github list' to find your GitHub App UUID.
|
||||
Use 'coolify github repos <app-uuid>' to list accessible repositories.
|
||||
|
||||
Examples:
|
||||
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--github-app-uuid <uuid> --git-repository "owner/repo" --git-branch main \
|
||||
--build-pack nixpacks --ports-exposes 3000
|
||||
|
||||
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--github-app-uuid <uuid> --git-repository "owner/repo" --git-branch main \
|
||||
--build-pack dockerfile --ports-exposes 8080 --instant-deploy`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get required flags
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
gitHubAppUUID, _ := cmd.Flags().GetString("github-app-uuid")
|
||||
gitRepository, _ := cmd.Flags().GetString("git-repository")
|
||||
gitBranch, _ := cmd.Flags().GetString("git-branch")
|
||||
buildPack, _ := cmd.Flags().GetString("build-pack")
|
||||
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
// Validate required fields
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
if gitHubAppUUID == "" {
|
||||
return fmt.Errorf("--github-app-uuid is required")
|
||||
}
|
||||
if gitRepository == "" || gitBranch == "" {
|
||||
return fmt.Errorf("--git-repository and --git-branch are required")
|
||||
}
|
||||
if buildPack == "" || portsExposes == "" {
|
||||
return fmt.Errorf("--build-pack and --ports-exposes are required")
|
||||
}
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.ApplicationCreateGitHubAppRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
GitHubAppUUID: gitHubAppUUID,
|
||||
GitRepository: gitRepository,
|
||||
GitBranch: gitBranch,
|
||||
BuildPack: buildPack,
|
||||
PortsExposes: portsExposes,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
setOptionalStringFlag(cmd, "name", &req.Name)
|
||||
setOptionalStringFlag(cmd, "description", &req.Description)
|
||||
setOptionalStringFlag(cmd, "domains", &req.Domains)
|
||||
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
|
||||
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
|
||||
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
|
||||
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
|
||||
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
|
||||
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
|
||||
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
|
||||
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
|
||||
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
|
||||
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
|
||||
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
|
||||
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
|
||||
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
|
||||
|
||||
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.CreateGitHubApp(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create 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)
|
||||
},
|
||||
}
|
||||
|
||||
// Required 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("github-app-uuid", "", "GitHub App UUID (required)")
|
||||
cmd.Flags().String("git-repository", "", "Git repository in format 'owner/repo' (required)")
|
||||
cmd.Flags().String("git-branch", "", "Git branch (required)")
|
||||
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("domains", "", "Domain(s) for the application")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("build-command", "", "Custom build command")
|
||||
cmd.Flags().String("start-command", "", "Custom start command")
|
||||
cmd.Flags().String("install-command", "", "Custom install command")
|
||||
cmd.Flags().String("base-directory", "", "Base directory for the application")
|
||||
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package create
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewPublicCommand returns the create public application command
|
||||
func NewPublicCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "public",
|
||||
Short: "Create an application from a public git repository",
|
||||
Long: `Create a new application from a public git repository.
|
||||
|
||||
Examples:
|
||||
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--git-repository "https://github.com/user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
|
||||
|
||||
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
|
||||
--git-repository "https://github.com/user/repo" --git-branch main --build-pack dockerfile --ports-exposes 8080 \
|
||||
--instant-deploy --domains "myapp.example.com"`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get required flags
|
||||
serverUUID, _ := cmd.Flags().GetString("server-uuid")
|
||||
projectUUID, _ := cmd.Flags().GetString("project-uuid")
|
||||
gitRepository, _ := cmd.Flags().GetString("git-repository")
|
||||
gitBranch, _ := cmd.Flags().GetString("git-branch")
|
||||
buildPack, _ := cmd.Flags().GetString("build-pack")
|
||||
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
|
||||
environmentName, _ := cmd.Flags().GetString("environment-name")
|
||||
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
|
||||
|
||||
// Validate required fields
|
||||
if serverUUID == "" || projectUUID == "" {
|
||||
return fmt.Errorf("--server-uuid and --project-uuid are required")
|
||||
}
|
||||
if gitRepository == "" || gitBranch == "" {
|
||||
return fmt.Errorf("--git-repository and --git-branch are required")
|
||||
}
|
||||
if buildPack == "" || portsExposes == "" {
|
||||
return fmt.Errorf("--build-pack and --ports-exposes are required")
|
||||
}
|
||||
if environmentName == "" && environmentUUID == "" {
|
||||
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
|
||||
}
|
||||
|
||||
req := &models.ApplicationCreatePublicRequest{
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
GitRepository: gitRepository,
|
||||
GitBranch: gitBranch,
|
||||
BuildPack: buildPack,
|
||||
PortsExposes: portsExposes,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = &environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Optional fields
|
||||
setOptionalStringFlag(cmd, "name", &req.Name)
|
||||
setOptionalStringFlag(cmd, "description", &req.Description)
|
||||
setOptionalStringFlag(cmd, "domains", &req.Domains)
|
||||
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
|
||||
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
|
||||
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
|
||||
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
|
||||
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
|
||||
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
|
||||
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
|
||||
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
|
||||
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
|
||||
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
|
||||
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
|
||||
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
|
||||
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
|
||||
|
||||
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.CreatePublic(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create 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)
|
||||
},
|
||||
}
|
||||
|
||||
// Required 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("git-repository", "", "Git repository URL (required)")
|
||||
cmd.Flags().String("git-branch", "", "Git branch (required)")
|
||||
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
|
||||
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Application name")
|
||||
cmd.Flags().String("description", "", "Application description")
|
||||
cmd.Flags().String("domains", "", "Domain(s) for the application")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().String("build-command", "", "Custom build command")
|
||||
cmd.Flags().String("start-command", "", "Custom start command")
|
||||
cmd.Flags().String("install-command", "", "Custom install command")
|
||||
cmd.Flags().String("base-directory", "", "Base directory for the application")
|
||||
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
|
||||
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
|
||||
cmd.Flags().String("limits-cpus", "", "CPU limit")
|
||||
cmd.Flags().String("limits-memory", "", "Memory limit")
|
||||
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
|
||||
cmd.Flags().String("health-check-path", "", "Health check path")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Helper functions for optional flags
|
||||
func setOptionalStringFlag(cmd *cobra.Command, flagName string, target **string) {
|
||||
if cmd.Flags().Changed(flagName) {
|
||||
val, _ := cmd.Flags().GetString(flagName)
|
||||
*target = &val
|
||||
}
|
||||
}
|
||||
|
||||
func setOptionalBoolFlag(cmd *cobra.Command, flagName string, target **bool) {
|
||||
if cmd.Flags().Changed(flagName) {
|
||||
val, _ := cmd.Flags().GetBool(flagName)
|
||||
*target = &val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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)
|
||||
_, err := fmt.Scanln(&response)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read input: %w", err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
func NewDeploymentsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "deployments",
|
||||
Short: "Deployment related commands for an application",
|
||||
Long: `Manage deployments for a specific application. List deployments or view deployment logs.`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(NewListDeploymentsCommand())
|
||||
cmd.AddCommand(NewLogsDeploymentsCommand())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewListDeploymentsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list <app-uuid>",
|
||||
Short: "List all deployments for an application",
|
||||
Long: `Retrieve a list of all deployments for a specific application.`,
|
||||
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)
|
||||
}
|
||||
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
deployments, err := deploySvc.ListByApplication(ctx, appUUID)
|
||||
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 err
|
||||
}
|
||||
return formatter.Format(deployments)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewLogsDeploymentsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "logs <app-uuid> [deployment-uuid]",
|
||||
Short: "Get deployment logs for an application",
|
||||
Long: `Retrieve deployment logs for a specific application or deployment.
|
||||
|
||||
If only app-uuid is provided, retrieves logs from the latest deployment.
|
||||
If deployment-uuid is also provided, retrieves logs for that specific deployment.
|
||||
|
||||
Use --follow to continuously stream new logs.`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
appUUID := args[0]
|
||||
var deploymentUUID string
|
||||
if len(args) == 2 {
|
||||
deploymentUUID = args[1]
|
||||
}
|
||||
|
||||
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")
|
||||
debugLogs, _ := cmd.Flags().GetBool("debuglogs")
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
deploySvc := service.NewDeploymentService(client)
|
||||
|
||||
// Function to get logs based on whether we have a deployment UUID
|
||||
// Returns raw or formatted based on format flag
|
||||
getLogs := func() (string, error) {
|
||||
if deploymentUUID != "" {
|
||||
return deploySvc.GetLogsByDeploymentWithFormat(ctx, deploymentUUID, debugLogs, format)
|
||||
}
|
||||
// Get logs from the latest deployment
|
||||
// Use take=1 internally to efficiently fetch only the most recent deployment
|
||||
return deploySvc.GetLogsByApplicationWithFormat(ctx, appUUID, 1, debugLogs, format)
|
||||
}
|
||||
|
||||
if !follow {
|
||||
logs, err := getLogs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get deployment logs: %w", err)
|
||||
}
|
||||
|
||||
// Apply line limit if specified (only for text output)
|
||||
if lines > 0 && format == "table" {
|
||||
logs = limitLogLines(logs, lines)
|
||||
}
|
||||
|
||||
fmt.Print(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 := ""
|
||||
|
||||
logs, err := getLogs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get deployment logs: %w", err)
|
||||
}
|
||||
fmt.Print(logs)
|
||||
lastLogs = logs
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-sigChan:
|
||||
fmt.Println("\nStopping log follow...")
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
logs, err := getLogs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if logs != lastLogs {
|
||||
if len(logs) > len(lastLogs) && strings.HasPrefix(logs, lastLogs) {
|
||||
fmt.Print(logs[len(lastLogs):])
|
||||
} else {
|
||||
fmt.Print(logs)
|
||||
}
|
||||
lastLogs = logs
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntP("lines", "n", 0, "Number of log lines to display (0 = all)")
|
||||
cmd.Flags().BoolP("follow", "f", false, "Follow log output (like tail -f)")
|
||||
cmd.Flags().Bool("debuglogs", false, "Show debug logs (includes hidden commands and internal operations)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// limitLogLines limits the output to the last N lines
|
||||
func limitLogLines(logs string, n int) string {
|
||||
if n <= 0 {
|
||||
return logs
|
||||
}
|
||||
|
||||
// Trim trailing newline to avoid empty element at the end
|
||||
logs = strings.TrimRight(logs, "\n")
|
||||
lines := strings.Split(logs, "\n")
|
||||
|
||||
// If we have fewer lines than requested, return all
|
||||
if len(lines) <= n {
|
||||
return logs + "\n"
|
||||
}
|
||||
|
||||
// Get the last N lines
|
||||
lastLines := lines[len(lines)-n:]
|
||||
return strings.Join(lastLines, "\n") + "\n"
|
||||
}
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
appUUID := 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")
|
||||
isRuntime, _ := cmd.Flags().GetBool("runtime")
|
||||
|
||||
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
|
||||
}
|
||||
if cmd.Flags().Changed("runtime") {
|
||||
req.IsRuntime = &isRuntime
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.CreateEnv(ctx, appUUID, 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", true, "Available at build time (default: true)")
|
||||
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")
|
||||
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
|
||||
return cmd
|
||||
}
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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): ")
|
||||
_, err := fmt.Scanln(&response)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read confirmation: %w", err)
|
||||
}
|
||||
|
||||
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
+60
@@ -0,0 +1,60 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
func NewGetEnvCommand() *cobra.Command {
|
||||
cmd := &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, "<app_uuid> <env_uuid_or_key>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
envUUIDOrKey := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
|
||||
// First try to get by the identifier directly
|
||||
env, err := appSvc.GetEnv(ctx, appUUID, envUUIDOrKey)
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewListEnvCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list <app_uuid>",
|
||||
Short: "List all environment variables for an application",
|
||||
Long: `List all environment variables for a specific application. By default, only non-preview environment variables are shown. Use --preview to show preview environment variables instead, or --all to show all variables (non-preview first, then preview).`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
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)
|
||||
}
|
||||
|
||||
// Filter by preview/all flags
|
||||
showAll, _ := cmd.Flags().GetBool("all")
|
||||
showPreview, _ := cmd.Flags().GetBool("preview")
|
||||
|
||||
if showAll {
|
||||
// Sort: non-preview first, then preview
|
||||
sort.SliceStable(envs, func(i, j int) bool {
|
||||
if envs[i].IsPreview != envs[j].IsPreview {
|
||||
return !envs[i].IsPreview // non-preview (false) comes before preview (true)
|
||||
}
|
||||
return false // maintain original order within groups
|
||||
})
|
||||
} else {
|
||||
// Filter by preview flag
|
||||
var filtered []models.EnvironmentVariable
|
||||
for _, env := range envs {
|
||||
if env.IsPreview == showPreview {
|
||||
filtered = append(filtered, env)
|
||||
}
|
||||
}
|
||||
envs = filtered
|
||||
}
|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().Bool("preview", false, "Show preview environment variables instead of regular ones")
|
||||
cmd.Flags().Bool("all", false, "Show all environment variables (non-preview first, then preview)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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")
|
||||
isRuntime, _ := cmd.Flags().GetBool("runtime")
|
||||
|
||||
// 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
|
||||
}
|
||||
if cmd.Flags().Changed("runtime") {
|
||||
req.IsRuntime = &isRuntime
|
||||
}
|
||||
|
||||
// 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", true, "Make all variables available at build time (default: true)")
|
||||
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)")
|
||||
syncEnvCmd.Flags().Bool("runtime", true, "Make all variables available at runtime (default: true)")
|
||||
return syncEnvCmd
|
||||
}
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
func NewUpdateEnvCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <app_uuid>",
|
||||
Short: "Update an environment variable",
|
||||
Long: `Update an existing environment variable. UUID is the application.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Check minimum version requirement
|
||||
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.469"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableUpdateRequest{}
|
||||
|
||||
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 cmd.Flags().Changed("runtime") {
|
||||
isRuntime, _ := cmd.Flags().GetBool("runtime")
|
||||
req.IsRuntime = &isRuntime
|
||||
}
|
||||
|
||||
if req.Key == nil {
|
||||
return fmt.Errorf("--key is required")
|
||||
}
|
||||
if req.Value == nil {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
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", true, "Available at build time (default: true)")
|
||||
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")
|
||||
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all applications",
|
||||
Long: `List all applications in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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,97 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
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,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
)
|
||||
|
||||
// NewConfigCommand creates the config command
|
||||
func NewConfigCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Show configuration file location",
|
||||
Long: "Display the path to the Coolify CLI configuration file",
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Println(config.Path())
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
)
|
||||
|
||||
func TestNewConfigCommand(t *testing.T) {
|
||||
cmd := NewConfigCommand()
|
||||
|
||||
if cmd == nil {
|
||||
t.Fatal("NewConfigCommand() returned nil")
|
||||
}
|
||||
|
||||
if cmd.Use != "config" {
|
||||
t.Errorf("Expected Use to be 'config', got '%s'", cmd.Use)
|
||||
}
|
||||
|
||||
if cmd.Short == "" {
|
||||
t.Error("Short description should not be empty")
|
||||
}
|
||||
|
||||
if cmd.Long == "" {
|
||||
t.Error("Long description should not be empty")
|
||||
}
|
||||
|
||||
if cmd.Run == nil {
|
||||
t.Error("Run function should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigCommand_Output(t *testing.T) {
|
||||
// Test that the command returns the expected config path
|
||||
expectedPath := config.Path()
|
||||
|
||||
// The path should not be empty
|
||||
if expectedPath == "" {
|
||||
t.Error("Expected config path to not be empty")
|
||||
}
|
||||
|
||||
// The path should end with config.json
|
||||
if !strings.HasSuffix(expectedPath, "config.json") {
|
||||
t.Errorf("Expected path to end with 'config.json', got '%s'", expectedPath)
|
||||
}
|
||||
|
||||
// The path should contain the coolify directory
|
||||
if !strings.Contains(expectedPath, "coolify") {
|
||||
t.Errorf("Expected path to contain 'coolify', got '%s'", expectedPath)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
)
|
||||
|
||||
// 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",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("%s already exists.\n", name)
|
||||
fmt.Println("\nNote: Use --force to force overwrite.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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,28 @@
|
||||
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())
|
||||
cmd.AddCommand(NewVerifyCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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",
|
||||
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
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)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
|
||||
if instanceMap["default"] == true {
|
||||
if len(instances) > 0 {
|
||||
instances[0].(map[string]interface{})["default"] = true
|
||||
viper.Set("instances", instances)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
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 nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("context '%s' not found", Name)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
)
|
||||
|
||||
// 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,70 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all configured contexts",
|
||||
RunE: func(cmd *cobra.Command, _ []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,66 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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]
|
||||
|
||||
raw := viper.Get("instances")
|
||||
instances, ok := raw.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instances configuration")
|
||||
}
|
||||
|
||||
// Check if instance exists
|
||||
var found bool
|
||||
for _, instance := range instances {
|
||||
instanceMap, ok := instance.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instance configuration")
|
||||
}
|
||||
if val, ok := instanceMap["name"].(string); ok && val == name {
|
||||
found = true
|
||||
instanceMap["default"] = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
}
|
||||
|
||||
// Only unset other defaults if we found the target instance
|
||||
for _, instance := range instances {
|
||||
instanceMap, ok := instance.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instance configuration")
|
||||
}
|
||||
|
||||
if val, ok := instanceMap["name"].(string); ok && val != 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,48 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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",
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
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 {
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
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)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to update token for context '%s': %w", name, err)
|
||||
}
|
||||
fmt.Printf("Token updated for context '%s'.\n", name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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 == "" {
|
||||
return fmt.Errorf("at least one of --name, --url, or --token must be provided")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("context '%s' not found", oldName)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("context with name '%s' already exists", newName)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
|
||||
// Use the new name if renamed, otherwise use old name
|
||||
finalName := oldName
|
||||
if newName != "" {
|
||||
finalName = newName
|
||||
}
|
||||
fmt.Printf("Context '%s' updated successfully.\n", finalName)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
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,67 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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(_ *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
raw := viper.Get("instances")
|
||||
|
||||
instances, ok := raw.([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instances configuration")
|
||||
}
|
||||
// Check if instance exists
|
||||
var found bool
|
||||
for _, instance := range instances {
|
||||
instanceMap, ok := instance.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instance configuration")
|
||||
}
|
||||
if val, ok := instanceMap["name"].(string); ok && val == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
}
|
||||
|
||||
// Update default
|
||||
for _, instance := range instances {
|
||||
instanceMap, ok := instance.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid instance configuration")
|
||||
}
|
||||
|
||||
if val, ok := instanceMap["name"].(string); ok && val == 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,41 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewVerifyCommand creates the verify command for contexts
|
||||
func NewVerifyCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify current context connection and authentication",
|
||||
Long: `Verify that the current context is properly configured by testing the connection
|
||||
to the Coolify instance and validating the API token.`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client - this will use the current default context
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Try to get version - this verifies both connection and authentication
|
||||
version, err := client.GetVersion(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification failed: %w", err)
|
||||
}
|
||||
|
||||
// If we got here, connection and authentication are working
|
||||
fmt.Printf("✓ Connection successful\n")
|
||||
fmt.Printf("✓ Authentication valid\n")
|
||||
fmt.Printf("✓ Coolify version: %s\n", version)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/api"
|
||||
)
|
||||
|
||||
// TestVerifyCommand_APIIntegration tests the verify logic using the API client directly
|
||||
// This tests the core functionality that the verify command relies on
|
||||
func TestVerifyCommand_APIIntegration(t *testing.T) {
|
||||
t.Run("successful verification", func(t *testing.T) {
|
||||
// Create a test HTTP server that responds to /api/v1/version
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v1/version", r.URL.Path)
|
||||
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("4.0.0-beta.383"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create API client and verify connection
|
||||
client := api.NewClient(server.URL, "test-token")
|
||||
version, err := client.GetVersion(context.Background())
|
||||
|
||||
// Verify results
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "4.0.0-beta.383", version)
|
||||
})
|
||||
|
||||
t.Run("unauthorized - invalid token", func(t *testing.T) {
|
||||
// Create a test HTTP server that returns 401
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"message": "Invalid token",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create API client with invalid token
|
||||
client := api.NewClient(server.URL, "invalid-token")
|
||||
_, err := client.GetVersion(context.Background())
|
||||
|
||||
// Verify error
|
||||
require.Error(t, err)
|
||||
assert.True(t, api.IsUnauthorized(err))
|
||||
})
|
||||
|
||||
t.Run("server error", func(t *testing.T) {
|
||||
// Create a test HTTP server that returns 500
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"error": "Internal server error",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create API client
|
||||
client := api.NewClient(server.URL, "test-token", api.WithRetries(0))
|
||||
_, err := client.GetVersion(context.Background())
|
||||
|
||||
// Verify error
|
||||
require.Error(t, err)
|
||||
var apiErr *api.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
assert.Equal(t, 500, apiErr.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
// Create a test HTTP server that returns 404
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"message": "Endpoint not found",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Create API client
|
||||
client := api.NewClient(server.URL, "test-token")
|
||||
_, err := client.GetVersion(context.Background())
|
||||
|
||||
// Verify error
|
||||
require.Error(t, err)
|
||||
assert.True(t, api.IsNotFound(err))
|
||||
})
|
||||
}
|
||||
|
||||
// TestNewVerifyCommand tests that the command is properly configured
|
||||
func TestNewVerifyCommand(t *testing.T) {
|
||||
cmd := NewVerifyCommand()
|
||||
|
||||
assert.Equal(t, "verify", cmd.Use)
|
||||
assert.NotEmpty(t, cmd.Short)
|
||||
assert.NotEmpty(t, cmd.Long)
|
||||
assert.NotNil(t, cmd.RunE)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// 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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// 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,129 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Check minimum version requirement
|
||||
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.436"); err != nil {
|
||||
return 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"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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,75 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Check minimum version requirement
|
||||
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.436"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
force, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse force flag: %w", err)
|
||||
}
|
||||
|
||||
// Prompt for confirmation unless --force is used
|
||||
if !force {
|
||||
fmt.Printf("Are you sure you want to cancel deployment %s? (yes/no): ", uuid)
|
||||
var response string
|
||||
if _, err := fmt.Scanln(&response); err != nil {
|
||||
return fmt.Errorf("failed to read confirmation: %w", err)
|
||||
}
|
||||
|
||||
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, err := cmd.Flags().GetString("format")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get format flag: %w", err)
|
||||
}
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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
|
||||
}
|
||||
+11
-8
@@ -23,11 +23,11 @@ var manCmd = &cobra.Command{
|
||||
The man pages will be written to the specified directory (default: ./man).`,
|
||||
Example: ` coolify docs man
|
||||
coolify docs man --output-dir=/usr/local/share/man/man1`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
outputDir, _ := cmd.Flags().GetString("output-dir")
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(outputDir, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -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,18 +56,20 @@ 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.
|
||||
|
||||
The markdown files will be written to the specified directory (default: ./docs).`,
|
||||
Example: ` coolify docs markdown
|
||||
coolify docs markdown --output-dir=./documentation`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
outputDir, _ := cmd.Flags().GetString("output-dir")
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
if err := os.MkdirAll(outputDir, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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,59 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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)
|
||||
_, err := fmt.Scanln(&response)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read confirmation: %w", err)
|
||||
}
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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,46 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
// Check minimum version requirement
|
||||
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.436"); err != nil {
|
||||
return 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/models"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all private keys",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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,70 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewCreateCommand returns the create project command
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new project",
|
||||
Long: `Create a new project in Coolify.
|
||||
|
||||
Examples:
|
||||
coolify project create --name "My Project"
|
||||
coolify project create --name "My Project" --description "A description"`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
req := &models.ProjectCreateRequest{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("description") {
|
||||
desc, _ := cmd.Flags().GetString("description")
|
||||
req.Description = &desc
|
||||
}
|
||||
|
||||
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.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create project: %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(project)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("name", "", "Project name (required)")
|
||||
cmd.Flags().String("description", "", "Project description")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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 := cmd.Context()
|
||||
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 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// 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, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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,20 @@
|
||||
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())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
)
|
||||
|
||||
// NewListCommand returns the list projects command
|
||||
func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all resources",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user