mirror of
https://github.com/coollabsio/coolify-cli.git
synced 2026-06-19 15:45:03 +00:00
Compare commits
93 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 |
@@ -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."
|
||||
+2
-1
@@ -1,11 +1,12 @@
|
||||
coolify-cli
|
||||
coolify
|
||||
/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]
|
||||
+4
-1
@@ -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,7 +22,7 @@ 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/`:
|
||||
- `context.go` - manage Coolify context (add, remove, list, set default/token)
|
||||
@@ -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,13 +2,44 @@
|
||||
|
||||
## 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)
|
||||
|
||||
```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`
|
||||
@@ -34,6 +65,13 @@ You can change the default context with `coolify context use <context_name>` or
|
||||
### Update
|
||||
- `coolify update` - Update the CLI to the latest version
|
||||
|
||||
### 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
|
||||
@@ -48,25 +86,23 @@ You can change the default context with `coolify context use <context_name>` or
|
||||
- `--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
|
||||
<<<<<<< HEAD
|
||||
- `coolify servers add <server_name> <ip_address> <private_key_uuid>` - Add a new server
|
||||
- `--port <port>` - SSH port (default: 22)
|
||||
- `--user <user>` - SSH user (default: root)
|
||||
=======
|
||||
- `coolify servers add <name> <ip> <private_key_uuid>` - Add a new server
|
||||
- `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)
|
||||
>>>>>>> origin/v4.x
|
||||
- `--validate` - Validate server immediately after adding
|
||||
- `coolify servers remove <uuid>` - Remove a server
|
||||
- `coolify servers validate <uuid>` - Validate a server connection
|
||||
- `coolify servers domains <uuid>` - Get server domains by UUID
|
||||
- `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
|
||||
@@ -113,10 +149,30 @@ You can change the default context with `coolify context use <context_name>` or
|
||||
- `--build-time` - Available at build time
|
||||
- `--is-literal` - Treat value as literal (don't interpolate variables)
|
||||
- `--is-multiline` - Value is multiline
|
||||
- `coolify app env update <app_uuid> <env_uuid>` - Update an environment variable
|
||||
- `coolify app env 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
|
||||
@@ -183,22 +239,26 @@ You can change the default context with `coolify context use <context_name>` or
|
||||
- `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
|
||||
<<<<<<< HEAD
|
||||
- `--force` - Force deployment
|
||||
- `coolify deploy name <resource_name>` - Deploy a resource by name
|
||||
- `--force` - Force deployment
|
||||
=======
|
||||
- `-f, --force` - Force deployment
|
||||
- `coolify deploy name <name>` - Deploy a resource by name
|
||||
- `-f, --force` - Force deployment
|
||||
>>>>>>> origin/v4.x
|
||||
- `coolify deploy batch <name1,name2,...>` - Deploy multiple resources at once
|
||||
- `-f, --force` - Force all deployments
|
||||
- `coolify deploy list` - List all deployments
|
||||
@@ -236,20 +296,19 @@ You can change the default context with `coolify context use <context_name>` or
|
||||
- `coolify team members list [team_id]` - List team members
|
||||
|
||||
### Private Keys
|
||||
- `coolify privatekeys list` - List all private keys
|
||||
- `coolify privatekeys create <key_name> <private-key>` - Create a new private key
|
||||
- Use `@filename` to read from file: `coolify privatekeys create mykey @~/.ssh/id_rsa`
|
||||
- `coolify privatekeys delete <uuid>` - Delete a private key
|
||||
|
||||
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:
|
||||
|
||||
<<<<<<< HEAD
|
||||
- `--context <name>` - Use a specific context instead of default
|
||||
=======
|
||||
- `--instance <name>` - Use a specific instance profile instead of default
|
||||
>>>>>>> origin/v4.x
|
||||
- `--host <fqdn>` - Override the Coolify instance hostname
|
||||
- `--token <token>` - Override the authentication token
|
||||
- `--format <format>` - Output format: `table` (default), `json`, or `pretty`
|
||||
@@ -299,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
|
||||
@@ -414,13 +476,13 @@ coolify team members list
|
||||
|
||||
```bash
|
||||
# List servers in production
|
||||
coolify --context=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
|
||||
@@ -429,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
|
||||
@@ -450,7 +512,7 @@ This CLI follows a clean architecture with:
|
||||
|
||||
```bash
|
||||
# Build
|
||||
go build -o coolify .
|
||||
go build -o coolify ./coolify
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
@@ -459,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
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/cmd/application/create"
|
||||
"github.com/coollabsio/coolify-cli/cmd/application/env"
|
||||
)
|
||||
|
||||
@@ -18,12 +19,14 @@ func NewAppCommand() *cobra.Command {
|
||||
// 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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete an application. This action cannot be undone.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
@@ -24,7 +24,11 @@ func NewDeleteCommand() *cobra.Command {
|
||||
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)
|
||||
_, 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.")
|
||||
|
||||
@@ -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
+12
-7
@@ -1,13 +1,13 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateEnvCommand() *cobra.Command {
|
||||
@@ -15,10 +15,10 @@ func NewCreateEnvCommand() *cobra.Command {
|
||||
Use: "create <app_uuid>",
|
||||
Short: "Create an environment variable for an application",
|
||||
Long: `Create a new environment variable for a specific application. Use --key and --value flags to specify the variable.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
uuid := args[0]
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
@@ -31,6 +31,7 @@ func NewCreateEnvCommand() *cobra.Command {
|
||||
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")
|
||||
@@ -56,9 +57,12 @@ func NewCreateEnvCommand() *cobra.Command {
|
||||
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, uuid, req)
|
||||
env, err := appSvc.CreateEnv(ctx, appUUID, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create environment variable: %w", err)
|
||||
}
|
||||
@@ -71,9 +75,10 @@ func NewCreateEnvCommand() *cobra.Command {
|
||||
|
||||
cmd.Flags().String("key", "", "Environment variable key (required)")
|
||||
cmd.Flags().String("value", "", "Environment variable value (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("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
+8
-4
@@ -1,12 +1,12 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteEnvCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewDeleteEnvCommand() *cobra.Command {
|
||||
Long: `Delete an environment variable from an application. First UUID is the application, second is the specific environment variable to delete.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
@@ -31,7 +31,11 @@ func NewDeleteEnvCommand() *cobra.Command {
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
_, 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.")
|
||||
|
||||
Vendored
+11
-7
@@ -1,25 +1,25 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetEnvCommand() *cobra.Command {
|
||||
return &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, "<uuid1> <uuid2>"),
|
||||
Args: cli.ExactArgs(2, "<app_uuid> <env_uuid_or_key>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
envUUID := args[1]
|
||||
envUUIDOrKey := args[1]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
@@ -27,7 +27,9 @@ func NewGetEnvCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
env, err := appSvc.GetEnv(ctx, appUUID, envUUID)
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -53,4 +55,6 @@ func NewGetEnvCommand() *cobra.Command {
|
||||
return formatter.Format(env)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
Vendored
+35
-5
@@ -1,23 +1,25 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListEnvCommand() *cobra.Command {
|
||||
return &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.`,
|
||||
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 := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -31,6 +33,29 @@ func NewListEnvCommand() *cobra.Command {
|
||||
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")
|
||||
|
||||
@@ -54,4 +79,9 @@ func NewListEnvCommand() *cobra.Command {
|
||||
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
+9
-4
@@ -1,15 +1,15 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSyncEnvCommand() *cobra.Command {
|
||||
@@ -24,7 +24,7 @@ func NewSyncEnvCommand() *cobra.Command {
|
||||
Example: coolify app env sync abc123 --file .env.production`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -40,6 +40,7 @@ Example: coolify app env sync abc123 --file .env.production`,
|
||||
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)
|
||||
@@ -87,6 +88,9 @@ Example: coolify app env sync abc123 --file .env.production`,
|
||||
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") {
|
||||
@@ -147,8 +151,9 @@ Example: coolify app env sync abc123 --file .env.production`,
|
||||
}
|
||||
|
||||
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("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
+22
-12
@@ -1,35 +1,37 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateEnvCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update <app_uuid> <env_uuid>",
|
||||
Use: "update <app_uuid>",
|
||||
Short: "Update an environment variable",
|
||||
Long: `Update an existing environment variable. First UUID is the application, second is the specific environment variable to update.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
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 := context.Background()
|
||||
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)
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableUpdateRequest{
|
||||
UUID: envUUID,
|
||||
// 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
|
||||
@@ -54,9 +56,16 @@ func NewUpdateEnvCommand() *cobra.Command {
|
||||
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 && 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")
|
||||
if req.Key == nil {
|
||||
return fmt.Errorf("--key is required")
|
||||
}
|
||||
if req.Value == nil {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
appSvc := service.NewApplicationService(client)
|
||||
@@ -72,9 +81,10 @@ func NewUpdateEnvCommand() *cobra.Command {
|
||||
|
||||
cmd.Flags().String("key", "", "New environment variable key")
|
||||
cmd.Flags().String("value", "", "New environment variable value")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("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
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Long: `Retrieve detailed information about a specific application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
@@ -16,8 +16,8 @@ func NewListCommand() *cobra.Command {
|
||||
Use: "list",
|
||||
Short: "List all applications",
|
||||
Long: `List all applications in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -9,9 +8,10 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewLogsCommand() *cobra.Command {
|
||||
@@ -21,7 +21,7 @@ func NewLogsCommand() *cobra.Command {
|
||||
Long: `Retrieve logs for an application. Use --follow to continuously stream new logs.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewRestartCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewRestartCommand() *cobra.Command {
|
||||
Long: `Restart a running application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewStartCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewStartCommand() *cobra.Command {
|
||||
Long: `Start an application (initiates a deployment).`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewStopCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewStopCommand() *cobra.Command {
|
||||
Long: `Stop a running application.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
Long: `Update configuration for a specific application. Only specified fields will be updated.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
func NewCompletionsCommand() *cobra.Command {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -3,10 +3,11 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
)
|
||||
|
||||
// NewAddCommand creates the add command
|
||||
@@ -16,7 +17,7 @@ func NewAddCommand() *cobra.Command {
|
||||
Example: `context add myserver https://coolify.example.com your-api-token`,
|
||||
Args: cli.ExactArgs(3, "<context_name> <url> <token>"),
|
||||
Short: "Add a new context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
host := args[1]
|
||||
token := args[2]
|
||||
@@ -44,12 +45,14 @@ func NewAddCommand() *cobra.Command {
|
||||
fmt.Printf("%s already exists. Force overwriting.\n", name)
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
return
|
||||
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
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +80,9 @@ func NewAddCommand() *cobra.Command {
|
||||
|
||||
viper.Set("instances", instances)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
fmt.Printf("failed to write config: %v\n", err)
|
||||
return fmt.Errorf("failed to write config: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ func NewContextCommand() *cobra.Command {
|
||||
cmd.AddCommand(NewSetTokenCommand())
|
||||
cmd.AddCommand(NewSetDefaultCommand())
|
||||
cmd.AddCommand(NewVersionCommand())
|
||||
cmd.AddCommand(NewVerifyCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
+12
-7
@@ -2,11 +2,12 @@ package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"slices"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewDeleteCommand creates the delete command
|
||||
@@ -17,7 +18,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Delete a context",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
Name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for i, instance := range instances {
|
||||
@@ -25,13 +26,17 @@ func NewDeleteCommand() *cobra.Command {
|
||||
if instanceMap["name"] == Name {
|
||||
instances = slices.Delete(instances, i, i+1)
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
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)
|
||||
viper.WriteConfig()
|
||||
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 {
|
||||
@@ -40,10 +45,10 @@ func NewDeleteCommand() *cobra.Command {
|
||||
} else {
|
||||
fmt.Printf("Context '%s' deleted.\n", Name)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fmt.Printf("Context '%s' not found.\n", Name)
|
||||
return fmt.Errorf("context '%s' not found", Name)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -3,11 +3,12 @@ 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"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/output"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
@@ -12,7 +13,7 @@ func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all configured contexts",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
// Get instances from viper (returns []interface{})
|
||||
instancesRaw := viper.Get("instances")
|
||||
if instancesRaw == nil {
|
||||
|
||||
+23
-11
@@ -3,9 +3,10 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewSetTokenCommand creates the set-token command
|
||||
@@ -18,13 +19,20 @@ func NewSetDefaultCommand() *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
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 := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
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
|
||||
}
|
||||
@@ -32,13 +40,17 @@ func NewSetDefaultCommand() *cobra.Command {
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Context '%s' not found", name)
|
||||
} else {
|
||||
// Only unset other defaults if we found the target instance
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] != name {
|
||||
instanceMap["default"] = false
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewSetTokenCommand creates the set-token command
|
||||
@@ -15,7 +16,7 @@ func NewSetTokenCommand() *cobra.Command {
|
||||
Example: `context set-token myserver your-new-api-token`,
|
||||
Args: cli.ExactArgs(2, "<context_name> <token>"),
|
||||
Short: "Update the API token for a context",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
token := args[1]
|
||||
var found interface{}
|
||||
@@ -27,8 +28,7 @@ func NewSetTokenCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
fmt.Printf("Context '%s' not found.\n", name)
|
||||
return
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
for _, instance := range instances {
|
||||
@@ -38,8 +38,11 @@ func NewSetTokenCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
viper.Set("instances", instances)
|
||||
viper.WriteConfig()
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+9
-13
@@ -3,9 +3,10 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewUpdateCommand creates the update command
|
||||
@@ -15,7 +16,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
Example: `context update myserver --name newname --url https://new.coolify.com --token newtoken`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Update a context's properties (name, URL, token)",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
oldName := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
|
||||
@@ -26,9 +27,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
|
||||
// Check if at least one flag is provided
|
||||
if newName == "" && newURL == "" && newToken == "" {
|
||||
fmt.Println("Error: At least one of --name, --url, or --token must be provided")
|
||||
fmt.Println("\nUsage: coolify context update <context_name> [--name <new_name>] [--url <new_url>] [--token <new_token>]")
|
||||
return
|
||||
return fmt.Errorf("at least one of --name, --url, or --token must be provided")
|
||||
}
|
||||
|
||||
// Find the context
|
||||
@@ -44,8 +43,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Printf("Context '%s' not found.\n", oldName)
|
||||
return
|
||||
return fmt.Errorf("context '%s' not found", oldName)
|
||||
}
|
||||
|
||||
// If renaming, check if new name already exists
|
||||
@@ -53,8 +51,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == newName {
|
||||
fmt.Printf("Error: Context with name '%s' already exists.\n", newName)
|
||||
return
|
||||
return fmt.Errorf("context with name '%s' already exists", newName)
|
||||
}
|
||||
}
|
||||
contextToUpdate["name"] = newName
|
||||
@@ -72,10 +69,8 @@ func NewUpdateCommand() *cobra.Command {
|
||||
|
||||
// Save changes
|
||||
viper.Set("instances", instances)
|
||||
err := viper.WriteConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
return
|
||||
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
|
||||
@@ -84,6 +79,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
finalName = newName
|
||||
}
|
||||
fmt.Printf("Context '%s' updated successfully.\n", finalName)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -3,9 +3,10 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewUseCommand creates the use command
|
||||
@@ -15,15 +16,22 @@ func NewUseCommand() *cobra.Command {
|
||||
Example: `context use myserver`,
|
||||
Args: cli.ExactArgs(1, "<context_name>"),
|
||||
Short: "Switch to a different context (set as default)",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
instances := viper.Get("instances").([]interface{})
|
||||
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 := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
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
|
||||
}
|
||||
@@ -35,8 +43,12 @@ func NewUseCommand() *cobra.Command {
|
||||
|
||||
// Update default
|
||||
for _, instance := range instances {
|
||||
instanceMap := instance.(map[string]interface{})
|
||||
if instanceMap["name"] == name {
|
||||
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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
)
|
||||
|
||||
// NewVersionCommand creates the version command for contexts
|
||||
@@ -13,8 +13,8 @@ func NewVersionCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Get current context's Coolify version",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCreateCommand creates a new database
|
||||
@@ -21,7 +21,7 @@ func NewCreateCommand() *cobra.Command {
|
||||
Example: coolify database backup create abc123 --frequency "0 0 * * *" --enabled`,
|
||||
Args: cli.ExactArgs(1, "<database_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -29,6 +29,11 @@ Example: coolify database backup create abc123 --frequency "0 0 * * *" --enabled
|
||||
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
|
||||
|
||||
@@ -2,14 +2,14 @@ package backup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteExecutionCommand lists all databases
|
||||
@@ -20,7 +20,7 @@ func NewDeleteExecutionCommand() *cobra.Command {
|
||||
Long: `Delete a specific backup execution and optionally from S3. First UUID is the database, second is the backup configuration, third is the specific execution.`,
|
||||
Args: cli.ExactArgs(3, "<database_uuid> <backup_uuid> <execution_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
executionUUID := args[2]
|
||||
|
||||
@@ -2,14 +2,14 @@ package backup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a database
|
||||
@@ -20,7 +20,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete a backup configuration and optionally all its executions from S3. First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewExecutionCommand lists all databases
|
||||
@@ -18,7 +18,7 @@ func NewExecutionCommand() *cobra.Command {
|
||||
Long: `List all executions for a backup configuration. First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all databases
|
||||
@@ -18,7 +18,7 @@ func NewListCommand() *cobra.Command {
|
||||
Long: `List all backup configurations for a specific database.`,
|
||||
Args: cli.ExactArgs(1, "<database_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewTriggerCommand triggers a database backup
|
||||
@@ -18,7 +18,7 @@ func NewTriggerCommand() *cobra.Command {
|
||||
Long: `Trigger an immediate backup for a specific backup configuration. First UUID is the database, second is the specific backup configuration to trigger.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUpdateCommand updates a database
|
||||
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
Long: `Update a backup configuration settings (frequency, retention, S3, etc.). First UUID is the database, second is the specific backup configuration.`,
|
||||
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbUUID := args[0]
|
||||
backupUUID := args[1]
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
@@ -25,7 +25,7 @@ Examples:
|
||||
coolify databases create mysql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --name="My MySQL"`,
|
||||
Args: cli.ExactArgs(1, "<type>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
dbType := args[0]
|
||||
validTypes := []string{"postgresql", "mysql", "mariadb", "mongodb", "redis", "keydb", "clickhouse", "dragonfly"}
|
||||
isValid := false
|
||||
@@ -116,7 +116,7 @@ Examples:
|
||||
}
|
||||
if cmd.Flags().Changed("postgres-db") {
|
||||
db, _ := cmd.Flags().GetString("postgres-db")
|
||||
req.PostgresDb = &db
|
||||
req.PostgresDB = &db
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ package database
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a database
|
||||
@@ -20,7 +20,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete a database and optionally clean up its configurations, volumes, and networks.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand gets database details
|
||||
@@ -18,7 +18,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Long: `Get detailed information about a specific database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all databases
|
||||
@@ -16,8 +16,8 @@ func NewListCommand() *cobra.Command {
|
||||
Use: "list",
|
||||
Short: "List all databases",
|
||||
Long: `List all databases in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewRestartCommand restarts a database
|
||||
@@ -17,7 +17,7 @@ func NewRestartCommand() *cobra.Command {
|
||||
Long: `Restart a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStartCommand starts a database
|
||||
@@ -17,7 +17,7 @@ func NewStartCommand() *cobra.Command {
|
||||
Long: `Start a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStopCommand stops a database
|
||||
@@ -17,7 +17,7 @@ func NewStopCommand() *cobra.Command {
|
||||
Long: `Stop a database by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUpdateCommand updates a database
|
||||
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
Long: `Update a database's configuration by UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
req := &models.DatabaseUpdateRequest{}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewBatchCommand deploys multiple resources by name
|
||||
@@ -20,7 +20,7 @@ Provide resource names as comma-separated values.
|
||||
Example: coolify deploy batch app1,app2,app3`,
|
||||
Args: cli.ExactArgs(1, "<names>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
namesStr := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCancelCommand cancels a deployment
|
||||
@@ -18,7 +18,7 @@ func NewCancelCommand() *cobra.Command {
|
||||
Long: `Cancel an in-progress deployment. This will stop the deployment process and clean up any temporary resources.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -26,13 +26,23 @@ func NewCancelCommand() *cobra.Command {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
// 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 {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to cancel deployment %s? (yes/no): ", uuid)
|
||||
fmt.Scanln(&response)
|
||||
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.")
|
||||
@@ -46,7 +56,11 @@ func NewCancelCommand() *cobra.Command {
|
||||
return fmt.Errorf("failed to cancel deployment: %w", err)
|
||||
}
|
||||
|
||||
format, _ := cmd.Flags().GetString("format")
|
||||
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)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand gets deployment details
|
||||
@@ -18,7 +18,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Long: `Get detailed information about a specific deployment by its UUID.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand lists all deployments
|
||||
@@ -16,8 +16,8 @@ func NewListCommand() *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()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewNameCommand deploys a resource by name
|
||||
@@ -17,7 +17,7 @@ func NewNameCommand() *cobra.Command {
|
||||
Short: "Deploy by resource name",
|
||||
Args: cli.ExactArgs(1, "<resource_name>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
name := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ResultDisplay represents a deploy result for table display
|
||||
@@ -23,7 +23,7 @@ func NewUUIDCommand() *cobra.Command {
|
||||
Short: "Deploy by uuid",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
+4
-4
@@ -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)
|
||||
}
|
||||
|
||||
@@ -65,11 +65,11 @@ var markdownCmd = &cobra.Command{
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListBranchesCommand() *cobra.Command {
|
||||
@@ -19,7 +19,7 @@ func NewListBranchesCommand() *cobra.Command {
|
||||
Example: coolify github branches abc-123-def owner/repository`,
|
||||
Args: cli.ExactArgs(2, "<app_uuid> <owner/repo>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
// Parse owner/repo
|
||||
|
||||
+12
-12
@@ -1,14 +1,14 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
@@ -20,8 +20,8 @@ func NewCreateCommand() *cobra.Command {
|
||||
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()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
@@ -100,14 +100,14 @@ Example: coolify github create --name "My GitHub App" --api-url "https://api.git
|
||||
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")
|
||||
_ = 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
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete a GitHub App integration. The app must not be used by any applications.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -30,7 +30,11 @@ func NewDeleteCommand() *cobra.Command {
|
||||
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)
|
||||
_, 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.")
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Long: `Get detailed information about a specific GitHub App integration.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
+9
-4
@@ -1,13 +1,13 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
@@ -15,14 +15,19 @@ func NewListCommand() *cobra.Command {
|
||||
Use: "list",
|
||||
Short: "List all GitHub App integrations",
|
||||
Long: `List all GitHub App integrations configured in Coolify.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
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 {
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListRepositoriesCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewListRepositoriesCommand() *cobra.Command {
|
||||
Long: `List all repositories that are accessible by the specified GitHub App.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUpdateCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewUpdateCommand() *cobra.Command {
|
||||
Long: `Update an existing GitHub App integration. Provide the app UUID and the fields you want to update.`,
|
||||
Args: cli.ExactArgs(1, "<app_uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
appUUID := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCreateCommand creates the create command
|
||||
@@ -19,7 +19,7 @@ func NewCreateCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(2, "<key_name> <private_key_or_file>"),
|
||||
Short: "Add a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
name := args[0]
|
||||
privateKeyInput := args[1]
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand creates the delete command
|
||||
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Remove a private key",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package privatekeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all private keys",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// EnvironmentRow represents an environment for display
|
||||
@@ -23,7 +23,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Short: "Get a project by uuid",
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -55,7 +55,7 @@ func NewGetCommand() *cobra.Command {
|
||||
var rows []EnvironmentRow
|
||||
|
||||
// If the project has environments, expand them
|
||||
if project.Environments != nil && len(project.Environments) > 0 {
|
||||
if len(project.Environments) > 0 {
|
||||
for _, env := range project.Environments {
|
||||
rows = append(rows, EnvironmentRow{
|
||||
UUID: env.UUID,
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ListRow represents a project for list display (without environments)
|
||||
@@ -22,8 +22,8 @@ func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all projects",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ func NewProjectCommand() *cobra.Command {
|
||||
// Add all project subcommands
|
||||
cmd.AddCommand(NewListCommand())
|
||||
cmd.AddCommand(NewGetCommand())
|
||||
cmd.AddCommand(NewCreateCommand())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand returns the list projects command
|
||||
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all resources",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
|
||||
+19
-33
@@ -1,13 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/cmd/application"
|
||||
"github.com/coollabsio/coolify-cli/cmd/completion"
|
||||
configcmd "github.com/coollabsio/coolify-cli/cmd/config"
|
||||
"github.com/coollabsio/coolify-cli/cmd/context"
|
||||
"github.com/coollabsio/coolify-cli/cmd/database"
|
||||
"github.com/coollabsio/coolify-cli/cmd/deployment"
|
||||
@@ -22,9 +26,6 @@ import (
|
||||
cliversion "github.com/coollabsio/coolify-cli/cmd/version"
|
||||
"github.com/coollabsio/coolify-cli/internal/config"
|
||||
"github.com/coollabsio/coolify-cli/internal/version"
|
||||
compareVersion "github.com/hashicorp/go-version"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Legacy global variables - kept for backward compatibility during migration
|
||||
@@ -38,10 +39,9 @@ var (
|
||||
Debug bool
|
||||
ShowSensitive bool
|
||||
Format string
|
||||
JsonMode bool
|
||||
JSONMode bool
|
||||
PrettyMode bool
|
||||
SetDefaultInstance bool
|
||||
w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -50,7 +50,7 @@ var rootCmd = &cobra.Command{
|
||||
Long: `A CLI tool to interact with Coolify API.`,
|
||||
SilenceUsage: true, // Don't show usage on errors
|
||||
SilenceErrors: false, // Still print errors
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -59,7 +59,7 @@ var rootCmd = &cobra.Command{
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(0)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ func init() {
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "coolify",
|
||||
Short: "Coolify CLI",
|
||||
Long: fmt.Sprintf("A CLI tool to interact with Coolify API.\nVersion: %s", version.CliVersion),
|
||||
Long: fmt.Sprintf("A CLI tool to interact with Coolify API.\nVersion: %s", version.GetVersion()),
|
||||
SilenceUsage: true, // Don't show usage on errors
|
||||
SilenceErrors: false, // Still print errors
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -86,8 +86,9 @@ func init() {
|
||||
|
||||
// Register all subcommands
|
||||
rootCmd.AddCommand(application.NewAppCommand())
|
||||
rootCmd.AddCommand(context.NewContextCommand())
|
||||
rootCmd.AddCommand(completion.NewCompletionsCommand())
|
||||
rootCmd.AddCommand(configcmd.NewConfigCommand())
|
||||
rootCmd.AddCommand(context.NewContextCommand())
|
||||
rootCmd.AddCommand(database.NewDatabaseCommand())
|
||||
rootCmd.AddCommand(deployment.NewDeploymentCommand())
|
||||
rootCmd.AddCommand(github.NewGitHubCommand())
|
||||
@@ -110,11 +111,14 @@ func initConfig() {
|
||||
// Ensure config directory exists
|
||||
configDir := config.Path()[:len(config.Path())-len("/config.json")]
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(configDir, 0755)
|
||||
if err := os.MkdirAll(configDir, 0750); err != nil {
|
||||
log.Printf("Failed to create config directory: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
var notFoundErr viper.ConfigFileNotFoundError
|
||||
if errors.As(err, ¬FoundErr) {
|
||||
log.Println("Config file not found. Creating a new one at", config.Path())
|
||||
if err := config.CreateDefault(); err != nil {
|
||||
log.Printf("Failed to create default config: %v\n", err)
|
||||
@@ -139,24 +143,6 @@ func initConfig() {
|
||||
// They are loaded on-demand by getAPIClient() based on --instance or default instance
|
||||
// This allows --instance flag to work correctly
|
||||
|
||||
// Check for updates
|
||||
latestVersionStr, err := version.CheckLatestVersionOfCli(Debug)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
log.Println("Failed to check for updates:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Compare versions properly using semantic versioning
|
||||
if latestVersionStr != "" {
|
||||
latestVersion, err := compareVersion.NewVersion(latestVersionStr)
|
||||
if err == nil {
|
||||
currentVersion, err := compareVersion.NewVersion(version.CliVersion)
|
||||
if err == nil && latestVersion.GreaterThan(currentVersion) {
|
||||
if Debug {
|
||||
log.Printf("New version of Coolify CLI is available: %s\n", latestVersionStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for updates (errors are handled silently inside the function)
|
||||
_, _ = version.CheckLatestVersionOfCli(Debug)
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewAddCommand creates the add command
|
||||
@@ -17,7 +17,7 @@ func NewAddCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(3, "<server_name> <ip_address> <private_key_uuid>"),
|
||||
Short: "Add a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -28,7 +28,7 @@ func NewAddCommand() *cobra.Command {
|
||||
// Parse arguments and flags
|
||||
name := args[0]
|
||||
ip := args[1]
|
||||
privateKeyUuid := args[2]
|
||||
privateKeyUUID := args[2]
|
||||
port, _ := cmd.Flags().GetInt("port")
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
validate, _ := cmd.Flags().GetBool("validate")
|
||||
@@ -39,7 +39,7 @@ func NewAddCommand() *cobra.Command {
|
||||
IP: ip,
|
||||
Port: port,
|
||||
User: user,
|
||||
PrivateKeyUUID: privateKeyUuid,
|
||||
PrivateKeyUUID: privateKeyUUID,
|
||||
InstantValidate: validate,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
@@ -18,7 +18,7 @@ func NewGetDomainsCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Get server domains by uuid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewGetCommand creates the get command
|
||||
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Get server details by uuid",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewListCommand creates the list command
|
||||
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all servers",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewRemoveCommand creates the remove command
|
||||
@@ -16,7 +16,7 @@ func NewRemoveCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Remove a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewValidateCommand creates the validate command
|
||||
@@ -16,7 +16,7 @@ func NewValidateCommand() *cobra.Command {
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
Short: "Validate a server",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Get API client
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
package service
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// validServiceTypes contains all supported one-click service types
|
||||
var validServiceTypes = []string{
|
||||
"activepieces",
|
||||
"appsmith",
|
||||
"appwrite",
|
||||
"authentik",
|
||||
"babybuddy",
|
||||
"budge",
|
||||
"changedetection",
|
||||
"chatwoot",
|
||||
"classicpress-with-mariadb",
|
||||
"classicpress-with-mysql",
|
||||
"classicpress-without-database",
|
||||
"cloudflared",
|
||||
"code-server",
|
||||
"dashboard",
|
||||
"directus",
|
||||
"directus-with-postgresql",
|
||||
"docker-registry",
|
||||
"docuseal",
|
||||
"docuseal-with-postgres",
|
||||
"dokuwiki",
|
||||
"duplicati",
|
||||
"emby",
|
||||
"embystat",
|
||||
"fider",
|
||||
"filebrowser",
|
||||
"firefly",
|
||||
"formbricks",
|
||||
"ghost",
|
||||
"gitea",
|
||||
"gitea-with-mariadb",
|
||||
"gitea-with-mysql",
|
||||
"gitea-with-postgresql",
|
||||
"glance",
|
||||
"glances",
|
||||
"glitchtip",
|
||||
"grafana",
|
||||
"grafana-with-postgresql",
|
||||
"grocy",
|
||||
"heimdall",
|
||||
"homepage",
|
||||
"jellyfin",
|
||||
"kuzzle",
|
||||
"listmonk",
|
||||
"logto",
|
||||
"mediawiki",
|
||||
"meilisearch",
|
||||
"metabase",
|
||||
"metube",
|
||||
"minio",
|
||||
"moodle",
|
||||
"n8n",
|
||||
"n8n-with-postgresql",
|
||||
"next-image-transformation",
|
||||
"nextcloud",
|
||||
"nocodb",
|
||||
"odoo",
|
||||
"openblocks",
|
||||
"pairdrop",
|
||||
"penpot",
|
||||
"phpmyadmin",
|
||||
"pocketbase",
|
||||
"posthog",
|
||||
"reactive-resume",
|
||||
"rocketchat",
|
||||
"shlink",
|
||||
"slash",
|
||||
"snapdrop",
|
||||
"statusnook",
|
||||
"stirling-pdf",
|
||||
"supabase",
|
||||
"syncthing",
|
||||
"tolgee",
|
||||
"trigger",
|
||||
"trigger-with-external-database",
|
||||
"twenty",
|
||||
"umami",
|
||||
"unleash-with-postgresql",
|
||||
"unleash-without-database",
|
||||
"uptime-kuma",
|
||||
"vaultwarden",
|
||||
"vikunja",
|
||||
"weblate",
|
||||
"whoogle",
|
||||
"wordpress-with-mariadb",
|
||||
"wordpress-with-mysql",
|
||||
"wordpress-without-database",
|
||||
}
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create <type>",
|
||||
Short: "Create a new one-click service",
|
||||
Long: `Create a new one-click service of the specified type.
|
||||
|
||||
Use 'coolify service create --list-types' to see all available service types.
|
||||
|
||||
Examples:
|
||||
coolify service create wordpress-with-mysql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production
|
||||
coolify service create ghost --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --name="My Blog"
|
||||
coolify service create n8n --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --instant-deploy
|
||||
|
||||
Popular service types:
|
||||
- wordpress-with-mysql, wordpress-with-mariadb, wordpress-without-database
|
||||
- ghost, plausible, umami, uptime-kuma
|
||||
- n8n, n8n-with-postgresql
|
||||
- nextcloud, gitea, minio
|
||||
- grafana, metabase, nocodb
|
||||
- supabase, pocketbase, appwrite`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Handle --list-types flag
|
||||
listTypes, _ := cmd.Flags().GetBool("list-types")
|
||||
if listTypes {
|
||||
fmt.Println("Available one-click service types:")
|
||||
fmt.Println()
|
||||
for _, t := range validServiceTypes {
|
||||
fmt.Printf(" %s\n", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Require type argument if not listing
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("service type is required. Use --list-types to see available types")
|
||||
}
|
||||
|
||||
serviceType := args[0]
|
||||
|
||||
// Validate service type
|
||||
isValid := false
|
||||
for _, t := range validServiceTypes {
|
||||
if t == serviceType {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
return fmt.Errorf("invalid service type '%s'. Use --list-types to see available types", serviceType)
|
||||
}
|
||||
|
||||
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.ServiceCreateRequest{
|
||||
Type: serviceType,
|
||||
ServerUUID: serverUUID,
|
||||
ProjectUUID: projectUUID,
|
||||
}
|
||||
|
||||
if environmentName != "" {
|
||||
req.EnvironmentName = environmentName
|
||||
}
|
||||
if environmentUUID != "" {
|
||||
req.EnvironmentUUID = &environmentUUID
|
||||
}
|
||||
|
||||
// Handle optional 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("destination-uuid") {
|
||||
dest, _ := cmd.Flags().GetString("destination-uuid")
|
||||
req.Destination = &dest
|
||||
}
|
||||
if cmd.Flags().Changed("instant-deploy") {
|
||||
instant, _ := cmd.Flags().GetBool("instant-deploy")
|
||||
req.InstantDeploy = &instant
|
||||
}
|
||||
if cmd.Flags().Changed("docker-compose") {
|
||||
compose, _ := cmd.Flags().GetString("docker-compose")
|
||||
req.DockerCompose = &compose
|
||||
}
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
svc := service.NewService(client)
|
||||
result, err := svc.Create(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create service: %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)
|
||||
},
|
||||
}
|
||||
|
||||
// List types flag
|
||||
cmd.Flags().Bool("list-types", false, "List all available service types")
|
||||
|
||||
// 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")
|
||||
|
||||
// Optional flags
|
||||
cmd.Flags().String("name", "", "Service name")
|
||||
cmd.Flags().String("description", "", "Service description")
|
||||
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
|
||||
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
|
||||
cmd.Flags().String("docker-compose", "", "Custom Docker Compose content (for advanced customization)")
|
||||
|
||||
// Add completion for service type positional argument
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
return validServiceTypes, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeleteCommand deletes a service
|
||||
@@ -17,7 +17,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete a service and optionally clean up its configurations, volumes, and networks.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -35,7 +35,11 @@ func NewDeleteCommand() *cobra.Command {
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this service? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
_, 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.")
|
||||
@@ -43,7 +47,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
err = serviceSvc.Delete(ctx, uuid, deleteConfigurations, deleteVolumes, dockerCleanup, deleteConnectedNetworks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete service: %w", err)
|
||||
|
||||
Vendored
+11
-11
@@ -1,13 +1,13 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewCreateCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewCreateCommand() *cobra.Command {
|
||||
Long: `Create a new environment variable for a specific service. Use --key and --value flags to specify the variable.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -28,9 +28,9 @@ func NewCreateCommand() *cobra.Command {
|
||||
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")
|
||||
@@ -39,7 +39,7 @@ func NewCreateCommand() *cobra.Command {
|
||||
return fmt.Errorf("--value is required")
|
||||
}
|
||||
|
||||
req := &models.EnvironmentVariableCreateRequest{
|
||||
req := &models.ServiceEnvironmentVariableCreateRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
@@ -48,17 +48,17 @@ func NewCreateCommand() *cobra.Command {
|
||||
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
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
env, err := serviceSvc.CreateEnv(ctx, uuid, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create environment variable: %w", err)
|
||||
@@ -71,10 +71,10 @@ func NewCreateCommand() *cobra.Command {
|
||||
|
||||
cmd.Flags().String("key", "", "Environment variable key (required)")
|
||||
cmd.Flags().String("value", "", "Environment variable value (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Available in preview deployments")
|
||||
cmd.Flags().Bool("build-time", true, "Available at build time (default: true)")
|
||||
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
+5
-5
@@ -1,12 +1,12 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/coollabsio/coolify-cli/internal/cli"
|
||||
"github.com/coollabsio/coolify-cli/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewDeleteCommand() *cobra.Command {
|
||||
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
Long: `Delete an environment variable from a service. First UUID is the service, second is the specific environment variable to delete.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
serviceUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
@@ -31,7 +31,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
if !force {
|
||||
var response string
|
||||
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
|
||||
fmt.Scanln(&response)
|
||||
_, _ = fmt.Scanln(&response)
|
||||
|
||||
if response != "yes" && response != "y" {
|
||||
fmt.Println("Delete cancelled.")
|
||||
@@ -39,7 +39,7 @@ func NewDeleteCommand() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
err = serviceSvc.DeleteEnv(ctx, serviceUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete environment variable: %w", err)
|
||||
|
||||
Vendored
+4
-4
@@ -1,13 +1,13 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewGetCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
|
||||
Long: `Get detailed information about a specific environment variable. First UUID is the service, second is the environment variable UUID or key name.`,
|
||||
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
serviceUUID := args[0]
|
||||
envUUID := args[1]
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewGetCommand() *cobra.Command {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
env, err := serviceSvc.GetEnv(ctx, serviceUUID, envUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get environment variable: %w", err)
|
||||
|
||||
Vendored
+4
-4
@@ -1,13 +1,13 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
@@ -17,7 +17,7 @@ func NewListCommand() *cobra.Command {
|
||||
Long: `List all environment variables for a specific service.`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -25,7 +25,7 @@ func NewListCommand() *cobra.Command {
|
||||
return fmt.Errorf("failed to get API client: %w", err)
|
||||
}
|
||||
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
envs, err := serviceSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list environment variables: %w", err)
|
||||
|
||||
Vendored
+15
-15
@@ -1,15 +1,15 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSyncCommand() *cobra.Command {
|
||||
@@ -24,7 +24,7 @@ func NewSyncCommand() *cobra.Command {
|
||||
Example: coolify service env sync abc123 --file .env.production`,
|
||||
Args: cli.ExactArgs(1, "<uuid>"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx := cmd.Context()
|
||||
uuid := args[0]
|
||||
|
||||
client, err := cli.GetAPIClient(cmd)
|
||||
@@ -38,8 +38,8 @@ Example: coolify service env sync abc123 --file .env.production`,
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -55,24 +55,24 @@ Example: coolify service env sync abc123 --file .env.production`,
|
||||
fmt.Printf("Found %d environment variables in file. Syncing...\n", len(envVars))
|
||||
|
||||
// Fetch existing environment variables
|
||||
serviceSvc := service.NewServiceService(client)
|
||||
serviceSvc := service.NewService(client)
|
||||
existingEnvs, err := serviceSvc.ListEnvs(ctx, uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list existing environment variables: %w", err)
|
||||
}
|
||||
|
||||
// Build a map of existing env vars by key
|
||||
existingMap := make(map[string]models.EnvironmentVariable)
|
||||
existingMap := make(map[string]models.ServiceEnvironmentVariable)
|
||||
for _, env := range existingEnvs {
|
||||
existingMap[env.Key] = env
|
||||
}
|
||||
|
||||
// Separate into updates and creates
|
||||
var toUpdate []models.EnvironmentVariableCreateRequest
|
||||
var toCreate []models.EnvironmentVariableCreateRequest
|
||||
var toUpdate []models.ServiceEnvironmentVariableCreateRequest
|
||||
var toCreate []models.ServiceEnvironmentVariableCreateRequest
|
||||
|
||||
for _, envVar := range envVars {
|
||||
req := models.EnvironmentVariableCreateRequest{
|
||||
req := models.ServiceEnvironmentVariableCreateRequest{
|
||||
Key: envVar.Key,
|
||||
Value: envVar.Value,
|
||||
}
|
||||
@@ -81,12 +81,12 @@ Example: coolify service env sync abc123 --file .env.production`,
|
||||
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") {
|
||||
@@ -108,7 +108,7 @@ Example: coolify service env sync abc123 --file .env.production`,
|
||||
// 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{
|
||||
bulkReq := &models.ServiceEnvBulkUpdateRequest{
|
||||
Data: toUpdate,
|
||||
}
|
||||
_, err := serviceSvc.BulkUpdateEnvs(ctx, uuid, bulkReq)
|
||||
@@ -147,9 +147,9 @@ Example: coolify service env sync abc123 --file .env.production`,
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
|
||||
cmd.Flags().Bool("build-time", false, "Make all variables available at build time")
|
||||
cmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
|
||||
cmd.Flags().Bool("build-time", true, "Make all variables available at build time (default: true)")
|
||||
cmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
|
||||
cmd.Flags().Bool("runtime", true, "Make all variables available at runtime (default: true)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user