Skip to content

Commit dd0b9b1

Browse files
committed
Version pinning for templates
Templates can now be pinned in three ways: - no pinning - get HEAD from default branch - Git tag / branch name - pass @ then the release i.e. 0.9.0 - Git SHA - requires full depth clone of whole respository followed by a git checkout Tested using the following: * Via build and publish with a templates configuration in stack.yaml * Via build and publish with no templates configuration in stack.yaml - the templates were resolved via the store * Via new - with and without a template being present - the template is pulled from the store if not found locally Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
1 parent f13bf93 commit dd0b9b1

39 files changed

+1082
-454
lines changed

.ssyncignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/.git

builder/build.go

Lines changed: 147 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -30,158 +30,186 @@ import (
3030
// Can also be passed as a build arg hence needs to be accessed from commands
3131
const AdditionalPackageBuildArg = "ADDITIONAL_PACKAGE"
3232

33-
// BuildImage construct Docker image from function parameters
34-
// TODO: refactor signature to a struct to simplify the length of the method header
35-
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {
33+
func getTemplate(lang string) (string, *stack.LanguageTemplate, error) {
3634

37-
if stack.IsValidTemplate(language) {
38-
pathToTemplateYAML := fmt.Sprintf("./template/%s/template.yml", language)
39-
if _, err := os.Stat(pathToTemplateYAML); err != nil && os.IsNotExist(err) {
40-
return err
41-
}
35+
cwd, err := os.Getwd()
36+
if err != nil {
37+
return "", nil, fmt.Errorf("can't get current working directory: %w", err)
38+
}
4239

43-
langTemplate, err := stack.ParseYAMLForLanguageTemplate(pathToTemplateYAML)
44-
if err != nil {
45-
return fmt.Errorf("error reading language template: %s", err.Error())
46-
}
40+
templateDir := filepath.Join(cwd, "template")
41+
if _, err := os.Stat(templateDir); err != nil {
42+
return "", nil, fmt.Errorf("template directory not found")
43+
}
4744

48-
mountSSH := false
49-
if langTemplate.MountSSH {
50-
mountSSH = true
51-
}
45+
files, err := os.ReadDir(templateDir)
46+
if err != nil {
47+
return "", nil, fmt.Errorf("can't read template directory: %w", err)
48+
}
5249

53-
if err := ensureHandlerPath(handler); err != nil {
54-
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
50+
found := ""
51+
for _, file := range files {
52+
if file.IsDir() {
53+
if file.Name() == lang {
54+
found = filepath.Join(templateDir, file.Name())
55+
break
56+
}
5557
}
58+
}
5659

57-
opts := []builder.BuildContextOption{}
58-
if len(langTemplate.HandlerFolder) > 0 {
59-
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
60-
}
60+
if len(found) == 0 {
61+
return "", nil, fmt.Errorf("template %s not found", lang)
62+
}
63+
parsed, err := stack.ParseYAMLForLanguageTemplate(filepath.Join(found, "template.yml"))
64+
if err != nil {
65+
return "", nil, fmt.Errorf("can't parse template: %w", err)
66+
}
67+
return found, parsed, nil
68+
}
6169

62-
buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
63-
if err != nil {
64-
return err
65-
}
70+
// BuildImage construct Docker image from function parameters
71+
// TODO: refactor signature to a struct to simplify the length of the method header
72+
func BuildImage(image string, handler string, functionName string, language string, nocache bool, squash bool, shrinkwrap bool, buildArgMap map[string]string, buildOptions []string, tagFormat schema.BuildFormat, buildLabelMap map[string]string, quietBuild bool, copyExtraPaths []string, remoteBuilder, payloadSecretPath string, forcePull bool) error {
6673

67-
if shrinkwrap {
68-
fmt.Printf("%s shrink-wrapped to %s\n", functionName, buildContext)
69-
return nil
70-
}
74+
_, langTemplate, err := getTemplate(language)
75+
if err != nil {
76+
return fmt.Errorf("language template: %s not supported, build a custom Dockerfile, error: %w", language, err)
77+
}
7178

72-
branch, version, err := GetImageTagValues(tagFormat, handler)
73-
if err != nil {
74-
return err
75-
}
79+
mountSSH := false
80+
if langTemplate.MountSSH {
81+
mountSSH = true
82+
}
7683

77-
imageName := schema.BuildImageName(tagFormat, image, version, branch)
84+
if err := ensureHandlerPath(handler); err != nil {
85+
return fmt.Errorf("building %s, %s is an invalid path", functionName, handler)
86+
}
7887

79-
buildOptPackages, err := getBuildOptionPackages(buildOptions, language, langTemplate.BuildOptions)
80-
if err != nil {
81-
return err
88+
opts := []builder.BuildContextOption{}
89+
if len(langTemplate.HandlerFolder) > 0 {
90+
opts = append(opts, builder.WithHandlerOverlay(langTemplate.HandlerFolder))
91+
}
8292

83-
}
84-
buildArgMap = appendAdditionalPackages(buildArgMap, buildOptPackages)
93+
buildContext, err := builder.CreateBuildContext(functionName, handler, language, copyExtraPaths, opts...)
94+
if err != nil {
95+
return err
96+
}
8597

86-
fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language)
98+
if shrinkwrap {
99+
fmt.Printf("%s shrink-wrapped to %s\n", functionName, buildContext)
100+
return nil
101+
}
87102

88-
if remoteBuilder != "" {
89-
tempDir, err := os.MkdirTemp(os.TempDir(), "openfaas-build-*")
90-
if err != nil {
91-
return fmt.Errorf("failed to create temporary directory: %w", err)
92-
}
93-
defer os.RemoveAll(tempDir)
103+
branch, version, err := GetImageTagValues(tagFormat, handler)
104+
if err != nil {
105+
return err
106+
}
94107

95-
tarPath := path.Join(tempDir, "req.tar")
108+
imageName := schema.BuildImageName(tagFormat, image, version, branch)
96109

97-
buildConfig := builder.BuildConfig{
98-
Image: imageName,
99-
BuildArgs: buildArgMap,
100-
}
110+
buildOptPackages, err := getBuildOptionPackages(buildOptions, language, langTemplate.BuildOptions)
111+
if err != nil {
112+
return err
101113

102-
// Prepare a tar archive that contains the build config and build context.
103-
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
104-
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
105-
}
114+
}
115+
buildArgMap = appendAdditionalPackages(buildArgMap, buildOptPackages)
106116

107-
// Get the HMAC secret used for payload authentication with the builder API.
108-
payloadSecret, err := os.ReadFile(payloadSecretPath)
109-
if err != nil {
110-
return fmt.Errorf("failed to read payload secret: %w", err)
111-
}
112-
payloadSecret = bytes.TrimSpace(payloadSecret)
117+
fmt.Printf("Building: %s with %s template. Please wait..\n", imageName, language)
113118

114-
// Initialize a new builder client.
115-
u, _ := url.Parse(remoteBuilder)
116-
builderURL := &url.URL{
117-
Scheme: u.Scheme,
118-
Host: u.Host,
119-
}
120-
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
119+
if remoteBuilder != "" {
120+
tempDir, err := os.MkdirTemp(os.TempDir(), "openfaas-build-*")
121+
if err != nil {
122+
return fmt.Errorf("failed to create temporary directory: %w", err)
123+
}
124+
defer os.RemoveAll(tempDir)
121125

122-
stream, err := b.BuildWithStream(tarPath)
123-
if err != nil {
124-
return fmt.Errorf("failed to invoke builder: %w", err)
125-
}
126-
defer stream.Close()
126+
tarPath := path.Join(tempDir, "req.tar")
127127

128-
for result := range stream.Results() {
129-
if !quietBuild {
130-
for _, logMsg := range result.Log {
131-
fmt.Printf("%s\n", logMsg)
132-
}
133-
}
128+
buildConfig := builder.BuildConfig{
129+
Image: imageName,
130+
BuildArgs: buildArgMap,
131+
}
132+
133+
// Prepare a tar archive that contains the build config and build context.
134+
if err := builder.MakeTar(tarPath, path.Join("build", functionName), &buildConfig); err != nil {
135+
return fmt.Errorf("failed to create tar file for %s, error: %w", functionName, err)
136+
}
137+
138+
// Get the HMAC secret used for payload authentication with the builder API.
139+
payloadSecret, err := os.ReadFile(payloadSecretPath)
140+
if err != nil {
141+
return fmt.Errorf("failed to read payload secret: %w", err)
142+
}
143+
payloadSecret = bytes.TrimSpace(payloadSecret)
144+
145+
// Initialize a new builder client.
146+
u, _ := url.Parse(remoteBuilder)
147+
builderURL := &url.URL{
148+
Scheme: u.Scheme,
149+
Host: u.Host,
150+
}
151+
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
134152

135-
switch result.Status {
136-
case builder.BuildSuccess:
137-
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
138-
case builder.BuildFailed:
139-
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
153+
stream, err := b.BuildWithStream(tarPath)
154+
if err != nil {
155+
return fmt.Errorf("failed to invoke builder: %w", err)
156+
}
157+
defer stream.Close()
158+
159+
for result := range stream.Results() {
160+
if !quietBuild {
161+
for _, logMsg := range result.Log {
162+
fmt.Printf("%s\n", logMsg)
140163
}
141164
}
142165

143-
} else {
144-
dockerBuildVal := dockerBuild{
145-
Image: imageName,
146-
NoCache: nocache,
147-
Squash: squash,
148-
HTTPProxy: os.Getenv("http_proxy"),
149-
HTTPSProxy: os.Getenv("https_proxy"),
150-
BuildArgMap: buildArgMap,
151-
BuildLabelMap: buildLabelMap,
152-
ForcePull: forcePull,
166+
switch result.Status {
167+
case builder.BuildSuccess:
168+
log.Printf("%s success building and pushing image: %s", functionName, result.Image)
169+
case builder.BuildFailed:
170+
return fmt.Errorf("%s failure while building or pushing image %s: %s", functionName, imageName, result.Error)
153171
}
172+
}
154173

155-
command, args := getDockerBuildCommand(dockerBuildVal)
174+
} else {
175+
dockerBuildVal := dockerBuild{
176+
Image: imageName,
177+
NoCache: nocache,
178+
Squash: squash,
179+
HTTPProxy: os.Getenv("http_proxy"),
180+
HTTPSProxy: os.Getenv("https_proxy"),
181+
BuildArgMap: buildArgMap,
182+
BuildLabelMap: buildLabelMap,
183+
ForcePull: forcePull,
184+
}
156185

157-
envs := os.Environ()
158-
if mountSSH {
159-
envs = append(envs, "DOCKER_BUILDKIT=1")
160-
}
161-
log.Printf("Build flags: %+v\n", args)
162-
163-
task := v2execute.ExecTask{
164-
Cwd: buildContext,
165-
Command: command,
166-
Args: args,
167-
StreamStdio: !quietBuild,
168-
Env: envs,
169-
}
186+
command, args := getDockerBuildCommand(dockerBuildVal)
170187

171-
res, err := task.Execute(context.TODO())
188+
envs := os.Environ()
189+
if mountSSH {
190+
envs = append(envs, "DOCKER_BUILDKIT=1")
191+
}
192+
log.Printf("Build flags: %+v\n", args)
193+
194+
task := v2execute.ExecTask{
195+
Cwd: buildContext,
196+
Command: command,
197+
Args: args,
198+
StreamStdio: !quietBuild,
199+
Env: envs,
200+
}
172201

173-
if err != nil {
174-
return err
175-
}
202+
res, err := task.Execute(context.TODO())
176203

177-
if res.ExitCode != 0 {
178-
return fmt.Errorf("[%s] received non-zero exit code from build, error: %s", functionName, res.Stderr)
179-
}
204+
if err != nil {
205+
return err
206+
}
180207

181-
fmt.Printf("Image: %s built.\n", imageName)
208+
if res.ExitCode != 0 {
209+
return fmt.Errorf("[%s] received non-zero exit code from build, error: %s", functionName, res.Stderr)
182210
}
183-
} else {
184-
return fmt.Errorf("language template: %s not supported, build a custom Dockerfile", language)
211+
212+
fmt.Printf("Image: %s built.\n", imageName)
185213
}
186214

187215
return nil

builder/copy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ func copyDir(src, dest string) error {
3333
return fmt.Errorf("error reading dest stats: %s", err.Error())
3434
}
3535

36-
if err := os.MkdirAll(dest, info.Mode()); err != nil {
37-
return fmt.Errorf("error creating path: %s - %s", dest, err.Error())
36+
if err := os.MkdirAll(dest, info.Mode()); err != nil && !os.IsExist(err) {
37+
return fmt.Errorf("error creating directory: %s - %w", dest, err)
3838
}
3939

4040
infos, err := ioutil.ReadDir(src)

0 commit comments

Comments
 (0)