Skip to content

Commit 7a4f748

Browse files
authored
Merge pull request #99 from thin-edge/feat-container-group-pull
feat(container-group): pull images in before calling docker compose
2 parents 84c57f9 + 011ffb2 commit 7a4f748

File tree

180 files changed

+28102
-11
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+28102
-11
lines changed

cli/container_group/install.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"log/slog"
99
"os"
1010
"path/filepath"
11+
"time"
1112

1213
"github.com/codeclysm/extract/v4"
1314
"github.com/spf13/cobra"
@@ -85,17 +86,40 @@ func (c *InstallCommand) RunE(cmd *cobra.Command, args []string) error {
8586
return err
8687
}
8788

89+
composeFile := filepath.Join(workingDir, "docker-compose.yaml")
90+
8891
composeUpExtraArgs := []string{"--build"}
8992
if err := extract.Archive(ctx, file, workingDir, nil); err != nil {
9093
// Fallback to treating it as a text file
91-
dst := filepath.Join(workingDir, "docker-compose.yaml")
92-
slog.Info("Copying file.", "src", c.File, "dst", dst)
93-
if err := utils.CopyFile(c.File, dst); err != nil {
94+
slog.Info("Copying file.", "src", c.File, "dst", composeFile)
95+
if err := utils.CopyFile(c.File, composeFile); err != nil {
9496
return err
9597
}
9698
composeUpExtraArgs = []string{}
9799
}
98100

101+
// Pull images which allows uses to avoid having to set any private credentials
102+
// as tedge-container-plugin supports user set credentials
103+
if !utils.PathExists(composeFile) {
104+
if p := filepath.Join(workingDir, "docker-compose.yml"); utils.PathExists(p) {
105+
composeFile = p
106+
}
107+
}
108+
images, err := container.ReadImages(ctx, []string{composeFile}, workingDir)
109+
if err != nil {
110+
return err
111+
}
112+
for _, imageRef := range images {
113+
if _, err := cli.ImagePullWithRetries(ctx, imageRef, c.CommandContext.ImageAlwaysPull(), container.ImagePullOptions{
114+
AuthFunc: c.CommandContext.GetContainerRepositoryCredentialsFunc(imageRef),
115+
MaxAttempts: 2,
116+
Wait: 5 * time.Second,
117+
}); err != nil {
118+
// Proceed anyway so docker-compose can potentially pull in the images
119+
slog.Warn("Error whilst pulling images. Trying to proceed anyway.", "err", err)
120+
}
121+
}
122+
99123
// Create shared network
100124
if err := cli.CreateSharedNetwork(ctx, c.CommandContext.GetSharedContainerNetwork()); err != nil {
101125
return err

go.mod

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ go 1.22.0
44

55
require (
66
github.com/codeclysm/extract/v4 v4.0.0
7+
github.com/compose-spec/compose-go/v2 v2.4.5
78
github.com/distribution/reference v0.6.0
89
github.com/docker/docker v27.3.1+incompatible
10+
github.com/docker/go-connections v0.5.0
911
github.com/docker/go-units v0.5.0
1012
github.com/eclipse/paho.mqtt.golang v1.5.0
1113
github.com/pkg/errors v0.9.1
@@ -20,11 +22,11 @@ require (
2022
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
2123
github.com/containerd/log v0.1.0 // indirect
2224
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
23-
github.com/docker/go-connections v0.5.0 // indirect
2425
github.com/felixge/httpsnoop v1.0.4 // indirect
2526
github.com/fsnotify/fsnotify v1.7.0 // indirect
2627
github.com/go-logr/logr v1.4.2 // indirect
2728
github.com/go-logr/stdr v1.2.2 // indirect
29+
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
2830
github.com/gogo/protobuf v1.3.2 // indirect
2931
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
3032
github.com/google/go-querystring v1.1.0 // indirect
@@ -36,6 +38,7 @@ require (
3638
github.com/juju/loggo v1.0.0 // indirect
3739
github.com/klauspost/compress v1.17.2 // indirect
3840
github.com/magiconair/properties v1.8.7 // indirect
41+
github.com/mattn/go-shellwords v1.0.12 // indirect
3942
github.com/mitchellh/mapstructure v1.5.0 // indirect
4043
github.com/moby/docker-image-spec v1.3.1 // indirect
4144
github.com/moby/term v0.5.0 // indirect
@@ -48,6 +51,7 @@ require (
4851
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
4952
github.com/sagikazarmark/locafero v0.4.0 // indirect
5053
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
54+
github.com/sirupsen/logrus v1.9.3 // indirect
5155
github.com/sourcegraph/conc v0.3.0 // indirect
5256
github.com/spf13/afero v1.11.0 // indirect
5357
github.com/spf13/cast v1.6.0 // indirect
@@ -57,6 +61,9 @@ require (
5761
github.com/tidwall/match v1.1.1 // indirect
5862
github.com/tidwall/pretty v1.2.1 // indirect
5963
github.com/ulikunitz/xz v0.5.12 // indirect
64+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
65+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
66+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
6067
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
6168
go.opentelemetry.io/otel v1.31.0 // indirect
6269
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
@@ -65,7 +72,7 @@ require (
6572
go.opentelemetry.io/otel/trace v1.31.0 // indirect
6673
go.uber.org/multierr v1.11.0 // indirect
6774
go.uber.org/zap v1.27.0 // indirect
68-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
75+
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
6976
golang.org/x/net v0.30.0 // indirect
7077
golang.org/x/sync v0.8.0 // indirect
7178
golang.org/x/sys v0.26.0 // indirect

go.sum

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1
1010
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo=
1111
github.com/codeclysm/extract/v4 v4.0.0 h1:H87LFsUNaJTu2e/8p/oiuiUsOK/TaPQ5wxsjPnwPEIY=
1212
github.com/codeclysm/extract/v4 v4.0.0/go.mod h1:SFju1lj6as7FvUgalpSct7torJE0zttbJUWtryPRG6s=
13+
github.com/compose-spec/compose-go/v2 v2.4.5 h1:p4ih4Jb6VgGPLPxh3fSFVKAjFHtZd+7HVLCSFzcFx9Y=
14+
github.com/compose-spec/compose-go/v2 v2.4.5/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
1315
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
1416
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
1517
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -38,6 +40,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
3840
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
3941
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
4042
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
43+
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
44+
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
4145
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
4246
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
4347
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
@@ -80,6 +84,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
8084
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
8185
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
8286
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
87+
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
88+
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
8389
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
8490
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
8591
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -138,6 +144,8 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
138144
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
139145
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
140146
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
147+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
148+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
141149
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
142150
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
143151
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -154,6 +162,12 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
154162
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
155163
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
156164
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
165+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
166+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
167+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
168+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
169+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
170+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
157171
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
158172
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
159173
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
@@ -181,8 +195,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
181195
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
182196
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
183197
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
184-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
185-
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
198+
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
199+
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
186200
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
187201
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
188202
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -201,6 +215,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
201215
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
202216
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
203217
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
218+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
204219
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
205220
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
206221
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

pkg/container/compose.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package container
22

33
import (
4+
"context"
45
"fmt"
56
"log/slog"
67
"os/exec"
78
"strings"
9+
10+
composeCli "github.com/compose-spec/compose-go/v2/cli"
811
)
912

1013
func detectCompose() (command string, args []string, err error) {
@@ -55,3 +58,28 @@ func filter(ss []string, test func(string) bool) (ret []string) {
5558
}
5659
return
5760
}
61+
62+
func ReadImages(ctx context.Context, paths []string, workingDir string) ([]string, error) {
63+
images := make([]string, 0)
64+
65+
project, err := composeCli.NewProjectOptions(
66+
paths,
67+
composeCli.WithDotEnv,
68+
)
69+
if err != nil {
70+
return images, err
71+
}
72+
73+
projectT, err := project.LoadProject(ctx)
74+
if err != nil {
75+
return images, err
76+
}
77+
for name, service := range projectT.Services {
78+
if service.Image != "" {
79+
slog.Info("Found image for service.", "service", name, "image", service.Image)
80+
images = append(images, service.Image)
81+
}
82+
}
83+
84+
return images, nil
85+
}

pkg/container/compose_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package container
22

3-
import "testing"
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
)
49

510
func Test_Podman(t *testing.T) {
611
output := `
@@ -35,3 +40,33 @@ exit code: 0
3540
t.Errorf("did not expect an error")
3641
}
3742
}
43+
44+
func Test_DetectImagesInCompose(t *testing.T) {
45+
contents := `
46+
services:
47+
app1:
48+
image: hello-world
49+
50+
app2:
51+
build: "."
52+
`
53+
workingDir, err := os.MkdirTemp("", "compose")
54+
if err != nil {
55+
t.Fail()
56+
}
57+
defer os.RemoveAll(workingDir)
58+
59+
composeFile := filepath.Join(workingDir, "docker-compose.yml")
60+
if err := os.WriteFile(composeFile, []byte(contents), 0o644); err != nil {
61+
t.Fail()
62+
}
63+
64+
images, err := ReadImages(context.Background(), []string{composeFile}, workingDir)
65+
if err != nil {
66+
t.Errorf("Failed to parse images. %s", err)
67+
}
68+
69+
if images[0] != "hello-world" {
70+
t.Errorf("Image not found. got=%s, wanted=%s", images[0], "hello-world")
71+
}
72+
}

pkg/container/container.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,15 +498,39 @@ type ImagePullOptions struct {
498498
Wait time.Duration
499499
}
500500

501+
// Check if the given docker.io image has fully qualified (e.g. docker.io/library/<image>)
502+
// if not, then expand it to its fully qualified name.
503+
func ResolveDockerIOImage(imageRef string) (string, bool) {
504+
if strings.HasPrefix(imageRef, "docker.io/library/") {
505+
// Is already normalized
506+
return imageRef, false
507+
}
508+
if !strings.HasPrefix(imageRef, "docker.io/") {
509+
// Not a docker.io image
510+
return imageRef, false
511+
}
512+
513+
return "docker.io/library/" + strings.TrimPrefix(imageRef, "docker.io/"), true
514+
}
515+
501516
// Pull a container image. The image will be verified if it exists afterwards
502517
//
503518
// Use credentials function to generate initial credentials
504519
// and call again if the credentials fail which gives the credentials
505520
// helper to invalid its own cache
506521
func (c *ContainerClient) ImagePullWithRetries(ctx context.Context, imageRef string, alwaysPull bool, opts ImagePullOptions) (*image.Summary, error) {
507522
// Check if an image
523+
filterArgs := filters.NewArgs(filters.Arg("reference", imageRef))
524+
525+
// Include full image name of docker.io image as the user can
526+
// provide the short form (docker.io/<image>) which results in the post-pull image check
527+
// to fail. Add the fully qualified docker.io image to the image list filter options to find both
528+
if fullImageRef, ok := ResolveDockerIOImage(imageRef); ok {
529+
filterArgs.Add("reference", fullImageRef)
530+
}
531+
508532
images, err := c.Client.ImageList(ctx, image.ListOptions{
509-
Filters: filters.NewArgs(filters.Arg("reference", imageRef)),
533+
Filters: filterArgs,
510534
})
511535
if err != nil {
512536
return nil, err
@@ -542,7 +566,7 @@ func (c *ContainerClient) ImagePullWithRetries(ctx context.Context, imageRef str
542566
//
543567
// Check if image is not present
544568
imageList, imageErr := c.Client.ImageList(ctx, image.ListOptions{
545-
Filters: filters.NewArgs(filters.Arg("reference", imageRef)),
569+
Filters: filterArgs,
546570
})
547571
if imageErr != nil {
548572
return nil, imageErr

0 commit comments

Comments
 (0)