github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/manager/serverless/docker.go (about) 1 package serverless 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/docker/distribution/reference" 7 "github.com/docker/docker/api/types" 8 "github.com/docker/docker/api/types/container" 9 "github.com/docker/docker/api/types/filters" 10 "github.com/docker/docker/api/types/mount" 11 "github.com/docker/docker/api/types/strslice" 12 "github.com/docker/docker/client" 13 "github.com/docker/docker/pkg/jsonmessage" 14 "github.com/hazelops/ize/pkg/terminal" 15 "os" 16 "time" 17 ) 18 19 func (sls *Manager) deployWithDocker(s terminal.Step) error { 20 cli, err := client.NewClientWithOpts(client.FromEnv) 21 if err != nil { 22 return err 23 } 24 25 image := "node:" + sls.App.NodeVersion 26 27 s.Update("%s: checking for Docker image: %s", sls.App.Name, image) 28 29 imageRef, err := reference.ParseNormalizedNamed(image) 30 if err != nil { 31 return fmt.Errorf("error parsing Docker image: %s", err) 32 } 33 34 imageList, err := cli.ImageList(context.Background(), types.ImageListOptions{ 35 Filters: filters.NewArgs(filters.KeyValuePair{ 36 Key: "reference", 37 Value: reference.FamiliarString(imageRef), 38 }), 39 }) 40 if err != nil { 41 return err 42 } 43 44 if len(imageList) == 0 { 45 s.Update("%s: pulling image: %s", sls.App.Name, image) 46 47 resp, err := cli.ImagePull(context.Background(), reference.FamiliarString(imageRef), types.ImagePullOptions{}) 48 if err != nil { 49 return err 50 } 51 defer resp.Close() 52 53 err = jsonmessage.DisplayJSONMessagesStream(resp, s.TermOutput(), os.Stdout.Fd(), true, nil) 54 if err != nil { 55 return fmt.Errorf("unable to stream pull logs to the terminal: %s", err) 56 } 57 } 58 59 s.Update("%s: downloading npm modules...", sls.App.Name) 60 61 err = sls.npm(cli, []string{"npm", "install", "--save-dev"}, s) 62 if err != nil { 63 return fmt.Errorf("can't deploy %s: %w", sls.App.Name, err) 64 } 65 66 s.Done() 67 68 if sls.App.CreateDomain { 69 s.Update("%s: creating domain...", sls.App.Name) 70 err = sls.serverless(cli, []string{ 71 "create_domain", 72 "--verbose", 73 "--region", sls.App.AwsRegion, 74 "--profile", sls.App.AwsProfile, 75 "--stage", sls.Project.Env, 76 }, s) 77 if err != nil { 78 return err 79 } 80 81 s.Done() 82 } 83 84 s.Update("%s: deploying app...", sls.App.Name) 85 86 err = sls.serverless(cli, []string{ 87 "deploy", 88 "--config", sls.App.File, 89 "--service", sls.App.Name, 90 "--verbose", 91 "--region", sls.App.AwsRegion, 92 "--profile", sls.App.AwsProfile, 93 "--stage", sls.Project.Env, 94 }, s) 95 if err != nil { 96 s.Abort() 97 time.Sleep(time.Second) 98 return err 99 } 100 101 return nil 102 } 103 104 func (sls *Manager) removeWithDocker(s terminal.Step) error { 105 cli, err := client.NewClientWithOpts(client.FromEnv) 106 if err != nil { 107 return err 108 } 109 110 image := "node:" + sls.App.NodeVersion 111 112 s.Update("%s: checking for Docker image: %s", sls.App.Name, image) 113 114 imageRef, err := reference.ParseNormalizedNamed(image) 115 if err != nil { 116 return fmt.Errorf("error parsing Docker image: %s", err) 117 } 118 119 imageList, err := cli.ImageList(context.Background(), types.ImageListOptions{ 120 Filters: filters.NewArgs(filters.KeyValuePair{ 121 Key: "reference", 122 Value: reference.FamiliarString(imageRef), 123 }), 124 }) 125 if err != nil { 126 return err 127 } 128 129 if len(imageList) == 0 { 130 s.Update("%s: pulling image: %s", sls.App.Name, image) 131 132 resp, err := cli.ImagePull(context.Background(), reference.FamiliarString(imageRef), types.ImagePullOptions{}) 133 if err != nil { 134 return err 135 } 136 defer resp.Close() 137 138 err = jsonmessage.DisplayJSONMessagesStream(resp, s.TermOutput(), os.Stdout.Fd(), true, nil) 139 if err != nil { 140 return fmt.Errorf("unable to stream pull logs to the terminal: %s", err) 141 } 142 } 143 144 s.Done() 145 s.Update("%s: destroying app...", sls.App.Name) 146 147 err = sls.serverless(cli, []string{ 148 "remove", 149 "--config", sls.App.File, 150 "--service", sls.App.Name, 151 "--verbose", 152 "--region", sls.App.AwsRegion, 153 "--stage", sls.Project.Env, 154 "--profile", sls.App.AwsProfile, 155 }, s) 156 if err != nil { 157 s.Abort() 158 return err 159 } 160 161 return nil 162 } 163 164 func (sls *Manager) serverless(cli *client.Client, cmd []string, step terminal.Step) error { 165 command := []string{"serverless"} 166 command = append(command, cmd...) 167 168 contConfig := &container.Config{ 169 Image: fmt.Sprintf("node:%v", sls.App.NodeVersion), 170 Entrypoint: strslice.StrSlice{"/usr/local/bin/npx"}, 171 WorkingDir: "/app", 172 Env: sls.App.Env, 173 Tty: true, 174 Cmd: command, 175 AttachStdin: true, 176 AttachStdout: true, 177 AttachStderr: true, 178 } 179 180 contHostConfig := sls.getHostConfig(sls.Project.Home, sls.Project.RootDir) 181 182 cont, err := cli.ContainerCreate( 183 context.Background(), 184 contConfig, 185 contHostConfig, 186 nil, 187 nil, 188 "ize_sls", 189 ) 190 if err != nil { 191 return fmt.Errorf("can't deploy app: %w", err) 192 } 193 194 if err := cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}); err != nil { 195 return fmt.Errorf("can't deploy app: %w", err) 196 } 197 198 body, err := cli.ContainerAttach(context.Background(), cont.ID, types.ContainerAttachOptions{Stream: true, Stdout: true, Stderr: true, Stdin: true}) 199 if err != nil { 200 return err 201 } 202 203 msgs := make(chan []byte) 204 msgsErr := make(chan error) 205 206 go func() { 207 for { 208 msg, er := body.Reader.ReadBytes('\n') 209 if er != nil { 210 msgsErr <- er 211 return 212 } 213 msgs <- msg 214 } 215 }() 216 217 msgLoop: 218 for { 219 select { 220 case msg := <-msgs: 221 fmt.Fprintf(step.TermOutput(), "%s", msg) 222 case <-msgsErr: 223 break msgLoop 224 } 225 } 226 227 wait, errC := cli.ContainerWait(context.Background(), cont.ID, container.WaitConditionRemoved) 228 229 select { 230 case status := <-wait: 231 if status.StatusCode == 0 { 232 return nil 233 } 234 return fmt.Errorf("container exit status code %d", status.StatusCode) 235 case err := <-errC: 236 return fmt.Errorf("can't deploy app: %w", err) 237 } 238 } 239 240 func (sls *Manager) npm(cli *client.Client, cmd []string, s terminal.Step) error { 241 contConfig := &container.Config{ 242 WorkingDir: "/app", 243 Image: fmt.Sprintf("node:%v", sls.App.NodeVersion), 244 Tty: true, 245 Cmd: cmd, 246 AttachStdin: true, 247 AttachStdout: true, 248 AttachStderr: true, 249 OpenStdin: true, 250 } 251 252 contHostConfig := sls.getHostConfig(sls.Project.Home, sls.Project.RootDir) 253 254 cont, err := cli.ContainerCreate( 255 context.Background(), 256 contConfig, 257 contHostConfig, 258 nil, 259 nil, 260 "sls", 261 ) 262 if err != nil { 263 return fmt.Errorf("can't deploy app: %w", err) 264 } 265 266 if err := cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}); err != nil { 267 return fmt.Errorf("can't deploy app: %w", err) 268 } 269 270 body, err := cli.ContainerAttach(context.Background(), cont.ID, types.ContainerAttachOptions{Stream: true, Stdout: true}) 271 if err != nil { 272 return err 273 } 274 275 msgs := make(chan []byte) 276 msgsErr := make(chan error) 277 278 go func() { 279 for { 280 msg, er := body.Reader.ReadBytes('\n') 281 if er != nil { 282 msgsErr <- er 283 return 284 } 285 msgs <- msg 286 } 287 }() 288 289 msgLoop: 290 for { 291 select { 292 case msg := <-msgs: 293 fmt.Fprintf(s.TermOutput(), "%s", msg) 294 case <-msgsErr: 295 break msgLoop 296 } 297 } 298 299 defer close(msgs) 300 defer close(msgsErr) 301 defer body.Close() 302 303 wait, errC := cli.ContainerWait(context.Background(), cont.ID, container.WaitConditionNotRunning) 304 305 select { 306 case status := <-wait: 307 if status.StatusCode == 0 { 308 return nil 309 } 310 return fmt.Errorf("container exit status code %d", status.StatusCode) 311 case err := <-errC: 312 return fmt.Errorf("can't deploy app: %w", err) 313 } 314 } 315 316 func (sls *Manager) getHostConfig(homeDir, rootDir string) *container.HostConfig { 317 return &container.HostConfig{ 318 AutoRemove: true, 319 Mounts: []mount.Mount{ 320 { 321 Type: mount.TypeBind, 322 ReadOnly: true, 323 Source: fmt.Sprintf("%v/.aws", homeDir), 324 Target: "/root/.aws", 325 }, 326 { 327 Type: mount.TypeBind, 328 Source: fmt.Sprintf("%s/%s", rootDir, sls.App.Path), 329 Target: "/app", 330 }, 331 { 332 Type: mount.TypeBind, 333 Source: fmt.Sprintf("%s/%s/.serverless/", rootDir, sls.App.Path), 334 Target: "/root/.config", 335 }, 336 { 337 Type: mount.TypeBind, 338 Source: fmt.Sprintf("%s/.npm/", rootDir), 339 Target: "/root/.npm", 340 }, 341 { 342 Type: mount.TypeVolume, 343 Source: sls.App.SLSNodeModuleCacheMount, 344 Target: "/app/node_modules", 345 }, 346 }, 347 } 348 }