Skip to content

Commit 29d9f06

Browse files
committed
Add stack package from faas-cli
The stack package can be used to parse/load stack.yaml files from your own utilities - i.e. within build pipelines. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
1 parent dcc0aac commit 29d9f06

39 files changed

+14084
-0
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ require github.com/google/go-cmp v0.7.0
99
require (
1010
github.com/alexellis/go-execute v0.6.0
1111
github.com/alexellis/hmac/v2 v2.0.0
12+
github.com/drone/envsubst v1.0.3
13+
github.com/ryanuber/go-glob v1.0.0
14+
gopkg.in/yaml.v3 v3.0.1
1215
)

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,39 @@ github.com/alexellis/go-execute v0.6.0 h1:FVGoudJnWSObwf9qmehbvVuvhK6g1UpKOCBjS+
22
github.com/alexellis/go-execute v0.6.0/go.mod h1:nlg2F6XdYydUm1xXQMMiuibQCV1mveybBkNWfdNznjk=
33
github.com/alexellis/hmac/v2 v2.0.0 h1:/sH/UJxDXPpJorUeg2DudeKSeUrWPF32Yamw2TiDoOQ=
44
github.com/alexellis/hmac/v2 v2.0.0/go.mod h1:O7hZZgTfh5fp5+vAamzodZPlbw+aQK+nnrrJNHsEvL0=
5+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
56
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
7+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
68
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
9+
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
10+
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
11+
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
712
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
813
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
14+
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
915
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
16+
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
1017
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
18+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
1119
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
1220
github.com/openfaas/faas-provider v0.25.8 h1:3W1ZyhUvpqTOiNQoV7jzdZhDJeJJlJelosAtjFzMNyw=
1321
github.com/openfaas/faas-provider v0.25.8/go.mod h1:rMXbj+AYVpn82UoHIOgWHiDeV118t0bSxyoC9d00jpc=
22+
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
1423
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
24+
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
1525
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
26+
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
1627
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
28+
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
1729
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
30+
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
31+
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
1832
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
1933
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
34+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
2035
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
36+
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
2137
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
38+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
39+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
40+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

stack/language_template.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) OpenFaaS Ltd 2025. All rights reserved.
2+
// Copyright (c) Alex Ellis 2017. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
5+
package stack
6+
7+
import (
8+
"fmt"
9+
"net/url"
10+
"os"
11+
"strings"
12+
13+
yaml "gopkg.in/yaml.v3"
14+
)
15+
16+
func ParseYAMLForLanguageTemplate(file string) (*LanguageTemplate, error) {
17+
var err error
18+
var fileData []byte
19+
20+
urlParsed, err := url.Parse(file)
21+
if err == nil && len(urlParsed.Scheme) > 0 {
22+
fmt.Println("Parsed: " + urlParsed.String())
23+
fileData, err = fetchYAML(urlParsed)
24+
if err != nil {
25+
return nil, err
26+
}
27+
} else {
28+
fileData, err = os.ReadFile(file)
29+
if err != nil {
30+
return nil, err
31+
}
32+
}
33+
34+
return ParseYAMLDataForLanguageTemplate(fileData)
35+
}
36+
37+
// ParseYAMLDataForLanguageTemplate parses YAML data into language template
38+
func ParseYAMLDataForLanguageTemplate(fileData []byte) (*LanguageTemplate, error) {
39+
var langTemplate LanguageTemplate
40+
var err error
41+
42+
err = yaml.Unmarshal(fileData, &langTemplate)
43+
if err != nil {
44+
fmt.Printf("Error with YAML file\n")
45+
return nil, err
46+
}
47+
48+
return &langTemplate, err
49+
}
50+
51+
func IsValidTemplate(lang string) bool {
52+
var found bool
53+
54+
lang = strings.ToLower(lang)
55+
56+
if _, err := os.Stat("./template/" + lang); err == nil {
57+
templateYAMLPath := "./template/" + lang + "/template.yml"
58+
59+
if _, err := ParseYAMLForLanguageTemplate(templateYAMLPath); err == nil {
60+
found = true
61+
}
62+
}
63+
64+
return found
65+
}
66+
67+
// LoadLanguageTemplate loads language template details from template.yml file.
68+
func LoadLanguageTemplate(lang string) (*LanguageTemplate, error) {
69+
lang = strings.ToLower(lang)
70+
_, err := os.Stat("./template/" + lang)
71+
72+
if err == nil {
73+
templateYAMLPath := "./template/" + lang + "/template.yml"
74+
languageTemplate, err := ParseYAMLForLanguageTemplate(templateYAMLPath)
75+
return languageTemplate, err
76+
}
77+
return nil, err
78+
}

stack/language_template_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) OpenFaaS Ltd 2025. All rights reserved.
2+
// Copyright (c) Alex Ellis 2017. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
package stack
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"reflect"
10+
"testing"
11+
)
12+
13+
func Test_ParseYAMLDataForLanguageTemplate(t *testing.T) {
14+
langTemplateTest := []struct {
15+
Input string
16+
Expected *LanguageTemplate
17+
}{
18+
{
19+
`
20+
language: python
21+
fprocess: python index.py
22+
`,
23+
&LanguageTemplate{
24+
Language: "python",
25+
FProcess: "python index.py",
26+
},
27+
},
28+
{
29+
`
30+
language: python
31+
`,
32+
&LanguageTemplate{
33+
Language: "python",
34+
},
35+
},
36+
{
37+
`
38+
fprocess: python index.py
39+
`,
40+
&LanguageTemplate{
41+
FProcess: "python index.py",
42+
},
43+
},
44+
}
45+
46+
for k, i := range langTemplateTest {
47+
t.Run(fmt.Sprintf("%d", k), func(t *testing.T) {
48+
if actual, err := ParseYAMLDataForLanguageTemplate([]byte(i.Input)); err != nil {
49+
t.Errorf("test failed, %s", err)
50+
} else {
51+
if !reflect.DeepEqual(actual, i.Expected) {
52+
t.Errorf("does not match expected result;\n parsedYAML: [%+v]\n expected: [%+v]",
53+
actual,
54+
i.Expected,
55+
)
56+
}
57+
}
58+
})
59+
}
60+
}
61+
62+
func Test_IsValidTemplate(t *testing.T) {
63+
if IsValidTemplate("unknown-language") {
64+
t.Fatalf("unknown-language must be invalid")
65+
}
66+
67+
os.MkdirAll("template/python", 0600)
68+
defer func() {
69+
os.RemoveAll("template")
70+
}()
71+
if IsValidTemplate("python") {
72+
t.Fatalf("python must is not valid because it does not contain template.yml")
73+
}
74+
}

stack/schema.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) OpenFaaS Ltd 2025. All rights reserved.
2+
// // Copyright (c) Alex Ellis 2017. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
5+
package stack
6+
7+
// Provider for the FaaS set of functions.
8+
type Provider struct {
9+
Name string `yaml:"name"`
10+
GatewayURL string `yaml:"gateway"`
11+
}
12+
13+
// Function as deployed or built on FaaS
14+
type Function struct {
15+
// Name of deployed function
16+
Name string `yaml:"-"`
17+
18+
Language string `yaml:"lang"`
19+
20+
// Handler Local folder to use for function
21+
Handler string `yaml:"handler"`
22+
23+
// Image Docker image name
24+
Image string `yaml:"image"`
25+
26+
FProcess string `yaml:"fprocess"`
27+
28+
Environment map[string]string `yaml:"environment"`
29+
30+
// Secrets list of secrets to be made available to function
31+
Secrets []string `yaml:"secrets,omitempty"`
32+
33+
SkipBuild bool `yaml:"skip_build,omitempty"`
34+
35+
Constraints *[]string `yaml:"constraints,omitempty"`
36+
37+
// EnvironmentFile is a list of files to import and override environmental variables.
38+
// These are overriden in order.
39+
EnvironmentFile []string `yaml:"environment_file,omitempty"`
40+
41+
Labels *map[string]string `yaml:"labels,omitempty"`
42+
43+
// Limits for function
44+
Limits *FunctionResources `yaml:"limits,omitempty"`
45+
46+
// Requests of resources requested by function
47+
Requests *FunctionResources `yaml:"requests,omitempty"`
48+
49+
// ReadOnlyRootFilesystem is used to set the container filesystem to read-only
50+
ReadOnlyRootFilesystem bool `yaml:"readonly_root_filesystem,omitempty"`
51+
52+
// BuildOptions to determine native packages
53+
BuildOptions []string `yaml:"build_options,omitempty"`
54+
55+
// Annotations
56+
Annotations *map[string]string `yaml:"annotations,omitempty"`
57+
58+
// Namespace of the function
59+
Namespace string `yaml:"namespace,omitempty"`
60+
61+
// BuildArgs for providing build-args
62+
BuildArgs map[string]string `yaml:"build_args,omitempty"`
63+
64+
// Platforms for use with buildx and faas-cli publish
65+
Platforms string `yaml:"platforms,omitempty"`
66+
67+
// BuildSecrets is a set of secrets to mount with buildkit
68+
BuildSecrets map[string]string `yaml:"build_secrets,omitempty"`
69+
}
70+
71+
// Configuration for the stack.yaml file
72+
type Configuration struct {
73+
StackConfig StackConfiguration `yaml:"configuration"`
74+
}
75+
76+
// StackConfiguration for the overall stack.yaml
77+
type StackConfiguration struct {
78+
TemplateConfigs []TemplateSource `yaml:"templates"`
79+
80+
// CopyExtraPaths specifies additional paths (relative to the stack file) that will be copied
81+
// into the functions build context, e.g. specifying `"common"` will look for and copy the
82+
// "common/" folder of file in the same root as the stack file. All paths must be contained
83+
// within the project root defined by the location of the stack file.
84+
//
85+
// The yaml uses the shorter name `copy` to make it easier for developers to read and use
86+
CopyExtraPaths []string `yaml:"copy"`
87+
}
88+
89+
// TemplateSource for build templates
90+
type TemplateSource struct {
91+
Name string `yaml:"name"`
92+
Source string `yaml:"source,omitempty"`
93+
}
94+
95+
// FunctionResources Memory and CPU
96+
type FunctionResources struct {
97+
Memory string `yaml:"memory"`
98+
CPU string `yaml:"cpu"`
99+
}
100+
101+
// EnvironmentFile represents external file for environment data
102+
type EnvironmentFile struct {
103+
Environment map[string]string `yaml:"environment"`
104+
}
105+
106+
// Services root level YAML file to define FaaS function-set
107+
type Services struct {
108+
Version string `yaml:"version,omitempty"`
109+
Functions map[string]Function `yaml:"functions,omitempty"`
110+
Provider Provider `yaml:"provider,omitempty"`
111+
StackConfiguration StackConfiguration `yaml:"configuration,omitempty"`
112+
}
113+
114+
// LanguageTemplate read from template.yml within root of a language template folder
115+
type LanguageTemplate struct {
116+
Language string `yaml:"language,omitempty"`
117+
FProcess string `yaml:"fprocess,omitempty"`
118+
119+
BuildOptions []BuildOption `yaml:"build_options,omitempty"`
120+
121+
// WelcomeMessage is printed to the user after generating a function
122+
WelcomeMessage string `yaml:"welcome_message,omitempty"`
123+
124+
// HandlerFolder to copy the function code into
125+
HandlerFolder string `yaml:"handler_folder,omitempty"`
126+
127+
MountSSH bool `yaml:"mount_ssh,omitempty"`
128+
}
129+
130+
// BuildOption a named build option for one or more packages
131+
type BuildOption struct {
132+
Name string `yaml:"name"`
133+
Packages []string `yaml:"packages"`
134+
}

0 commit comments

Comments
 (0)