Skip to content

Commit

Permalink
✨ Add Version variable (slsa-framework#225)
Browse files Browse the repository at this point in the history
* updates

* updates

* updates

* updates

* updates

* updates

* updates
  • Loading branch information
laurentsimon authored Jun 10, 2022
1 parent 4950c68 commit 7dda3af
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 31 deletions.
2 changes: 1 addition & 1 deletion internal/builders/go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ If you are already using Goreleaser, you may be able to migrate to our builder u
In the meantime, you can use both Goreleaser and this builder in the same repository. For example, you can pick one build you would like to start generating provenance for. Goreleaser and this builder can co-exist without interfering with one another, so long as they build fr different OS/Arch. We think gradual adoption is good for project to get used to SLSA.
The configuration file accepts many of the common fields Goreleaser uses, as you can see in the [example](#configuration-file). The configuration file also supports two variables: `{{ .Os }}` and `{{ .Arch }}`. If you need suppport for other variables, please [open an issue](https://github.com/slsa-framework/slsa-github-generator/issues/new).
The configuration file accepts many of the common fields Goreleaser uses, as you can see in the [example](#configuration-file). The configuration file also supports two variables: `{{ .Os }}`, `{{ .Arch }}` and `{{ .Version }}`. If you need suppport for other variables, please [open an issue](https://github.com/slsa-framework/slsa-github-generator/issues/new).

### Workflow inputs

Expand Down
84 changes: 57 additions & 27 deletions internal/builders/go/pkg/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"syscall"
)
Expand All @@ -31,6 +32,8 @@ var (
errorInvalidFilename = errors.New("invalid filename")
)

var unknownVersion = "unknown"

// See `go build help`.
// `-asmflags`, `-n`, `-mod`, `-installsuffix`, `-modfile`,
// `-workfile`, `-overlay`, `-pkgdir`, `-toolexec`, `-o`,
Expand Down Expand Up @@ -272,22 +275,30 @@ func (b *GoBuild) SetArgEnvVariables(envs string) error {
}

func (b *GoBuild) generateOutputFilename() (string, error) {
const alpha = "abcdefghijklmnopqrstuvwxyz1234567890-_"
// Note: the `.` is needed to accomodate the semantic version
// as part of the name.
const alpha = ".abcdefghijklmnopqrstuvwxyz1234567890-_"

var name string

// Replace .Os variable.
if strings.Contains(b.cfg.Binary, "{{ .Os }}") && b.cfg.Goos == "" {
return "", fmt.Errorf("%w", errorEnvVariableNameEmpty)
return "", fmt.Errorf("%w: {{ .Os }}", errorEnvVariableNameEmpty)
}
name = strings.ReplaceAll(b.cfg.Binary, "{{ .Os }}", b.cfg.Goos)

// Replace .Arch variable.
if strings.Contains(name, "{{ .Arch }}") && b.cfg.Goarch == "" {
return "", fmt.Errorf("%w", errorEnvVariableNameEmpty)
return "", fmt.Errorf("%w: {{ .Arch }}", errorEnvVariableNameEmpty)
}
name = strings.ReplaceAll(name, "{{ .Arch }}", b.cfg.Goarch)

// Resolve other variables.
if strings.Contains(name, "{{ .Version }}") {
version := getVersion()
name = strings.ReplaceAll(name, "{{ .Version }}", version)
}

for _, char := range name {
if !strings.Contains(alpha, strings.ToLower(string(char))) {
return "", fmt.Errorf("%w: found character '%c'", errorInvalidFilename, char)
Expand All @@ -297,6 +308,12 @@ func (b *GoBuild) generateOutputFilename() (string, error) {
if name == "" {
return "", fmt.Errorf("%w: filename is empty", errorInvalidFilename)
}

// Validate the path, since we allow '.' in the name.
if err := validatePath(name); err != nil {
return "", err
}

return name, nil
}

Expand Down Expand Up @@ -338,43 +355,56 @@ func isAllowedEnvVariable(name string) bool {
// TODO: maybe not needed if handled directly by go compiler.
func (b *GoBuild) generateLdflags() (string, error) {
var a []string
reVar := regexp.MustCompile(`{{ \.([A-Z][a-z]*) }}`)
reDyn := regexp.MustCompile(`{{ \.Env\.(\w+) }}`)

// Resolve variables.
for _, v := range b.cfg.Ldflags {
var res string
ss := "{{ .Env."
es := "}}"
found := false
for {
start := strings.Index(v, ss)
if start == -1 {
break
}
end := strings.Index(string(v[start+len(ss):]), es)
if end == -1 {
return "", fmt.Errorf("%w: %s", errorInvalidEnvArgument, v)
}

name := strings.Trim(string(v[start+len(ss):start+len(ss)+end]), " ")
if name == "" {
return "", fmt.Errorf("%w: %s", errorEnvVariableNameEmpty, v)
// Special variables.
names := reVar.FindAllString(v, -1)
for _, n := range names {

name := strings.ReplaceAll(n, "{{ .", "")
name = strings.ReplaceAll(name, " }}", "")

// {{ .Version }} variable.
if name != "Version" {
return "", fmt.Errorf("%w: %s", errorInvalidEnvArgument, n)
}

version := getVersion()
v = strings.ReplaceAll(v, n, version)
}

// Dynamic env variables provided by caller.
names = reDyn.FindAllString(v, -1)
for _, n := range names {

name := strings.ReplaceAll(n, "{{ .Env.", "")
name = strings.ReplaceAll(name, " }}", "")

val, exists := b.argEnv[name]
if !exists {
return "", fmt.Errorf("%w: %s", errorEnvVariableNameEmpty, name)
return "", fmt.Errorf("%w: %s", errorEnvVariableNameEmpty, n)
}
res = fmt.Sprintf("%s%s%s", res, v[:start], val)
found = true
v = v[start+len(ss)+end+len(es):]
}
if !found {
res = v
v = strings.ReplaceAll(v, n, val)
}
a = append(a, res)

a = append(a, v)
}

if len(a) > 0 {
return strings.Join(a, " "), nil
}

return "", nil
}

func getVersion() string {
version := os.Getenv("GITHUB_REF_NAME")
if version == "" {
return unknownVersion
}
return version
}
134 changes: 131 additions & 3 deletions internal/builders/go/pkg/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func Test_generateOutputFilename(t *testing.T) {
filename string
goos string
goarch string
envs string
envs map[string]string
expected struct {
err error
fn string
Expand Down Expand Up @@ -305,12 +305,89 @@ func Test_generateOutputFilename(t *testing.T) {
err: errorInvalidFilename,
},
},
{
name: "filename amd64/linux v1.2.3",
filename: "name-{{ .Os }}-{{ .Arch }}-{{ .Version }}",
goarch: "amd64",
goos: "linux",
envs: map[string]string{
"GITHUB_REF_NAME": "v1.2.3",
},
expected: struct {
err error
fn string
}{
err: nil,
fn: "name-linux-amd64-v1.2.3",
},
},
{
name: "filename twice v1.2.3",
filename: "name-{{ .Version }}-{{ .Version }}",
goarch: "amd64",
goos: "linux",
envs: map[string]string{
"GITHUB_REF_NAME": "v1.2.3",
},
expected: struct {
err error
fn string
}{
err: nil,
fn: "name-v1.2.3-v1.2.3",
},
},
{
name: "filename twice empty versions",
filename: "name-{{ .Version }}-{{ .Version }}",
goarch: "amd64",
goos: "linux",
envs: map[string]string{
"GITHUB_REF_NAME": "",
},
expected: struct {
err error
fn string
}{
err: nil,
fn: fmt.Sprintf("name-%s-%s", unknownVersion, unknownVersion),
},
},
{
name: "invalid name with version",
filename: "name-{{ .Version }}/../bla",
goarch: "amd64",
goos: "linux",
envs: map[string]string{
"GITHUB_REF_NAME": "v1.2.3",
},
expected: struct {
err error
fn string
}{
err: errorInvalidFilename,
},
},
{
name: "filename twice unset versions",
filename: "name-{{ .Version }}-{{ .Version }}",
goarch: "amd64",
goos: "linux",
expected: struct {
err error
fn string
}{
err: nil,
fn: fmt.Sprintf("name-%s-%s", unknownVersion, unknownVersion),
},
},
}

for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Note: disable parallelism to avoid env variable clobbering between tests.
// t.Parallel()

cfg := goReleaserConfigFile{
Binary: tt.filename,
Expand All @@ -322,13 +399,25 @@ func Test_generateOutputFilename(t *testing.T) {
if err != nil {
t.Errorf("fromConfig: %v", err)
}

// Set env variables.
for k, v := range tt.envs {
os.Setenv(k, v)
}

b := GoBuildNew("go compiler", c)

fn, err := b.generateOutputFilename()
if !errCmp(err, tt.expected.err) {
t.Errorf(cmp.Diff(err, tt.expected.err))
}

// Unset env variables, so that they don't
// affect other tests.
for k := range tt.envs {
os.Unsetenv(k)
}

if err != nil {
return
}
Expand Down Expand Up @@ -588,6 +677,7 @@ func Test_generateLdflags(t *testing.T) {
name string
argEnv string
inldflags []string
githubEnv map[string]string
err error
outldflags string
}{
Expand Down Expand Up @@ -662,12 +752,38 @@ func Test_generateLdflags(t *testing.T) {
},
outldflags: "value1-name-value2 value1-name-value3 value3-name-value1 value3-name-value2",
},
{
name: "several ldflags with start/end",
argEnv: "VAR1:value1, VAR2:value2, VAR3:value3",
inldflags: []string{
"start-{{ .Env.VAR1 }}-name-{{ .Env.VAR2 }}-end",
"start-{{ .Env.VAR1 }}-name-{{ .Env.VAR3 }}-end",
"start-{{ .Env.VAR3 }}-name-{{ .Env.VAR1 }}-end",
"start-{{ .Env.VAR3 }}-name-{{ .Env.VAR2 }}-end",
},
outldflags: "start-value1-name-value2-end start-value1-name-value3-end start-value3-name-value1-end start-value3-name-value2-end",
},
{
name: "several ldflags and version",
argEnv: "VAR1:value1, VAR2:value2, VAR3:value3",
githubEnv: map[string]string{
"GITHUB_REF_NAME": "v1.2.3",
},
inldflags: []string{
"start-{{ .Env.VAR1 }}-name-{{ .Env.VAR2 }}-{{ .Version }}-end",
"{{ .Env.VAR1 }}-name-{{ .Env.VAR3 }}",
"{{ .Env.VAR3 }}-name-{{ .Env.VAR1 }}-{{ .Version }}-{{ .Version }}",
"{{ .Env.VAR3 }}-name-{{ .Env.VAR2 }}-{{ .Version }}-end",
},
outldflags: "start-value1-name-value2-v1.2.3-end value1-name-value3 value3-name-value1-v1.2.3-v1.2.3 value3-name-value2-v1.2.3-end",
},
}

for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Disable to avoid env clobbering between tests.
// t.Parallel()

cfg := goReleaserConfigFile{
Version: 1,
Expand All @@ -677,6 +793,12 @@ func Test_generateLdflags(t *testing.T) {
if err != nil {
t.Errorf("fromConfig: %v", err)
}

// Set GitHub env variables.
for k, v := range tt.githubEnv {
os.Setenv(k, v)
}

b := GoBuildNew("go compiler", c)

err = b.SetArgEnvVariables(tt.argEnv)
Expand All @@ -685,6 +807,12 @@ func Test_generateLdflags(t *testing.T) {
}
ldflags, err := b.generateLdflags()

// Unset env variables, so that they don't
// affect other tests.
for k := range tt.githubEnv {
os.Unsetenv(k)
}

if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
Expand Down

0 comments on commit 7dda3af

Please sign in to comment.