mirror of https://github.com/buildpacks/pack.git
332 lines
7.8 KiB
Go
332 lines
7.8 KiB
Go
//go:build acceptance
|
|
|
|
package invoke
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/Masterminds/semver"
|
|
|
|
acceptanceOS "github.com/buildpacks/pack/acceptance/os"
|
|
h "github.com/buildpacks/pack/testhelpers"
|
|
)
|
|
|
|
type PackInvoker struct {
|
|
testObject *testing.T
|
|
assert h.AssertionManager
|
|
path string
|
|
home string
|
|
dockerConfigDir string
|
|
fixtureManager PackFixtureManager
|
|
verbose bool
|
|
}
|
|
|
|
type packPathsProvider interface {
|
|
Path() string
|
|
FixturePaths() []string
|
|
}
|
|
|
|
func NewPackInvoker(
|
|
testObject *testing.T,
|
|
assert h.AssertionManager,
|
|
packAssets packPathsProvider,
|
|
dockerConfigDir string,
|
|
) *PackInvoker {
|
|
|
|
testObject.Helper()
|
|
|
|
home, err := os.MkdirTemp("", "buildpack.pack.home.")
|
|
if err != nil {
|
|
testObject.Fatalf("couldn't create home folder for pack: %s", err)
|
|
}
|
|
|
|
return &PackInvoker{
|
|
testObject: testObject,
|
|
assert: assert,
|
|
path: packAssets.Path(),
|
|
home: home,
|
|
dockerConfigDir: dockerConfigDir,
|
|
verbose: true,
|
|
fixtureManager: PackFixtureManager{
|
|
testObject: testObject,
|
|
assert: assert,
|
|
locations: packAssets.FixturePaths(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (i *PackInvoker) Cleanup() {
|
|
if i == nil {
|
|
return
|
|
}
|
|
i.testObject.Helper()
|
|
|
|
err := os.RemoveAll(i.home)
|
|
i.assert.Nil(err)
|
|
}
|
|
|
|
func (i *PackInvoker) cmd(name string, args ...string) *exec.Cmd {
|
|
i.testObject.Helper()
|
|
|
|
cmdArgs := append([]string{name}, args...)
|
|
cmdArgs = append(cmdArgs, "--no-color")
|
|
if i.verbose {
|
|
cmdArgs = append(cmdArgs, "--verbose")
|
|
}
|
|
|
|
cmd := i.baseCmd(cmdArgs...)
|
|
|
|
cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+i.dockerConfigDir)
|
|
if i.home != "" {
|
|
cmd.Env = append(cmd.Env, "PACK_HOME="+i.home)
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func (i *PackInvoker) baseCmd(parts ...string) *exec.Cmd {
|
|
return exec.Command(i.path, parts...)
|
|
}
|
|
|
|
func (i *PackInvoker) Run(name string, args ...string) (string, error) {
|
|
i.testObject.Helper()
|
|
|
|
output, err := i.cmd(name, args...).CombinedOutput()
|
|
|
|
return string(output), err
|
|
}
|
|
|
|
func (i *PackInvoker) SetVerbose(verbose bool) {
|
|
i.verbose = verbose
|
|
}
|
|
|
|
func (i *PackInvoker) RunSuccessfully(name string, args ...string) string {
|
|
i.testObject.Helper()
|
|
|
|
output, err := i.Run(name, args...)
|
|
i.assert.NilWithMessage(err, output)
|
|
|
|
return output
|
|
}
|
|
|
|
func (i *PackInvoker) JustRunSuccessfully(name string, args ...string) {
|
|
i.testObject.Helper()
|
|
|
|
_ = i.RunSuccessfully(name, args...)
|
|
}
|
|
|
|
func (i *PackInvoker) StartWithWriter(combinedOutput *bytes.Buffer, name string, args ...string) *InterruptCmd {
|
|
cmd := i.cmd(name, args...)
|
|
cmd.Stderr = combinedOutput
|
|
cmd.Stdout = combinedOutput
|
|
|
|
err := cmd.Start()
|
|
i.assert.Nil(err)
|
|
|
|
return &InterruptCmd{
|
|
testObject: i.testObject,
|
|
assert: i.assert,
|
|
cmd: cmd,
|
|
combinedOutput: combinedOutput,
|
|
}
|
|
}
|
|
|
|
func (i *PackInvoker) Home() string {
|
|
return i.home
|
|
}
|
|
|
|
type InterruptCmd struct {
|
|
testObject *testing.T
|
|
assert h.AssertionManager
|
|
cmd *exec.Cmd
|
|
combinedOutput *bytes.Buffer
|
|
outputMux sync.Mutex
|
|
}
|
|
|
|
func (c *InterruptCmd) TerminateAtStep(pattern string) {
|
|
c.testObject.Helper()
|
|
|
|
for {
|
|
c.outputMux.Lock()
|
|
if strings.Contains(c.combinedOutput.String(), pattern) {
|
|
err := c.cmd.Process.Signal(acceptanceOS.InterruptSignal)
|
|
c.assert.Nil(err)
|
|
h.AssertNil(c.testObject, err)
|
|
return
|
|
}
|
|
c.outputMux.Unlock()
|
|
}
|
|
}
|
|
|
|
func (c *InterruptCmd) Wait() error {
|
|
return c.cmd.Wait()
|
|
}
|
|
|
|
func (i *PackInvoker) Version() string {
|
|
i.testObject.Helper()
|
|
return strings.TrimSpace(i.RunSuccessfully("version"))
|
|
}
|
|
|
|
func (i *PackInvoker) SanitizedVersion() string {
|
|
i.testObject.Helper()
|
|
// Sanitizing any git commit sha and build number from the version output
|
|
re := regexp.MustCompile(`\d+\.\d+\.\d+`)
|
|
return re.FindString(strings.TrimSpace(i.RunSuccessfully("version")))
|
|
}
|
|
|
|
func (i *PackInvoker) EnableExperimental() {
|
|
i.testObject.Helper()
|
|
|
|
i.JustRunSuccessfully("config", "experimental", "true")
|
|
}
|
|
|
|
// Supports returns whether or not the executor's pack binary supports a
|
|
// given command string. The command string can take one of four forms:
|
|
// - "<command>" (e.g. "create-builder")
|
|
// - "<flag>" (e.g. "--verbose")
|
|
// - "<command> <flag>" (e.g. "build --network")
|
|
// - "<command>... <flag>" (e.g. "config trusted-builder --network")
|
|
//
|
|
// Any other form may return false.
|
|
func (i *PackInvoker) Supports(command string) bool {
|
|
i.testObject.Helper()
|
|
|
|
parts := strings.Split(command, " ")
|
|
|
|
var cmdParts = []string{"help"}
|
|
var search string
|
|
|
|
if len(parts) > 1 {
|
|
last := len(parts) - 1
|
|
cmdParts = append(cmdParts, parts[:last]...)
|
|
search = parts[last]
|
|
} else {
|
|
cmdParts = append(cmdParts, command)
|
|
search = command
|
|
}
|
|
|
|
re := regexp.MustCompile(fmt.Sprint(`\b%s\b`, search))
|
|
output, err := i.baseCmd(cmdParts...).CombinedOutput()
|
|
i.assert.Nil(err)
|
|
|
|
// FIXME: this doesn't appear to be working as expected,
|
|
// as tests against "build --creation-time" and "build --cache" are returning unsupported
|
|
// even on the latest version of pack.
|
|
return re.MatchString(string(output)) && !strings.Contains(string(output), "Unknown help topic")
|
|
}
|
|
|
|
type Feature int
|
|
|
|
const (
|
|
CreationTime = iota
|
|
Cache
|
|
BuildImageExtensions
|
|
RunImageExtensions
|
|
StackValidation
|
|
ForceRebase
|
|
BuildpackFlatten
|
|
MetaBuildpackFolder
|
|
PlatformRetries
|
|
FlattenBuilderCreationV2
|
|
FixesRunImageMetadata
|
|
ManifestCommands
|
|
PlatformOption
|
|
MultiPlatformBuildersAndBuildPackages
|
|
StackWarning
|
|
)
|
|
|
|
var featureTests = map[Feature]func(i *PackInvoker) bool{
|
|
CreationTime: func(i *PackInvoker) bool {
|
|
return i.Supports("build --creation-time")
|
|
},
|
|
Cache: func(i *PackInvoker) bool {
|
|
return i.Supports("build --cache")
|
|
},
|
|
BuildImageExtensions: func(i *PackInvoker) bool {
|
|
return i.laterThan("v0.27.0")
|
|
},
|
|
RunImageExtensions: func(i *PackInvoker) bool {
|
|
return i.laterThan("v0.29.0")
|
|
},
|
|
StackValidation: func(i *PackInvoker) bool {
|
|
return !i.atLeast("v0.30.0")
|
|
},
|
|
ForceRebase: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.30.0")
|
|
},
|
|
BuildpackFlatten: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.30.0")
|
|
},
|
|
MetaBuildpackFolder: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.30.0")
|
|
},
|
|
PlatformRetries: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.32.1")
|
|
},
|
|
FlattenBuilderCreationV2: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.33.1")
|
|
},
|
|
FixesRunImageMetadata: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.34.0")
|
|
},
|
|
ManifestCommands: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.34.0")
|
|
},
|
|
PlatformOption: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.34.0")
|
|
},
|
|
MultiPlatformBuildersAndBuildPackages: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.34.0")
|
|
},
|
|
StackWarning: func(i *PackInvoker) bool {
|
|
return i.atLeast("v0.37.0")
|
|
},
|
|
}
|
|
|
|
func (i *PackInvoker) SupportsFeature(f Feature) bool {
|
|
return featureTests[f](i)
|
|
}
|
|
|
|
func (i *PackInvoker) semanticVersion() *semver.Version {
|
|
version := i.Version()
|
|
semanticVersion, err := semver.NewVersion(strings.TrimPrefix(strings.Split(version, " ")[0], "v"))
|
|
i.assert.Nil(err)
|
|
|
|
return semanticVersion
|
|
}
|
|
|
|
// laterThan returns true if pack version is older than the provided version
|
|
func (i *PackInvoker) laterThan(version string) bool {
|
|
providedVersion := semver.MustParse(version)
|
|
ver := i.semanticVersion()
|
|
return ver.Compare(providedVersion) > 0 || ver.Equal(semver.MustParse("0.0.0"))
|
|
}
|
|
|
|
// atLeast returns true if pack version is the same or older than the provided version
|
|
func (i *PackInvoker) atLeast(version string) bool {
|
|
minimalVersion := semver.MustParse(version)
|
|
ver := i.semanticVersion()
|
|
return ver.Equal(minimalVersion) || ver.GreaterThan(minimalVersion) || ver.Equal(semver.MustParse("0.0.0"))
|
|
}
|
|
|
|
func (i *PackInvoker) ConfigFileContents() string {
|
|
i.testObject.Helper()
|
|
|
|
contents, err := os.ReadFile(filepath.Join(i.home, "config.toml"))
|
|
i.assert.Nil(err)
|
|
|
|
return string(contents)
|
|
}
|
|
|
|
func (i *PackInvoker) FixtureManager() PackFixtureManager {
|
|
return i.fixtureManager
|
|
}
|