mirror of https://github.com/knative/func.git
feat: function version migrations (#664)
* feat: function version migrations * unmarshall functin now part of initialization * regenerate schema * spelling errors
This commit is contained in:
parent
0eb3fef080
commit
ccf00152be
82
client.go
82
client.go
|
@ -17,16 +17,16 @@ const (
|
|||
// DefaultRegistry through which containers of Functions will be shuttled.
|
||||
DefaultRegistry = "docker.io"
|
||||
|
||||
// DefaultRuntime is the language runtime for a new Function, including
|
||||
// the template written and builder invoked on deploy.
|
||||
DefaultRuntime = "node"
|
||||
|
||||
// DefaultTemplate is the default Function signature / environmental context
|
||||
// of the resultant function. All runtimes are expected to have at least
|
||||
// one implementation of each supported function signature. Currently that
|
||||
// includes an HTTP Handler ("http") and Cloud Events handler ("events")
|
||||
DefaultTemplate = "http"
|
||||
|
||||
// DefaultVersion is the initial value for string members whose implicit type
|
||||
// is a semver.
|
||||
DefaultVersion = "0.0.0"
|
||||
|
||||
// The name of the config directory within ~/.config (or configured location)
|
||||
configDirName = "func"
|
||||
)
|
||||
|
@ -386,9 +386,8 @@ func (c *Client) New(ctx context.Context, cfg Function) (err error) {
|
|||
c.progressListener.Stopping()
|
||||
}()
|
||||
|
||||
// Create local template
|
||||
err = c.Create(cfg)
|
||||
if err != nil {
|
||||
// Create Function at path indidcated by Config
|
||||
if err = c.Create(cfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -428,50 +427,63 @@ func (c *Client) New(ctx context.Context, cfg Function) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Create a new Function project locally using the settings provided on a
|
||||
// Function object.
|
||||
// Create a new Function from the given defaults.
|
||||
// <path> will default to the absolute path of the current working directory.
|
||||
// <name> will default to the current working directory.
|
||||
// When <name> is provided but <path> is not, a directory <name> is created
|
||||
// in the current working directory and used for <path>.
|
||||
func (c *Client) Create(cfg Function) (err error) {
|
||||
// convert Root path to absolute
|
||||
cfg.Root, err = filepath.Abs(cfg.Root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create project root directory, if it doesn't already exist
|
||||
if err = os.MkdirAll(cfg.Root, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Root must not already be a Function
|
||||
//
|
||||
// Instantiate a Function struct about the given root path, but
|
||||
// immediately exit with error (prior to actual creation) if this is
|
||||
// a Function already initialized at that path (Create should never
|
||||
// clobber a pre-existing Function)
|
||||
f, err := NewFunctionFromDefaults(cfg)
|
||||
// Create should never clobber a pre-existing Function
|
||||
hasFunc, err := hasInitializedFunction(cfg.Root)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
if f.Initialized() {
|
||||
err = fmt.Errorf("Function at '%v' already initialized", f.Root)
|
||||
return
|
||||
if hasFunc {
|
||||
return fmt.Errorf("Function at '%v' already initialized", cfg.Root)
|
||||
}
|
||||
|
||||
// Root must not contain any visible files
|
||||
//
|
||||
// We know from above that the target directory does not contain a Function,
|
||||
// but also immediately exit if the target directoy contains any visible files
|
||||
// at all, or any of the known hidden files that will be written.
|
||||
// This is to ensure that if a user inadvertently chooses an incorrect directory
|
||||
// for their new Function, the template and config file writing steps do not
|
||||
// cause data loss.
|
||||
if err = assertEmptyRoot(f.Root); err != nil {
|
||||
return
|
||||
// The path for the new Function should not have any contentious files
|
||||
// (hidden files OK, unless it's one used by Func)
|
||||
if err := assertEmptyRoot(cfg.Root); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write out the template for a Function
|
||||
// returns a Function which may be mutated based on the content of
|
||||
// the template (default Function, builders, buildpacks, etc).
|
||||
// Path is defaulted to the current working directory
|
||||
if cfg.Root == "" {
|
||||
if cfg.Root, err = os.Getwd(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Name is defaulted to the directory of the given path.
|
||||
if cfg.Name == "" {
|
||||
cfg.Name = nameFromPath(cfg.Root)
|
||||
}
|
||||
|
||||
// Create a new Function
|
||||
f := NewFunctionWith(cfg)
|
||||
|
||||
// Write out the new Function's Template files.
|
||||
// Templates contain values which may result in the Function being mutated
|
||||
// (default builders, etc), so a new (potentially mutated) Function is
|
||||
// returned from Templates.Write
|
||||
f, err = c.Templates().Write(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Mark it as having been created via this client library and Write (save)
|
||||
// Mark the Function as having been created
|
||||
f.Created = time.Now()
|
||||
if err = f.Write(); err != nil {
|
||||
return
|
||||
|
@ -502,7 +514,7 @@ func (c *Client) Build(ctx context.Context, path string) (err error) {
|
|||
"Don't give up on me",
|
||||
"This is taking a while",
|
||||
"Still building"}
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
go func() {
|
||||
for {
|
||||
|
|
149
client_test.go
149
client_test.go
|
@ -40,11 +40,55 @@ func TestNew(t *testing.T) {
|
|||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry), fn.WithVerbose(true))
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Root: root, Runtime: TestRuntime}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRuntimeRequired ensures that the the runtime is an expected value.
|
||||
func TestRuntimeRequired(t *testing.T) {
|
||||
// Create a root for the new Function
|
||||
root := "testdata/example.com/testRuntimeRequired"
|
||||
defer Using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Create a new function at root with all defaults.
|
||||
err := client.New(context.Background(), fn.Function{Root: root})
|
||||
if err == nil {
|
||||
t.Fatalf("did not receive error creating a function without specifying runtime")
|
||||
}
|
||||
}
|
||||
|
||||
// TestNameDefaults ensures that a newly created Function has its name defaulted
|
||||
// to a name which can be dervied from the last part of the given root path.
|
||||
func TestNameDefaults(t *testing.T) {
|
||||
root := "testdata/example.com/testNameDefaults"
|
||||
defer Using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
f := fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
// NO NAME
|
||||
Root: root,
|
||||
}
|
||||
|
||||
if err := client.New(context.Background(), f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := "testNameDefaults"
|
||||
if f.Name != expected {
|
||||
t.Fatalf("name was not defaulted. expected '%v' got '%v'", expected, f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWritesTemplate ensures the config file and files from the template
|
||||
// are written on new.
|
||||
func TestWritesTemplate(t *testing.T) {
|
||||
|
@ -53,7 +97,7 @@ func TestWritesTemplate(t *testing.T) {
|
|||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -77,7 +121,7 @@ func TestExtantAborts(t *testing.T) {
|
|||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// First .New should succeed...
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -126,37 +170,11 @@ func TestHiddenFilesIgnored(t *testing.T) {
|
|||
}
|
||||
|
||||
// Should succeed without error, ignoring the hidden file.
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultRuntime ensures that the default runtime is applied to new
|
||||
// Functions and persisted.
|
||||
func TestDefaultRuntime(t *testing.T) {
|
||||
// Create a root for the new Function
|
||||
root := "testdata/example.com/testDefaultRuntime"
|
||||
defer Using(t, root)()
|
||||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Create a new function at root with all defaults.
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Load the function
|
||||
f, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure it has defaulted runtime
|
||||
if f.Runtime != fn.DefaultRuntime {
|
||||
t.Fatal("The default runtime was not applied or persisted.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRepositoriesExtensible ensures that templates are extensible
|
||||
// using a custom path to template repositories on disk. The custom repositories
|
||||
// location is not defined herein but expected to be provided because, for
|
||||
|
@ -274,7 +292,7 @@ func TestNamed(t *testing.T) {
|
|||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root, Name: name}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root, Name: name}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -321,7 +339,7 @@ func TestDeriveImage(t *testing.T) {
|
|||
|
||||
// Create the function which calculates fields such as name and image.
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -350,7 +368,7 @@ func TestDeriveImageDefaultRegistry(t *testing.T) {
|
|||
// Rather than use TestRegistry, use a single-token name and expect
|
||||
// the DefaultRegistry to be prepended.
|
||||
client := fn.New(fn.WithRegistry("alice"))
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -397,12 +415,8 @@ func TestNewDelegates(t *testing.T) {
|
|||
// The builder should be invoked with a path to a Function project's source
|
||||
// An example image name is returned.
|
||||
builder.BuildFn = func(f fn.Function) error {
|
||||
expectedPath, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expectedPath != f.Root {
|
||||
t.Fatalf("builder expected path %v, got '%v'", expectedPath, f.Root)
|
||||
if root != f.Root {
|
||||
t.Fatalf("builder expected path %v, got '%v'", root, f.Root)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -429,7 +443,7 @@ func TestNewDelegates(t *testing.T) {
|
|||
|
||||
// Invoke the creation, triggering the Function delegates, and
|
||||
// perform follow-up assertions that the Functions were indeed invoked.
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -454,7 +468,7 @@ func TestRun(t *testing.T) {
|
|||
// Create a client with the mock runner and the new test Function
|
||||
runner := mock.NewRunner()
|
||||
client := fn.New(fn.WithRegistry(TestRegistry), fn.WithRunner(runner))
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -467,12 +481,8 @@ func TestRun(t *testing.T) {
|
|||
if !runner.RunInvoked {
|
||||
t.Fatal("run did not invoke the runner")
|
||||
}
|
||||
absRoot, err := filepath.Abs(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if runner.RootRequested != absRoot {
|
||||
t.Fatalf("expected path '%v', got '%v'", absRoot, runner.RootRequested)
|
||||
if runner.RootRequested != root {
|
||||
t.Fatalf("expected path '%v', got '%v'", root, runner.RootRequested)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,7 +509,7 @@ func TestUpdate(t *testing.T) {
|
|||
fn.WithDeployer(deployer))
|
||||
|
||||
// create the new Function which will be updated
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -567,7 +577,7 @@ func TestRemoveByPath(t *testing.T) {
|
|||
fn.WithRegistry(TestRegistry),
|
||||
fn.WithRemover(remover))
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -603,7 +613,7 @@ func TestRemoveByName(t *testing.T) {
|
|||
fn.WithRegistry(TestRegistry),
|
||||
fn.WithRemover(remover))
|
||||
|
||||
if err := client.Create(fn.Function{Root: root}); err != nil {
|
||||
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -701,7 +711,7 @@ func TestDeployUnbuilt(t *testing.T) {
|
|||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Initialize (half-create) a new Function at root
|
||||
if err := client.Create(fn.Function{Root: root}); err != nil {
|
||||
if err := client.Create(fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -746,29 +756,34 @@ func TestEmit(t *testing.T) {
|
|||
func TestWithConfiguredBuilders(t *testing.T) {
|
||||
root := "testdata/example.com/testConfiguredBuilders" // Root from which to run the test
|
||||
defer Using(t, root)()
|
||||
|
||||
builders := map[string]string{
|
||||
"custom": "docker.io/example/custom",
|
||||
}
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
if err := client.Create(fn.Function{
|
||||
Root: root,
|
||||
Builders: builders,
|
||||
}); err != nil {
|
||||
|
||||
// A Function with predefined builders
|
||||
f0 := fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
Root: root,
|
||||
Builders: map[string]string{
|
||||
"custom": "docker.io/example/custom",
|
||||
}}
|
||||
|
||||
// Create the Function, which should preserve custom builders
|
||||
if err := client.Create(f0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := fn.NewFunction(root)
|
||||
|
||||
// Load the Function from disk
|
||||
f1, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Assert that our custom builder array was set
|
||||
if !reflect.DeepEqual(f.Builders, builders) {
|
||||
t.Fatalf("Expected %v but got %v", builders, f.Builders)
|
||||
// Assert that our custom builders were retained
|
||||
if !reflect.DeepEqual(f0.Builders, f1.Builders) {
|
||||
t.Fatalf("Expected %v but got %v", f0.Builders, f1.Builders)
|
||||
}
|
||||
|
||||
// But that the default still exists
|
||||
if f.Builder == "" {
|
||||
// But that the default exists
|
||||
if f1.Builder == "" {
|
||||
t.Fatal("Expected default builder to be set")
|
||||
}
|
||||
}
|
||||
|
@ -786,6 +801,7 @@ func TestWithConfiguredBuildersWithDefault(t *testing.T) {
|
|||
}
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
if err := client.Create(fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
Root: root,
|
||||
Builders: builders,
|
||||
}); err != nil {
|
||||
|
@ -818,6 +834,7 @@ func TestWithConfiguredBuildpacks(t *testing.T) {
|
|||
}
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
if err := client.Create(fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
Root: root,
|
||||
Buildpacks: buildpacks,
|
||||
}); err != nil {
|
||||
|
@ -889,7 +906,7 @@ func TestCreateStamp(t *testing.T) {
|
|||
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
if err := client.New(context.Background(), fn.Function{Root: root}); err != nil {
|
||||
if err := client.New(context.Background(), fn.Function{Runtime: TestRuntime, Root: root}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
160
function.go
160
function.go
|
@ -17,6 +17,11 @@ import (
|
|||
const FunctionFile = "func.yaml"
|
||||
|
||||
type Function struct {
|
||||
// Version at which this function is known to be compatible.
|
||||
// More specifically, it is the highest migration which has been applied.
|
||||
// For details see the .Migrated() and .Migrate() methods.
|
||||
Version string // semver format
|
||||
|
||||
// Root on disk at which to find/create source and config files.
|
||||
Root string `yaml:"-"`
|
||||
|
||||
|
@ -89,35 +94,85 @@ type Function struct {
|
|||
Created time.Time
|
||||
}
|
||||
|
||||
// NewFunction loads a Function from a path on disk.
|
||||
// Errors are returned if the path is not valid, the serialized field could not
|
||||
// be accessed, or if the contents of the file could not be unmarshaled into a
|
||||
// Function. A valid path with no associated FunctionFile is not an error but
|
||||
// rather returns a Function with static defaults set, and will return false
|
||||
// from .Initialized().
|
||||
func NewFunction(root string) (f Function, err error) {
|
||||
// NewFunction is essentially a convenience/decorator over the more fully-
|
||||
// featured constructor which takes a full function object as defaults.
|
||||
return NewFunctionFromDefaults(Function{Root: root})
|
||||
// NewFunctionWith defaults as provided.
|
||||
func NewFunctionWith(defaults Function) Function {
|
||||
if defaults.Version == "" {
|
||||
defaults.Version = DefaultVersion
|
||||
}
|
||||
if defaults.Template == "" {
|
||||
defaults.Template = DefaultTemplate
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
// NewFunctionFromDefaults is equivalent to calling NewFunction, but will use
|
||||
// the provided function as defaults.
|
||||
func NewFunctionFromDefaults(f Function) (Function, error) {
|
||||
var err error
|
||||
if f.Runtime == "" {
|
||||
f.Runtime = DefaultRuntime
|
||||
// NewFunction from a given path.
|
||||
// Invalid paths, or no Function at path are errors.
|
||||
// Syntactic errors are returned immediately (yaml unmarshal errors).
|
||||
// Functions which are syntactically valid are also then logically validated.
|
||||
// Functions from earlier versions are brought up to current using migrations.
|
||||
// Migrations are run prior to validators such that validation can omit
|
||||
// concerning itself with backwards compatibility. Migrators must therefore
|
||||
// selectively consider the minimal validation necesssary to ehable migration.
|
||||
func NewFunction(path string) (f Function, err error) {
|
||||
f.Root = path // path is not persisted, as this is the purvew of the FS itself
|
||||
var filename = filepath.Join(path, FunctionFile)
|
||||
if _, err = os.Stat(filename); err != nil {
|
||||
return
|
||||
}
|
||||
if f.Template == "" {
|
||||
f.Template = DefaultTemplate
|
||||
bb, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if f.Root, err = filepath.Abs(f.Root); err != nil {
|
||||
return f, err
|
||||
if err = yaml.UnmarshalStrict(bb, &f); err != nil {
|
||||
err = formatUnmarshalError(err) // human-friendly unmarshalling errors
|
||||
return
|
||||
}
|
||||
if f, err = f.Migrate(); err != nil {
|
||||
return
|
||||
}
|
||||
return f, f.Validate()
|
||||
}
|
||||
|
||||
// Validate Function is logically correct, returning a bundled, and quite
|
||||
// verbose, formatted error detailing any issues.
|
||||
func (f Function) Validate() error {
|
||||
if f.Name == "" {
|
||||
f.Name = nameFromPath(f.Root)
|
||||
return errors.New("function name is required")
|
||||
}
|
||||
return unmarshalFunction(f)
|
||||
if f.Runtime == "" {
|
||||
return errors.New("function language runtime is required")
|
||||
}
|
||||
if f.Root == "" {
|
||||
return errors.New("function root path is required")
|
||||
}
|
||||
|
||||
var ctr int
|
||||
errs := [][]string{
|
||||
validateVolumes(f.Volumes),
|
||||
ValidateBuildEnvs(f.BuildEnvs),
|
||||
ValidateEnvs(f.Envs),
|
||||
validateOptions(f.Options),
|
||||
ValidateLabels(f.Labels),
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("'%v' contains errors:", FunctionFile))
|
||||
|
||||
for _, ee := range errs {
|
||||
if len(ee) > 0 {
|
||||
b.WriteString("\n") // Precede each group of errors with a linebreak
|
||||
}
|
||||
for _, e := range ee {
|
||||
ctr++
|
||||
b.WriteString("\t" + e)
|
||||
}
|
||||
}
|
||||
|
||||
if ctr == 0 {
|
||||
return nil // Return nil if there were no validation errors.
|
||||
}
|
||||
|
||||
return errors.New(b.String())
|
||||
}
|
||||
|
||||
// nameFromPath returns the default name for a Function derived from a path.
|
||||
|
@ -282,7 +337,7 @@ var contentiousFiles = []string{
|
|||
FunctionFile,
|
||||
}
|
||||
|
||||
// contentiousFilesIn the given directoy
|
||||
// contentiousFilesIn the given directory
|
||||
func contentiousFilesIn(dir string) (contentious []string, err error) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
for _, file := range files {
|
||||
|
@ -310,66 +365,29 @@ func isEffectivelyEmpty(dir string) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// unmarshalFunction from disk (FunctionFile) using the passed Function as
|
||||
// its defaults. If no serialized function exists at path, the Function
|
||||
// returned is equivalent to the default passed.
|
||||
func unmarshalFunction(f Function) (Function, error) {
|
||||
// returns true if the given path contains an initialized Function.
|
||||
func hasInitializedFunction(path string) (bool, error) {
|
||||
var err error
|
||||
var filename = filepath.Join(f.Root, FunctionFile)
|
||||
var filename = filepath.Join(path, FunctionFile)
|
||||
|
||||
// Return if there is no file to load, or if there is an error reading.
|
||||
if _, err = os.Stat(filename); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // missing file is not an error.
|
||||
return false, nil
|
||||
}
|
||||
return f, err
|
||||
return false, err // invalid path or access error
|
||||
}
|
||||
|
||||
// Load the file
|
||||
bb, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return f, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Unmarshal as yaml
|
||||
f := Function{}
|
||||
if err = yaml.UnmarshalStrict(bb, &f); err != nil {
|
||||
// Return immediately if there are syntactic errors.
|
||||
return f, formatUnmarshalError(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return f, validateFunction(f)
|
||||
}
|
||||
|
||||
// Validate Function is logically correct, returning a bundled, and quite
|
||||
// verbose, formatted error detailing any issues.
|
||||
func validateFunction(f Function) error {
|
||||
var ctr int
|
||||
errs := [][]string{
|
||||
validateVolumes(f.Volumes),
|
||||
ValidateBuildEnvs(f.BuildEnvs),
|
||||
ValidateEnvs(f.Envs),
|
||||
validateOptions(f.Options),
|
||||
ValidateLabels(f.Labels),
|
||||
if f, err = f.Migrate(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("'%v' contains errors:", FunctionFile))
|
||||
|
||||
for _, ee := range errs {
|
||||
if len(ee) > 0 {
|
||||
b.WriteString("\n") // Precede each group of errors with a linebreak
|
||||
}
|
||||
for _, e := range ee {
|
||||
ctr++
|
||||
b.WriteString("\t" + e)
|
||||
}
|
||||
}
|
||||
|
||||
if ctr == 0 {
|
||||
return nil // Return nil if there were no validation errors.
|
||||
}
|
||||
|
||||
return errors.New(b.String())
|
||||
return f.Initialized(), nil
|
||||
}
|
||||
|
||||
// Format yaml unmarshall error to be more human friendly.
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package function
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
// Migrate applies any necessary migrations, returning a new migrated
|
||||
// version of the Function. It is the caller's responsibility to
|
||||
// .Write() the Function to persist to disk.
|
||||
func (f Function) Migrate() (migrated Function, err error) {
|
||||
// Return immediately if the Function indicates it has already been
|
||||
// migrated.
|
||||
if f.Migrated() {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// If the version is empty, treat it as 0.0.0
|
||||
if f.Version == "" {
|
||||
f.Version = DefaultVersion
|
||||
}
|
||||
|
||||
migrated = f // initially equivalent
|
||||
for _, m := range migrations {
|
||||
// Skip this migration if the current function's version is not less than
|
||||
// the migration's applicable verion.
|
||||
if !semver.New(migrated.Version).LessThan(*semver.New(m.version)) {
|
||||
continue
|
||||
}
|
||||
// Apply this migration when the Function's version is less than that which
|
||||
// the migration will impart.
|
||||
migrated, err = m.migrate(migrated, m)
|
||||
if err != nil {
|
||||
return // fail fast on any migration errors
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// migration is a migration which should be applied to Functions whose version
|
||||
// is below that indicated.
|
||||
type migration struct {
|
||||
version string // version before which this migration may be needed.
|
||||
migrate migrator // Migrator migrates.
|
||||
}
|
||||
|
||||
// migrator is a function which returns a migrated copy of an inbound function.
|
||||
type migrator func(Function, migration) (Function, error)
|
||||
|
||||
// Migrated returns whether or not the Function has been migrated to the highest
|
||||
// level the currently executing system is aware of (or beyond).
|
||||
// returns true.
|
||||
func (f Function) Migrated() bool {
|
||||
// If the function has no Version, it is pre-migrations and is implicitly
|
||||
// not migrated.
|
||||
if f.Version == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// lastMigration is the last registered migration.
|
||||
lastMigration := semver.New(migrations[len(migrations)-1].version)
|
||||
|
||||
// Fail the migration test if the Function's version is less than
|
||||
// the latest available.
|
||||
return !semver.New(f.Version).LessThan(*lastMigration)
|
||||
}
|
||||
|
||||
// Migrations registry
|
||||
// -------------------
|
||||
|
||||
// migrations are all migrators in ascending order.
|
||||
// No two migrations may have the exact version number (introduce a patch
|
||||
// version for the migration if necessary)
|
||||
var migrations = []migration{
|
||||
{"0.19.0", migrateToCreationStamp},
|
||||
// New Migrations Here.
|
||||
}
|
||||
|
||||
// Individual Migration implementations
|
||||
// ------------------------------------
|
||||
|
||||
// migrateToCreationStamp is the initial migration which brings a Function from
|
||||
// some unknown point in the past to the point at which it is versioned,
|
||||
// migrated and includes a creation timestamp. Without this migration,
|
||||
// instantiation of old functions will fail with a "Function at path X not
|
||||
// initialized" in Func versions above v0.19.0
|
||||
//
|
||||
// This migration must be aware of the difference between a Function which
|
||||
// was previously created (but with no create stamp), and a Function which
|
||||
// exists only in memory and should legitimately fail the .Initialized() check.
|
||||
// The only way to know is to check a side-effect of earlier versions:
|
||||
// are the .Name and .Runtime fields populated. This was the way the
|
||||
// .Initialized check was implemented prior to versioning being introduced, so
|
||||
// it is equivalent logically to use this here as well.
|
||||
|
||||
// In summary: if the creation stamp is zero, but name and runtime fields are
|
||||
// populated, then this is an old Function and should be migrated to having a
|
||||
// create stamp. Otherwise, this is an in-memory (new) Function that is
|
||||
// currently in the process of being created and as such need not be mutated
|
||||
// to consider this migration having been evaluated.
|
||||
func migrateToCreationStamp(f Function, m migration) (Function, error) {
|
||||
// For functions with no creation timestamp, but appear to have been pre-
|
||||
// existing, populate their create stamp and version.
|
||||
// Yes, it's a little gnarly, but bootstrapping into the lovelieness of a
|
||||
// versioned/migrated system takes cleaning up the trash.
|
||||
if f.Created.IsZero() { // If there is no create stamp
|
||||
if f.Name != "" && f.Runtime != "" { // and it appears to be an old Function
|
||||
f.Created = time.Now() // Migrate it to having a timestamp.
|
||||
}
|
||||
}
|
||||
f.Version = m.version // Record this migration was evaluated.
|
||||
return f, nil
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package function
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
// TestMigrated ensures that the .Migrated() method returns whether or not the
|
||||
// migrations were applied based on its self-reported .Version member.
|
||||
func TestMigrated(t *testing.T) {
|
||||
// A Function with no migration stamp
|
||||
f := Function{}
|
||||
if f.Migrated() {
|
||||
t.Fatal("function with no version stamp should be not migrated.")
|
||||
}
|
||||
|
||||
// A Function with a migration stamp that is explicitly less than the
|
||||
// latest known.
|
||||
f = Function{Version: "0.0.1"}
|
||||
if f.Migrated() {
|
||||
t.Fatalf("function with version %v when latest is %v should be !migrated",
|
||||
f.Version, latestMigrationVersion())
|
||||
}
|
||||
|
||||
// A Function with a version stamp equivalent to the latest is up-to-date
|
||||
// and should be considered migrated.
|
||||
f = Function{Version: latestMigrationVersion()}
|
||||
if !f.Migrated() {
|
||||
t.Fatalf("function version %v should me considered migrated (latest is %v)",
|
||||
f.Version, latestMigrationVersion())
|
||||
}
|
||||
|
||||
// A Function with a version stamp beyond what is recognized by the current
|
||||
// codebase is considered fully migrated, for purposes of this version of func
|
||||
v0 := semver.New(latestMigrationVersion())
|
||||
v0.BumpMajor()
|
||||
f = Function{Version: v0.String()}
|
||||
if !f.Migrated() {
|
||||
t.Fatalf("Function with version %v should be considered migrated when latest known by this codebase is %v", f.Version, latestMigrationVersion())
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrate ensures that Functions have migrations apply the version
|
||||
// stamp on instantiation indicating migrations have been applied.
|
||||
func TestMigrate(t *testing.T) {
|
||||
// Load an old Function, as it an earlier version it has registered migrations
|
||||
// that will need to be applied.
|
||||
root := "testdata/migrations/v0.19.0"
|
||||
|
||||
// Instantiate the Function with the antiquated structure, which should cause
|
||||
// migrations to be applied in order, and result in a function whose version
|
||||
// compatibility is equivalent to the latest registered migration.
|
||||
f, err := NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.Version != latestMigrationVersion() {
|
||||
t.Fatalf("Function was not migrated to %v on instantiation: version is %v",
|
||||
latestMigrationVersion(), f.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrateToCreationStamp ensures that the creation timestamp migration
|
||||
// introduced for functions 0.19.0 and earlier is applied.
|
||||
func TestMigrateToCreationStamp(t *testing.T) {
|
||||
// Load a Function of version 0.19.0, which should have the migration applied
|
||||
root := "testdata/migrations/v0.19.0"
|
||||
|
||||
now := time.Now()
|
||||
f, err := NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.Created.Before(now) {
|
||||
t.Fatalf("migration not applied: expected timestamp to be now, got %v.", f.Created)
|
||||
}
|
||||
}
|
||||
|
||||
func latestMigrationVersion() string {
|
||||
return migrations[len(migrations)-1].version
|
||||
}
|
|
@ -4,21 +4,79 @@
|
|||
package function_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
fn "knative.dev/kn-plugin-func"
|
||||
. "knative.dev/kn-plugin-func/testing"
|
||||
)
|
||||
|
||||
// TestFunctionNameDefault ensures that a Function's name is defaulted to that
|
||||
// which can be derived from the last part of its path.
|
||||
func TestFunctionNameDefault(t *testing.T) {
|
||||
root := "testdata/testFunctionNameDefault"
|
||||
defer Using(t, root)()
|
||||
_, err := fn.NewFunction(root)
|
||||
|
||||
// TestWriteIdempotency ensures that a Function can be written repeatedly
|
||||
// without change.
|
||||
func TestWriteIdempotency(t *testing.T) {
|
||||
root, rm := Mktemp(t)
|
||||
defer rm()
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
|
||||
// Create a function
|
||||
f := fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
Root: root,
|
||||
}
|
||||
if err := client.Create(f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Load the function and write it again
|
||||
f1, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// TODO
|
||||
// Test that the name was defaulted
|
||||
}
|
||||
if err := f1.Write(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Load it again and compare
|
||||
f2, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(f1, f2) {
|
||||
t.Fatalf("function differs after reload.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFunctionNameDefault ensures that a Function's name is defaulted to that
|
||||
// which can be derived from the last part of its path.
|
||||
// Creating a new Function from a path will error if there is no Function at
|
||||
// that path. Creating using the client initializes the default.
|
||||
func TestFunctionNameDefault(t *testing.T) {
|
||||
// A path at which there is no Function currently
|
||||
root := "testdata/testFunctionNameDefault"
|
||||
defer Using(t, root)()
|
||||
f, err := fn.NewFunction(root)
|
||||
if err == nil {
|
||||
t.Fatal("expected error instantiating a nonexistant Function")
|
||||
}
|
||||
|
||||
// Create the Function at the path
|
||||
client := fn.New(fn.WithRegistry(TestRegistry))
|
||||
f = fn.Function{
|
||||
Runtime: TestRuntime,
|
||||
Root: root,
|
||||
}
|
||||
if err := client.Create(f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Load the (now extant) Function
|
||||
f, err = fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify the name was defaulted as expected
|
||||
if f.Name != "testFunctionNameDefault" {
|
||||
t.Fatalf("expected name 'testFunctionNameDefault', got '%v'", f.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,20 +119,14 @@ func Test_DerivedImage(t *testing.T) {
|
|||
root := "testdata/" + tt.fnName
|
||||
defer Using(t, root)()
|
||||
|
||||
f := Function{
|
||||
Name: tt.fnName,
|
||||
Root: root,
|
||||
Image: tt.image,
|
||||
}
|
||||
|
||||
// write out the function
|
||||
client := New()
|
||||
err := client.Create(f)
|
||||
err := client.Create(Function{Runtime: "go", Name: tt.fnName, Root: root})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := DerivedImage(f.Root, tt.registry)
|
||||
got, err := DerivedImage(root, tt.registry)
|
||||
if err != nil {
|
||||
t.Errorf("DerivedImage() for image %v and registry %v; got error %v", tt.image, tt.registry, err)
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
|||
github.com/buildpacks/pack v0.22.0
|
||||
github.com/cloudevents/sdk-go/v2 v2.5.0
|
||||
github.com/containers/image/v5 v5.10.6
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/docker/cli v20.10.10+incompatible
|
||||
github.com/docker/docker v20.10.10+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.6.4
|
||||
|
|
1
go.sum
1
go.sum
|
@ -334,6 +334,7 @@ github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmeka
|
|||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
|
|
@ -203,7 +203,7 @@ func filesystemFromPath(uri string) (f Filesystem, err error) {
|
|||
return osFilesystem{root: parsed.Path}, nil
|
||||
}
|
||||
|
||||
// repositoryRuntimes returns runtimes defined in this repository's filesytem.
|
||||
// repositoryRuntimes returns runtimes defined in this repository's filesystem.
|
||||
// The views are denormalized, using the parent repository's values
|
||||
// for inherited fields BuildConfig and HealthEndpoints as the default values
|
||||
// for the runtimes and templates. The runtimes and templates themselves can
|
||||
|
@ -429,7 +429,7 @@ func (r *Repository) Runtime(name string) (runtime Runtime, err error) {
|
|||
func (r *Repository) Write(path string) error {
|
||||
// NOTE: Writing internal .git directory does not work
|
||||
//
|
||||
// A quirk of the git library's implementation is that the filesytem
|
||||
// A quirk of the git library's implementation is that the filesystem
|
||||
// returned does not include the .git directory. This is usually not an
|
||||
// issue when utilizing the repository's filesystem (for writing templates),
|
||||
// but it does cause problems here (used for installing a repo locally) where
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
},
|
||||
"Function": {
|
||||
"required": [
|
||||
"Version",
|
||||
"name",
|
||||
"namespace",
|
||||
"runtime",
|
||||
|
@ -39,6 +40,9 @@
|
|||
"Created"
|
||||
],
|
||||
"properties": {
|
||||
"Version": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
|
||||
"type": "string"
|
||||
|
|
|
@ -96,11 +96,17 @@ func (t *Templates) Get(runtime, fullname string) (Template, error) {
|
|||
return repo.Template(runtime, tplName)
|
||||
}
|
||||
|
||||
// Write a template to disk for the given Function
|
||||
// Write a function's template to disk.
|
||||
// Returns a Function which may have been modified dependent on the content
|
||||
// of the template (which can define default Function fields, builders,
|
||||
// buildpacks, etc)
|
||||
func (t *Templates) Write(f Function) (Function, error) {
|
||||
// Templates require an initially valid Function to write
|
||||
// (has name, path, runtime etc)
|
||||
if err := f.Validate(); err != nil {
|
||||
return f, err
|
||||
}
|
||||
|
||||
// The Function's Template
|
||||
template, err := t.Get(f.Runtime, f.Template)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Migrations
|
||||
|
||||
Contains Functions created with earlier versions to ensure backwards
|
||||
compatibility and test migrations where applicable.
|
|
@ -0,0 +1,22 @@
|
|||
name: testfunc
|
||||
namespace: ""
|
||||
runtime: go
|
||||
image: ""
|
||||
imageDigest: ""
|
||||
builder: gcr.io/paketo-buildpacks/builder:base
|
||||
builders:
|
||||
base: gcr.io/paketo-buildpacks/builder:base
|
||||
default: gcr.io/paketo-buildpacks/builder:base
|
||||
full: gcr.io/paketo-buildpacks/builder:full
|
||||
buildpacks:
|
||||
- paketo-buildpacks/go-dist
|
||||
- ghcr.io/boson-project/go-function-buildpack:tip
|
||||
healthEndpoints:
|
||||
liveness: /health/liveness
|
||||
readiness: /health/readiness
|
||||
volumes: []
|
||||
buildEnvs: []
|
||||
envs: []
|
||||
annotations: {}
|
||||
options: {}
|
||||
labels: []
|
|
@ -25,7 +25,6 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
// USING: Make specified dir. Return deferrable cleanup fn.
|
||||
// Using the given path, create it as a new directory and return a deferrable
|
||||
// which will remove it.
|
||||
// usage:
|
||||
|
@ -73,7 +72,7 @@ func Within(t *testing.T, root string) func() {
|
|||
// Mktemp creates a temporary directory, CDs the current processes (test) to
|
||||
// said directory, and returns the path to said directory.
|
||||
// Usage:
|
||||
// path, rm := mktemp(t)
|
||||
// path, rm := Mktemp(t)
|
||||
// defer rm()
|
||||
// CWD is now 'path'
|
||||
// errors encountererd fail the current test.
|
||||
|
@ -148,6 +147,9 @@ func WithEnvVar(t *testing.T, name, value string) func() {
|
|||
}
|
||||
}
|
||||
|
||||
// WithExecutable creates an executable of the given name and source in a temp
|
||||
// directory which is then added to PATH. Returned is a deferrable which will
|
||||
// clean up both the script and PATH.
|
||||
func WithExecutable(t *testing.T, name, goSrc string) func() {
|
||||
var err error
|
||||
binDir := t.TempDir()
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,5 @@
|
|||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,5 @@
|
|||
CoreOS Project
|
||||
Copyright 2018 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
|
@ -0,0 +1,296 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Semantic Versions http://semver.org
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Major int64
|
||||
Minor int64
|
||||
Patch int64
|
||||
PreRelease PreRelease
|
||||
Metadata string
|
||||
}
|
||||
|
||||
type PreRelease string
|
||||
|
||||
func splitOff(input *string, delim string) (val string) {
|
||||
parts := strings.SplitN(*input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
*input = parts[0]
|
||||
val = parts[1]
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func New(version string) *Version {
|
||||
return Must(NewVersion(version))
|
||||
}
|
||||
|
||||
func NewVersion(version string) (*Version, error) {
|
||||
v := Version{}
|
||||
|
||||
if err := v.Set(version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
|
||||
func Must(v *Version, err error) *Version {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Set parses and updates v from the given version string. Implements flag.Value
|
||||
func (v *Version) Set(version string) error {
|
||||
metadata := splitOff(&version, "+")
|
||||
preRelease := PreRelease(splitOff(&version, "-"))
|
||||
dotParts := strings.SplitN(version, ".", 3)
|
||||
|
||||
if len(dotParts) != 3 {
|
||||
return fmt.Errorf("%s is not in dotted-tri format", version)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(string(preRelease)); err != nil {
|
||||
return fmt.Errorf("failed to validate pre-release: %v", err)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(metadata); err != nil {
|
||||
return fmt.Errorf("failed to validate metadata: %v", err)
|
||||
}
|
||||
|
||||
parsed := make([]int64, 3, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
parsed[i] = val
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.Metadata = metadata
|
||||
v.PreRelease = preRelease
|
||||
v.Major = parsed[0]
|
||||
v.Minor = parsed[1]
|
||||
v.Patch = parsed[2]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
|
||||
}
|
||||
|
||||
if v.Metadata != "" {
|
||||
fmt.Fprintf(&buffer, "+%s", v.Metadata)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var data string
|
||||
if err := unmarshal(&data); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.Set(data)
|
||||
}
|
||||
|
||||
func (v Version) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + v.String() + `"`), nil
|
||||
}
|
||||
|
||||
func (v *Version) UnmarshalJSON(data []byte) error {
|
||||
l := len(data)
|
||||
if l == 0 || string(data) == `""` {
|
||||
return nil
|
||||
}
|
||||
if l < 2 || data[0] != '"' || data[l-1] != '"' {
|
||||
return errors.New("invalid semver string")
|
||||
}
|
||||
return v.Set(string(data[1 : l-1]))
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than versionB,
|
||||
// returning -1, 0, or +1 respectively.
|
||||
func (v Version) Compare(versionB Version) int {
|
||||
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return preReleaseCompare(v, versionB)
|
||||
}
|
||||
|
||||
// Equal tests if v is equal to versionB.
|
||||
func (v Version) Equal(versionB Version) bool {
|
||||
return v.Compare(versionB) == 0
|
||||
}
|
||||
|
||||
// LessThan tests if v is less than versionB.
|
||||
func (v Version) LessThan(versionB Version) bool {
|
||||
return v.Compare(versionB) < 0
|
||||
}
|
||||
|
||||
// Slice converts the comparable parts of the semver into a slice of integers.
|
||||
func (v Version) Slice() []int64 {
|
||||
return []int64{v.Major, v.Minor, v.Patch}
|
||||
}
|
||||
|
||||
func (p PreRelease) Slice() []string {
|
||||
preRelease := string(p)
|
||||
return strings.Split(preRelease, ".")
|
||||
}
|
||||
|
||||
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||
a := versionA.PreRelease
|
||||
b := versionB.PreRelease
|
||||
|
||||
/* Handle the case where if two versions are otherwise equal it is the
|
||||
* one without a PreRelease that is greater */
|
||||
if len(a) == 0 && (len(b) > 0) {
|
||||
return 1
|
||||
} else if len(b) == 0 && (len(a) > 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// If there is a prerelease, check and compare each part.
|
||||
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||
}
|
||||
|
||||
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||
if len(versionA) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursiveCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||
// A larger set of pre-release fields has a higher precedence than a smaller set,
|
||||
// if all of the preceding identifiers are equal.
|
||||
if len(versionA) == 0 {
|
||||
if len(versionB) > 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
} else if len(versionB) == 0 {
|
||||
// We're longer than versionB so return 1.
|
||||
return 1
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
aInt := false
|
||||
bInt := false
|
||||
|
||||
aI, err := strconv.Atoi(versionA[0])
|
||||
if err == nil {
|
||||
aInt = true
|
||||
}
|
||||
|
||||
bI, err := strconv.Atoi(versionB[0])
|
||||
if err == nil {
|
||||
bInt = true
|
||||
}
|
||||
|
||||
// Numeric identifiers always have lower precedence than non-numeric identifiers.
|
||||
if aInt && !bInt {
|
||||
return -1
|
||||
} else if !aInt && bInt {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Handle Integer Comparison
|
||||
if aInt && bInt {
|
||||
if aI > bI {
|
||||
return 1
|
||||
} else if aI < bI {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Handle String Comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMajor() {
|
||||
v.Major += 1
|
||||
v.Minor = 0
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpMinor() {
|
||||
v.Minor += 1
|
||||
v.Patch = 0
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
|
||||
func (v *Version) BumpPatch() {
|
||||
v.Patch += 1
|
||||
v.PreRelease = PreRelease("")
|
||||
v.Metadata = ""
|
||||
}
|
||||
|
||||
// validateIdentifier makes sure the provided identifier satisfies semver spec
|
||||
func validateIdentifier(id string) error {
|
||||
if id != "" && !reIdentifier.MatchString(id) {
|
||||
return fmt.Errorf("%s is not a valid semver identifier", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reIdentifier is a regular expression used to check that pre-release and metadata
|
||||
// identifiers satisfy the spec requirements
|
||||
var reIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package semver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Versions []*Version
|
||||
|
||||
func (s Versions) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Versions) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s Versions) Less(i, j int) bool {
|
||||
return s[i].LessThan(*s[j])
|
||||
}
|
||||
|
||||
// Sort sorts the given slice of Version
|
||||
func Sort(versions []*Version) {
|
||||
sort.Sort(Versions(versions))
|
||||
}
|
|
@ -170,6 +170,9 @@ github.com/containers/storage/pkg/mount
|
|||
github.com/containers/storage/pkg/reexec
|
||||
github.com/containers/storage/pkg/system
|
||||
github.com/containers/storage/pkg/unshare
|
||||
# github.com/coreos/go-semver v0.3.0
|
||||
## explicit
|
||||
github.com/coreos/go-semver/semver
|
||||
# github.com/creack/pty v1.1.11
|
||||
github.com/creack/pty
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
|
|
Loading…
Reference in New Issue