@@ -13,6 +13,7 @@ import (
1313 "github.com/localstack/lstk/internal/auth"
1414 "github.com/localstack/lstk/internal/config"
1515 "github.com/localstack/lstk/internal/output"
16+ "github.com/localstack/lstk/internal/ports"
1617 "github.com/localstack/lstk/internal/runtime"
1718)
1819
@@ -43,66 +44,118 @@ func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, platformCl
4344 if err != nil {
4445 return err
4546 }
47+ productName , err := c .ProductName ()
48+ if err != nil {
49+ return err
50+ }
4651
4752 env := append (c .Env , "LOCALSTACK_AUTH_TOKEN=" + token )
4853 containers [i ] = runtime.ContainerConfig {
49- Image : image ,
50- Name : c .Name (),
51- Port : c .Port ,
52- HealthPath : healthPath ,
53- Env : env ,
54+ Image : image ,
55+ Name : c .Name (),
56+ Port : c .Port ,
57+ HealthPath : healthPath ,
58+ Env : env ,
59+ Tag : c .Tag ,
60+ ProductName : productName ,
5461 }
5562 }
5663
57- // Pull all images first
58- for _ , config := range containers {
64+ containers , err = selectContainersToStart (ctx , rt , sink , containers )
65+ if err != nil {
66+ return err
67+ }
68+ if len (containers ) == 0 {
69+ return nil
70+ }
71+
72+ // TODO validate license for tag "latest" without resolving the actual image version,
73+ // and avoid pulling all images first
74+ if err := pullImages (ctx , rt , sink , containers ); err != nil {
75+ return err
76+ }
77+
78+ if err := validateLicenses (ctx , rt , sink , platformClient , containers , token ); err != nil {
79+ return err
80+ }
81+
82+ return startContainers (ctx , rt , sink , containers )
83+ }
84+
85+ func pullImages (ctx context.Context , rt runtime.Runtime , sink output.Sink , containers []runtime.ContainerConfig ) error {
86+ for _ , c := range containers {
5987 // Remove any existing stopped container with the same name
60- if err := rt .Remove (ctx , config .Name ); err != nil && ! errdefs .IsNotFound (err ) {
61- return fmt .Errorf ("failed to remove existing container %s: %w" , config .Name , err )
88+ if err := rt .Remove (ctx , c .Name ); err != nil && ! errdefs .IsNotFound (err ) {
89+ return fmt .Errorf ("failed to remove existing container %s: %w" , c .Name , err )
6290 }
6391
64- output .EmitStatus (sink , "pulling" , config .Image , "" )
92+ output .EmitStatus (sink , "pulling" , c .Image , "" )
6593 progress := make (chan runtime.PullProgress )
6694 go func () {
6795 for p := range progress {
68- output .EmitProgress (sink , config .Image , p .LayerID , p .Status , p .Current , p .Total )
96+ output .EmitProgress (sink , c .Image , p .LayerID , p .Status , p .Current , p .Total )
6997 }
7098 }()
71- if err := rt .PullImage (ctx , config .Image , progress ); err != nil {
72- return fmt .Errorf ("failed to pull image %s: %w" , config .Image , err )
99+ if err := rt .PullImage (ctx , c .Image , progress ); err != nil {
100+ return fmt .Errorf ("failed to pull image %s: %w" , c .Image , err )
73101 }
74102 }
103+ return nil
104+ }
75105
76- // TODO validate license for tag "latest" without resolving the actual image version,
77- // and avoid pulling all images first
78- for i , c := range cfg .Containers {
79- if err := validateLicense (ctx , rt , sink , platformClient , containers [i ], & c , token ); err != nil {
106+ func validateLicenses (ctx context.Context , rt runtime.Runtime , sink output.Sink , platformClient api.PlatformAPI , containers []runtime.ContainerConfig , token string ) error {
107+ for _ , c := range containers {
108+ if err := validateLicense (ctx , rt , sink , platformClient , c , token ); err != nil {
80109 return err
81110 }
82111 }
112+ return nil
113+ }
83114
84- // Start containers
85- for _ , config := range containers {
86- output .EmitStatus (sink , "starting" , config .Name , "" )
87- containerID , err := rt .Start (ctx , config )
115+ func startContainers ( ctx context. Context , rt runtime. Runtime , sink output. Sink , containers []runtime. ContainerConfig ) error {
116+ for _ , c := range containers {
117+ output .EmitStatus (sink , "starting" , c .Name , "" )
118+ containerID , err := rt .Start (ctx , c )
88119 if err != nil {
89- return fmt .Errorf ("failed to start %s: %w" , config .Name , err )
120+ return fmt .Errorf ("failed to start %s: %w" , c .Name , err )
90121 }
91122
92- output .EmitStatus (sink , "waiting" , config .Name , "" )
93- healthURL := fmt .Sprintf ("http://localhost:%s%s" , config .Port , config .HealthPath )
94- if err := awaitStartup (ctx , rt , sink , containerID , config .Name , healthURL ); err != nil {
123+ output .EmitStatus (sink , "waiting" , c .Name , "" )
124+ healthURL := fmt .Sprintf ("http://localhost:%s%s" , c .Port , c .HealthPath )
125+ if err := awaitStartup (ctx , rt , sink , containerID , c .Name , healthURL ); err != nil {
95126 return err
96127 }
97128
98- output .EmitStatus (sink , "ready" , config .Name , fmt .Sprintf ("containerId: %s" , containerID [:12 ]))
129+ output .EmitStatus (sink , "ready" , c .Name , fmt .Sprintf ("containerId: %s" , containerID [:12 ]))
99130 }
100-
101131 return nil
102132}
103133
104- func validateLicense (ctx context.Context , rt runtime.Runtime , sink output.Sink , platformClient api.PlatformAPI , containerConfig runtime.ContainerConfig , cfgContainer * config.ContainerConfig , token string ) error {
105- version := cfgContainer .Tag
134+ func selectContainersToStart (ctx context.Context , rt runtime.Runtime , sink output.Sink , containers []runtime.ContainerConfig ) ([]runtime.ContainerConfig , error ) {
135+ var filtered []runtime.ContainerConfig
136+ for _ , c := range containers {
137+ running , err := rt .IsRunning (ctx , c .Name )
138+ if err != nil && ! errdefs .IsNotFound (err ) {
139+ return nil , fmt .Errorf ("failed to check container status: %w" , err )
140+ }
141+ if running {
142+ output .EmitLog (sink , fmt .Sprintf ("%s is already running" , c .Name ))
143+ continue
144+ }
145+ if err := ports .CheckAvailable (c .Port ); err != nil {
146+ configPath , pathErr := config .ConfigFilePath ()
147+ if pathErr != nil {
148+ return nil , err
149+ }
150+ return nil , fmt .Errorf ("%w\n To use a different port, edit %s" , err , configPath )
151+ }
152+ filtered = append (filtered , c )
153+ }
154+ return filtered , nil
155+ }
156+
157+ func validateLicense (ctx context.Context , rt runtime.Runtime , sink output.Sink , platformClient api.PlatformAPI , containerConfig runtime.ContainerConfig , token string ) error {
158+ version := containerConfig .Tag
106159 if version == "" || version == "latest" {
107160 actualVersion , err := rt .GetImageVersion (ctx , containerConfig .Image )
108161 if err != nil {
@@ -111,16 +164,12 @@ func validateLicense(ctx context.Context, rt runtime.Runtime, sink output.Sink,
111164 version = actualVersion
112165 }
113166
114- productName , err := cfgContainer .ProductName ()
115- if err != nil {
116- return err
117- }
118167 output .EmitStatus (sink , "validating license" , containerConfig .Name , version )
119168
120169 hostname , _ := os .Hostname ()
121170 licenseReq := & api.LicenseRequest {
122171 Product : api.ProductInfo {
123- Name : productName ,
172+ Name : containerConfig . ProductName ,
124173 Version : version ,
125174 },
126175 Credentials : api.CredentialsInfo {
@@ -134,7 +183,7 @@ func validateLicense(ctx context.Context, rt runtime.Runtime, sink output.Sink,
134183 }
135184
136185 if err := platformClient .GetLicense (ctx , licenseReq ); err != nil {
137- return fmt .Errorf ("license validation failed for %s:%s: %w" , productName , version , err )
186+ return fmt .Errorf ("license validation failed for %s:%s: %w" , containerConfig . ProductName , version , err )
138187 }
139188
140189 return nil
0 commit comments