99 dockeropts "github.com/docker/cli/opts"
1010 "github.com/docker/compose/v2/pkg/progress"
1111 "github.com/docker/docker/daemon/names"
12+ "github.com/docker/go-units"
1213 "github.com/psviderski/uncloud/internal/cli"
1314 "github.com/psviderski/uncloud/internal/secret"
1415 "github.com/psviderski/uncloud/pkg/api"
@@ -32,6 +33,7 @@ type runOptions struct {
3233 publish []string
3334 pull string
3435 replicas uint
36+ ulimits []string
3537 user string
3638 volumes []string
3739}
@@ -102,6 +104,12 @@ func NewRunCommand(groupID string) *cobra.Command {
102104 cmd .Flags ().StringVarP (& opts .user , "user" , "u" , "" ,
103105 "User name or UID and optionally group name or GID used for running the command inside service containers.\n " +
104106 "Format: USER[:GROUP] or UID[:GID]. If not specified, the user is set to the default user of the image." )
107+ cmd .Flags ().StringSliceVar (& opts .ulimits , "ulimit" , nil ,
108+ "Set resource limits for service containers. Can be specified multiple times.\n " +
109+ "Format: type=soft_limit[:hard_limit]. If hard limit is not specified, soft limit is used for both.\n " +
110+ "Examples:\n " +
111+ " --ulimit nofile=1024:2048 Set soft limit to 1024 and hard limit to 2048 for number of open files\n " +
112+ " --ulimit nproc=65535 Set both soft and hard limits to 65535 for number of processes" )
105113 cmd .Flags ().StringSliceVarP (& opts .volumes , "volume" , "v" , nil ,
106114 "Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n " +
107115 "the volume is located. Can be specified multiple times.\n " +
@@ -194,13 +202,18 @@ func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
194202 ports [i ] = port
195203 }
196204
197- volumes , mounts , err := parseVolumeFlags (opts .volumes )
205+ placement := api.Placement {
206+ Machines : cli .ExpandCommaSeparatedValues (opts .machines ),
207+ }
208+
209+ ulimits , err := parseUlimits (opts .ulimits )
198210 if err != nil {
199211 return spec , err
200212 }
201213
202- placement := api.Placement {
203- Machines : cli .ExpandCommaSeparatedValues (opts .machines ),
214+ volumes , mounts , err := parseVolumeFlags (opts .volumes )
215+ if err != nil {
216+ return spec , err
204217 }
205218
206219 spec = api.ServiceSpec {
@@ -211,8 +224,9 @@ func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
211224 Privileged : opts .privileged ,
212225 PullPolicy : opts .pull ,
213226 Resources : api.ContainerResources {
214- CPU : opts .cpu .Value (),
215- Memory : opts .memory .Value (),
227+ CPU : opts .cpu .Value (),
228+ Memory : opts .memory .Value (),
229+ Ulimits : ulimits ,
216230 },
217231 User : opts .user ,
218232 VolumeMounts : mounts ,
@@ -302,6 +316,24 @@ func parseVolumeFlags(volumes []string) ([]api.VolumeSpec, []api.VolumeMount, er
302316 return specs , mounts , nil
303317}
304318
319+ // parseUlimits parses ulimit flag values in Docker CLI format (type=soft[:hard]).
320+ func parseUlimits (ulimits []string ) (map [string ]api.Ulimit , error ) {
321+ if len (ulimits ) == 0 {
322+ return nil , nil
323+ }
324+
325+ result := make (map [string ]api.Ulimit , len (ulimits ))
326+ for _ , u := range ulimits {
327+ parsed , err := units .ParseUlimit (u )
328+ if err != nil {
329+ return nil , fmt .Errorf ("invalid ulimit '%s': %w" , u , err )
330+ }
331+ result [parsed .Name ] = api.Ulimit {Soft : parsed .Soft , Hard : parsed .Hard }
332+ }
333+
334+ return result , nil
335+ }
336+
305337func parseVolumeFlagValue (volume string ) (api.VolumeSpec , api.VolumeMount , error ) {
306338 var spec api.VolumeSpec
307339 var mount api.VolumeMount
0 commit comments