@@ -16,8 +16,10 @@ import (
16
16
17
17
"github.com/digitalocean/doctl/commands/charm/template"
18
18
"github.com/digitalocean/godo"
19
+ "github.com/docker/cli/cli/command/image/build"
19
20
dockertypes "github.com/docker/docker/api/types"
20
21
"github.com/docker/docker/pkg/archive"
22
+ "github.com/docker/docker/pkg/idtools"
21
23
)
22
24
23
25
// DockerComponentBuilder builds components using a Dockerfile.
@@ -58,23 +60,20 @@ func (b *DockerComponentBuilder) Build(ctx context.Context) (ComponentBuilderRes
58
60
return ComponentBuilderResult {}, fmt .Errorf ("configuring environment variables: %w" , err )
59
61
}
60
62
61
- buildContext := b .contextDir
62
- if sd := filepath .Clean (b .component .GetSourceDir ()); sd != "." && sd != "/" {
63
- buildContext = filepath .Join (buildContext , sd )
64
- }
65
- // TODO Dockerfile must be relative to the source dir.
66
- // Make it relative and if it's outside the source dir add it to the archive.
67
- // ref: https://github.com/docker/cli/blob/9400e3dbe8ebd0bede3ab7023f744a8d7f4397d2/cli/command/image/build.go#L280-L286
68
- template .Render (lw , `{{success checkmark}} building image using dockerfile {{highlight .}}{{nl 2}}` , b .dockerComponent .GetDockerfilePath ())
63
+ template .Render (lw ,
64
+ `{{success checkmark}} building image using dockerfile {{highlight .}}{{nl 2}}` ,
65
+ b .dockerComponent .GetDockerfilePath (),
66
+ )
69
67
start := time .Now ()
70
- tar , err := archive .TarWithOptions (buildContext , & archive.TarOptions {})
68
+
69
+ imageBuildContext , imageBuildDockerfile , err := b .getImageBuildContext (ctx )
71
70
if err != nil {
72
71
return ComponentBuilderResult {}, fmt .Errorf ("preparing build context: %w" , err )
73
72
}
74
73
75
74
res := ComponentBuilderResult {}
76
- dockerRes , err := b .cli .ImageBuild (ctx , tar , dockertypes.ImageBuildOptions {
77
- Dockerfile : b . dockerComponent . GetDockerfilePath () ,
75
+ dockerRes , err := b .cli .ImageBuild (ctx , imageBuildContext , dockertypes.ImageBuildOptions {
76
+ Dockerfile : imageBuildDockerfile ,
78
77
Tags : []string {
79
78
b .AppImageOutputName (),
80
79
},
@@ -106,6 +105,65 @@ func (b *DockerComponentBuilder) Build(ctx context.Context) (ComponentBuilderRes
106
105
return res , nil
107
106
}
108
107
108
+ func (b * DockerComponentBuilder ) getImageBuildContext (ctx context.Context ) (io.Reader , string , error ) {
109
+ // this assembles the build context in a way that fits cli.ImageBuild's expectations around
110
+ // dockerfiles and .dockerignore.
111
+ // much of this logic is copied from the `docker` cli implementation:
112
+ // https://github.com/docker/cli/blob/9400e3dbe8ebd0bede3ab7023f744a8d7f4397d2/cli/command/image/build.go#L180
113
+ // specifically the "build context is a local directory" flow.
114
+
115
+ absSourceDir , err := filepath .Abs (filepath .Join (b .contextDir , b .dockerComponent .GetSourceDir ()))
116
+ if err != nil {
117
+ return nil , "" , fmt .Errorf ("parsing source_dir: %w" , err )
118
+ }
119
+ absDockerfile , err := filepath .Abs (filepath .Join (b .contextDir , b .dockerComponent .GetDockerfilePath ()))
120
+ if err != nil {
121
+ return nil , "" , fmt .Errorf ("parsing dockerfile_path: %w" , err )
122
+ }
123
+ relDockerfile , err := filepath .Rel (absSourceDir , absDockerfile )
124
+ if err != nil {
125
+ return nil , "" , err
126
+ }
127
+
128
+ excludes , err := build .ReadDockerignore (absSourceDir )
129
+ if err != nil {
130
+ return nil , "" , fmt .Errorf ("reading .dockerignore: %w" , err )
131
+ }
132
+
133
+ if err := build .ValidateContextDirectory (absSourceDir , excludes ); err != nil {
134
+ return nil , "" , err
135
+ }
136
+
137
+ // canonicalize dockerfile name to a platform-independent one
138
+ relDockerfile = archive .CanonicalTarNameForPath (relDockerfile )
139
+ excludes = build .TrimBuildFilesFromExcludes (excludes , relDockerfile , false )
140
+ tar , err := archive .TarWithOptions (absSourceDir , & archive.TarOptions {
141
+ ExcludePatterns : excludes ,
142
+ ChownOpts : & idtools.Identity {UID : 0 , GID : 0 },
143
+ })
144
+ if err != nil {
145
+ return nil , "" , fmt .Errorf ("preparing build context: %w" , err )
146
+ }
147
+
148
+ // NOTE: archive.CanonicalTarNameForPath normalizes path separators so the relative path will use /
149
+ // even on windows.
150
+ if strings .HasPrefix (relDockerfile , "../" ) {
151
+ dockerfileReader , err := os .Open (absDockerfile )
152
+ if err != nil {
153
+ return nil , "" , fmt .Errorf ("opening dockerfile: %w" , err )
154
+ }
155
+ defer dockerfileReader .Close ()
156
+ // dockerfile_path is outside of source_dir. we need to copy it inside the build context
157
+ // so that the docker engine can access it.
158
+ tar , relDockerfile , err = build .AddDockerfileToBuildContext (dockerfileReader , tar )
159
+ if err != nil {
160
+ return nil , "" , fmt .Errorf ("copying external dockerfile inside build context: %w" , err )
161
+ }
162
+ }
163
+
164
+ return tar , relDockerfile , nil
165
+ }
166
+
109
167
// buildStaticSiteImage builds a container image that runs a webserver hosting the static site content
110
168
func (b * DockerComponentBuilder ) buildStaticSiteImage (ctx context.Context ) error {
111
169
c , ok := b .component .(* godo.AppStaticSiteSpec )
0 commit comments