@@ -23,19 +23,26 @@ Examples:
2323 coderun deploy my-app:latest --name web-app --http-port 8080 --env-file .env
2424 coderun deploy redis:latest --name my-redis --tcp-port 6379
2525 coderun deploy postgres:latest --name my-db --tcp-port 5432 --env-file database.env
26- coderun deploy my-app:latest --name prod-app --replicas 2 --cpu 200m --memory 512Mi --http-port 3000 --env-file production.env` ,
26+ coderun deploy my-app:latest --name prod-app --replicas 2 --cpu 200m --memory 512Mi --http-port 3000 --env-file production.env
27+
28+ # With persistent storage (automatically forces replicas to 1):
29+ coderun deploy postgres:15 --name my-postgres --tcp-port 5432 --storage-size 5Gi --storage-path /var/lib/postgresql/data
30+ coderun deploy mysql:8 --name my-mysql --tcp-port 3306 --storage-size 10Gi --storage-path /var/lib/mysql
31+ coderun deploy nginx:latest --name web-server --http-port 80 --storage-size 1Gi --storage-path /usr/share/nginx/html` ,
2732 Args : cobra .ExactArgs (1 ),
2833 Run : runDeploy ,
2934}
3035
3136var (
32- replicas int
33- cpu string
34- memory string
35- httpPort int
36- tcpPort int
37- envFile string
38- appName string
37+ replicas int
38+ cpu string
39+ memory string
40+ httpPort int
41+ tcpPort int
42+ envFile string
43+ appName string
44+ persistentVolumeSize string
45+ persistentVolumeMountPath string
3946)
4047
4148func init () {
@@ -49,6 +56,10 @@ func init() {
4956 deployCmd .Flags ().IntVar (& tcpPort , "tcp-port" , 0 , "TCP port to expose" )
5057 deployCmd .Flags ().StringVar (& envFile , "env-file" , "" , "Path to environment file" )
5158 deployCmd .Flags ().StringVar (& appName , "name" , "" , "Application name (required, 3-30 chars, lowercase letters/numbers/hyphens only)" )
59+
60+ // Persistent storage flags
61+ deployCmd .Flags ().StringVar (& persistentVolumeSize , "storage-size" , "" , "Size of persistent volume (e.g., '1Gi', '500Mi', '10Gi')" )
62+ deployCmd .Flags ().StringVar (& persistentVolumeMountPath , "storage-path" , "" , "Path where to mount the volume (e.g., '/data', '/var/lib/mysql')" )
5263}
5364
5465// parseValidationError tries to parse backend validation errors and return user-friendly messages
@@ -91,6 +102,23 @@ func parseValidationError(errorMsg string) string {
91102 return "Image name cannot be empty"
92103 }
93104
105+ // Persistent storage validation errors
106+ if strings .Contains (lowerError , "persistent_volume_size" ) || strings .Contains (lowerError , "storage" ) {
107+ if strings .Contains (lowerError , "together" ) || strings .Contains (lowerError , "provided together" ) {
108+ return "When using persistent storage, both --storage-size and --storage-path are required"
109+ }
110+ if strings .Contains (lowerError , "format" ) || strings .Contains (lowerError , "10gi" ) || strings .Contains (lowerError , "500mi" ) {
111+ return "Storage size must be in format like '1Gi', '500Mi', '10Gi'"
112+ }
113+ return "Invalid storage configuration. Use --storage-size and --storage-path together"
114+ }
115+ if strings .Contains (lowerError , "persistent_volume_mount_path" ) || strings .Contains (lowerError , "mount" ) {
116+ if strings .Contains (lowerError , "absolute" ) || strings .Contains (lowerError , "starting with" ) {
117+ return "Storage path must be an absolute path starting with '/' (e.g., '/data', '/var/lib/mysql')"
118+ }
119+ return "Invalid storage path. Must be absolute path like '/data' or '/var/lib/mysql'"
120+ }
121+
94122 // Generic validation error
95123 if strings .Contains (lowerError , "422" ) || strings .Contains (lowerError , "validation" ) {
96124 return "Validation error: Please check your input parameters"
@@ -173,6 +201,38 @@ func runDeploy(cmd *cobra.Command, args []string) {
173201 os .Exit (1 )
174202 }
175203
204+ // Validate persistent storage flags
205+ if persistentVolumeSize != "" || persistentVolumeMountPath != "" {
206+ // Both flags must be provided together
207+ if persistentVolumeSize == "" {
208+ fmt .Println ("When using persistent storage, both --storage-size and --storage-path are required" )
209+ os .Exit (1 )
210+ }
211+ if persistentVolumeMountPath == "" {
212+ fmt .Println ("When using persistent storage, both --storage-size and --storage-path are required" )
213+ os .Exit (1 )
214+ }
215+
216+ // Validate storage size format
217+ matched , _ := regexp .MatchString (`^\d+[MGT]i$` , persistentVolumeSize )
218+ if ! matched {
219+ fmt .Println ("Storage size must be in format like '1Gi', '500Mi', '10Gi'" )
220+ os .Exit (1 )
221+ }
222+
223+ // Validate mount path format (must be absolute path)
224+ if ! strings .HasPrefix (persistentVolumeMountPath , "/" ) {
225+ fmt .Println ("Storage path must be an absolute path starting with '/' (e.g., '/data', '/var/lib/mysql')" )
226+ os .Exit (1 )
227+ }
228+
229+ // Force replicas to 1 when using persistent storage
230+ if replicas > 1 {
231+ fmt .Printf ("Warning: Persistent storage requested, forcing replicas to 1 (was %d)\n " , replicas )
232+ replicas = 1
233+ }
234+ }
235+
176236 // Parse environment file if provided
177237 var envVars map [string ]string
178238 if envFile != "" {
@@ -194,6 +254,12 @@ func runDeploy(cmd *cobra.Command, args []string) {
194254 EnvironmentVars : envVars ,
195255 }
196256
257+ // Add persistent storage if specified
258+ if persistentVolumeSize != "" && persistentVolumeMountPath != "" {
259+ deployReq .PersistentVolumeSize = persistentVolumeSize
260+ deployReq .PersistentVolumeMountPath = persistentVolumeMountPath
261+ }
262+
197263 // Add HTTP port if specified
198264 if httpPort > 0 {
199265 deployReq .HTTPPort = & httpPort
0 commit comments