Skip to content

Commit 242e8e8

Browse files
committed
feat(build): Do not build images twice with the same content
1 parent 20429ad commit 242e8e8

File tree

5 files changed

+59
-7
lines changed

5 files changed

+59
-7
lines changed

pkg/build/build.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ import (
2929
"github.com/nitrictech/cli/pkg/target"
3030
)
3131

32+
func dynamicDockerfile(dir, name string) (*os.File, error) {
33+
// create a more stable file name for the hashing
34+
return os.CreateTemp(dir, "nitric.dynamic.Dockerfile.*")
35+
}
36+
3237
func Create(s *stack.Stack, t *target.Target) error {
3338
cr, err := containerengine.Discover()
3439
if err != nil {
@@ -44,7 +49,7 @@ func Create(s *stack.Stack, t *target.Target) error {
4449
}
4550
}
4651

47-
fh, err := os.CreateTemp(s.Dir, "Dockerfile.*")
52+
fh, err := dynamicDockerfile(s.Dir, f.Name)
4853
if err != nil {
4954
return err
5055
}
@@ -95,7 +100,7 @@ func CreateBaseDev(s *stack.Stack) error {
95100
continue
96101
}
97102

98-
f, err := os.CreateTemp(s.Dir, fmt.Sprintf("%s.*.dockerfile", lang))
103+
f, err := dynamicDockerfile(s.Dir, f.Name)
99104
if err != nil {
100105
return err
101106
}

pkg/cmd/deployment/root.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ var deploymentApplyCmd = &cobra.Command{
5353
Short: "Create or Update a new application deployment",
5454
Long: `Applies a Nitric application deployment.`,
5555
Example: `nitric deployment apply
56+
57+
# When using a nitric.yaml
5658
nitric deployment apply -n prod-aws -s ../project/ -t prod
59+
60+
# When using code-as-config, specify the functions.
5761
nitric deployment apply -n prod-aws -s ../project/ -t prod "functions/*.ts"
5862
`,
5963
Run: func(cmd *cobra.Command, args []string) {

pkg/cmd/run/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ var runCmd = &cobra.Command{
3939
Short: "run a nitric stack",
4040
Long: `Run a nitric stack locally for development or testing
4141
`,
42-
Example: `nitric run -s projectX "functions/*.ts"`,
42+
Example: `nitric run -s ../projectX "functions/*.ts"`,
4343
Run: func(cmd *cobra.Command, args []string) {
4444
term := make(chan os.Signal, 1)
4545
signal.Notify(term, os.Interrupt, syscall.SIGTERM)

pkg/containerengine/docker.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package containerengine
1919
import (
2020
"bufio"
2121
"context"
22+
"crypto/md5"
23+
"encoding/hex"
2224
"encoding/json"
2325
"fmt"
2426
"io"
@@ -93,23 +95,64 @@ func tarContextDir(relDockerfile, contextDir string, extraExcludes []string) (io
9395
})
9496
}
9597

98+
func imageNameFromBuildContext(dockerfile, srcPath, imageTag string, excludes []string) (string, error) {
99+
var buildContext io.ReadCloser
100+
var err error
101+
if strings.Contains(dockerfile, "nitric.dynamic.") {
102+
// don't include the dynamic dockerfile as the timestamp on the file will cause it to have a different hash.
103+
buildContext, err = tarContextDir("", srcPath, append(excludes, dockerfile))
104+
} else {
105+
buildContext, err = tarContextDir(dockerfile, srcPath, excludes)
106+
}
107+
if err != nil {
108+
return "", err
109+
}
110+
111+
hash := md5.New()
112+
_, err = io.Copy(hash, buildContext)
113+
if err != nil {
114+
return "", err
115+
}
116+
117+
imageName := imageTag
118+
if strings.Contains(imageTag, ":") {
119+
imageName = strings.Split(imageTag, ":")[0]
120+
}
121+
122+
return imageName + ":" + hex.EncodeToString(hash.Sum(nil)), nil
123+
}
124+
96125
func (d *docker) Build(dockerfile, srcPath, imageTag string, buildArgs map[string]string, excludes []string) error {
97126
ctx, cancel := context.WithTimeout(context.Background(), buildTimeout())
98127
defer cancel()
99128

100-
dockerBuildContext, err := tarContextDir(dockerfile, srcPath, excludes)
129+
imageTagWithHash, err := imageNameFromBuildContext(dockerfile, srcPath, imageTag, excludes)
130+
if err != nil {
131+
return err
132+
}
133+
134+
buildContext, err := tarContextDir(dockerfile, srcPath, excludes)
101135
if err != nil {
102136
return err
103137
}
138+
139+
// try and find an existing image with this hash.
140+
listOpts := types.ImageListOptions{Filters: filters.NewArgs()}
141+
listOpts.Filters.Add("reference", imageTagWithHash)
142+
imageSummaries, err := d.cli.ImageList(ctx, listOpts)
143+
if err == nil && len(imageSummaries) > 0 {
144+
return nil
145+
}
146+
104147
opts := types.ImageBuildOptions{
105148
SuppressOutput: false,
106149
Dockerfile: dockerfile,
107-
Tags: []string{imageTag},
150+
Tags: []string{imageTag, imageTagWithHash},
108151
Remove: true,
109152
ForceRemove: true,
110153
PullParent: true,
111154
}
112-
res, err := d.cli.ImageBuild(ctx, dockerBuildContext, opts)
155+
res, err := d.cli.ImageBuild(ctx, buildContext, opts)
113156
if err != nil {
114157
return err
115158
}

pkg/stack/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var (
3535

3636
func wrapStatError(err error) error {
3737
if os.IsNotExist(err) {
38-
return errors.WithMessage(err, "Please provide the correct path to the stack (eg. -s ./nitric.yaml)")
38+
return errors.WithMessage(err, "Please provide the correct path to the stack (eg. -s ../projectX)")
3939
}
4040
if os.IsPermission(err) {
4141
return errors.WithMessagef(err, "Please make sure that %s has the correct permissions", stackPath)

0 commit comments

Comments
 (0)