Skip to content

Commit 541f633

Browse files
andrasbacsaiclaude
andcommitted
feat: add create commands for applications, projects, and services
Add comprehensive create functionality for three main resource types: - Applications: public, private (GitHub App & deploy key), Dockerfile, Docker image - Projects: simple project creation with optional description - Services: one-click service deployment with 80+ service types Includes full service layer implementation with 15+ test cases covering success and error scenarios. Also fixed EnvironmentUUID handling in service creation requests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0f23b02 commit 541f633

File tree

18 files changed

+1714
-1
lines changed

18 files changed

+1714
-1
lines changed

cmd/application/application.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package application
33
import (
44
"github.com/spf13/cobra"
55

6+
"github.com/coollabsio/coolify-cli/cmd/application/create"
67
"github.com/coollabsio/coolify-cli/cmd/application/env"
78
)
89

@@ -18,6 +19,7 @@ func NewAppCommand() *cobra.Command {
1819
// Add main subcommands
1920
cmd.AddCommand(NewListCommand())
2021
cmd.AddCommand(NewGetCommand())
22+
cmd.AddCommand(create.NewCreateCommand())
2123
cmd.AddCommand(NewUpdateCommand())
2224
cmd.AddCommand(NewDeleteCommand())
2325
cmd.AddCommand(NewStartCommand())

cmd/application/create/create.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package create
2+
3+
import "github.com/spf13/cobra"
4+
5+
// NewCreateCommand creates the create parent command with all subcommands
6+
func NewCreateCommand() *cobra.Command {
7+
cmd := &cobra.Command{
8+
Use: "create",
9+
Short: "Create a new application",
10+
Long: `Create a new application from various sources.
11+
12+
Available source types:
13+
public Create from a public git repository
14+
github Create from a private repository using GitHub App
15+
deploy-key Create from a private repository using SSH deploy key
16+
dockerfile Create from a custom Dockerfile
17+
dockerimage Create from a pre-built Docker image
18+
19+
Examples:
20+
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
21+
--git-repository "https://github.com/user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
22+
23+
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
24+
--github-app-uuid <uuid> --git-repository "user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
25+
26+
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
27+
--docker-registry-image-name "nginx:latest" --ports-exposes 80`,
28+
}
29+
30+
// Add all create subcommands
31+
cmd.AddCommand(NewPublicCommand())
32+
cmd.AddCommand(NewGitHubCommand())
33+
cmd.AddCommand(NewDeployKeyCommand())
34+
cmd.AddCommand(NewDockerfileCommand())
35+
cmd.AddCommand(NewDockerImageCommand())
36+
37+
return cmd
38+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package create
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/coollabsio/coolify-cli/internal/cli"
9+
"github.com/coollabsio/coolify-cli/internal/models"
10+
"github.com/coollabsio/coolify-cli/internal/output"
11+
"github.com/coollabsio/coolify-cli/internal/service"
12+
)
13+
14+
// NewDeployKeyCommand returns the create deploy-key application command
15+
func NewDeployKeyCommand() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "deploy-key",
18+
Short: "Create an application from a private repository using SSH deploy key",
19+
Long: `Create a new application from a private git repository using SSH deploy key authentication.
20+
21+
Use 'coolify privatekeys list' to find your private key UUID.
22+
23+
Examples:
24+
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
25+
--private-key-uuid <uuid> --git-repository "git@github.com:owner/repo.git" --git-branch main \
26+
--build-pack nixpacks --ports-exposes 3000
27+
28+
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
29+
--private-key-uuid <uuid> --git-repository "git@gitlab.com:owner/repo.git" --git-branch main \
30+
--build-pack dockerfile --ports-exposes 8080 --instant-deploy`,
31+
RunE: func(cmd *cobra.Command, _ []string) error {
32+
ctx := cmd.Context()
33+
34+
// Get required flags
35+
serverUUID, _ := cmd.Flags().GetString("server-uuid")
36+
projectUUID, _ := cmd.Flags().GetString("project-uuid")
37+
privateKeyUUID, _ := cmd.Flags().GetString("private-key-uuid")
38+
gitRepository, _ := cmd.Flags().GetString("git-repository")
39+
gitBranch, _ := cmd.Flags().GetString("git-branch")
40+
buildPack, _ := cmd.Flags().GetString("build-pack")
41+
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
42+
environmentName, _ := cmd.Flags().GetString("environment-name")
43+
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
44+
45+
// Validate required fields
46+
if serverUUID == "" || projectUUID == "" {
47+
return fmt.Errorf("--server-uuid and --project-uuid are required")
48+
}
49+
if privateKeyUUID == "" {
50+
return fmt.Errorf("--private-key-uuid is required")
51+
}
52+
if gitRepository == "" || gitBranch == "" {
53+
return fmt.Errorf("--git-repository and --git-branch are required")
54+
}
55+
if buildPack == "" || portsExposes == "" {
56+
return fmt.Errorf("--build-pack and --ports-exposes are required")
57+
}
58+
if environmentName == "" && environmentUUID == "" {
59+
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
60+
}
61+
62+
req := &models.ApplicationCreateDeployKeyRequest{
63+
ServerUUID: serverUUID,
64+
ProjectUUID: projectUUID,
65+
PrivateKeyUUID: privateKeyUUID,
66+
GitRepository: gitRepository,
67+
GitBranch: gitBranch,
68+
BuildPack: buildPack,
69+
PortsExposes: portsExposes,
70+
}
71+
72+
if environmentName != "" {
73+
req.EnvironmentName = &environmentName
74+
}
75+
if environmentUUID != "" {
76+
req.EnvironmentUUID = &environmentUUID
77+
}
78+
79+
// Optional fields
80+
setOptionalStringFlag(cmd, "name", &req.Name)
81+
setOptionalStringFlag(cmd, "description", &req.Description)
82+
setOptionalStringFlag(cmd, "domains", &req.Domains)
83+
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
84+
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
85+
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
86+
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
87+
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
88+
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
89+
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
90+
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
91+
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
92+
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
93+
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
94+
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
95+
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
96+
97+
client, err := cli.GetAPIClient(cmd)
98+
if err != nil {
99+
return fmt.Errorf("failed to get API client: %w", err)
100+
}
101+
102+
appSvc := service.NewApplicationService(client)
103+
app, err := appSvc.CreateDeployKey(ctx, req)
104+
if err != nil {
105+
return fmt.Errorf("failed to create application: %w", err)
106+
}
107+
108+
format, _ := cmd.Flags().GetString("format")
109+
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
110+
111+
formatter, err := output.NewFormatter(format, output.Options{
112+
ShowSensitive: showSensitive,
113+
})
114+
if err != nil {
115+
return err
116+
}
117+
118+
return formatter.Format(app)
119+
},
120+
}
121+
122+
// Required flags
123+
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
124+
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
125+
cmd.Flags().String("environment-name", "", "Environment name")
126+
cmd.Flags().String("environment-uuid", "", "Environment UUID")
127+
cmd.Flags().String("private-key-uuid", "", "Private key UUID (required)")
128+
cmd.Flags().String("git-repository", "", "Git repository SSH URL, e.g., 'git@github.com:owner/repo.git' (required)")
129+
cmd.Flags().String("git-branch", "", "Git branch (required)")
130+
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
131+
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
132+
133+
// Optional flags
134+
cmd.Flags().String("name", "", "Application name")
135+
cmd.Flags().String("description", "", "Application description")
136+
cmd.Flags().String("domains", "", "Domain(s) for the application")
137+
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
138+
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
139+
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
140+
cmd.Flags().String("build-command", "", "Custom build command")
141+
cmd.Flags().String("start-command", "", "Custom start command")
142+
cmd.Flags().String("install-command", "", "Custom install command")
143+
cmd.Flags().String("base-directory", "", "Base directory for the application")
144+
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
145+
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
146+
cmd.Flags().String("limits-cpus", "", "CPU limit")
147+
cmd.Flags().String("limits-memory", "", "Memory limit")
148+
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
149+
cmd.Flags().String("health-check-path", "", "Health check path")
150+
151+
return cmd
152+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package create
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/coollabsio/coolify-cli/internal/cli"
9+
"github.com/coollabsio/coolify-cli/internal/models"
10+
"github.com/coollabsio/coolify-cli/internal/output"
11+
"github.com/coollabsio/coolify-cli/internal/service"
12+
)
13+
14+
// NewDockerfileCommand returns the create dockerfile application command
15+
func NewDockerfileCommand() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "dockerfile",
18+
Short: "Create an application from a custom Dockerfile",
19+
Long: `Create a new application from a custom Dockerfile content.
20+
21+
Examples:
22+
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
23+
--dockerfile "FROM node:18\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"npm\", \"start\"]"
24+
25+
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
26+
--dockerfile "$(cat Dockerfile)" --ports-exposes 3000 --instant-deploy`,
27+
RunE: func(cmd *cobra.Command, _ []string) error {
28+
ctx := cmd.Context()
29+
30+
// Get required flags
31+
serverUUID, _ := cmd.Flags().GetString("server-uuid")
32+
projectUUID, _ := cmd.Flags().GetString("project-uuid")
33+
dockerfile, _ := cmd.Flags().GetString("dockerfile")
34+
environmentName, _ := cmd.Flags().GetString("environment-name")
35+
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
36+
37+
// Validate required fields
38+
if serverUUID == "" || projectUUID == "" {
39+
return fmt.Errorf("--server-uuid and --project-uuid are required")
40+
}
41+
if dockerfile == "" {
42+
return fmt.Errorf("--dockerfile is required")
43+
}
44+
if environmentName == "" && environmentUUID == "" {
45+
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
46+
}
47+
48+
req := &models.ApplicationCreateDockerfileRequest{
49+
ServerUUID: serverUUID,
50+
ProjectUUID: projectUUID,
51+
Dockerfile: dockerfile,
52+
}
53+
54+
if environmentName != "" {
55+
req.EnvironmentName = &environmentName
56+
}
57+
if environmentUUID != "" {
58+
req.EnvironmentUUID = &environmentUUID
59+
}
60+
61+
// Optional fields
62+
setOptionalStringFlag(cmd, "name", &req.Name)
63+
setOptionalStringFlag(cmd, "description", &req.Description)
64+
setOptionalStringFlag(cmd, "domains", &req.Domains)
65+
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
66+
setOptionalStringFlag(cmd, "ports-exposes", &req.PortsExposes)
67+
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
68+
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
69+
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
70+
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
71+
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
72+
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
73+
74+
client, err := cli.GetAPIClient(cmd)
75+
if err != nil {
76+
return fmt.Errorf("failed to get API client: %w", err)
77+
}
78+
79+
appSvc := service.NewApplicationService(client)
80+
app, err := appSvc.CreateDockerfile(ctx, req)
81+
if err != nil {
82+
return fmt.Errorf("failed to create application: %w", err)
83+
}
84+
85+
format, _ := cmd.Flags().GetString("format")
86+
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
87+
88+
formatter, err := output.NewFormatter(format, output.Options{
89+
ShowSensitive: showSensitive,
90+
})
91+
if err != nil {
92+
return err
93+
}
94+
95+
return formatter.Format(app)
96+
},
97+
}
98+
99+
// Required flags
100+
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
101+
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
102+
cmd.Flags().String("environment-name", "", "Environment name")
103+
cmd.Flags().String("environment-uuid", "", "Environment UUID")
104+
cmd.Flags().String("dockerfile", "", "Dockerfile content (required)")
105+
106+
// Optional flags
107+
cmd.Flags().String("name", "", "Application name")
108+
cmd.Flags().String("description", "", "Application description")
109+
cmd.Flags().String("domains", "", "Domain(s) for the application")
110+
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
111+
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
112+
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080'")
113+
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
114+
cmd.Flags().String("limits-cpus", "", "CPU limit")
115+
cmd.Flags().String("limits-memory", "", "Memory limit")
116+
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
117+
cmd.Flags().String("health-check-path", "", "Health check path")
118+
119+
return cmd
120+
}

0 commit comments

Comments
 (0)