diff --git a/README.md b/README.md index ff57614..b4f8c32 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# `libjavabuildpack` -`libjavabuildpack` is a Go library with useful functionality for building Java Buildpack-related buildpacks. +# `libcfbuildpack` +`libcfbuildpack` is a Go library with useful functionality for building Cloud Foundry-related buildpacks. ## License This library is released under version 2.0 of the [Apache License][a]. diff --git a/build.go b/build.go index 90e7cf6..01ec344 100644 --- a/build.go +++ b/build.go @@ -16,51 +16,51 @@ package libjavabuildpack -import ( - "fmt" - - "github.com/buildpack/libbuildpack" -) - -// Build is an extension to libbuildpack.Build that allows additional functionality to be added. -type Build struct { - libbuildpack.Build - - // Buildpack represents the metadata associated with a buildpack. - Buildpack Buildpack - - // Cache represents the cache layers contributed by a buildpack. - Cache Cache - - // Launch represents the launch layers contributed by the buildpack. - Launch Launch - - // Logger is used to write debug and info to the console. - Logger Logger -} - -// String makes Build satisfy the Stringer interface. -func (b Build) String() string { - return fmt.Sprintf("Build{ Build: %s, Buildpack: %s, Cache: %s, Logger: %s }", - b.Build, b.Buildpack, b.Cache, b.Logger) -} - -// DefaultBuild creates a new instance of Build using default values. -func DefaultBuild() (Build, error) { - b, err := libbuildpack.DefaultBuild() - if err != nil { - return Build{}, err - } - - logger := Logger{b.Logger} - buildpack := NewBuildpack(b.Buildpack) - cache := Cache{b.Cache, buildpack.CacheRoot, logger} - - return Build{ - b, - buildpack, - cache, - Launch{b.Launch, cache, logger}, - logger, - }, nil -} +// import ( +// "fmt" +// +// "github.com/buildpack/libbuildpack" +// ) +// +// // Build is an extension to libbuildpack.Build that allows additional functionality to be added. +// type Build struct { +// libbuildpack.Build +// +// // Buildpack represents the metadata associated with a buildpack. +// Buildpack Buildpack +// +// // Cache represents the cache layers contributed by a buildpack. +// Cache Cache +// +// // Launch represents the launch layers contributed by the buildpack. +// Launch Launch +// +// // Logger is used to write debug and info to the console. +// Logger Logger +// } +// +// // String makes Build satisfy the Stringer interface. +// func (b Build) String() string { +// return fmt.Sprintf("Build{ Build: %s, Buildpack: %s, Cache: %s, Logger: %s }", +// b.Build, b.Buildpack, b.Cache, b.Logger) +// } +// +// // DefaultBuild creates a new instance of Build using default values. +// func DefaultBuild() (Build, error) { +// b, err := libbuildpack.DefaultBuild() +// if err != nil { +// return Build{}, err +// } +// +// logger := Logger{b.Logger} +// buildpack := NewBuildpack(b.Buildpack) +// cache := Cache{b.Cache, buildpack.CacheRoot, logger} +// +// return Build{ +// b, +// buildpack, +// cache, +// Launch{b.Launch, cache, logger}, +// logger, +// }, nil +// } diff --git a/build_test.go b/build_test.go index fd78b2b..ce3ec52 100644 --- a/build_test.go +++ b/build_test.go @@ -16,226 +16,159 @@ package libjavabuildpack_test -import ( - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/test" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestBuild(t *testing.T) { - spec.Run(t, "Build", testBuild, spec.Report(report.Terminal{})) -} - -func testBuild(t *testing.T, when spec.G, it spec.S) { - - it("contains default values", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - console, d := test.ReplaceConsole(t) - defer d() - - console.In(t, `[alpha] - version = "alpha-version" - name = "alpha-name" - -[bravo] - name = "bravo-name" -`) - - in := strings.NewReader(`[buildpack] -id = "buildpack-id" -name = "buildpack-name" -version = "buildpack-version" - -[[stacks]] -id = 'stack-id' -build-images = ["build-image-tag"] -run-images = ["run-image-tag"] - -[metadata] -test-key = "test-value" -`) - - err := libjavabuildpack.WriteToFile(in, filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() - - build, err := libjavabuildpack.DefaultBuild() - if err != nil { - t.Fatal(err) - } - - if reflect.DeepEqual(build.Application, libbuildpack.Application{}) { - t.Errorf("detect.Application should not be empty") - } - - if reflect.DeepEqual(build.Buildpack, libjavabuildpack.Buildpack{}) { - t.Errorf("detect.Buildpack should not be empty") - } - - if reflect.DeepEqual(build.BuildPlan, libbuildpack.BuildPlan{}) { - t.Errorf("detect.BuildPlan should not be empty") - } - - if reflect.DeepEqual(build.Cache, libjavabuildpack.Cache{}) { - t.Errorf("detect.Cache should not be empty") - } - - if reflect.DeepEqual(build.Launch, libbuildpack.Launch{}) { - t.Errorf("detect.Launch should not be empty") - } - - if reflect.DeepEqual(build.Logger, libjavabuildpack.Logger{}) { - t.Errorf("detect.Logger should not be empty") - } - - if reflect.DeepEqual(build.Platform, libbuildpack.Platform{}) { - t.Errorf("detect.Platform should not be empty") - } - - if reflect.DeepEqual(build.Stack, "") { - t.Errorf("detect.Stack should not be empty") - } - }) - - it("suppresses debug output", func() { - root := test.ScratchDir(t, "build") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() - - build, err := libjavabuildpack.DefaultBuild() - if err != nil { - t.Fatal(err) - } - - build.Logger.Debug("test-debug-output") - build.Logger.Info("test-info-output") - - if strings.Contains(c.Err(t), "test-debug-output") { - t.Errorf("stderr contained test-debug-output, expected not") - } - - if !strings.Contains(c.Out(t), "test-info-output") { - t.Errorf("stdout did not contain test-info-output, expected to") - } - }) - - it("allows debug output if BP_DEBUG is set", func() { - root := test.ScratchDir(t, "build") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() - defer test.ReplaceEnv(t, "BP_DEBUG", "")() - - build, err := libjavabuildpack.DefaultBuild() - if err != nil { - t.Fatal(err) - } - - build.Logger.Debug("test-debug-output") - build.Logger.Info("test-info-output") - - if !strings.Contains(c.Err(t), "test-debug-output") { - t.Errorf("stderr did not contain test-debug-output, expected to") - } - - if !strings.Contains(c.Out(t), "test-info-output") { - t.Errorf("stdout did not contain test-info-output, expected to") - } - }) - - it("returns 0 when successful", func() { - root := test.ScratchDir(t, "build") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() - - build, err := libjavabuildpack.DefaultBuild() - if err != nil { - t.Fatal(err) - } - - actual, d := test.CaptureExitStatus(t) - defer d() - - build.Success() - - if *actual != 0 { - t.Errorf("os.Exit = %d, expected 0", *actual) - } - }) - - it("returns code when failing", func() { - root := test.ScratchDir(t, "build") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() - - build, err := libjavabuildpack.DefaultBuild() - if err != nil { - t.Fatal(err) - } - - actual, d := test.CaptureExitStatus(t) - defer d() - - build.Failure(42) - - if *actual != 42 { - t.Errorf("os.Exit = %d, expected 42", *actual) - } - }) -} +// import ( +// "path/filepath" +// "reflect" +// "strings" +// "testing" +// +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/test" +// "github.com/sclevine/spec" +// "github.com/sclevine/spec/report" +// ) +// +// func TestBuild(t *testing.T) { +// spec.Run(t, "Build", testBuild, spec.Report(report.Terminal{})) +// } +// +// func testBuild(t *testing.T, when spec.G, it spec.S) { +// +// it("contains default values", func() { +// root := test.ScratchDir(t, "detect") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// console, d := test.ReplaceConsole(t) +// defer d() +// +// console.In(t, `[alpha] +// version = "alpha-version" +// name = "alpha-name" +// +// [bravo] +// name = "bravo-name" +// `) +// +// in := strings.NewReader(`[buildpack] +// id = "buildpack-id" +// name = "buildpack-name" +// version = "buildpack-version" +// +// [[stacks]] +// id = 'stack-id' +// build-images = ["build-image-tag"] +// run-images = ["run-image-tag"] +// +// [metadata] +// test-key = "test-value" +// `) +// +// err := libjavabuildpack.WriteToFile(in, filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() +// +// build, err := libjavabuildpack.DefaultBuild() +// if err != nil { +// t.Fatal(err) +// } +// +// if reflect.DeepEqual(build.Application, libbuildpack.Application{}) { +// t.Errorf("detect.Application should not be empty") +// } +// +// if reflect.DeepEqual(build.Buildpack, libjavabuildpack.Buildpack{}) { +// t.Errorf("detect.Buildpack should not be empty") +// } +// +// if reflect.DeepEqual(build.BuildPlan, libbuildpack.BuildPlan{}) { +// t.Errorf("detect.BuildPlan should not be empty") +// } +// +// if reflect.DeepEqual(build.Cache, libjavabuildpack.Cache{}) { +// t.Errorf("detect.Cache should not be empty") +// } +// +// if reflect.DeepEqual(build.Launch, libbuildpack.Launch{}) { +// t.Errorf("detect.Launch should not be empty") +// } +// +// if reflect.DeepEqual(build.Logger, libjavabuildpack.Logger{}) { +// t.Errorf("detect.Logger should not be empty") +// } +// +// if reflect.DeepEqual(build.Platform, libbuildpack.Platform{}) { +// t.Errorf("detect.Platform should not be empty") +// } +// +// if reflect.DeepEqual(build.Stack, "") { +// t.Errorf("detect.Stack should not be empty") +// } +// }) +// +// it("returns 0 when successful", func() { +// root := test.ScratchDir(t, "build") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// c, d := test.ReplaceConsole(t) +// defer d() +// c.In(t, "") +// +// err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() +// +// build, err := libjavabuildpack.DefaultBuild() +// if err != nil { +// t.Fatal(err) +// } +// +// actual, d := test.CaptureExitStatus(t) +// defer d() +// +// build.Success(libbuildpack.BuildPlan{}) +// +// if *actual != 0 { +// t.Errorf("os.Exit = %d, expected 0", *actual) +// } +// }) +// +// it("returns code when failing", func() { +// root := test.ScratchDir(t, "build") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// c, d := test.ReplaceConsole(t) +// defer d() +// c.In(t, "") +// +// err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root, root, root)() +// +// build, err := libjavabuildpack.DefaultBuild() +// if err != nil { +// t.Fatal(err) +// } +// +// actual, d := test.CaptureExitStatus(t) +// defer d() +// +// build.Failure(42) +// +// if *actual != 42 { +// t.Errorf("os.Exit = %d, expected 42", *actual) +// } +// }) +// } diff --git a/buildpack.go b/buildpack.go deleted file mode 100644 index 408585d..0000000 --- a/buildpack.go +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack - -import ( - "fmt" - "path/filepath" - "sort" - - "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" -) - -// Buildpack is an extension to libbuildpack.Buildpack that adds additional opinionated behaviors. -type Buildpack struct { - libbuildpack.Buildpack - - // CacheRoot is the path to the root directory for the buildpack's dependency cache. - CacheRoot string -} - -// Dependencies returns the collection of dependencies extracted from the generic buildpack metadata. -func (b Buildpack) Dependencies() (Dependencies, error) { - d, ok := b.Metadata["dependencies"] - if !ok { - return Dependencies{}, nil - } - - deps, ok := d.([]map[string]interface{}) - if !ok { - return Dependencies{}, fmt.Errorf("dependencies have invalid structure") - } - - var dependencies Dependencies - for _, dep := range deps { - d, err := b.dependency(dep) - if err != nil { - return Dependencies{}, err - } - - dependencies = append(dependencies, d) - } - - b.Logger.Debug("Dependencies: %s", dependencies) - return dependencies, nil -} - -// IncludeFiles returns the include_files buildpack metadata. -func (b Buildpack) IncludeFiles() ([]string, error) { - i, ok := b.Metadata["include_files"] - if !ok { - return []string{}, nil - } - - files, ok := i.([]interface{}) - if !ok { - return []string{}, fmt.Errorf("include_files is not an array of strings") - } - - var includes []string - for _, candidate := range files { - file, ok := candidate.(string) - if !ok { - return []string{}, fmt.Errorf("include_files is not an array of strings") - } - - includes = append(includes, file) - } - - return includes, nil -} - -// PrePackage returns the pre_package buildpack metadata. -func (b Buildpack) PrePackage() (string, bool) { - p, ok := b.Metadata["pre_package"] - if !ok { - return "", false - } - - s, ok := p.(string) - return s, ok -} - -func (b Buildpack) dependency(dep map[string]interface{}) (Dependency, error) { - id, ok := dep["id"].(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency id missing or wrong format") - } - - name, ok := dep["name"].(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency name missing or wrong format") - } - - v, ok := dep["version"].(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency version missing or wrong format") - } - - version, err := semver.NewVersion(v) - if err != nil { - return Dependency{}, err - } - - uri, ok := dep["uri"].(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency uri missing or wrong format") - } - - sha256, ok := dep["sha256"].(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency sha256 missing or wrong format") - } - - s, ok := dep["stacks"].([]interface{}) - if !ok { - return Dependency{}, fmt.Errorf("dependency stacks missing or wrong format") - } - - if len(s) == 0 { - return Dependency{}, fmt.Errorf("at least one dependency stack is required") - } - - var stacks Stacks - for _, t := range s { - u, ok := t.(string) - if !ok { - return Dependency{}, fmt.Errorf("dependency stack missing or wrong format") - } - - stacks = append(stacks, u) - } - - l, ok := dep["licenses"].([]map[string]interface{}) - if !ok { - return Dependency{}, fmt.Errorf("dependency licenses missing or wrong format") - } - - if len(l) == 0 { - return Dependency{}, fmt.Errorf("at least one dependency license is required") - } - - var licenses Licenses - for _, t := range l { - lt, tok := t["type"].(string) - - lu, uok := t["uri"].(string) - - if !tok && !uok { - return Dependency{}, fmt.Errorf("dependency license must have at least one of type or uri") - } - - licenses = append(licenses, License{lt, lu}) - } - - return Dependency{ - id, - name, - Version{version}, - uri, - sha256, - stacks, - licenses, - }, nil -} - -// Dependencies is a collection of Dependency instances. -type Dependencies []Dependency - -// Best returns the best (latest version) dependency within a collection of Dependencies. The candidate set is first -// filtered by id, version, and stack, then the remaining candidates are sorted for the best result. If the -// versionConstraint is not specified (""), then the latest wildcard ("*") is used. -func (d Dependencies) Best(id string, versionConstraint string, stack string) (Dependency, error) { - var candidates Dependencies - - vc := versionConstraint - if vc == "" { - vc = "*" - } - - constraint, err := semver.NewConstraint(vc) - if err != nil { - return Dependency{}, err - } - - for _, c := range d { - if c.ID == id && constraint.Check(c.Version.Version) && c.Stacks.contains(stack) { - candidates = append(candidates, c) - } - } - - if len(candidates) == 0 { - return Dependency{}, fmt.Errorf("no valid dependencies for %s, %s, and %s in %s", id, vc, stack, d) - } - - sort.Sort(candidates) - - return candidates[len(candidates)-1], nil -} - -// Len makes Dependencies satisfy the sort.Interface interface. -func (d Dependencies) Len() int { - return len(d) -} - -// Less makes Dependencies satisfy the sort.Interface interface. -func (d Dependencies) Less(i int, j int) bool { - return d[i].Version.LessThan(d[j].Version.Version) -} - -// Swap makes Dependencies satisfy the sort.Interface interface. -func (d Dependencies) Swap(i int, j int) { - d[i], d[j] = d[j], d[i] -} - -// Dependency represents a buildpack dependency. -type Dependency struct { - // ID is the dependency ID. - ID string `toml:"id"` - - // Name is the dependency ID. - Name string `toml:"name"` - - // Version is the dependency version. - Version Version `toml:"version"` - - // URI is the dependency URI. - URI string `toml:"uri"` - - // SHA256 is the hash of the dependency. - SHA256 string `toml:"sha256"` - - // Stacks are the stacks the dependency is compatible with. - Stacks Stacks `toml:"stacks"` - - // Licenses are the stacks the dependency is distributed under. - Licenses Licenses `toml:"licenses"` -} - -// String makes Dependency satisfy the Stringer interface. -func (d Dependency) String() string { - return fmt.Sprintf("Dependency{ ID: %s, Name: %s, Version: %s, URI: %s, SHA256: %s, Stacks: %s}", - d.ID, d.Name, d.Version, d.URI, d.SHA256, d.Stacks) -} - -type Version struct { - *semver.Version -} - -// MarshalText makes Version satisfy the encoding.TextMarshaler interface. -func (v Version) MarshalText() ([]byte, error) { - return []byte(v.Version.Original()), nil -} - -// UnmarshalText makes Version satisfy the encoding.TextUnmarshaler interface. -func (v *Version) UnmarshalText(text []byte) error { - s := string(text) - - w, err := semver.NewVersion(s) - if err != nil { - return fmt.Errorf("invalid semantic version %s", s) - } - - v.Version = w - return nil -} - -// Stacks is a collection of stack ids -type Stacks []string - -func (s Stacks) contains(stack string) bool { - for _, v := range s { - if v == stack { - return true - } - } - - return false -} - -// Licenses is a collection of licenses -type Licenses []License - -// License represents a license that a Dependency is distributed under. At least one of Name or URI MUST be specified. -type License struct { - // Type is the type of the license. This is typically the SPDX short identifier. - Type string `toml:"type"` - - // URI is the location where the license can be found. - URI string `toml:"uri"` -} - -// NewBuildpack creates a new instance of Buildpack from a specified libbuildpack.Buildpack. -func NewBuildpack(buildpack libbuildpack.Buildpack) Buildpack { - return Buildpack{ - buildpack, - filepath.Join(buildpack.Root, "cache"), - } -} diff --git a/buildpack/buildpack.go b/buildpack/buildpack.go new file mode 100644 index 0000000..ade9240 --- /dev/null +++ b/buildpack/buildpack.go @@ -0,0 +1,123 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" + "path/filepath" + + "github.com/buildpack/libbuildpack/buildpack" + "github.com/mitchellh/mapstructure" +) + +// Buildpack is an extension to libbuildpack.Buildpack that adds additional opinionated behaviors. +type Buildpack struct { + buildpack.Buildpack + + // CacheRoot is the path to the root directory for the buildpack's dependency cache. + CacheRoot string +} + +// Dependencies returns the collection of dependencies extracted from the generic buildpack metadata. +func (b Buildpack) Dependencies() (Dependencies, error) { + d, ok := b.Metadata["dependencies"] + if !ok { + return Dependencies{}, nil + } + + deps, ok := d.([]map[string]interface{}) + if !ok { + return Dependencies{}, fmt.Errorf("dependencies have invalid structure") + } + + var dependencies Dependencies + for _, dep := range deps { + d, err := b.dependency(dep) + if err != nil { + return Dependencies{}, err + } + + dependencies = append(dependencies, d) + } + + b.Logger.Debug("Dependencies: %s", dependencies) + return dependencies, nil +} + +// IncludeFiles returns the include_files buildpack metadata. +func (b Buildpack) IncludeFiles() ([]string, error) { + i, ok := b.Metadata["include_files"] + if !ok { + return []string{}, nil + } + + files, ok := i.([]interface{}) + if !ok { + return []string{}, fmt.Errorf("include_files is not an array of strings") + } + + var includes []string + for _, candidate := range files { + file, ok := candidate.(string) + if !ok { + return []string{}, fmt.Errorf("include_files is not an array of strings") + } + + includes = append(includes, file) + } + + return includes, nil +} + +// PrePackage returns the pre_package buildpack metadata. +func (b Buildpack) PrePackage() (string, bool) { + p, ok := b.Metadata["pre_package"] + if !ok { + return "", false + } + + s, ok := p.(string) + return s, ok +} + +func (b Buildpack) dependency(dep map[string]interface{}) (Dependency, error) { + var d Dependency + + config := mapstructure.DecoderConfig{ + DecodeHook: unmarshalText, + Result: &d, + } + + decoder, err := mapstructure.NewDecoder(&config) + if err != nil { + return Dependency{}, err + } + + if err := decoder.Decode(dep); err != nil { + return Dependency{}, err + } + + return d, nil +} + +// NewBuildpack creates a new instance of Buildpack from a specified buildpack.Buildpack. +func NewBuildpack(buildpack buildpack.Buildpack) Buildpack { + return Buildpack{ + buildpack, + filepath.Join(buildpack.Root, "cache"), + } +} diff --git a/buildpack/buildpack_test.go b/buildpack/buildpack_test.go new file mode 100644 index 0000000..a4ddc3f --- /dev/null +++ b/buildpack/buildpack_test.go @@ -0,0 +1,211 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "reflect" + "testing" + + buildpackBp "github.com/buildpack/libbuildpack/buildpack" + buildpackCf "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestBuildpack(t *testing.T) { + spec.Run(t, "Buildpack", testBuildpack, spec.Report(report.Terminal{})) +} + +func testBuildpack(t *testing.T, when spec.G, it spec.S) { + + it("returns error with incorrectly defined dependencies", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{"dependencies": "test-dependency"}, + } + + _, err := buildpackCf.Buildpack{Buildpack: b}.Dependencies() + + if err.Error() != "dependencies have invalid structure" { + t.Errorf("Buildpack.Dependencies = %s, expected dependencies have invalid structure", err.Error()) + } + }) + + it("returns dependencies", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{ + "dependencies": []map[string]interface{}{ + { + "id": "test-id-1", + "name": "test-name-1", + "version": "1.0", + "uri": "test-uri-1", + "sha256": "test-sha256-1", + "stacks": []interface{}{"test-stack-1a", "test-stack-1b"}, + "licenses": []map[string]interface{}{ + { + "type": "test-type-1", + "uri": "test-uri-1", + }, + { + "type": "test-type-2", + "uri": "test-uri-2", + }, + }, + }, + { + "id": "test-id-2", + "name": "test-name-2", + "version": "2.0", + "uri": "test-uri-2", + "sha256": "test-sha256-2", + "stacks": []interface{}{"test-stack-2a", "test-stack-2b"}, + "licenses": []map[string]interface{}{ + { + "type": "test-type-1", + "uri": "test-uri-1", + }, + { + "type": "test-type-2", + "uri": "test-uri-2", + }, + }, + }, + }, + }, + } + + expected := buildpackCf.Dependencies{ + buildpackCf.Dependency{ + ID: "test-id-1", + Name: "test-name-1", + Version: newVersion(t, "1.0"), + URI: "test-uri-1", + SHA256: "test-sha256-1", + Stacks: buildpackCf.Stacks{"test-stack-1a", "test-stack-1b"}, + Licenses: buildpackCf.Licenses{ + buildpackCf.License{Type: "test-type-1", URI: "test-uri-1"}, + buildpackCf.License{Type: "test-type-2", URI: "test-uri-2"}, + }, + }, + buildpackCf.Dependency{ + ID: "test-id-2", + Name: "test-name-2", + Version: newVersion(t, "2.0"), + URI: "test-uri-2", + SHA256: "test-sha256-2", + Stacks: buildpackCf.Stacks{"test-stack-2a", "test-stack-2b"}, + Licenses: buildpackCf.Licenses{ + buildpackCf.License{Type: "test-type-1", URI: "test-uri-1"}, + buildpackCf.License{Type: "test-type-2", URI: "test-uri-2"}, + }, + }, + } + + actual, err := buildpackCf.Buildpack{Buildpack: b}.Dependencies() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Buildpack.Dependencies = %s, expected %s", actual, expected) + } + }) + + it("returns include_files if it exists", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{ + "include_files": []interface{}{"test-file-1", "test-file-2"}, + }, + } + + actual, err := buildpackCf.Buildpack{Buildpack: b}.IncludeFiles() + if err != nil { + t.Fatal(err) + } + + expected := []string{"test-file-1", "test-file-2"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Buildpack.IncludeFiles = %s, expected empty []string", actual) + } + }) + + it("returns empty []string if include_files does not exist", func() { + b := buildpackBp.Buildpack{} + + actual, err := buildpackCf.Buildpack{Buildpack: b}.IncludeFiles() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, []string{}) { + t.Errorf("Buildpack.IncludeFiles = %s, expected empty []string", actual) + } + }) + + it("returns false if include_files is not []string", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{ + "include_files": 1, + }, + } + + _, err := buildpackCf.Buildpack{Buildpack: b}.IncludeFiles() + if err.Error() != "include_files is not an array of strings" { + t.Errorf("Buildpack.IncludeFiles = %s, expected include_files is not an array of strings", err.Error()) + } + }) + + it("returns pre_package if it exists", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{ + "pre_package": "test-package", + }, + } + + actual, ok := buildpackCf.Buildpack{Buildpack: b}.PrePackage() + if !ok { + t.Errorf("Buildpack.PrePackage() = %t, expected true", ok) + } + + if actual != "test-package" { + t.Errorf("Buildpack.PrePackage() %s, expected test-package", actual) + } + }) + + it("returns false if pre_package does not exist", func() { + b := buildpackBp.Buildpack{} + + _, ok := buildpackCf.Buildpack{Buildpack: b}.PrePackage() + if ok { + t.Errorf("Buildpack.PrePackage() = %t, expected false", ok) + } + }) + + it("returns false if pre_package is not string", func() { + b := buildpackBp.Buildpack{ + Metadata: buildpackBp.Metadata{ + "pre_package": 1, + }, + } + + _, ok := buildpackCf.Buildpack{Buildpack: b}.PrePackage() + if ok { + t.Errorf("Buildpack.PrePackage() = %t, expected false", ok) + } + }) + +} diff --git a/buildpack/dependencies.go b/buildpack/dependencies.go new file mode 100644 index 0000000..fb35549 --- /dev/null +++ b/buildpack/dependencies.go @@ -0,0 +1,73 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" + "sort" + + "github.com/Masterminds/semver" +) + +// Dependencies is a collection of Dependency instances. +type Dependencies []Dependency + +// Best returns the best (latest version) dependency within a collection of Dependencies. The candidate set is first +// filtered by id, version, and stack, then the remaining candidates are sorted for the best result. If the +// versionConstraint is not specified (""), then the latest wildcard ("*") is used. +func (d Dependencies) Best(id string, versionConstraint string, stack string) (Dependency, error) { + var candidates Dependencies + + vc := versionConstraint + if vc == "" { + vc = "*" + } + + constraint, err := semver.NewConstraint(vc) + if err != nil { + return Dependency{}, err + } + + for _, c := range d { + if c.ID == id && constraint.Check(c.Version.Version) && c.Stacks.contains(stack) { + candidates = append(candidates, c) + } + } + + if len(candidates) == 0 { + return Dependency{}, fmt.Errorf("no valid dependencies for %s, %s, and %s in %s", id, vc, stack, d) + } + + sort.Sort(candidates) + + return candidates[len(candidates)-1], nil +} + +// Len makes Dependencies satisfy the sort.Interface interface. +func (d Dependencies) Len() int { + return len(d) +} + +// Less makes Dependencies satisfy the sort.Interface interface. +func (d Dependencies) Less(i int, j int) bool { + return d[i].Version.LessThan(d[j].Version.Version) +} + +// Swap makes Dependencies satisfy the sort.Interface interface. +func (d Dependencies) Swap(i int, j int) { + d[i], d[j] = d[j], d[i] +} diff --git a/buildpack/dependencies_test.go b/buildpack/dependencies_test.go new file mode 100644 index 0000000..543eb72 --- /dev/null +++ b/buildpack/dependencies_test.go @@ -0,0 +1,231 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "reflect" + "strings" + "testing" + + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestDependencies(t *testing.T) { + spec.Run(t, "Dependencies", testDependencies, spec.Report(report.Terminal{})) +} + +func testDependencies(t *testing.T, when spec.G, it spec.S) { + + it("filters by id", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id-1", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + buildpack.Dependency{ + ID: "test-id-2", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + } + + expected := buildpack.Dependency{ + ID: "test-id-2", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}} + + actual, err := d.Best("test-id-2", "1.0", "test-stack-1") + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) + } + }) + + it("filters by version constraint", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "2.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + } + + expected := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "2.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}} + + actual, err := d.Best("test-id", "2.0", "test-stack-1") + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) + } + }) + + it("filters by stack", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-3"}}, + } + + expected := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-3"}} + + actual, err := d.Best("test-id", "1.0", "test-stack-3") + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) + } + }) + + it("returns the best dependency", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.1"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-3"}}, + } + + expected := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.1"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}} + + actual, err := d.Best("test-id", "1.*", "test-stack-1") + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) + } + }) + + it("returns error if there are no matching dependencies", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-3"}}, + } + + _, err := d.Best("test-id-2", "1.0", "test-stack-1") + if !strings.HasPrefix(err.Error(), "no valid dependencies") { + t.Errorf("Dependencies.Best = %s, expected no valid dependencies...", err.Error()) + } + }) + + it("substitutes all wildcard for unspecified version constraint", func() { + d := buildpack.Dependencies{ + buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.1"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}}, + } + + expected := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.1"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: []string{"test-stack-1", "test-stack-2"}} + + actual, err := d.Best("test-id", "", "test-stack-1") + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) + } + }) +} diff --git a/buildpack/dependency.go b/buildpack/dependency.go new file mode 100644 index 0000000..d8c68ff --- /dev/null +++ b/buildpack/dependency.go @@ -0,0 +1,84 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" +) + +// Dependency represents a buildpack dependency. +type Dependency struct { + // ID is the dependency ID. + ID string `mapstruct:"id" toml:"id"` + + // Name is the dependency ID. + Name string `mapstruct:"name" toml:"name"` + + // Version is the dependency version. + Version Version `mapstruct:"version" toml:"version"` + + // URI is the dependency URI. + URI string `mapstruct:"uri" toml:"uri"` + + // SHA256 is the hash of the dependency. + SHA256 string `mapstruct:"sha256" toml:"sha256"` + + // Stacks are the stacks the dependency is compatible with. + Stacks Stacks `mapstruct:"stacks" toml:"stacks"` + + // Licenses are the stacks the dependency is distributed under. + Licenses Licenses `mapstruct:"licenses" toml:"licenses"` +} + +// String makes Dependency satisfy the Stringer interface. +func (d Dependency) String() string { + return fmt.Sprintf("Dependency{ ID: %s, Name: %s, Version: %s, URI: %s, SHA256: %s, Stacks: %s, Licenses: %s }", + d.ID, d.Name, d.Version, d.URI, d.SHA256, d.Stacks, d.Licenses) +} + +// Validate ensures that the dependency is valid. +func (d Dependency) Validate() error { + if "" == d.ID { + return fmt.Errorf("id is required") + } + + if "" == d.Name { + return fmt.Errorf("name is required") + } + + if (Version{} == d.Version) { + return fmt.Errorf("version is required") + } + + if "" == d.URI { + return fmt.Errorf("uri is required") + } + + if "" == d.SHA256 { + return fmt.Errorf("sha256 is required") + } + + if err := d.Stacks.Validate(); err != nil { + return err + } + + if err := d.Licenses.Validate(); err != nil { + return err + } + + return nil +} diff --git a/buildpack/dependency_test.go b/buildpack/dependency_test.go new file mode 100644 index 0000000..0cb0ab0 --- /dev/null +++ b/buildpack/dependency_test.go @@ -0,0 +1,160 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "testing" + + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestDependency(t *testing.T) { + spec.Run(t, "Dependency", testDependency, spec.Report(report.Terminal{})) +} + +func testDependency(t *testing.T, when spec.G, it spec.S) { + + it("validates", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err != nil { + t.Errorf("Dependency.Validate() = %s expected no error", err) + } + }) + + it("does not validate with invalid id", func() { + err := buildpack.Dependency{ + Name: "test-name", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid name", func() { + err := buildpack.Dependency{ + ID: "test-id", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid version", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + URI: "test-uri", + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid uri", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0.0"), + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid sha256", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + Stacks: buildpack.Stacks{"test-stack"}, + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid stacks", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + SHA256: "test-sha256", + Licenses: buildpack.Licenses{ + {Type: "test-type"}, + }, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + + it("does not validate with invalid licenses", func() { + err := buildpack.Dependency{ + ID: "test-id", + Name: "test-name", + Version: newVersion(t, "1.0.0"), + URI: "test-uri", + SHA256: "test-sha256", + Stacks: buildpack.Stacks{"test-stack"}, + }.Validate() + if err == nil { + t.Errorf("Dependency.Validate() = nil expected error") + } + }) + +} diff --git a/buildpack/license.go b/buildpack/license.go new file mode 100644 index 0000000..90f1267 --- /dev/null +++ b/buildpack/license.go @@ -0,0 +1,44 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" +) + +// License represents a license that a Dependency is distributed under. At least one of Name or URI MUST be specified. +type License struct { + // Type is the type of the license. This is typically the SPDX short identifier. + Type string `mapstruct:"type" toml:"type"` + + // URI is the location where the license can be found. + URI string `mapstruct:"uri" toml:"uri"` +} + +// String makes License satisfy the Stringer interface. +func (l License) String() string { + return fmt.Sprintf("License{ Type: %s, URI: %s }", l.Type, l.URI) +} + +// Validate ensures that license has at least one of type or uri +func (l License) Validate() error { + if "" == l.Type && "" == l.URI { + return fmt.Errorf("license must have at least one of type or uri") + } + + return nil +} diff --git a/buildpack/license_test.go b/buildpack/license_test.go new file mode 100644 index 0000000..29b1421 --- /dev/null +++ b/buildpack/license_test.go @@ -0,0 +1,60 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "testing" + + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestLicense(t *testing.T) { + spec.Run(t, "License", testLicense, spec.Report(report.Terminal{})) +} + +func testLicense(t *testing.T, when spec.G, it spec.S) { + + it("validates with type set", func() { + err := buildpack.License{Type: "test-type"}.Validate() + if err != nil { + t.Errorf("License.Validate() = %s expected no error", err) + } + }) + + it("validates with uri set", func() { + err := buildpack.License{URI: "test-uri"}.Validate() + if err != nil { + t.Errorf("License.Validate() = %s expected no error", err) + } + }) + + it("validates with type and uri set", func() { + err := buildpack.License{Type: "test-type", URI: "test-uri"}.Validate() + if err != nil { + t.Errorf("License.Validate() = %s expected no error", err) + } + }) + + it("does not validate without type and uri set", func() { + err := buildpack.License{}.Validate() + if err == nil { + t.Errorf("License.Validate() = nil expected error") + } + }) +} diff --git a/buildpack/licenses.go b/buildpack/licenses.go new file mode 100644 index 0000000..a3e3413 --- /dev/null +++ b/buildpack/licenses.go @@ -0,0 +1,39 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" +) + +// Licenses is a collection of licenses +type Licenses []License + +// Validate ensures that there is at least one license and all licenses are valid +func (l Licenses) Validate() error { + if len(l) == 0 { + return fmt.Errorf("at least one license is required") + } + + for _, license := range l { + if err := license.Validate(); err != nil { + return err + } + } + + return nil +} diff --git a/buildpack/licenses_test.go b/buildpack/licenses_test.go new file mode 100644 index 0000000..a6aefdb --- /dev/null +++ b/buildpack/licenses_test.go @@ -0,0 +1,61 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "testing" + + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestLicenses(t *testing.T) { + spec.Run(t, "Licenses", testLicenses, spec.Report(report.Terminal{})) +} + +func testLicenses(t *testing.T, when spec.G, it spec.S) { + + it("does not validate if there is not at least one license", func() { + err := buildpack.Licenses{}.Validate() + if err == nil { + t.Errorf("Licenses.Validate() = nil expected error") + } + }) + + it("validates when all licenses are valid", func() { + err := buildpack.Licenses{ + {Type: "test-type"}, + {URI: "test-uri"}, + }.Validate() + + if err != nil { + t.Errorf("Licenses.Validate() = %s expected no error", err) + } + }) + + it("does not validate when a license is invalid", func() { + err := buildpack.Licenses{ + {Type: "test-type"}, + {}, + }.Validate() + + if err == nil { + t.Errorf("Licenses.Validate() = nil expected error") + } + }) +} diff --git a/buildpack/stacks.go b/buildpack/stacks.go new file mode 100644 index 0000000..d706f98 --- /dev/null +++ b/buildpack/stacks.go @@ -0,0 +1,43 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" +) + +// Stacks is a collection of stack ids. +type Stacks []string + +// Validate ensures that there is at least one stack. +func (s Stacks) Validate() error { + if len(s) == 0 { + return fmt.Errorf("at least one stack is required") + } + + return nil +} + +func (s Stacks) contains(stack string) bool { + for _, v := range s { + if v == stack { + return true + } + } + + return false +} diff --git a/buildpack/stacks_test.go b/buildpack/stacks_test.go new file mode 100644 index 0000000..0f48be6 --- /dev/null +++ b/buildpack/stacks_test.go @@ -0,0 +1,39 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack_test + +import ( + "testing" + + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestStacks(t *testing.T) { + spec.Run(t, "Stacks", testStacks, spec.Report(report.Terminal{})) +} + +func testStacks(t *testing.T, when spec.G, it spec.S) { + + it("does not validate if there is not at least one stack", func() { + err := buildpack.Stacks{}.Validate() + if err == nil { + t.Errorf("Stacks.Validate() = nil expected error") + } + }) +} diff --git a/libjavabuildpack.go b/buildpack/util_test.go similarity index 65% rename from libjavabuildpack.go rename to buildpack/util_test.go index d924a93..1eecc63 100644 --- a/libjavabuildpack.go +++ b/buildpack/util_test.go @@ -14,5 +14,22 @@ * limitations under the License. */ -// Package libjavabuildpack contains types and functions for implementing a Java Buildpack-related buildpack. -package libjavabuildpack +package buildpack_test + +import ( + "testing" + + "github.com/Masterminds/semver" + "github.com/cloudfoundry/libcfbuildpack/buildpack" +) + +func newVersion(t *testing.T, version string) buildpack.Version { + t.Helper() + + v, err := semver.NewVersion(version) + if err != nil { + t.Fatal(err) + } + + return buildpack.Version{Version: v} +} diff --git a/buildpack/version.go b/buildpack/version.go new file mode 100644 index 0000000..ee343f2 --- /dev/null +++ b/buildpack/version.go @@ -0,0 +1,63 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 buildpack + +import ( + "fmt" + "reflect" + + "github.com/Masterminds/semver" +) + +type Version struct { + *semver.Version +} + +// MarshalText makes Version satisfy the encoding.TextMarshaler interface. +func (v Version) MarshalText() ([]byte, error) { + return []byte(v.Version.Original()), nil +} + +// UnmarshalText makes Version satisfy the encoding.TextUnmarshaler interface. +func (v *Version) UnmarshalText(text []byte) error { + s := string(text) + + w, err := semver.NewVersion(s) + if err != nil { + return fmt.Errorf("invalid semantic version %s", s) + } + + v.Version = w + return nil +} + +func unmarshalText(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { + if from.Kind() != reflect.String { + return data, nil + } + + if to != reflect.TypeOf(Version{}) { + return data, nil + } + + w, err := semver.NewVersion(data.(string)) + if err != nil { + return nil, fmt.Errorf("invalid semantic version %s", data) + } + + return Version{w}, nil +} diff --git a/buildpack_test.go b/buildpack_test.go deleted file mode 100644 index d366f06..0000000 --- a/buildpack_test.go +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack_test - -import ( - "reflect" - "strings" - "testing" - - "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestBuildpack(t *testing.T) { - spec.Run(t, "Buildpack", testBuildpack, spec.Report(report.Terminal{})) -} - -func testBuildpack(t *testing.T, when spec.G, it spec.S) { - - it("returns error with no defined dependencies", func() { - b := libbuildpack.Buildpack{} - - actual, err := libjavabuildpack.Buildpack{Buildpack: b}.Dependencies() - if err != nil { - t.Fatal(err) - } - - expected := libjavabuildpack.Dependencies{} - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Buildpack.Dependencies = %s, expected %s", actual, expected) - } - }) - - it("returns error with incorrectly defined dependencies", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{"dependencies": "test-dependency"}, - } - - _, err := libjavabuildpack.Buildpack{Buildpack: b}.Dependencies() - - if err.Error() != "dependencies have invalid structure" { - t.Errorf("Buildpack.Dependencies = %s, expected dependencies have invalid structure", err.Error()) - } - }) - - it("returns dependencies", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{ - "dependencies": []map[string]interface{}{ - { - "id": "test-id-1", - "name": "test-name-1", - "version": "1.0", - "uri": "test-uri-1", - "sha256": "test-sha256-1", - "stacks": []interface{}{"test-stack-1a", "test-stack-1b"}, - "licenses": []map[string]interface{}{ - { - "type": "test-type-1", - "uri": "test-uri-1", - }, - { - "type": "test-type-2", - "uri": "test-uri-2", - }, - }, - }, - { - "id": "test-id-2", - "name": "test-name-2", - "version": "2.0", - "uri": "test-uri-2", - "sha256": "test-sha256-2", - "stacks": []interface{}{"test-stack-2a", "test-stack-2b"}, - "licenses": []map[string]interface{}{ - { - "type": "test-type-1", - "uri": "test-uri-1", - }, - { - "type": "test-type-2", - "uri": "test-uri-2", - }, - }, - }, - }, - }, - } - - expected := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id-1", - Name: "test-name-1", - Version: newVersion(t, "1.0"), - URI: "test-uri-1", - SHA256: "test-sha256-1", - Stacks: libjavabuildpack.Stacks{"test-stack-1a", "test-stack-1b"}, - Licenses: libjavabuildpack.Licenses{ - libjavabuildpack.License{Type: "test-type-1", URI: "test-uri-1"}, - libjavabuildpack.License{Type: "test-type-2", URI: "test-uri-2"}, - }, - }, - libjavabuildpack.Dependency{ - ID: "test-id-2", - Name: "test-name-2", - Version: newVersion(t, "2.0"), - URI: "test-uri-2", - SHA256: "test-sha256-2", - Stacks: libjavabuildpack.Stacks{"test-stack-2a", "test-stack-2b"}, - Licenses: libjavabuildpack.Licenses{ - libjavabuildpack.License{Type: "test-type-1", URI: "test-uri-1"}, - libjavabuildpack.License{Type: "test-type-2", URI: "test-uri-2"}, - }, - }, - } - - actual, err := libjavabuildpack.Buildpack{Buildpack: b}.Dependencies() - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Buildpack.Dependencies = %s, expected %s", actual, expected) - } - }) - - it("returns include_files if it exists", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{ - "include_files": []interface{}{"test-file-1", "test-file-2"}, - }, - } - - actual, err := libjavabuildpack.Buildpack{Buildpack: b}.IncludeFiles() - if err != nil { - t.Fatal(err) - } - - expected := []string{"test-file-1", "test-file-2"} - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Buildpack.IncludeFiles = %s, expected empty []string", actual) - } - }) - - it("returns empty []string if include_files does not exist", func() { - b := libbuildpack.Buildpack{} - - actual, err := libjavabuildpack.Buildpack{Buildpack: b}.IncludeFiles() - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, []string{}) { - t.Errorf("Buildpack.IncludeFiles = %s, expected empty []string", actual) - } - }) - - it("returns false if include_files is not []string", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{ - "include_files": 1, - }, - } - - _, err := libjavabuildpack.Buildpack{Buildpack: b}.IncludeFiles() - if err.Error() != "include_files is not an array of strings" { - t.Errorf("Buildpack.IncludeFiles = %s, expected include_files is not an array of strings", err.Error()) - } - }) - - it("returns pre_package if it exists", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{ - "pre_package": "test-package", - }, - } - - actual, ok := libjavabuildpack.Buildpack{Buildpack: b}.PrePackage() - if !ok { - t.Errorf("Buildpack.PrePackage() = %t, expected true", ok) - } - - if actual != "test-package" { - t.Errorf("Buildpack.PrePackage() %s, expected test-package", actual) - } - }) - - it("returns false if pre_package does not exist", func() { - b := libbuildpack.Buildpack{} - - _, ok := libjavabuildpack.Buildpack{Buildpack: b}.PrePackage() - if ok { - t.Errorf("Buildpack.PrePackage() = %t, expected false", ok) - } - }) - - it("returns false if pre_package is not string", func() { - b := libbuildpack.Buildpack{ - Metadata: libbuildpack.BuildpackMetadata{ - "pre_package": 1, - }, - } - - _, ok := libjavabuildpack.Buildpack{Buildpack: b}.PrePackage() - if ok { - t.Errorf("Buildpack.PrePackage() = %t, expected false", ok) - } - }) - - it("filters by id", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id-1", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - libjavabuildpack.Dependency{ - ID: "test-id-2", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - } - - expected := libjavabuildpack.Dependency{ - ID: "test-id-2", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}} - - actual, err := d.Best("test-id-2", "1.0", "test-stack-1") - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) - } - }) - - it("filters by version constraint", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "2.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - } - - expected := libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "2.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}} - - actual, err := d.Best("test-id", "2.0", "test-stack-1") - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) - } - }) - - it("filters by stack", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-3"}}, - } - - expected := libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-3"}} - - actual, err := d.Best("test-id", "1.0", "test-stack-3") - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) - } - }) - - it("returns the best dependency", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.1"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-3"}}, - } - - expected := libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.1"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}} - - actual, err := d.Best("test-id", "1.*", "test-stack-1") - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) - } - }) - - it("returns error if there are no matching dependencies", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.0"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-3"}}, - } - - _, err := d.Best("test-id-2", "1.0", "test-stack-1") - if !strings.HasPrefix(err.Error(), "no valid dependencies") { - t.Errorf("Dependencies.Best = %s, expected no valid dependencies...", err.Error()) - } - }) - - it("substitutes all wildcard for unspecified version constraint", func() { - d := libjavabuildpack.Dependencies{ - libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.1"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}}, - } - - expected := libjavabuildpack.Dependency{ - ID: "test-id", - Name: "test-name", - Version: newVersion(t, "1.1"), - URI: "test-uri", - SHA256: "test-sha256", - Stacks: []string{"test-stack-1", "test-stack-2"}} - - actual, err := d.Best("test-id", "", "test-stack-1") - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(actual, expected) { - t.Errorf("Dependencies.Best = %s, expected %s", actual, expected) - } - }) -} - -func newVersion(t *testing.T, version string) libjavabuildpack.Version { - t.Helper() - - v, err := semver.NewVersion(version) - if err != nil { - t.Fatal(err) - } - - return libjavabuildpack.Version{Version: v} -} diff --git a/cache.go b/cache.go deleted file mode 100644 index faf579d..0000000 --- a/cache.go +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "reflect" - "strings" - - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack/internal" - "github.com/fatih/color" -) - -// Cache is an extension to libbuildpack.Cache that allows additional functionality to be added. -type Cache struct { - libbuildpack.Cache - - // BuildpackCacheRoot is the path to the root directory for the buildpack's dependency cache. - BuildpackCacheRoot string - - // Logger is used to write debug and info to the console. - Logger Logger -} - -// DependencyLayer returns a DependencyCacheLayer unique to a dependency. -func (c Cache) DependencyLayer(dependency Dependency) DependencyCacheLayer { - return DependencyCacheLayer{ - c.Layer(dependency.ID), - c.Logger, - dependency, - c.DownloadLayer(dependency), - } -} - -// DownloadLayer returns a DownloadCacheLayer unique to a dependency. -func (c Cache) DownloadLayer(dependency Dependency) DownloadCacheLayer { - return DownloadCacheLayer{ - c.Layer(dependency.SHA256), - c.Logger, - filepath.Join(c.BuildpackCacheRoot, dependency.SHA256), - dependency, - } -} - -// String makes Cache satisfy the Stringer interface. -func (c Cache) String() string { - return fmt.Sprintf("Cache{ Cache: %s, BuildpackCacheRoot: %s, Logger: %s}", - c.Cache, c.BuildpackCacheRoot, c.Logger) -} - -// DependencyCacheLayer is an extension to CacheLayer that is unique to a dependency contribution. -type DependencyCacheLayer struct { - libbuildpack.CacheLayer - - // Logger is used to write debug and info to the console. - Logger Logger - - dependency Dependency - downloadLayer DownloadCacheLayer -} - -// CacheContributor defines a callback function that is called when a dependency needs to be contributed. -type CacheContributor func(artifact string, layer DependencyCacheLayer) error - -// AppendEnv appends the value of this environment variable to any previous declarations of the value without any -// delimitation. If delimitation is important during concatenation, callers are required to add it. -func (d DependencyCacheLayer) AppendEnv(name string, format string, args ...interface{}) error { - d.Logger.SubsequentLine("Writing %s", name) - return d.CacheLayer.AppendEnv(name, format, args...) -} - -// AppendPathEnv appends the value of this environment variable to any previous declarations of the value using the OS -// path delimiter. -func (d DependencyCacheLayer) AppendPathEnv(name string, format string, args ...interface{}) error { - d.Logger.SubsequentLine("Writing %s", name) - return d.CacheLayer.AppendPathEnv(name, format, args...) -} - -// Contribute contributes an artifact to a cache layer. If the artifact has already been contributed, the cache will be -// validated and used directly. -func (d DependencyCacheLayer) Contribute(contributor CacheContributor) error { - m, err := d.readMetadata() - if err != nil { - return err - } - - if reflect.DeepEqual(d.dependency, m) { - d.Logger.FirstLine("%s: %s cached dependency", - d.Logger.PrettyVersion(d.dependency), color.GreenString("Reusing")) - return nil - } - - d.Logger.Debug("Dependency metadata %s does not match expected %s", m, d.dependency) - - d.Logger.FirstLine("%s: %s to cache", - d.Logger.PrettyVersion(d.dependency), color.YellowString("Contributing")) - - if err := os.RemoveAll(d.Root); err != nil { - return err - } - - if err := os.MkdirAll(d.Root, 0755) ; err != nil { - return err - } - - a, err := d.downloadLayer.Artifact() - if err != nil { - return err - } - - if err := contributor(a, d); err != nil { - d.Logger.Debug("Error during contribution") - return err - } - - return d.writeMetadata() -} - -// Override overrides any existing value for an environment variable with this value. -func (d DependencyCacheLayer) OverrideEnv(name string, format string, args ...interface{}) error { - d.Logger.SubsequentLine("Writing %s", name) - return d.CacheLayer.OverrideEnv(name, format, args...) -} - -func (d DependencyCacheLayer) metadataPath() string { - return filepath.Join(d.Root, "dependency.toml") -} - -func (d DependencyCacheLayer) readMetadata() (Dependency, error) { - f := d.metadataPath() - - exists, err := FileExists(f) - if err != nil || !exists { - d.Logger.Debug("Dependency metadata %s does not exist", f) - return Dependency{}, err - } - - var dep Dependency - - if err = FromTomlFile(f, &dep); err != nil { - d.Logger.Debug("Dependency metadata %s is not structured correctly", f) - return Dependency{}, err - } - - d.Logger.Debug("Reading dependency metadata: %s => %s", f, dep) - return dep, nil -} - -func (d DependencyCacheLayer) writeMetadata() error { - f := d.metadataPath() - d.Logger.Debug("Writing dependency metadata: %s <= %s", f, d.dependency) - - toml, err := internal.ToTomlString(d.dependency) - if err != nil { - return err - } - - return WriteToFile(strings.NewReader(toml), f, 0644) -} - -// String makes DependencyCacheLayer satisfy the Stringer interface. -func (d DependencyCacheLayer) String() string { - return fmt.Sprintf("DependencyCacheLayer{ CacheLayer: %s, Logger: %s, dependency: %s }", - d.CacheLayer, d.Logger, d.dependency) -} - -// DownloadCacheLayer is an extension to CacheLayer that is unique to a dependency download. -type DownloadCacheLayer struct { - libbuildpack.CacheLayer - - // Logger is used to write debug and info to the console. - Logger Logger - - buildpackLayerRoot string - - dependency Dependency -} - -// Artifact returns the path to an artifact cached in the layer. If the artifact has already been downloaded, the cache -// will be validated and used directly. -func (d DownloadCacheLayer) Artifact() (string, error) { - m, err := d.readMetadata(d.buildpackLayerRoot) - if err != nil { - return "", err - } - - if reflect.DeepEqual(d.dependency, m) { - d.Logger.SubsequentLine("%s cached download from buildpack", color.GreenString("Reusing")) - return filepath.Join(d.buildpackLayerRoot, filepath.Base(d.dependency.URI)), nil - } - - m, err = d.readMetadata(d.Root) - if err != nil { - return "", err - } - - a := filepath.Join(d.Root, filepath.Base(d.dependency.URI)) - - if reflect.DeepEqual(d.dependency, m) { - d.Logger.SubsequentLine("%s cached download from previous build", color.GreenString("Reusing")) - return a, nil - } - - d.Logger.Debug("Download metadata %s does not match expected %s", m, d.dependency) - - d.Logger.SubsequentLine("%s from %s", color.YellowString("Downloading"), d.dependency.URI) - - err = d.download(a) - if err != nil { - return "", err - } - - d.Logger.SubsequentLine("Verifying checksum") - err = d.verify(a) - if err != nil { - return "", err - } - - if err := d.writeMetadata(d.Root); err != nil { - return "", err - } - - return a, nil -} - -// Metadata returns the path to the metadata file for an artifact cached in the later. -func (d DownloadCacheLayer) Metadata(root string) string { - return filepath.Join(root, "dependency.toml") -} - -// String makes DownloadCacheLayer satisfy the Stringer interface. -func (d DownloadCacheLayer) String() string { - return fmt.Sprintf("DownloadCacheLayer{ CacheLayer: %s, Logger: %s, buildpackLayerRoot: %s, dependency: %s }", - d.CacheLayer, d.Logger, d.buildpackLayerRoot, d.dependency) -} - -func (d DownloadCacheLayer) download(file string) error { - resp, err := http.Get(d.dependency.URI) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return fmt.Errorf("could not download: %bd", resp.StatusCode) - } - - return WriteToFile(resp.Body, file, 0644) -} - -func (d DownloadCacheLayer) readMetadata(root string) (Dependency, error) { - metadata := d.Metadata(root) - - exists, err := FileExists(metadata) - if err != nil || !exists { - d.Logger.Debug("Download metadata %s does not exist", metadata) - return Dependency{}, err - } - - var dep Dependency - - if err = FromTomlFile(metadata, &dep); err != nil { - d.Logger.Debug("Download metadata %s is not structured correctly", metadata) - return Dependency{}, err - } - - d.Logger.Debug("Reading download metadata: %s => %s", metadata, dep) - return dep, nil -} - -func (d DownloadCacheLayer) verify(file string) error { - s := sha256.New() - - f, err := os.Open(file) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(s, f) - if err != nil { - return err - } - - actualSha256 := hex.EncodeToString(s.Sum(nil)) - - if actualSha256 != d.dependency.SHA256 { - return fmt.Errorf("dependency sha256 mismatch: expected sha256 %s, actual sha256 %s", - d.dependency.SHA256, actualSha256) - } - return nil -} - -func (d DownloadCacheLayer) writeMetadata(root string) error { - f := d.Metadata(root) - d.Logger.Debug("Writing cache metadata: %s <= %s", f, d.dependency) - - toml, err := internal.ToTomlString(d.dependency) - if err != nil { - return err - } - - return WriteToFile(strings.NewReader(toml), f, 0644) -} diff --git a/cache_test.go b/cache_test.go index 4202bf5..04711d6 100644 --- a/cache_test.go +++ b/cache_test.go @@ -16,309 +16,309 @@ package libjavabuildpack_test -import ( - "path/filepath" - "strings" - "testing" - - "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/internal" - "github.com/cloudfoundry/libjavabuildpack/test" - "github.com/h2non/gock" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestCache(t *testing.T) { - spec.Run(t, "Cache", testCache, spec.Report(report.Terminal{})) -} - -func testCache(t *testing.T, when spec.G, it spec.S) { - - when("DependencyCacheLayer", func() { - - it("creates a dependency cache with the dependency id", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - dependency := libjavabuildpack.Dependency{ID: "test-id"} - - d := cache.DependencyLayer(dependency) - - expected := filepath.Join(root, "test-id") - if d.Root != expected { - t.Errorf("DependencyCacheLayer.Root = %s, expected %s", d.Root, expected) - } - }) - - it("contributes a dependency", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - defer gock.Off() - - gock.New("http://test.com"). - Get("/test-path"). - Reply(200). - BodyString("test-payload") - - contributed := false - - err = cache.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { - contributed = true - return nil - }) - if err != nil { - t.Fatal(err) - } - - if !contributed { - t.Errorf("Expected contribution but didn't contribute") - } - }) - - it("creates cache layer when contribute called", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - defer gock.Off() - - gock.New("http://test.com"). - Get("/test-path"). - Reply(200). - BodyString("test-payload") - - layer := cache.DependencyLayer(dependency) - err = layer.Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { - internal.FileExists(t, layer.Root) - return nil - }) - if err != nil { - t.Fatal(err) - } - }) - - it("does not contribute a dependency", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, dependency.ID, "dependency.toml"), 0644) - - contributed := false - - err = cache.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { - contributed = true - return nil - }) - if err != nil { - t.Fatal(err) - } - - if contributed { - t.Errorf("Expected non-contribution but did contribute") - } - }) - }) - - when("DownloadCacheLayer", func() { - - it("creates a download cache with the dependency SHA256 name", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - dependency := libjavabuildpack.Dependency{SHA256: "test-sha256"} - - d := cache.DownloadLayer(dependency) - - expected := filepath.Join(root, "test-sha256") - if d.Root != expected { - t.Errorf("DownloadCacheLayer.Root = %s, expected %s", d.Root, expected) - } - }) - - it("downloads a dependency", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - defer gock.Off() - - gock.New("http://test.com"). - Get("/test-path"). - Reply(200). - BodyString("test-payload") - - a, err := cache.DownloadLayer(dependency).Artifact() - if err != nil { - t.Fatal(err) - } - - expected := filepath.Join(root, dependency.SHA256, "test-path") - if a != expected { - t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) - } - - internal.BeFileLike(t, expected, 0644, "test-payload") - - expected = filepath.Join(root, dependency.SHA256, "dependency.toml") - internal.BeFileLike(t, expected, 0644, `id = "" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`) - }) - - it("does not download a buildpack cached dependency", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{ - Cache: libbuildpack.Cache{Root: root}, - BuildpackCacheRoot: filepath.Join(root, "buildpack"), - } - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, "buildpack", dependency.SHA256, "dependency.toml"), 0644) - - a, err := cache.DownloadLayer(dependency).Artifact() - if err != nil { - t.Fatal(err) - } - - expected := filepath.Join(root, "buildpack", dependency.SHA256, "test-path") - if a != expected { - t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) - } - }) - - it("does not download a previously cached dependency", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) - - a, err := cache.DownloadLayer(dependency).Artifact() - if err != nil { - t.Fatal(err) - } - - expected := filepath.Join(root, dependency.SHA256, "test-path") - if a != expected { - t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) - } - }) - - it("returns metadata location", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) - - layer := cache.DownloadLayer(dependency) - actual := layer.Metadata(layer.Root) - - expected := filepath.Join(root, dependency.SHA256, "dependency.toml") - if actual != expected { - t.Errorf("DownloadLayer.Metadata() = %s, expected %s", actual, expected) - } - }) - }) - -} +// import ( +// "path/filepath" +// "strings" +// "testing" +// +// "github.com/Masterminds/semver" +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/internal" +// "github.com/cloudfoundry/libjavabuildpack/test" +// "github.com/h2non/gock" +// "github.com/sclevine/spec" +// "github.com/sclevine/spec/report" +// ) +// +// func TestCache(t *testing.T) { +// spec.Run(t, "Cache", testCache, spec.Report(report.Terminal{})) +// } +// +// func testCache(t *testing.T, when spec.G, it spec.S) { +// +// when("DependencyCacheLayer", func() { +// +// it("creates a dependency cache with the dependency id", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// dependency := libjavabuildpack.Dependency{ID: "test-id"} +// +// d := cache.DependencyLayer(dependency) +// +// expected := filepath.Join(root, "test-id") +// if d.Root != expected { +// t.Errorf("DependencyCacheLayer.Root = %s, expected %s", d.Root, expected) +// } +// }) +// +// it("contributes a dependency", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// defer gock.Off() +// +// gock.New("http://test.com"). +// Get("/test-path"). +// Reply(200). +// BodyString("test-payload") +// +// contributed := false +// +// err = cache.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { +// contributed = true +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// if !contributed { +// t.Errorf("Expected contribution but didn't contribute") +// } +// }) +// +// it("creates cache layer when contribute called", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// defer gock.Off() +// +// gock.New("http://test.com"). +// Get("/test-path"). +// Reply(200). +// BodyString("test-payload") +// +// layer := cache.DependencyLayer(dependency) +// err = layer.Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { +// internal.FileExists(t, layer.Root) +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// }) +// +// it("does not contribute a dependency", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, dependency.ID, "dependency.toml"), 0644) +// +// contributed := false +// +// err = cache.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyCacheLayer) error { +// contributed = true +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// if contributed { +// t.Errorf("Expected non-contribution but did contribute") +// } +// }) +// }) +// +// when("DownloadCacheLayer", func() { +// +// it("creates a download cache with the dependency SHA256 name", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// dependency := libjavabuildpack.Dependency{SHA256: "test-sha256"} +// +// d := cache.DownloadLayer(dependency) +// +// expected := filepath.Join(root, "test-sha256") +// if d.Root != expected { +// t.Errorf("DownloadCacheLayer.Root = %s, expected %s", d.Root, expected) +// } +// }) +// +// it("downloads a dependency", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// defer gock.Off() +// +// gock.New("http://test.com"). +// Get("/test-path"). +// Reply(200). +// BodyString("test-payload") +// +// a, err := cache.DownloadLayer(dependency).Artifact() +// if err != nil { +// t.Fatal(err) +// } +// +// expected := filepath.Join(root, dependency.SHA256, "test-path") +// if a != expected { +// t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) +// } +// +// internal.BeFileLike(t, expected, 0644, "test-payload") +// +// expected = filepath.Join(root, dependency.SHA256, "dependency.toml") +// internal.BeFileLike(t, expected, 0644, `id = "" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `) +// }) +// +// it("does not download a buildpack cached dependency", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{ +// Cache: libbuildpack.Cache{Root: root}, +// BuildpackCacheRoot: filepath.Join(root, "buildpack"), +// } +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, "buildpack", dependency.SHA256, "dependency.toml"), 0644) +// +// a, err := cache.DownloadLayer(dependency).Artifact() +// if err != nil { +// t.Fatal(err) +// } +// +// expected := filepath.Join(root, "buildpack", dependency.SHA256, "test-path") +// if a != expected { +// t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) +// } +// }) +// +// it("does not download a previously cached dependency", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) +// +// a, err := cache.DownloadLayer(dependency).Artifact() +// if err != nil { +// t.Fatal(err) +// } +// +// expected := filepath.Join(root, dependency.SHA256, "test-path") +// if a != expected { +// t.Errorf("DownloadCacheLayer.Artifact() = %s, expected %s", a, expected) +// } +// }) +// +// it("returns metadata location", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) +// +// layer := cache.DownloadLayer(dependency) +// actual := layer.Metadata(layer.Root) +// +// expected := filepath.Join(root, dependency.SHA256, "dependency.toml") +// if actual != expected { +// t.Errorf("DownloadLayer.Metadata() = %s, expected %s", actual, expected) +// } +// }) +// }) +// +// } diff --git a/detect.go b/detect.go index dced538..c897cae 100644 --- a/detect.go +++ b/detect.go @@ -16,38 +16,38 @@ package libjavabuildpack -import ( - "fmt" - - "github.com/buildpack/libbuildpack" -) - -// Detect is an extension to libbuildpack.Detect that allows additional functionality to be added. -type Detect struct { - libbuildpack.Detect - - // Buildpack represents the metadata associated with a buildpack. - Buildpack Buildpack - - // Logger is used to write debug and info to the console. - Logger Logger -} - -// String makes Detect satisfy the Stringer interface. -func (d Detect) String() string { - return fmt.Sprintf("Detect{ Detect: %s, Buildpack: %s, Logger: %s }", d.Detect, d.Buildpack, d.Logger) -} - -// DefaultDetect creates a new instance of Detect using default values. -func DefaultDetect() (Detect, error) { - d, err := libbuildpack.DefaultDetect() - if err != nil { - return Detect{}, err - } - - return Detect{ - d, - NewBuildpack(d.Buildpack), - Logger{d.Logger}, - }, nil -} +// import ( +// "fmt" +// +// "github.com/buildpack/libbuildpack" +// ) +// +// // Detect is an extension to libbuildpack.Detect that allows additional functionality to be added. +// type Detect struct { +// libbuildpack.Detect +// +// // Buildpack represents the metadata associated with a buildpack. +// Buildpack Buildpack +// +// // Logger is used to write debug and info to the console. +// Logger Logger +// } +// +// // String makes Detect satisfy the Stringer interface. +// func (d Detect) String() string { +// return fmt.Sprintf("Detect{ Detect: %s, Buildpack: %s, Logger: %s }", d.Detect, d.Buildpack, d.Logger) +// } +// +// // DefaultDetect creates a new instance of Detect using default values. +// func DefaultDetect() (Detect, error) { +// d, err := libbuildpack.DefaultDetect() +// if err != nil { +// return Detect{}, err +// } +// +// return Detect{ +// d, +// NewBuildpack(d.Buildpack), +// Logger{d.Logger}, +// }, nil +// } diff --git a/detect_test.go b/detect_test.go index 7d44b77..ae61aed 100644 --- a/detect_test.go +++ b/detect_test.go @@ -16,269 +16,184 @@ package libjavabuildpack_test -import ( - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/test" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestDetect(t *testing.T) { - spec.Run(t, "Detect", testDetect, spec.Report(report.Terminal{})) -} - -func testDetect(t *testing.T, when spec.G, it spec.S) { - - it("contains default values", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - console, d := test.ReplaceConsole(t) - defer d() - - console.In(t, `[alpha] - version = "alpha-version" - name = "alpha-name" - -[bravo] - name = "bravo-name" -`) - - in := strings.NewReader(`[buildpack] -id = "buildpack-id" -name = "buildpack-name" -version = "buildpack-version" - -[[stacks]] -id = 'stack-id' -build-images = ["build-image-tag"] -run-images = ["run-image-tag"] - -[metadata] -test-key = "test-value" -`) - - err := libjavabuildpack.WriteToFile(in, filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - if reflect.DeepEqual(detect.Application, libbuildpack.Application{}) { - t.Errorf("detect.Application should not be empty") - } - - if reflect.DeepEqual(detect.Buildpack, libjavabuildpack.Buildpack{}) { - t.Errorf("detect.Buildpack should not be empty") - } - - if reflect.DeepEqual(detect.BuildPlan, libbuildpack.BuildPlan{}) { - t.Errorf("detect.BuildPlan should not be empty") - } - - if reflect.DeepEqual(detect.Logger, libjavabuildpack.Logger{}) { - t.Errorf("detect.Logger should not be empty") - } - - if reflect.DeepEqual(detect.Stack, "") { - t.Errorf("detect.Stack should not be empty") - } - }) - - it("suppresses debug output", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - detect.Logger.Debug("test-debug-output") - detect.Logger.Info("test-info-output") - - stderr := c.Err(t) - - if strings.Contains(stderr, "test-debug-output") { - t.Errorf("stderr contained test-debug-output, expected not") - } - - if !strings.Contains(stderr, "test-info-output") { - t.Errorf("stderr did not contain test-info-output, expected to") - } - - if c.Out(t) != "" { - t.Errorf("stdout was not empty, expected empty") - } - }) - - it("allows debug output if BP_DEBUG is set", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - defer test.ReplaceEnv(t, "BP_DEBUG", "")() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - detect.Logger.Debug("test-debug-output") - detect.Logger.Info("test-info-output") - - stderr := c.Err(t) - - if !strings.Contains(stderr, "test-debug-output") { - t.Errorf("stderr did not contain test-debug-output, expected to") - } - - if !strings.Contains(stderr, "test-info-output") { - t.Errorf("stderr did not contain test-info-output, expected to") - } - - if c.Out(t) != "" { - t.Errorf("stdout was not empty, expected empty") - } - }) - - it("returns code when erroring", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - actual, d := test.CaptureExitStatus(t) - defer d() - - detect.Error(42) - - if *actual != 42 { - t.Errorf("os.Exit = %d, expected 42", *actual) - } - }) - - it("returns 100 when failing", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - actual, d := test.CaptureExitStatus(t) - defer d() - - detect.Fail() - - if *actual != 100 { - t.Errorf("os.Exit = %d, expected 100", *actual) - } - }) - - it("returns 0 and BuildPlan when passing", func() { - root := test.ScratchDir(t, "detect") - defer test.ReplaceWorkingDirectory(t, root)() - defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() - - c, d := test.ReplaceConsole(t) - defer d() - c.In(t, "") - - err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) - if err != nil { - t.Fatal(err) - } - - defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"))() - - detect, err := libjavabuildpack.DefaultDetect() - if err != nil { - t.Fatal(err) - } - - actual, d := test.CaptureExitStatus(t) - defer d() - - detect.Pass(libbuildpack.BuildPlan{ - "alpha": libbuildpack.BuildPlanDependency{Provider: "test-provider", Version: "test-version"}, - }) - - if *actual != 0 { - t.Errorf("os.Exit = %d, expected 0", *actual) - } - - stdout := c.Out(t) - expectedStdout := `[alpha] - provider = "test-provider" - version = "test-version" -` - - if stdout != expectedStdout { - t.Errorf("stdout = %s, expected %s", stdout, expectedStdout) - } - }) -} +// import ( +// "path/filepath" +// "reflect" +// "strings" +// "testing" +// +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/internal" +// "github.com/cloudfoundry/libjavabuildpack/test" +// "github.com/sclevine/spec" +// "github.com/sclevine/spec/report" +// ) +// +// func TestDetect(t *testing.T) { +// spec.Run(t, "Detect", testDetect, spec.Report(report.Terminal{})) +// } +// +// func testDetect(t *testing.T, when spec.G, it spec.S) { +// +// it("contains default values", func() { +// root := test.ScratchDir(t, "detect") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// console, d := test.ReplaceConsole(t) +// defer d() +// +// console.In(t, `[alpha] +// version = "alpha-version" +// name = "alpha-name" +// +// [bravo] +// name = "bravo-name" +// `) +// +// in := strings.NewReader(`[buildpack] +// id = "buildpack-id" +// name = "buildpack-name" +// version = "buildpack-version" +// +// [[stacks]] +// id = 'stack-id' +// build-images = ["build-image-tag"] +// run-images = ["run-image-tag"] +// +// [metadata] +// test-key = "test-value" +// `) +// +// err := libjavabuildpack.WriteToFile(in, filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root)() +// +// detect, err := libjavabuildpack.DefaultDetect() +// if err != nil { +// t.Fatal(err) +// } +// +// if reflect.DeepEqual(detect.Application, libbuildpack.Application{}) { +// t.Errorf("detect.Application should not be empty") +// } +// +// if reflect.DeepEqual(detect.Buildpack, libjavabuildpack.Buildpack{}) { +// t.Errorf("detect.Buildpack should not be empty") +// } +// +// if reflect.DeepEqual(detect.BuildPlan, libbuildpack.BuildPlan{}) { +// t.Errorf("detect.BuildPlan should not be empty") +// } +// +// if reflect.DeepEqual(detect.Logger, libjavabuildpack.Logger{}) { +// t.Errorf("detect.Logger should not be empty") +// } +// +// if reflect.DeepEqual(detect.Stack, "") { +// t.Errorf("detect.Stack should not be empty") +// } +// }) +// +// it("returns code when erroring", func() { +// root := test.ScratchDir(t, "detect") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// c, d := test.ReplaceConsole(t) +// defer d() +// c.In(t, "") +// +// err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root)() +// +// detect, err := libjavabuildpack.DefaultDetect() +// if err != nil { +// t.Fatal(err) +// } +// +// actual, d := test.CaptureExitStatus(t) +// defer d() +// +// detect.Error(42) +// +// if *actual != 42 { +// t.Errorf("os.Exit = %d, expected 42", *actual) +// } +// }) +// +// it("returns 100 when failing", func() { +// root := test.ScratchDir(t, "detect") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// c, d := test.ReplaceConsole(t) +// defer d() +// c.In(t, "") +// +// err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root)() +// +// detect, err := libjavabuildpack.DefaultDetect() +// if err != nil { +// t.Fatal(err) +// } +// +// actual, d := test.CaptureExitStatus(t) +// defer d() +// +// detect.Fail() +// +// if *actual != 100 { +// t.Errorf("os.Exit = %d, expected 100", *actual) +// } +// }) +// +// it("returns 0 and BuildPlan when passing", func() { +// root := test.ScratchDir(t, "detect") +// defer test.ReplaceWorkingDirectory(t, root)() +// defer test.ReplaceEnv(t, "PACK_STACK_ID", "test-stack")() +// +// c, d := test.ReplaceConsole(t) +// defer d() +// c.In(t, "") +// +// err := libjavabuildpack.WriteToFile(strings.NewReader(""), filepath.Join(root, "buildpack.toml"), 0644) +// if err != nil { +// t.Fatal(err) +// } +// +// defer test.ReplaceArgs(t, filepath.Join(root, "bin", "test"), root)() +// +// detect, err := libjavabuildpack.DefaultDetect() +// if err != nil { +// t.Fatal(err) +// } +// +// actual, d := test.CaptureExitStatus(t) +// defer d() +// +// detect.Pass(libbuildpack.BuildPlan{ +// "alpha": libbuildpack.BuildPlanDependency{Version: "test-version"}, +// }) +// +// if *actual != 0 { +// t.Errorf("os.Exit = %d, expected 0", *actual) +// } +// +// internal.BeFileLike(t, filepath.Join(root, "env", "alpha"), 0644, `version = "test-version" +// `) +// }) +// } diff --git a/go.mod b/go.mod index fb2b8af..8acab60 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,13 @@ -module github.com/cloudfoundry/libjavabuildpack +module github.com/cloudfoundry/libcfbuildpack require ( github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.4.2 - github.com/bouk/monkey v1.0.1 - github.com/buildpack/libbuildpack v1.2.0 + github.com/buildpack/libbuildpack v1.3.1-0.20181128132724-fe6bbda9eae8 github.com/fatih/color v1.7.0 - github.com/h2non/gock v1.0.11 github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect - github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect - github.com/sclevine/spec v1.1.0 - golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc // indirect + github.com/mitchellh/mapstructure v1.1.2 + github.com/sclevine/spec v1.2.0 + golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect ) diff --git a/go.sum b/go.sum index f8b3128..950a1d8 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,19 @@ -bou.ke/monkey v1.0.1 h1:zEMLInw9xvNakzUUPjfS4Ds6jYPqCFx3m7bRmG5NH2U= -bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/bouk/monkey v1.0.1 h1:82kWEtyEjyfkRZb0DaQ5+7O5dJfe3GzF/o97+yUo5d0= -github.com/bouk/monkey v1.0.1/go.mod h1:PG/63f4XEUlVyW1ttIeOJmJhhe1+t9EC/je3eTjvFhE= -github.com/buildpack/libbuildpack v1.2.0 h1:JeHc0JakalZOmIfF/z9f4oxS8zdauXLs2BydEIoX7Io= -github.com/buildpack/libbuildpack v1.2.0/go.mod h1:mQkH0X/7BBA87G6rVJvNcjEzomueEgAJE6/J7pO9xcM= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/buildpack/libbuildpack v1.3.0 h1:eecLC/FBNzeisIjX94+9ydTDQDBMbV5xWEE0cjC/DeA= +github.com/buildpack/libbuildpack v1.3.1-0.20181128132724-fe6bbda9eae8 h1:gCyYg3L0ksVc4NHFmNqKHh2byCWIGOxw8weBYchjPDw= +github.com/buildpack/libbuildpack v1.3.1-0.20181128132724-fe6bbda9eae8/go.mod h1:H7wn9AUhH7wcIWQcS+M/lyiDd3cz1dsp/u9KUPjztmo= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/h2non/gock v1.0.11 h1:oBecVNc5BAdoNW19NoMfkxjlIaTV9xzfc4o/j1TDwFs= -github.com/h2non/gock v1.0.11/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sclevine/spec v1.0.0 h1:ILQ08A/CHCz8GGqivOvI54Hy1U40wwcpkf7WtB1MQfY= -github.com/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/sclevine/spec v1.1.0 h1:7EWESOB+NzthnQkqoUv/fgIhygAtb6Sx1FIyMcf+pV4= -github.com/sclevine/spec v1.1.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc h1:SdCq5U4J+PpbSDIl9bM0V1e1Ug1jsnBkAFvTs1htn7U= -golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/internal/matchers.go b/internal/matchers.go deleted file mode 100644 index 73406f2..0000000 --- a/internal/matchers.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 internal - -import ( - "io/ioutil" - "os" - "testing" -) - -// BeFileLike tests that a file exists, has a specific mode, and specific content. -func BeFileLike(t *testing.T, file string, mode os.FileMode, content string) { - t.Helper() - - FileExists(t, file) - fileModeMatches(t, file, mode) - fileContentMatches(t, file, content) -} - -// FileExists tests that a file exists -func FileExists(t *testing.T, file string) { - t.Helper() - - _, err := os.Stat(file) - if err != nil { - if os.IsNotExist(err) { - t.Errorf("File %s does not exist", file) - } - - t.Fatal(err) - } -} - -func fileModeMatches(t *testing.T, file string, mode os.FileMode) { - t.Helper() - - fi, err := os.Stat(file) - if err != nil { - t.Fatal(err) - } - - if fi.Mode() != mode { - t.Errorf("FileMode = %#o, wanted %#o", fi.Mode(), mode) - } -} - -func fileContentMatches(t *testing.T, file string, content string) { - t.Helper() - - b, err := ioutil.ReadFile(file) - if err != nil { - t.Fatal(err) - } - - actual := string(b) - if actual != content { - t.Errorf("File content = %s, wanted = %s", actual, content) - } -} diff --git a/internal/test_helpers.go b/internal/test_helpers.go new file mode 100644 index 0000000..11a974f --- /dev/null +++ b/internal/test_helpers.go @@ -0,0 +1,40 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 internal + +import ( + "io/ioutil" + "path/filepath" + "testing" +) + +// ScratchDir returns a safe scratch directory for tests to modify. +func ScratchDir(t *testing.T, prefix string) string { + t.Helper() + + tmp, err := ioutil.TempDir("", prefix) + if err != nil { + t.Fatal(err) + } + + abs, err := filepath.EvalSymlinks(tmp) + if err != nil { + t.Fatal(err) + } + + return abs +} diff --git a/internal/util.go b/internal/util.go index cb9bc19..b58980d 100644 --- a/internal/util.go +++ b/internal/util.go @@ -22,6 +22,7 @@ import ( "github.com/BurntSushi/toml" ) +// TODO: Maybe Remove? func ToTomlString(v interface{}) (string, error) { var b bytes.Buffer diff --git a/launch.go b/launch.go deleted file mode 100644 index 5e7f40b..0000000 --- a/launch.go +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - - "github.com/buildpack/libbuildpack" - "github.com/fatih/color" -) - -// Launch is an extension to libbuildpack.Launch that allows additional functionality to be added. -type Launch struct { - libbuildpack.Launch - - // Cache is the Cache to use to acquire dependencies. - Cache Cache - - // Logger logger is used to write debug and info to the console. - Logger Logger -} - -// DependencyLayer returns a DependencyLaunchLayer unique to a dependency. -func (l Launch) DependencyLayer(dependency Dependency) DependencyLaunchLayer { - return DependencyLaunchLayer{ - l.Layer(dependency.ID), - l.Logger, - dependency, - l.Cache.DownloadLayer(dependency), - } -} - -// String makes Launch satisfy the Stringer interface. -func (l Launch) String() string { - return fmt.Sprintf("Launch{ Launch: %s Cache: %s, Logger: %s }", l.Launch, l.Cache, l.Logger) -} - -// WriteMetadata writes Launch metadata to the filesystem. -func (l Launch) WriteMetadata(metadata libbuildpack.LaunchMetadata) error { - l.Logger.FirstLine("Process types:") - - max := l.maximumTypeLength(metadata) - - for _, t := range metadata.Processes { - s := color.CyanString(t.Type) + ":" - - for i := 0; i < (max - len(t.Type)); i++ { - s += " " - } - - l.Logger.SubsequentLine("%s %s", s, t.Command) - } - - return l.Launch.WriteMetadata(metadata) -} - -func (l Launch) maximumTypeLength(metadata libbuildpack.LaunchMetadata) int { - max := 0 - - for _, t := range metadata.Processes { - l := len(t.Type) - - if l > max { - max = l - } - } - - return max -} - -// DependencyLaunchLayer is an extension to LaunchLayer that is unique to a dependency. -type DependencyLaunchLayer struct { - libbuildpack.LaunchLayer - - // Logger is used to write debug and info to the console. - Logger Logger - - dependency Dependency - downloadLayer DownloadCacheLayer -} - -// ArtifactName returns the name portion of the download path for the dependency. -func (d DependencyLaunchLayer) ArtifactName() string { - return filepath.Base(d.dependency.URI) -} - -// String makes DependencyLaunchLayer satisfy the Stringer interface. -func (d DependencyLaunchLayer) String() string { - return fmt.Sprintf("DependencyLaunchLayer{ LaunchLayer: %s, Logger: %s, dependency: %s, downloadLayer: %s }", - d.LaunchLayer, d.Logger, d.dependency, d.dependency) -} - -// LaunchContributor defines a callback function that is called when a dependency needs to be contributed. -type LaunchContributor func(artifact string, layer DependencyLaunchLayer) error - -// Contribute facilitates custom contribution of an artifact to a launch layer. If the artifact has already been -// contributed, the contribution is validated and the contributor is not called. -func (d DependencyLaunchLayer) Contribute(contributor LaunchContributor) error { - var m Dependency - - if err := d.ReadMetadata(&m); err != nil { - d.Logger.Debug("Dependency metadata is not structured correctly") - return err - } - - if reflect.DeepEqual(d.dependency, m) { - d.Logger.FirstLine("%s: %s cached launch layer", - d.Logger.PrettyVersion(d.dependency), color.GreenString("Reusing")) - return nil - } - - d.Logger.Debug("Download metadata %s does not match expected %s", m, d.dependency) - - d.Logger.FirstLine("%s: %s to launch", - d.Logger.PrettyVersion(d.dependency), color.YellowString("Contributing")) - - if err := os.RemoveAll(d.Root); err != nil { - return err - } - - if err := os.MkdirAll(d.Root, 0755) ; err != nil { - return err - } - - a, err := d.downloadLayer.Artifact() - if err != nil { - return err - } - - if err := contributor(a, d); err != nil { - d.Logger.Debug("Error during contribution") - return err; - } - - return d.WriteMetadata(d.dependency) -} - -// WriteProfile writes a file to profile.d with this value. -func (d DependencyLaunchLayer) WriteProfile(file string, format string, args ...interface{}) error { - d.Logger.SubsequentLine("Writing .profile.d/%s", file) - return d.LaunchLayer.WriteProfile(file, format, args...) -} diff --git a/launch_test.go b/launch_test.go index feec776..20c2aef 100644 --- a/launch_test.go +++ b/launch_test.go @@ -16,189 +16,164 @@ package libjavabuildpack_test -import ( - "bytes" - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/internal" - "github.com/cloudfoundry/libjavabuildpack/test" - "github.com/fatih/color" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestLaunch(t *testing.T) { - spec.Run(t, "Launch", testLaunch, spec.Report(report.Terminal{})) -} - -func testLaunch(t *testing.T, when spec.G, it spec.S) { - - it("creates a dependency launch with the dependency id", func() { - root := test.ScratchDir(t, "launch") - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}} - dependency := libjavabuildpack.Dependency{ID: "test-id"} - - d := launch.DependencyLayer(dependency) - - expected := filepath.Join(root, "test-id") - if d.Root != expected { - t.Errorf("DependencyLaunchLayer.Root = %s, expected %s", d.Root, expected) - } - }) - - it("calls contributor to contribute launch layer", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) - - contributed := false - - err = launch.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { - contributed = true; - return nil - }) - if err != nil { - t.Fatal(err) - } - - if !contributed { - t.Errorf("Expected contribution but didn't contribute") - } - }) - - it("creates launch layer when contribute called", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) - - layer := launch.DependencyLayer(dependency) - err = layer.Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { - internal.FileExists(t, layer.Root) - return nil - }) - if err != nil { - t.Fatal(err) - } - }) - - it("does not call contributor for a cached launch layer", func() { - root := test.ScratchDir(t, "cache") - cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} - - v, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - dependency := libjavabuildpack.Dependency{ - ID: "test-id", - Version: libjavabuildpack.Version{Version: v}, - SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", - URI: "http://test.com/test-path", - } - - libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" -name = "" -version = "1.0" -uri = "http://test.com/test-path" -sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" -`), filepath.Join(root, fmt.Sprintf("%s.toml", dependency.ID)), 0644) - - contributed := false - - err = launch.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { - contributed = true; - return nil - }) - if err != nil { - t.Fatal(err) - } - - if contributed { - t.Errorf("Expected non-contribution but did contribute") - } - }) - - it("returns artifact name", func() { - root := test.ScratchDir(t, "launch") - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}} - dependency := libjavabuildpack.Dependency{ID: "test-id", URI: "http://localhost/path/test-artifact-name"} - - d := launch.DependencyLayer(dependency) - - if d.ArtifactName() != "test-artifact-name" { - t.Errorf("DependencyLaunchLayer.ArtifactName = %s, expected test-artifact-name", d.ArtifactName()) - } - }) - - it("logs process types", func() { - root := test.ScratchDir(t, "launch") - - var info bytes.Buffer - logger := libjavabuildpack.Logger{Logger: libbuildpack.NewLogger(nil, &info)} - launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Logger: logger} - - launch.WriteMetadata(libbuildpack.LaunchMetadata{ - Processes: []libbuildpack.Process{ - {"short", "test-command-1"}, - {"a-very-long-type", "test-command-2"}, - }, - }) - - expected := fmt.Sprintf(`%s Process types: - %s: test-command-1 - %s: test-command-2 -`, color.New(color.FgRed, color.Bold).Sprint("----->"), color.CyanString("short"), - color.CyanString("a-very-long-type")) - - if info.String() != expected { - t.Errorf("Process types log = %s, expected %s", info.String(), expected) - } - }) - -} +// import ( +// "bytes" +// "fmt" +// "path/filepath" +// "strings" +// "testing" +// +// "github.com/Masterminds/semver" +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/internal" +// "github.com/cloudfoundry/libjavabuildpack/test" +// "github.com/fatih/color" +// "github.com/sclevine/spec" +// "github.com/sclevine/spec/report" +// ) +// +// func TestLaunch(t *testing.T) { +// spec.Run(t, "Launch", testLaunch, spec.Report(report.Terminal{})) +// } +// +// func testLaunch(t *testing.T, when spec.G, it spec.S) { +// +// it("creates a dependency launch with the dependency id", func() { +// root := test.ScratchDir(t, "launch") +// launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}} +// dependency := libjavabuildpack.Dependency{ID: "test-id"} +// +// d := launch.DependencyLayer(dependency) +// +// expected := filepath.Join(root, "test-id") +// if d.Root != expected { +// t.Errorf("DependencyLaunchLayer.Root = %s, expected %s", d.Root, expected) +// } +// }) +// +// it("calls contributor to contribute launch layer", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) +// +// contributed := false +// +// err = launch.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { +// contributed = true; +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// if !contributed { +// t.Errorf("Expected contribution but didn't contribute") +// } +// }) +// +// it("creates launch layer when contribute called", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, dependency.SHA256, "dependency.toml"), 0644) +// +// layer := launch.DependencyLayer(dependency) +// err = layer.Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { +// internal.FileExists(t, layer.Root) +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// }) +// +// it("does not call contributor for a cached launch layer", func() { +// root := test.ScratchDir(t, "cache") +// cache := libjavabuildpack.Cache{Cache: libbuildpack.Cache{Root: root}} +// launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}, Cache: cache} +// +// v, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// dependency := libjavabuildpack.Dependency{ +// ID: "test-id", +// Version: libjavabuildpack.Version{Version: v}, +// SHA256: "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273", +// URI: "http://test.com/test-path", +// } +// +// libjavabuildpack.WriteToFile(strings.NewReader(`id = "test-id" +// name = "" +// version = "1.0" +// uri = "http://test.com/test-path" +// sha256 = "6f06dd0e26608013eff30bb1e951cda7de3fdd9e78e907470e0dd5c0ed25e273" +// `), filepath.Join(root, fmt.Sprintf("%s.toml", dependency.ID)), 0644) +// +// contributed := false +// +// err = launch.DependencyLayer(dependency).Contribute(func(artifact string, layer libjavabuildpack.DependencyLaunchLayer) error { +// contributed = true; +// return nil +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// if contributed { +// t.Errorf("Expected non-contribution but did contribute") +// } +// }) +// +// it("returns artifact name", func() { +// root := test.ScratchDir(t, "launch") +// launch := libjavabuildpack.Launch{Launch: libbuildpack.Launch{Root: root}} +// dependency := libjavabuildpack.Dependency{ID: "test-id", URI: "http://localhost/path/test-artifact-name"} +// +// d := launch.DependencyLayer(dependency) +// +// if d.ArtifactName() != "test-artifact-name" { +// t.Errorf("DependencyLaunchLayer.ArtifactName = %s, expected test-artifact-name", d.ArtifactName()) +// } +// }) +// +// } diff --git a/layers/dependency_layer.go b/layers/dependency_layer.go new file mode 100644 index 0000000..dc265da --- /dev/null +++ b/layers/dependency_layer.go @@ -0,0 +1,60 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers + +import ( + "fmt" + + "github.com/buildpack/libbuildpack/layers" + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/cloudfoundry/libcfbuildpack/logger" +) + +// DependencyLayer is an extension to Layer that is unique to a dependency. +type DependencyLayer struct { + Layer + + // Dependency is the dependency provided by this layer + Dependency buildpack.Dependency + + // Logger is used to write debug and info to the console. + Logger logger.Logger + + downloadLayer DownloadLayer +} + +// DependencyLayerContributor defines a callback function that is called when a dependency needs to be contributed. +type DependencyLayerContributor func(artifact string, layer DependencyLayer) error + +// Contribute facilitates custom contribution of an artifact to a layer. If the artifact has already been contributed, +// the contribution is validated and the contributor is not called. +func (l DependencyLayer) Contribute(contributor DependencyLayerContributor, flags ...layers.Flag) error { + return l.Layer.Contribute(l.Dependency, func(layer Layer) error { + a, err := l.downloadLayer.Artifact() + if err != nil { + return err + } + + return contributor(a, l) + }, flags...) +} + +// String makes DependencyLayer satisfy the Stringer interface. +func (l DependencyLayer) String() string { + return fmt.Sprintf("DependencyLayer{ Layer: %s, Dependency: %s, Logger: %s, downloadLayer: %s }", + l.Layer, l.Dependency, l.Logger, l.downloadLayer) +} diff --git a/layers/download_layer.go b/layers/download_layer.go new file mode 100644 index 0000000..1b7f2b4 --- /dev/null +++ b/layers/download_layer.go @@ -0,0 +1,112 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + + "github.com/buildpack/libbuildpack/layers" + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/cloudfoundry/libcfbuildpack/logger" + "github.com/fatih/color" +) + +// DownloadLayer is an extension to Layer that is unique to a dependency download. +type DownloadLayer struct { + cacheLayer Layer + dependency buildpack.Dependency + downloadLayer Layer + logger logger.Logger +} + +// Artifact returns the path to an artifact cached in the layer. If the artifact has already been downloaded, the cache +// will be validated and used directly. +func (l DownloadLayer) Artifact() (string, error) { + artifact := filepath.Join(l.cacheLayer.Root, filepath.Base(l.dependency.URI)) + + if err := l.cacheLayer.Contribute(l.dependency, func(layer Layer) error { + return fmt.Errorf("buildpack cached dependency does not exist") + }); err == nil { + l.logger.SubsequentLine("%s cached download from buildpack", color.GreenString("Reusing")) + return artifact, nil + } + + artifact = filepath.Join(l.downloadLayer.Root, filepath.Base(l.dependency.URI)) + + if err := l.downloadLayer.Contribute(l.dependency, func(layer Layer) error { + l.logger.SubsequentLine("%s from %s", color.YellowString("Downloading"), l.dependency.URI) + if err := l.download(artifact); err != nil { + return err + } + + l.logger.SubsequentLine("Verifying checksum") + return l.verify(artifact) + }, layers.Build, layers.Cache); err != nil { + return "", err + } + + return artifact, nil +} + +// String makes DownloadLayer satisfy the Stringer interface. +func (l DownloadLayer) String() string { + return fmt.Sprintf("DownloadLayer{ cacheLayer:%s, dependency: %s, downloadLayer: %s, logger: %s }", + l.cacheLayer, l.dependency, l.downloadLayer, l.logger) +} + +func (l DownloadLayer) download(file string) error { + resp, err := http.Get(l.dependency.URI) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("could not download: %bd", resp.StatusCode) + } + + return WriteToFile(resp.Body, file, 0644) +} + +func (l DownloadLayer) verify(file string) error { + s := sha256.New() + + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() + + _, err = io.Copy(s, f) + if err != nil { + return err + } + + actualSha256 := hex.EncodeToString(s.Sum(nil)) + + if actualSha256 != l.dependency.SHA256 { + return fmt.Errorf("dependency sha256 mismatch: expected sha256 %s, actual sha256 %s", + l.dependency.SHA256, actualSha256) + } + return nil +} diff --git a/layers/layer.go b/layers/layer.go new file mode 100644 index 0000000..2bc41d3 --- /dev/null +++ b/layers/layer.go @@ -0,0 +1,137 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers + +import ( + "fmt" + "reflect" + + "github.com/buildpack/libbuildpack/layers" + "github.com/cloudfoundry/libcfbuildpack/logger" + "github.com/fatih/color" +) + +// LaunchLayer is an extension to libbuildpack.LaunchLayer that allows additional functionality to be added +type Layer struct { + layers.Layer + + // Logger is used to write debug and info to the console. + Logger logger.Logger +} + +// AppendBuildEnv appends the value of this environment variable to any previous declarations of the value without any +// delimitation. If delimitation is important during concatenation, callers are required to add it. +func (l Layer) AppendBuildEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to build", name) + return l.Layer.AppendBuildEnv(name, format, args...) +} + +// AppendLaunchEnv appends the value of this environment variable to any previous declarations of the value without any +// delimitation. If delimitation is important during concatenation, callers are required to add it. +func (l Layer) AppendLaunchEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to launch", name) + return l.Layer.AppendLaunchEnv(name, format, args...) +} + +// AppendSharedEnv appends the value of this environment variable to any previous declarations of the value without any +// delimitation. If delimitation is important during concatenation, callers are required to add it. +func (l Layer) AppendSharedEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to shared", name) + return l.Layer.AppendSharedEnv(name, format, args...) +} + +// AppendPathBuildEnv appends the value of this environment variable to any previous declarations of the value using the +// OS path delimiter. +func (l Layer) AppendPathBuildEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to build", name) + return l.Layer.AppendPathBuildEnv(name, format, args...) +} + +// AppendPathLaunchEnv appends the value of this environment variable to any previous declarations of the value using +// the OS path delimiter. +func (l Layer) AppendPathLaunchEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to launch", name) + return l.Layer.AppendPathLaunchEnv(name, format, args...) +} + +// AppendPathSharedEnv appends the value of this environment variable to any previous declarations of the value using +// the OS path delimiter. +func (l Layer) AppendPathSharedEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to shared", name) + return l.Layer.AppendPathSharedEnv(name, format, args...) +} + +// OverrideBuildEnv overrides any existing value for an environment variable with this value. +func (l Layer) OverrideBuildEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to build", name) + return l.Layer.OverrideBuildEnv(name, format, args...) +} + +// OverrideLaunchEnv overrides any existing value for an environment variable with this value. +func (l Layer) OverrideLaunchEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to launch", name) + return l.Layer.OverrideLaunchEnv(name, format, args...) +} + +// OverrideSharedEnv overrides any existing value for an environment variable with this value. +func (l Layer) OverrideSharedEnv(name string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing %s to shared", name) + return l.Layer.OverrideSharedEnv(name, format, args...) +} + +// LayerContributor defines a callback function that is called when a layer needs to be contributed. +type LayerContributor func(layer Layer) error + +// Contribute facilitates custom contribution of a layer. If the layer has already been contributed, the contribution +// is validated and the contributor is not called. +func (l Layer) Contribute(expected interface{}, contributor LayerContributor, flags ...layers.Flag) error { + actual := reflect.New(reflect.TypeOf(expected)) + + if err := l.ReadMetadata(&actual); err != nil { + l.Logger.Debug("Dependency metadata is not structured correctly") + return err + } + + if reflect.DeepEqual(actual, expected) { + l.Logger.FirstLine("%s: %s cached layer", + l.Logger.PrettyVersion(expected), color.GreenString("Reusing")) + return nil + } + + l.Logger.Debug("Layer metadata %s does not match expected %s", actual, expected) + + l.Logger.FirstLine("%s: %s to layer", + l.Logger.PrettyVersion(expected), color.YellowString("Contributing")) + + if err := contributor(l); err != nil { + l.Logger.Debug("Error during contribution") + return err + } + + return l.WriteMetadata(expected, flags...) +} + +// String makes Layer satisfy the Stringer interface. +func (l Layer) String() string { + return fmt.Sprintf("Layer{ Layer: %s, Logger: %s }", l.Layer, l.Logger) +} + +// WriteProfile writes a file to profile.d with this value. +func (l Layer) WriteProfile(file string, format string, args ...interface{}) error { + l.Logger.SubsequentLine("Writing .profile.d/%s", file) + return l.Layer.WriteProfile(file, format, args...) +} diff --git a/layers/layers.go b/layers/layers.go new file mode 100644 index 0000000..fed3282 --- /dev/null +++ b/layers/layers.go @@ -0,0 +1,101 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers + +import ( + "fmt" + + "github.com/buildpack/libbuildpack/layers" + "github.com/cloudfoundry/libcfbuildpack/buildpack" + "github.com/cloudfoundry/libcfbuildpack/logger" + "github.com/fatih/color" +) + +// Layers is an extension allows additional functionality to be added. +type Layers struct { + layers.Layers + + // BuildpackCache are the cache of dependencies in the buildpack. + BuildpackCache Layers + + // Logger logger is used to write debug and info to the console. + Logger logger.Logger +} + +// DependencyLayer returns a DependencyLayer unique to a dependency. +func (l Layers) DependencyLayer(dependency buildpack.Dependency) DependencyLayer { + return DependencyLayer{ + l.Layer(dependency.ID), + dependency, + l.Logger, + l.DownloadLayer(dependency), + } +} + +// DownloadLayer returns a DownloadLayer unique to a dependency. +func (l Layers) DownloadLayer(dependency buildpack.Dependency) DownloadLayer { + return DownloadLayer{ + l.BuildpackCache.Layer(dependency.SHA256), + dependency, + l.Layer(dependency.SHA256), + l.Logger, + } +} + +// Layer creates a Layer with a specified name. +func (l Layers) Layer(name string) Layer { + return Layer{l.Layers.Layer(name), l.Logger} +} + +// String makes Layers satisfy the Stringer interface. +func (l Layers) String() string { + return fmt.Sprintf("Layers{ Layers: %s, BuildpackCache: %s, Logger: %s }", + l.Layers, l.BuildpackCache, l.Logger) +} + +// WriteMetadata writes Launch metadata to the filesystem. +func (l Layers) WriteMetadata(metadata layers.Metadata) error { + l.Logger.FirstLine("Process types:") + + max := l.maximumTypeLength(metadata) + + for _, t := range metadata.Processes { + s := color.CyanString(t.Type) + ":" + + for i := 0; i < (max - len(t.Type)); i++ { + s += " " + } + + l.Logger.SubsequentLine("%s %s", s, t.Command) + } + + return l.Layers.WriteMetadata(metadata) +} + +func (l Layers) maximumTypeLength(metadata layers.Metadata) int { + max := 0 + + for _, t := range metadata.Processes { + l := len(t.Type) + + if l > max { + max = l + } + } + + return max +} diff --git a/layers/layers_test.go b/layers/layers_test.go new file mode 100644 index 0000000..8879d92 --- /dev/null +++ b/layers/layers_test.go @@ -0,0 +1,66 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers_test + +import ( + "bytes" + "fmt" + "testing" + + layersBp "github.com/buildpack/libbuildpack/layers" + loggerBp "github.com/buildpack/libbuildpack/logger" + "github.com/cloudfoundry/libcfbuildpack/internal" + layersCf "github.com/cloudfoundry/libcfbuildpack/layers" + loggerCf "github.com/cloudfoundry/libcfbuildpack/logger" + "github.com/fatih/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestLayers(t *testing.T) { + spec.Run(t, "Layers", testLayers, spec.Report(report.Terminal{})) +} + +func testLayers(t *testing.T, when spec.G, it spec.S) { + + it("logs process types", func() { + root := internal.ScratchDir(t, "launch") + + var info bytes.Buffer + logger := loggerCf.Logger{Logger: loggerBp.NewLogger(nil, &info)} + layers := layersCf.Layers{Layers: layersBp.Layers{Root: root}, Logger: logger} + + if err := layers.WriteMetadata(layersBp.Metadata{ + Processes: []layersBp.Process{ + {"short", "test-command-1"}, + {"a-very-long-type", "test-command-2"}, + }, + }); err != nil { + t.Fatal(err) + } + + expected := fmt.Sprintf(`%s Process types: + %s: test-command-1 + %s: test-command-2 +`, color.New(color.FgRed, color.Bold).Sprint("----->"), color.CyanString("short"), + color.CyanString("a-very-long-type")) + + if info.String() != expected { + t.Errorf("Process types log = %s, expected %s", info.String(), expected) + } + }) +} diff --git a/layers/util.go b/layers/util.go new file mode 100644 index 0000000..e67ebe7 --- /dev/null +++ b/layers/util.go @@ -0,0 +1,228 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers + +import ( + "io" + "os" + "path/filepath" +) + +// // CopyDirectory copies srcDir to destDir +// func CopyDirectory(srcDir, destDir string) error { +// destExists, _ := FileExists(destDir) +// if !destExists { +// return errors.New("destination dir must exist") +// } +// +// files, err := ioutil.ReadDir(srcDir) +// if err != nil { +// return err +// } +// +// for _, f := range files { +// src := filepath.Join(srcDir, f.Name()) +// dest := filepath.Join(destDir, f.Name()) +// +// if m := f.Mode(); m&os.ModeSymlink != 0 { +// target, err := os.Readlink(src) +// if err != nil { +// return fmt.Errorf("Error while reading symlink '%s': %v", src, err) +// } +// if err := os.Symlink(target, dest); err != nil { +// return fmt.Errorf("Error while creating '%s' as symlink to '%s': %v", dest, target, err) +// } +// } else if f.IsDir() { +// err = os.MkdirAll(dest, f.Mode()) +// if err != nil { +// return err +// } +// if err := CopyDirectory(src, dest); err != nil { +// return err +// } +// } else { +// rc, err := os.Open(src) +// if err != nil { +// return err +// } +// +// err = WriteToFile(rc, dest, f.Mode()) +// if err != nil { +// rc.Close() +// return err +// } +// rc.Close() +// } +// } +// +// return nil +// } +// +// // CopyFile copies source file to destFile, creating all intermediate directories in destFile +// func CopyFile(source, destFile string) error { +// fh, err := os.Open(source) +// if err != nil { +// return err +// } +// +// fileInfo, err := fh.Stat() +// if err != nil { +// return err +// } +// +// defer fh.Close() +// +// return WriteToFile(fh, destFile, fileInfo.Mode()) +// } +// +// // ExtractTarGz extracts tarfile to destDir +// func ExtractTarGz(tarFile, destDir string, stripComponents int) error { +// file, err := os.Open(tarFile) +// if err != nil { +// return err +// } +// defer file.Close() +// gz, err := gzip.NewReader(file) +// if err != nil { +// return err +// } +// defer gz.Close() +// return extractTar(gz, destDir, stripComponents) +// } +// +// // ExtractZip extracts zipfile to destDir +// func ExtractZip(zipfile, destDir string, stripComponents int) error { +// r, err := zip.OpenReader(zipfile) +// if err != nil { +// return err +// } +// defer r.Close() +// +// for _, f := range r.File { +// pathComponents := strings.Split(f.Name, string(filepath.Separator)) +// if len(pathComponents) <= stripComponents { +// continue +// } +// +// path := filepath.Join(append([]string{destDir}, pathComponents[stripComponents:]...)...) +// +// rc, err := f.Open() +// if err != nil { +// return err +// } +// +// if f.FileInfo().IsDir() { +// err = os.MkdirAll(path, 0755) +// } else { +// err = WriteToFile(rc, path, f.Mode()) +// } +// +// rc.Close() +// if err != nil { +// return err +// } +// } +// +// return nil +// } +// +// // FileExists returns true if a file exists, otherwise false. +// func FileExists(file string) (bool, error) { +// _, err := os.Stat(file) +// if err != nil { +// if os.IsNotExist(err) { +// return false, nil +// } +// +// return false, err +// } +// +// return true, nil +// } +// +// // FromTomlFile decodes a TOML file into a struct. +// func FromTomlFile(file string, v interface{}) error { +// _, err := toml.DecodeFile(file, v) +// return err +// } + +// WriteToFile writes the contents of an io.Reader to a file. +func WriteToFile(source io.Reader, destFile string, mode os.FileMode) error { + err := os.MkdirAll(filepath.Dir(destFile), 0755) + if err != nil { + return err + } + + fh, err := os.OpenFile(destFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + defer fh.Close() + + _, err = io.Copy(fh, source) + if err != nil { + return err + } + + return nil +} + +// func extractTar(src io.Reader, destDir string, stripComponents int) error { +// tr := tar.NewReader(src) +// +// for { +// hdr, err := tr.Next() +// if err == io.EOF { +// break +// } +// +// pathComponents := strings.Split(hdr.Name, string(filepath.Separator)) +// if len(pathComponents) <= stripComponents { +// continue +// } +// +// path := filepath.Join(append([]string{destDir}, pathComponents[stripComponents:]...)...) +// fi := hdr.FileInfo() +// +// if fi.IsDir() { +// err = os.MkdirAll(path, hdr.FileInfo().Mode()) +// } else if fi.Mode()&os.ModeSymlink != 0 { +// target := hdr.Linkname +// if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { +// return err +// } +// if err = os.Symlink(target, path); err != nil { +// return err +// } +// } else { +// err = WriteToFile(tr, path, hdr.FileInfo().Mode()) +// } +// +// if err != nil { +// return err +// } +// } +// return nil +// } +// +// func osArgs(index int) (string, error) { +// if len(os.Args) < index+1 { +// return "", fmt.Errorf("incorrect number of command line arguments") +// } +// +// return os.Args[index], nil +// } diff --git a/layers/util_test.go b/layers/util_test.go new file mode 100644 index 0000000..92250c7 --- /dev/null +++ b/layers/util_test.go @@ -0,0 +1,111 @@ +/* + * Copyright 2018 the original author or authors. + * + * 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 layers_test +// +// import ( +// "path/filepath" +// "testing" +// +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/test" +// "github.com/sclevine/spec" +// "github.com/sclevine/spec/report" +// ) +// +// func TestExtractTarGz(t *testing.T) { +// spec.Run(t, "ExtractTarGz", testExtractTarGz, spec.Report(report.Terminal{})) +// } +// +// func testExtractTarGz(t *testing.T, when spec.G, it spec.S) { +// +// when("ExtractTarGz", func() { +// +// it("extracts the archive", func() { +// root := test.ScratchDir(t, "util") +// +// err := libjavabuildpack.ExtractTarGz(test.FixturePath(t, "test-archive.tar.gz"), root, 0) +// if err != nil { +// t.Fatal(err) +// } +// +// test.BeFileLike(t, filepath.Join(root, "fileA.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "dirA", "fileB.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "dirA", "fileC.txt"), 0644, "") +// }) +// +// it("skips stripped components", func() { +// root := test.ScratchDir(t, "util") +// +// err := libjavabuildpack.ExtractTarGz(test.FixturePath(t, "test-archive.tar.gz"), root, 1) +// if err != nil { +// t.Fatal(err) +// } +// +// exists, err := libjavabuildpack.FileExists(filepath.Join(root, "fileA.txt")) +// if err != nil { +// t.Fatal(err) +// } +// +// if exists { +// t.Errorf("fileA.txt exists, expected not to") +// } +// +// test.BeFileLike(t, filepath.Join(root, "fileB.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "fileC.txt"), 0644, "") +// }) +// +// }) +// +// when("ExtractZip", func() { +// +// it("extracts the archive", func() { +// root := test.ScratchDir(t, "util") +// +// err := libjavabuildpack.ExtractZip(test.FixturePath(t, "test-archive.zip"), root, 0) +// if err != nil { +// t.Fatal(err) +// } +// +// test.BeFileLike(t, filepath.Join(root, "fileA.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "dirA", "fileB.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "dirA", "fileC.txt"), 0644, "") +// }) +// +// it("skips stripped components", func() { +// root := test.ScratchDir(t, "util") +// +// err := libjavabuildpack.ExtractZip(test.FixturePath(t, "test-archive.zip"), root, 1) +// if err != nil { +// t.Fatal(err) +// } +// +// exists, err := libjavabuildpack.FileExists(filepath.Join(root, "fileA.txt")) +// if err != nil { +// t.Fatal(err) +// } +// +// if exists { +// t.Errorf("fileA.txt exists, expected not to") +// } +// +// test.BeFileLike(t, filepath.Join(root, "fileB.txt"), 0644, "") +// test.BeFileLike(t, filepath.Join(root, "fileC.txt"), 0644, "") +// }) +// +// }) +// +// } diff --git a/logger.go b/logger/logger.go similarity index 91% rename from logger.go rename to logger/logger.go index 20aa6ff..c91b981 100644 --- a/logger.go +++ b/logger/logger.go @@ -14,12 +14,13 @@ * limitations under the License. */ -package libjavabuildpack +package logger import ( "fmt" - "github.com/buildpack/libbuildpack" + "github.com/buildpack/libbuildpack/logger" + "github.com/cloudfoundry/libcfbuildpack/buildpack" "github.com/fatih/color" ) @@ -34,7 +35,7 @@ func init() { // Logger is an extension to libbuildpack.Logger to add additional functionality. type Logger struct { - libbuildpack.Logger + logger.Logger } // FirstLine prints the log messages with the first line eye catcher. @@ -61,10 +62,10 @@ func (l Logger) PrettyVersion(v interface{}) string { var version string switch t := v.(type) { - case Buildpack: + case buildpack.Buildpack: name = t.Info.Name version = t.Info.Version - case Dependency: + case buildpack.Dependency: name = t.Name if t.Version.Version != nil { diff --git a/logger_test.go b/logger/logger_test.go similarity index 73% rename from logger_test.go rename to logger/logger_test.go index f103b96..1a76239 100644 --- a/logger_test.go +++ b/logger/logger_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package libjavabuildpack_test +package logger_test import ( "bytes" @@ -22,8 +22,10 @@ import ( "testing" "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" + buildpackBp "github.com/buildpack/libbuildpack/buildpack" + loggerBp "github.com/buildpack/libbuildpack/logger" + buildpackCf "github.com/cloudfoundry/libcfbuildpack/buildpack" + loggerCf "github.com/cloudfoundry/libcfbuildpack/logger" "github.com/fatih/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -38,7 +40,7 @@ func testLogger(t *testing.T, when spec.G, it spec.S) { it("writes eye catcher on first line", func() { var info bytes.Buffer - logger := libjavabuildpack.Logger{Logger: libbuildpack.NewLogger(nil, &info)} + logger := loggerCf.Logger{Logger: loggerBp.NewLogger(nil, &info)} logger.FirstLine("test %s", "message") expected := fmt.Sprintf("%s test message\n", color.New(color.FgRed, color.Bold).Sprint("----->")) @@ -51,7 +53,7 @@ func testLogger(t *testing.T, when spec.G, it spec.S) { it("writes indent on second line", func() { var info bytes.Buffer - logger := libjavabuildpack.Logger{Logger: libbuildpack.NewLogger(nil, &info)} + logger := loggerCf.Logger{Logger: loggerBp.NewLogger(nil, &info)} logger.SubsequentLine("test %s", "message") if info.String() != " test message\n" { @@ -60,11 +62,11 @@ func testLogger(t *testing.T, when spec.G, it spec.S) { }) it("formats pretty version for buildpack", func() { - logger := libjavabuildpack.Logger{Logger: libbuildpack.NewLogger(nil, nil)} + logger := loggerCf.Logger{Logger: loggerBp.NewLogger(nil, nil)} - buildpack := libjavabuildpack.Buildpack{ - Buildpack: libbuildpack.Buildpack{ - Info: libbuildpack.BuildpackInfo{Name: "test-name", Version: "test-version"}, + buildpack := buildpackCf.Buildpack{ + Buildpack: buildpackBp.Buildpack{ + Info: buildpackBp.Info{Name: "test-name", Version: "test-version"}, }, } @@ -78,14 +80,14 @@ func testLogger(t *testing.T, when spec.G, it spec.S) { }) it("formats pretty version for dependency", func() { - logger := libjavabuildpack.Logger{Logger: libbuildpack.NewLogger(nil, nil)} + logger := loggerCf.Logger{Logger: loggerBp.NewLogger(nil, nil)} v, err := semver.NewVersion("1.0") if err != nil { t.Fatal(err) } - actual := logger.PrettyVersion(libjavabuildpack.Dependency{Name: "test-name", Version: libjavabuildpack.Version{v}}) + actual := logger.PrettyVersion(buildpackCf.Dependency{Name: "test-name", Version: buildpackCf.Version{v}}) expected := fmt.Sprintf("%s %s", color.New(color.FgBlue, color.Bold).Sprint("test-name"), color.BlueString("1.0")) diff --git a/packager.go b/packager.go index c4dbfd6..c217999 100644 --- a/packager.go +++ b/packager.go @@ -16,204 +16,204 @@ package libjavabuildpack -import ( - "archive/tar" - "compress/gzip" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/buildpack/libbuildpack" -) - -// Packager is a root element for packaging up a buildpack -type Packager struct { - Buildpack Buildpack - Cache Cache - Logger Logger -} - -// Create creates a new buildpack package. -func (p Packager) Create() error { - p.Logger.FirstLine("Packaging %s", p.Logger.PrettyVersion(p.Buildpack)) - - if err := p.prePackage(); err != nil { - return err - } - - includedFiles, err := p.Buildpack.IncludeFiles() - if err != nil { - return err - } - - dependencyFiles, err := p.cacheDependencies() - if err != nil { - return err - } - - return p.createArchive(append(includedFiles, dependencyFiles...)) -} - -func (p Packager) addFile(out *tar.Writer, path string) error { - p.Logger.SubsequentLine("Adding %s", path) - - file, err := os.Open(filepath.Join(p.Buildpack.Root, path)) - if err != nil { - return err - } - defer file.Close() - - stat, err := file.Stat() - if err != nil { - return err - } - - header := new(tar.Header) - header.Name = path - header.Size = stat.Size() - header.Mode = int64(stat.Mode()) - header.ModTime = stat.ModTime() - - if err := out.WriteHeader(header); err != nil { - return err - } - - _, err = io.Copy(out, file) - return err -} - -func (p Packager) archivePath() (string, error) { - dir, err := osArgs(1) - if err != nil { - return "", err - } - - info := p.Buildpack.Info - - path := []string{dir} - path = append(path, strings.Split(info.ID, ".")...) - path = append(path, info.ID, info.Version) - - f := fmt.Sprintf("%s-%s.tgz", info.ID, info.Version) - f = strings.Replace(f, "SNAPSHOT", fmt.Sprintf("%s-1", time.Now().Format("20060102.150405")), 1) - - path = append(path, f) - - return filepath.Join(path...), nil -} - -func (p Packager) createArchive(files []string) error { - archive, err := p.archivePath() - if err != nil { - return err - } - - p.Logger.FirstLine("Creating archive %s", archive) - - if err = os.MkdirAll(filepath.Dir(archive), 0755); err != nil { - return err - } - - file, err := os.OpenFile(archive, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return err - } - defer file.Close() - - gw := gzip.NewWriter(file) - defer gw.Close() - - tw := tar.NewWriter(gw) - defer tw.Close() - - for _, file := range files { - if err := p.addFile(tw, file); err != nil { - return err - } - } - - return nil -} - -func (p Packager) defaultLogger() libbuildpack.Logger { - var debug io.Writer - - if _, ok := os.LookupEnv("BP_DEBUG"); ok { - debug = os.Stderr - } - - return libbuildpack.NewLogger(debug, os.Stdout) -} - -func (p Packager) cacheDependencies() ([]string, error) { - var files []string - - deps, err := p.Buildpack.Dependencies() - if err != nil { - return nil, err - } - - for _, dep := range deps { - p.Logger.FirstLine("Caching %s", p.Logger.PrettyVersion(dep)) - - layer := p.Cache.DownloadLayer(dep) - - a, err := layer.Artifact() - if err != nil { - return nil, err - } - - artifact, err := filepath.Rel(p.Buildpack.Root, a) - if err != nil { - return nil, err - } - - metadata, err := filepath.Rel(p.Buildpack.Root, layer.Metadata(layer.Root)) - if err != nil { - return nil, err - } - - files = append(files, artifact, metadata) - } - - return files, nil -} - -func (p Packager) prePackage() error { - pp, ok := p.Buildpack.PrePackage() - if !ok { - return nil - } - - cmd := exec.Command(pp) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = p.Buildpack.Root - - p.Logger.FirstLine("Pre-Package with %s", strings.Join(cmd.Args, " ")) - - return cmd.Run() -} - -// DefaultPackager creates a new Packager, using the executable to find the root of the buildpack. -func DefaultPackager() (Packager, error) { - p := Packager{} - - logger := p.defaultLogger() - p.Logger = Logger{Logger: logger} - - buildpack, err := libbuildpack.DefaultBuildpack(logger) - if err != nil { - return Packager{}, err - } - p.Buildpack = NewBuildpack(buildpack) - - cache := libbuildpack.Cache{Root: p.Buildpack.CacheRoot, Logger: logger} - p.Cache = Cache{Cache: cache, Logger: p.Logger} - - return p, nil -} +// import ( +// "archive/tar" +// "compress/gzip" +// "fmt" +// "io" +// "os" +// "os/exec" +// "path/filepath" +// "strings" +// "time" +// +// "github.com/buildpack/libbuildpack" +// ) +// +// // Packager is a root element for packaging up a buildpack +// type Packager struct { +// Buildpack Buildpack +// Cache Cache +// Logger Logger +// } +// +// // Create creates a new buildpack package. +// func (p Packager) Create() error { +// p.Logger.FirstLine("Packaging %s", p.Logger.PrettyVersion(p.Buildpack)) +// +// if err := p.prePackage(); err != nil { +// return err +// } +// +// includedFiles, err := p.Buildpack.IncludeFiles() +// if err != nil { +// return err +// } +// +// dependencyFiles, err := p.cacheDependencies() +// if err != nil { +// return err +// } +// +// return p.createArchive(append(includedFiles, dependencyFiles...)) +// } +// +// func (p Packager) addFile(out *tar.Writer, path string) error { +// p.Logger.SubsequentLine("Adding %s", path) +// +// file, err := os.Open(filepath.Join(p.Buildpack.Root, path)) +// if err != nil { +// return err +// } +// defer file.Close() +// +// stat, err := file.Stat() +// if err != nil { +// return err +// } +// +// header := new(tar.Header) +// header.Name = path +// header.Size = stat.Size() +// header.Mode = int64(stat.Mode()) +// header.ModTime = stat.ModTime() +// +// if err := out.WriteHeader(header); err != nil { +// return err +// } +// +// _, err = io.Copy(out, file) +// return err +// } +// +// func (p Packager) archivePath() (string, error) { +// dir, err := osArgs(1) +// if err != nil { +// return "", err +// } +// +// info := p.Buildpack.Info +// +// path := []string{dir} +// path = append(path, strings.Split(info.ID, ".")...) +// path = append(path, info.ID, info.Version) +// +// f := fmt.Sprintf("%s-%s.tgz", info.ID, info.Version) +// f = strings.Replace(f, "SNAPSHOT", fmt.Sprintf("%s-1", time.Now().Format("20060102.150405")), 1) +// +// path = append(path, f) +// +// return filepath.Join(path...), nil +// } +// +// func (p Packager) createArchive(files []string) error { +// archive, err := p.archivePath() +// if err != nil { +// return err +// } +// +// p.Logger.FirstLine("Creating archive %s", archive) +// +// if err = os.MkdirAll(filepath.Dir(archive), 0755); err != nil { +// return err +// } +// +// file, err := os.OpenFile(archive, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) +// if err != nil { +// return err +// } +// defer file.Close() +// +// gw := gzip.NewWriter(file) +// defer gw.Close() +// +// tw := tar.NewWriter(gw) +// defer tw.Close() +// +// for _, file := range files { +// if err := p.addFile(tw, file); err != nil { +// return err +// } +// } +// +// return nil +// } +// +// func (p Packager) defaultLogger() libbuildpack.Logger { +// var debug io.Writer +// +// if _, ok := os.LookupEnv("BP_DEBUG"); ok { +// debug = os.Stderr +// } +// +// return libbuildpack.NewLogger(debug, os.Stdout) +// } +// +// func (p Packager) cacheDependencies() ([]string, error) { +// var files []string +// +// deps, err := p.Buildpack.Dependencies() +// if err != nil { +// return nil, err +// } +// +// for _, dep := range deps { +// p.Logger.FirstLine("Caching %s", p.Logger.PrettyVersion(dep)) +// +// layer := p.Cache.DownloadLayer(dep) +// +// a, err := layer.Artifact() +// if err != nil { +// return nil, err +// } +// +// artifact, err := filepath.Rel(p.Buildpack.Root, a) +// if err != nil { +// return nil, err +// } +// +// metadata, err := filepath.Rel(p.Buildpack.Root, layer.Metadata(layer.Root)) +// if err != nil { +// return nil, err +// } +// +// files = append(files, artifact, metadata) +// } +// +// return files, nil +// } +// +// func (p Packager) prePackage() error { +// pp, ok := p.Buildpack.PrePackage() +// if !ok { +// return nil +// } +// +// cmd := exec.Command(pp) +// cmd.Stdout = os.Stdout +// cmd.Stderr = os.Stderr +// cmd.Dir = p.Buildpack.Root +// +// p.Logger.FirstLine("Pre-Package with %s", strings.Join(cmd.Args, " ")) +// +// return cmd.Run() +// } +// +// // DefaultPackager creates a new Packager, using the executable to find the root of the buildpack. +// func DefaultPackager() (Packager, error) { +// p := Packager{} +// +// logger := p.defaultLogger() +// p.Logger = Logger{Logger: logger} +// +// buildpack, err := libbuildpack.DefaultBuildpack(logger) +// if err != nil { +// return Packager{}, err +// } +// p.Buildpack = NewBuildpack(buildpack) +// +// cache := libbuildpack.Cache{Root: p.Buildpack.CacheRoot, Logger: logger} +// p.Cache = Cache{Cache: cache, Logger: p.Logger} +// +// return p, nil +// } diff --git a/test/matchers.go b/test/matchers.go index f63f963..4f2955e 100644 --- a/test/matchers.go +++ b/test/matchers.go @@ -26,12 +26,13 @@ import ( func BeFileLike(t *testing.T, file string, mode os.FileMode, content string) { t.Helper() - fileExists(t, file) + FileExists(t, file) fileModeMatches(t, file, mode) fileContentMatches(t, file, content) } -func fileExists(t *testing.T, file string) { +// FileExists tests that a file exists +func FileExists(t *testing.T, file string) { t.Helper() _, err := os.Stat(file) diff --git a/test/test_build_factory.go b/test/test_build_factory.go index 516c719..acc2e39 100644 --- a/test/test_build_factory.go +++ b/test/test_build_factory.go @@ -16,125 +16,134 @@ package test -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "github.com/Masterminds/semver" - "github.com/buildpack/libbuildpack" - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/internal" -) - -// BuildFactory is a factory for creating a test Build. -type BuildFactory struct { - Build libjavabuildpack.Build -} - -// AddBuildPlan adds an entry to a build plan. -func (f *BuildFactory) AddBuildPlan(t *testing.T, name string, dependency libbuildpack.BuildPlanDependency) { - t.Helper() - f.Build.BuildPlan[name] = dependency -} - -// AddDependency adds a dependency to the buildpack metadata and copies a fixture into a cached dependency layer. -func (f *BuildFactory) AddDependency(t *testing.T, id string, fixture string) { - t.Helper() - - d := f.newDependency(t, id, fixture) - f.cacheFixture(t, d, fixture) - f.addDependency(t, d) -} - -func (f *BuildFactory) addDependency(t *testing.T, dependency libjavabuildpack.Dependency) { - t.Helper() - - metadata := f.Build.Buildpack.Metadata - dependencies := metadata["dependencies"].([]map[string]interface{}) - - var stacks []interface{} - for _, stack := range dependency.Stacks { - stacks = append(stacks, stack) - } - - var licenses []map[string]interface{} - for _, license := range dependency.Licenses { - licenses = append(licenses, map[string]interface{}{ - "type": license.Type, - "uri": license.URI, - }) - } - - metadata["dependencies"] = append(dependencies, map[string]interface{}{ - "id": dependency.ID, - "name": dependency.Name, - "version": dependency.Version.Version.Original(), - "uri": dependency.URI, - "sha256": dependency.SHA256, - "stacks": stacks, - "licenses": licenses, - }) -} - -func (f *BuildFactory) cacheFixture(t *testing.T, dependency libjavabuildpack.Dependency, fixture string) { - t.Helper() - - l := f.Build.Cache.Layer(dependency.SHA256) - if err := libjavabuildpack.CopyFile(FixturePath(t, fixture), filepath.Join(l.Root, filepath.Base(fixture))); err != nil { - t.Fatal(err) - } - - d, err := internal.ToTomlString(dependency) - if err != nil { - t.Fatal(err) - } - if err := libjavabuildpack.WriteToFile(strings.NewReader(d), filepath.Join(l.Root, "dependency.toml"), 0644); err != nil { - t.Fatal(err) - } -} - -func (f *BuildFactory) newDependency(t *testing.T, id string, fixture string) libjavabuildpack.Dependency { - t.Helper() - - version, err := semver.NewVersion("1.0") - if err != nil { - t.Fatal(err) - } - - return libjavabuildpack.Dependency{ - ID: id, - Name: "test-name", - Version: libjavabuildpack.Version{Version: version}, - URI: fmt.Sprintf("http://localhost/%s", filepath.Base(fixture)), - SHA256: "test-hash", - Stacks: libjavabuildpack.Stacks{f.Build.Stack}, - Licenses: libjavabuildpack.Licenses{ - libjavabuildpack.License{Type: "test-type"}, - }, - } -} - -// NewBuildFactory creates a new instance of BuildFactory. -func NewBuildFactory(t *testing.T) BuildFactory { - t.Helper() - f := BuildFactory{} - - root := ScratchDir(t, "test-build-factory") - - f.Build.Application.Root = filepath.Join(root, "app") - f.Build.BuildPlan = make(libbuildpack.BuildPlan) - - f.Build.Buildpack.Metadata = make(libbuildpack.BuildpackMetadata) - f.Build.Buildpack.Metadata["dependencies"] = make([]map[string]interface{}, 0) - - f.Build.Cache.Root = filepath.Join(root, "cache") - - f.Build.Launch.Root = filepath.Join(root, "launch") - f.Build.Launch.Cache = f.Build.Cache - - f.Build.Platform.Root = filepath.Join(root, "platform") - - return f -} +// import ( +// "fmt" +// "path/filepath" +// "strings" +// "testing" +// +// "github.com/Masterminds/semver" +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/internal" +// ) +// +// // BuildFactory is a factory for creating a test Build. +// type BuildFactory struct { +// Build libjavabuildpack.Build +// } +// +// // AddBuildPlan adds an entry to a build plan. +// func (f *BuildFactory) AddBuildPlan(t *testing.T, name string, dependency libbuildpack.BuildPlanDependency) { +// t.Helper() +// f.Build.BuildPlan[name] = dependency +// } +// +// // AddDependency adds a dependency to the buildpack metadata and copies a fixture into a cached dependency layer. +// func (f *BuildFactory) AddDependency(t *testing.T, id string, fixture string) { +// t.Helper() +// +// d := f.newDependency(t, id, fixture) +// f.cacheFixture(t, d, fixture) +// f.addDependency(t, d) +// } +// +// // AddEnv adds an environment variable to the Platform +// func (f *BuildFactory) AddEnv(t *testing.T, name string, value string) { +// t.Helper() +// +// if err := libjavabuildpack.WriteToFile(strings.NewReader(value), filepath.Join(f.Build.Platform.Root, "env", name), 0644); err != nil { +// t.Fatal(err) +// } +// } +// +// func (f *BuildFactory) addDependency(t *testing.T, dependency libjavabuildpack.Dependency) { +// t.Helper() +// +// metadata := f.Build.Buildpack.Metadata +// dependencies := metadata["dependencies"].([]map[string]interface{}) +// +// var stacks []interface{} +// for _, stack := range dependency.Stacks { +// stacks = append(stacks, stack) +// } +// +// var licenses []map[string]interface{} +// for _, license := range dependency.Licenses { +// licenses = append(licenses, map[string]interface{}{ +// "type": license.Type, +// "uri": license.URI, +// }) +// } +// +// metadata["dependencies"] = append(dependencies, map[string]interface{}{ +// "id": dependency.ID, +// "name": dependency.Name, +// "version": dependency.Version.Version.Original(), +// "uri": dependency.URI, +// "sha256": dependency.SHA256, +// "stacks": stacks, +// "licenses": licenses, +// }) +// } +// +// func (f *BuildFactory) cacheFixture(t *testing.T, dependency libjavabuildpack.Dependency, fixture string) { +// t.Helper() +// +// l := f.Build.Cache.Layer(dependency.SHA256) +// if err := libjavabuildpack.CopyFile(FixturePath(t, fixture), filepath.Join(l.Root, filepath.Base(fixture))); err != nil { +// t.Fatal(err) +// } +// +// d, err := internal.ToTomlString(dependency) +// if err != nil { +// t.Fatal(err) +// } +// if err := libjavabuildpack.WriteToFile(strings.NewReader(d), filepath.Join(l.Root, "dependency.toml"), 0644); err != nil { +// t.Fatal(err) +// } +// } +// +// func (f *BuildFactory) newDependency(t *testing.T, id string, fixture string) libjavabuildpack.Dependency { +// t.Helper() +// +// version, err := semver.NewVersion("1.0") +// if err != nil { +// t.Fatal(err) +// } +// +// return libjavabuildpack.Dependency{ +// ID: id, +// Name: "test-name", +// Version: libjavabuildpack.Version{Version: version}, +// URI: fmt.Sprintf("http://localhost/%s", filepath.Base(fixture)), +// SHA256: "test-hash", +// Stacks: libjavabuildpack.Stacks{f.Build.Stack}, +// Licenses: libjavabuildpack.Licenses{ +// libjavabuildpack.License{Type: "test-type"}, +// }, +// } +// } +// +// // NewBuildFactory creates a new instance of BuildFactory. +// func NewBuildFactory(t *testing.T) BuildFactory { +// t.Helper() +// f := BuildFactory{} +// +// root := ScratchDir(t, "test-build-factory") +// +// f.Build.Application.Root = filepath.Join(root, "app") +// f.Build.BuildPlan = make(libbuildpack.BuildPlan) +// +// f.Build.Buildpack.Metadata = make(libbuildpack.BuildpackMetadata) +// f.Build.Buildpack.Metadata["dependencies"] = make([]map[string]interface{}, 0) +// +// f.Build.Cache.Root = filepath.Join(root, "cache") +// +// f.Build.Launch.Root = filepath.Join(root, "launch") +// f.Build.Launch.Cache = f.Build.Cache +// +// f.Build.Platform.Root = filepath.Join(root, "platform") +// +// return f +// } diff --git a/test/test_detect_factory.go b/test/test_detect_factory.go index c6d5cce..b055cda 100644 --- a/test/test_detect_factory.go +++ b/test/test_detect_factory.go @@ -16,26 +16,46 @@ package test -import ( - "path/filepath" - "testing" - - "github.com/cloudfoundry/libjavabuildpack" -) - -// DetectFactory is a factory for creating a test Detect. -type DetectFactory struct { - Detect libjavabuildpack.Detect -} - -// NewDetectFactory creates a new instance of DetectFactory. -func NewDetectFactory(t *testing.T) DetectFactory { - t.Helper() - - f := DetectFactory{} - - root := ScratchDir(t, "test-detect-factory") - f.Detect.Application.Root = filepath.Join(root, "app") - - return f -} +// import ( +// "path/filepath" +// "strings" +// "testing" +// +// "github.com/buildpack/libbuildpack" +// "github.com/cloudfoundry/libjavabuildpack" +// ) +// +// // DetectFactory is a factory for creating a test Detect. +// type DetectFactory struct { +// Detect libjavabuildpack.Detect +// } +// +// // AddBuildPlan adds an entry to a build plan. +// func (f *DetectFactory) AddBuildPlan(t *testing.T, name string, dependency libbuildpack.BuildPlanDependency) { +// t.Helper() +// f.Detect.BuildPlan[name] = dependency +// } +// +// // AddEnv adds an environment variable to the Platform +// func (f *DetectFactory) AddEnv(t *testing.T, name string, value string) { +// t.Helper() +// +// if err := libjavabuildpack.WriteToFile(strings.NewReader(value), filepath.Join(f.Detect.Platform.Root, "env", name), 0644); err != nil { +// t.Fatal(err) +// } +// } +// +// // NewDetectFactory creates a new instance of DetectFactory. +// func NewDetectFactory(t *testing.T) DetectFactory { +// t.Helper() +// f := DetectFactory{} +// +// root := ScratchDir(t, "test-detect-factory") +// +// f.Detect.Application.Root = filepath.Join(root, "app") +// f.Detect.BuildPlan = make(libbuildpack.BuildPlan) +// +// f.Detect.Platform.Root = filepath.Join(root, "platform") +// +// return f +// } diff --git a/test/test_environment_factory.go b/test/test_environment_factory.go index d450bf7..3318e99 100644 --- a/test/test_environment_factory.go +++ b/test/test_environment_factory.go @@ -16,76 +16,87 @@ package test -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/internal" -) - -// EnvironmentFactory is a factory for creating a test environment to test detect and build functionality. -type EnvironmentFactory struct { - // Application is the root of the application - Application string - - // Console is the Console of the application. - Console Console - - // ExitStatus is the exit status code of the application. - ExitStatus *int - - defers []func() -} - -// Restore restores the original environment before testing. -// -// defer f.Restore() -func (f *EnvironmentFactory) Restore() { - for _, d := range f.defers { - d() - } -} - -// NewEnvironmentFactory creates a new instance of EnvironmentFactory. -func NewEnvironmentFactory(t *testing.T) EnvironmentFactory { - t.Helper() - f := EnvironmentFactory{} - - root := ScratchDir(t, "test-environment-factory") - - appRoot := filepath.Join(root, "app") - if err := os.MkdirAll(appRoot, 0755); err != nil { - t.Fatal(err) - } - f.Application = appRoot - - d := ReplaceWorkingDirectory(t, appRoot) - f.defers = append(f.defers, d) - - f.Console, d = ReplaceConsole(t) - f.defers = append(f.defers, d) - - d = ReplaceEnv(t, "PACK_STACK_ID", "test-stack") - f.defers = append(f.defers, d) - - buildpackRoot := filepath.Join(root, "buildpack") - b, err := internal.ToTomlString(libjavabuildpack.Buildpack{}) - if err != nil { - t.Fatal(err) - } - if err := libjavabuildpack.WriteToFile(strings.NewReader(b), filepath.Join(buildpackRoot, "buildpack.toml"), 0644); err != nil { - t.Fatal(err) - } - - d = ReplaceArgs(t, filepath.Join(buildpackRoot, "bin", "test"), filepath.Join(root, "platform"), - filepath.Join(root, "cache"), filepath.Join(root, "launch")) - f.defers = append(f.defers, d) - - f.ExitStatus, d = CaptureExitStatus(t) - f.defers = append(f.defers, d) - - return f -} +// import ( +// "os" +// "path/filepath" +// "strings" +// "testing" +// +// "github.com/cloudfoundry/libjavabuildpack" +// "github.com/cloudfoundry/libjavabuildpack/internal" +// ) +// +// // EnvironmentFactory is a factory for creating a test environment to test detect and build functionality. +// type EnvironmentFactory struct { +// // Application is the root of the application +// Application string +// +// // Cache is the root of the cache +// Cache string +// +// // Console is the Console of the application. +// Console Console +// +// // ExitStatus is the exit status code of the application. +// ExitStatus *int +// +// // Launch is the root of the launch +// Launch string +// +// // Platform is the root of the platform +// Platform string +// +// defers []func() +// } +// +// // Restore restores the original environment before testing. +// // +// // defer f.Restore() +// func (f *EnvironmentFactory) Restore() { +// for _, d := range f.defers { +// d() +// } +// } +// +// // NewEnvironmentFactory creates a new instance of EnvironmentFactory. +// func NewEnvironmentFactory(t *testing.T) EnvironmentFactory { +// t.Helper() +// f := EnvironmentFactory{} +// +// root := ScratchDir(t, "test-environment-factory") +// +// f.Application = filepath.Join(root, "app") +// if err := os.MkdirAll(f.Application, 0755); err != nil { +// t.Fatal(err) +// } +// +// f.Cache = filepath.Join(root, "cache") +// f.Launch = filepath.Join(root, "launch") +// f.Platform = filepath.Join(root, "platform") +// +// d := ReplaceWorkingDirectory(t, f.Application) +// f.defers = append(f.defers, d) +// +// f.Console, d = ReplaceConsole(t) +// f.defers = append(f.defers, d) +// +// d = ReplaceEnv(t, "PACK_STACK_ID", "test-stack") +// f.defers = append(f.defers, d) +// +// buildpackRoot := filepath.Join(root, "buildpack") +// b, err := internal.ToTomlString(libjavabuildpack.Buildpack{}) +// if err != nil { +// t.Fatal(err) +// } +// if err := libjavabuildpack.WriteToFile(strings.NewReader(b), filepath.Join(buildpackRoot, "buildpack.toml"), 0644); err != nil { +// t.Fatal(err) +// } +// +// d = ReplaceArgs(t, filepath.Join(buildpackRoot, "bin", "test"), f.Platform, f.Cache, f.Launch) +// f.defers = append(f.defers, d) +// +// f.ExitStatus, d = CaptureExitStatus(t) +// f.defers = append(f.defers, d) +// +// return f +// } diff --git a/test/test_helpers.go b/test/test_helpers.go index 6cf984e..98a9c99 100644 --- a/test/test_helpers.go +++ b/test/test_helpers.go @@ -16,257 +16,257 @@ package test -import ( - "fmt" - "io/ioutil" - "math" - "os" - "path/filepath" - "testing" - - "github.com/bouk/monkey" - "github.com/cloudfoundry/libjavabuildpack" -) - -// CaptureExitStatus returns a pointer to the exit status code when os.Exit() is called. Returns a function for use -// with defer in order to clean up after capture. +// import ( +// "fmt" +// "io/ioutil" +// "math" +// "os" +// "path/filepath" +// "testing" // -// c, d := CaptureExitStatus(t) -// defer d() -func CaptureExitStatus(t *testing.T) (*int, func()) { - t.Helper() - - code := math.MinInt64 - pg := monkey.Patch(os.Exit, func(c int) { - code = c - }) - - return &code, func() { - pg.Unpatch() - } -} - -// Console represents the standard console objects, stdin, stdout, and stderr. -type Console struct { - errRead *os.File - errWrite *os.File - inRead *os.File - inWrite *os.File - outRead *os.File - outWrite *os.File -} - -// Err returns a string representation of captured stderr. -func (c Console) Err(t *testing.T) string { - t.Helper() - - err := c.errWrite.Close() - if err != nil { - t.Fatal(err) - } - - bytes, err := ioutil.ReadAll(c.errRead) - if err != nil { - t.Fatal(err) - } - - return string(bytes) -} - -// In writes a string and closes the connection once complete. -func (c Console) In(t *testing.T, string string) { - t.Helper() - - _, err := fmt.Fprint(c.inWrite, string) - if err != nil { - t.Fatal(err) - } - - err = c.inWrite.Close() - if err != nil { - t.Fatal(err) - } -} - -// Out returns a string representation of captured stdout. -func (c Console) Out(t *testing.T) string { - t.Helper() - - err := c.outWrite.Close() - if err != nil { - t.Fatal(err) - } - - bytes, err := ioutil.ReadAll(c.outRead) - if err != nil { - t.Fatal(err) - } - - return string(bytes) -} - -// FindRoot returns the root of project being tested. -func FindRoot(t *testing.T) string { - t.Helper() - - dir, err := filepath.Abs(".") - if err != nil { - t.Fatal(err) - } - for { - if dir == "/" { - t.Fatalf("could not find go.mod in the directory hierarchy") - } - if exist, err := libjavabuildpack.FileExists(filepath.Join(dir, "go.mod")); err != nil { - t.Fatal(err) - } else if exist { - return dir - } - dir, err = filepath.Abs(filepath.Join(dir, "..")) - if err != nil { - t.Fatal(err) - } - } -} - -// FixturePath returns the absolute path to the desired fixture. -func FixturePath(t *testing.T, fixture string) string { - t.Helper() - return filepath.Join(FindRoot(t), "fixtures", fixture) -} - -// ProtectEnv protects a collection of environment variables. Returns a function for use with defer in order to reset -// the previous values. +// "github.com/bouk/monkey" +// "github.com/cloudfoundry/libjavabuildpack" +// ) // -// defer ProtectEnv(t, "alpha")() -func ProtectEnv(t *testing.T, keys ...string) func() { - t.Helper() - - type state struct { - value string - ok bool - } - - previous := make(map[string]state) - for _, key := range keys { - value, ok := os.LookupEnv(key) - previous[key] = state{value, ok} - } - - return func() { - for k, v := range previous { - if v.ok { - os.Setenv(k, v.value) - } else { - os.Unsetenv(k) - } - } - } -} - -// ReplaceArgs replaces the current command line arguments (os.Args) with a new collection of values. Returns a -// function suitable for use with defer in order to reset the previous values +// // CaptureExitStatus returns a pointer to the exit status code when os.Exit() is called. Returns a function for use +// // with defer in order to clean up after capture. +// // +// // c, d := CaptureExitStatus(t) +// // defer d() +// func CaptureExitStatus(t *testing.T) (*int, func()) { +// t.Helper() // -// defer ReplaceArgs(t, "alpha")() -func ReplaceArgs(t *testing.T, args ...string) func() { - t.Helper() - - previous := os.Args - os.Args = args - - return func() { os.Args = previous } -} - -// ReplaceConsole replaces the console files (os.Stderr, os.Stdin, os.Stdout). Returns a function for use with defer in -// order to reset the previous values +// code := math.MinInt64 +// pg := monkey.Patch(os.Exit, func(c int) { +// code = c +// }) // -// c, d := ReplaceConsole(t) -// defer d() -func ReplaceConsole(t *testing.T) (Console, func()) { - t.Helper() - - var console Console - var err error - - errPrevious := os.Stderr - console.errRead, console.errWrite, err = os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stderr = console.errWrite - - inPrevious := os.Stdin - console.inRead, console.inWrite, err = os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stdin = console.inRead - - outPrevious := os.Stdout - console.outRead, console.outWrite, err = os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stdout = console.outWrite - - return console, func() { - os.Stderr = errPrevious - os.Stdin = inPrevious - os.Stdout = outPrevious - } -} - -// ReplaceEnv replaces an environment variable. Returns a function for use with defer in order to reset the previous -// value. +// return &code, func() { +// pg.Unpatch() +// } +// } // -// defer ReplaceEnv(t, "alpha", "bravo")() -func ReplaceEnv(t *testing.T, key string, value string) func() { - t.Helper() - - previous, ok := os.LookupEnv(key) - os.Setenv(key, value) - - return func() { - if ok { - os.Setenv(key, previous) - } else { - os.Unsetenv(key) - } - } -} - -// ReplaceWorkingDirectory replaces the current working directory (os.Getwd()) with a new value. Returns a function for -// use with defer in order to reset the previous value +// // Console represents the standard console objects, stdin, stdout, and stderr. +// type Console struct { +// errRead *os.File +// errWrite *os.File +// inRead *os.File +// inWrite *os.File +// outRead *os.File +// outWrite *os.File +// } // -// defer ReplaceWorkingDirectory(t, "alpha")() -func ReplaceWorkingDirectory(t *testing.T, dir string) func() { - t.Helper() - - previous, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - - if err = os.Chdir(dir); err != nil { - t.Fatal(err) - } - - return func() { os.Chdir(previous) } -} - -// ScratchDir returns a safe scratch directory for tests to modify. -func ScratchDir(t *testing.T, prefix string) string { - t.Helper() - - tmp, err := ioutil.TempDir("", prefix) - if err != nil { - t.Fatal(err) - } - - abs, err := filepath.EvalSymlinks(tmp) - if err != nil { - t.Fatal(err) - } - - return abs -} +// // Err returns a string representation of captured stderr. +// func (c Console) Err(t *testing.T) string { +// t.Helper() +// +// err := c.errWrite.Close() +// if err != nil { +// t.Fatal(err) +// } +// +// bytes, err := ioutil.ReadAll(c.errRead) +// if err != nil { +// t.Fatal(err) +// } +// +// return string(bytes) +// } +// +// // In writes a string and closes the connection once complete. +// func (c Console) In(t *testing.T, string string) { +// t.Helper() +// +// _, err := fmt.Fprint(c.inWrite, string) +// if err != nil { +// t.Fatal(err) +// } +// +// err = c.inWrite.Close() +// if err != nil { +// t.Fatal(err) +// } +// } +// +// // Out returns a string representation of captured stdout. +// func (c Console) Out(t *testing.T) string { +// t.Helper() +// +// err := c.outWrite.Close() +// if err != nil { +// t.Fatal(err) +// } +// +// bytes, err := ioutil.ReadAll(c.outRead) +// if err != nil { +// t.Fatal(err) +// } +// +// return string(bytes) +// } +// +// // FindRoot returns the root of project being tested. +// func FindRoot(t *testing.T) string { +// t.Helper() +// +// dir, err := filepath.Abs(".") +// if err != nil { +// t.Fatal(err) +// } +// for { +// if dir == "/" { +// t.Fatalf("could not find go.mod in the directory hierarchy") +// } +// if exist, err := libjavabuildpack.FileExists(filepath.Join(dir, "go.mod")); err != nil { +// t.Fatal(err) +// } else if exist { +// return dir +// } +// dir, err = filepath.Abs(filepath.Join(dir, "..")) +// if err != nil { +// t.Fatal(err) +// } +// } +// } +// +// // FixturePath returns the absolute path to the desired fixture. +// func FixturePath(t *testing.T, fixture string) string { +// t.Helper() +// return filepath.Join(FindRoot(t), "fixtures", fixture) +// } +// +// // ProtectEnv protects a collection of environment variables. Returns a function for use with defer in order to reset +// // the previous values. +// // +// // defer ProtectEnv(t, "alpha")() +// func ProtectEnv(t *testing.T, keys ...string) func() { +// t.Helper() +// +// type state struct { +// value string +// ok bool +// } +// +// previous := make(map[string]state) +// for _, key := range keys { +// value, ok := os.LookupEnv(key) +// previous[key] = state{value, ok} +// } +// +// return func() { +// for k, v := range previous { +// if v.ok { +// os.Setenv(k, v.value) +// } else { +// os.Unsetenv(k) +// } +// } +// } +// } +// +// // ReplaceArgs replaces the current command line arguments (os.Args) with a new collection of values. Returns a +// // function suitable for use with defer in order to reset the previous values +// // +// // defer ReplaceArgs(t, "alpha")() +// func ReplaceArgs(t *testing.T, args ...string) func() { +// t.Helper() +// +// previous := os.Args +// os.Args = args +// +// return func() { os.Args = previous } +// } +// +// // ReplaceConsole replaces the console files (os.Stderr, os.Stdin, os.Stdout). Returns a function for use with defer in +// // order to reset the previous values +// // +// // c, d := ReplaceConsole(t) +// // defer d() +// func ReplaceConsole(t *testing.T) (Console, func()) { +// t.Helper() +// +// var console Console +// var err error +// +// errPrevious := os.Stderr +// console.errRead, console.errWrite, err = os.Pipe() +// if err != nil { +// t.Fatal(err) +// } +// os.Stderr = console.errWrite +// +// inPrevious := os.Stdin +// console.inRead, console.inWrite, err = os.Pipe() +// if err != nil { +// t.Fatal(err) +// } +// os.Stdin = console.inRead +// +// outPrevious := os.Stdout +// console.outRead, console.outWrite, err = os.Pipe() +// if err != nil { +// t.Fatal(err) +// } +// os.Stdout = console.outWrite +// +// return console, func() { +// os.Stderr = errPrevious +// os.Stdin = inPrevious +// os.Stdout = outPrevious +// } +// } +// +// // ReplaceEnv replaces an environment variable. Returns a function for use with defer in order to reset the previous +// // value. +// // +// // defer ReplaceEnv(t, "alpha", "bravo")() +// func ReplaceEnv(t *testing.T, key string, value string) func() { +// t.Helper() +// +// previous, ok := os.LookupEnv(key) +// os.Setenv(key, value) +// +// return func() { +// if ok { +// os.Setenv(key, previous) +// } else { +// os.Unsetenv(key) +// } +// } +// } +// +// // ReplaceWorkingDirectory replaces the current working directory (os.Getwd()) with a new value. Returns a function for +// // use with defer in order to reset the previous value +// // +// // defer ReplaceWorkingDirectory(t, "alpha")() +// func ReplaceWorkingDirectory(t *testing.T, dir string) func() { +// t.Helper() +// +// previous, err := os.Getwd() +// if err != nil { +// t.Fatal(err) +// } +// +// if err = os.Chdir(dir); err != nil { +// t.Fatal(err) +// } +// +// return func() { os.Chdir(previous) } +// } +// +// // ScratchDir returns a safe scratch directory for tests to modify. +// func ScratchDir(t *testing.T, prefix string) string { +// t.Helper() +// +// tmp, err := ioutil.TempDir("", prefix) +// if err != nil { +// t.Fatal(err) +// } +// +// abs, err := filepath.EvalSymlinks(tmp) +// if err != nil { +// t.Fatal(err) +// } +// +// return abs +// } diff --git a/util.go b/util.go deleted file mode 100644 index fbe93c1..0000000 --- a/util.go +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack - -import ( - "archive/tar" - "archive/zip" - "compress/gzip" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/BurntSushi/toml" -) - -// CopyDirectory copies srcDir to destDir -func CopyDirectory(srcDir, destDir string) error { - destExists, _ := FileExists(destDir) - if !destExists { - return errors.New("destination dir must exist") - } - - files, err := ioutil.ReadDir(srcDir) - if err != nil { - return err - } - - for _, f := range files { - src := filepath.Join(srcDir, f.Name()) - dest := filepath.Join(destDir, f.Name()) - - if m := f.Mode(); m&os.ModeSymlink != 0 { - target, err := os.Readlink(src) - if err != nil { - return fmt.Errorf("Error while reading symlink '%s': %v", src, err) - } - if err := os.Symlink(target, dest); err != nil { - return fmt.Errorf("Error while creating '%s' as symlink to '%s': %v", dest, target, err) - } - } else if f.IsDir() { - err = os.MkdirAll(dest, f.Mode()) - if err != nil { - return err - } - if err := CopyDirectory(src, dest); err != nil { - return err - } - } else { - rc, err := os.Open(src) - if err != nil { - return err - } - - err = WriteToFile(rc, dest, f.Mode()) - if err != nil { - rc.Close() - return err - } - rc.Close() - } - } - - return nil -} - -// CopyFile copies source file to destFile, creating all intermediate directories in destFile -func CopyFile(source, destFile string) error { - fh, err := os.Open(source) - if err != nil { - return err - } - - fileInfo, err := fh.Stat() - if err != nil { - return err - } - - defer fh.Close() - - return WriteToFile(fh, destFile, fileInfo.Mode()) -} - -// ExtractTarGz extracts tarfile to destDir -func ExtractTarGz(tarFile, destDir string, stripComponents int) error { - file, err := os.Open(tarFile) - if err != nil { - return err - } - defer file.Close() - gz, err := gzip.NewReader(file) - if err != nil { - return err - } - defer gz.Close() - return extractTar(gz, destDir, stripComponents) -} - -// ExtractZip extracts zipfile to destDir -func ExtractZip(zipfile, destDir string, stripComponents int) error { - r, err := zip.OpenReader(zipfile) - if err != nil { - return err - } - defer r.Close() - - for _, f := range r.File { - pathComponents := strings.Split(f.Name, string(filepath.Separator)) - if len(pathComponents) <= stripComponents { - continue - } - - path := filepath.Join(append([]string{destDir}, pathComponents[stripComponents:]...)...) - - rc, err := f.Open() - if err != nil { - return err - } - - if f.FileInfo().IsDir() { - err = os.MkdirAll(path, 0755) - } else { - err = WriteToFile(rc, path, f.Mode()) - } - - rc.Close() - if err != nil { - return err - } - } - - return nil -} - -// FileExists returns true if a file exists, otherwise false. -func FileExists(file string) (bool, error) { - _, err := os.Stat(file) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - - return false, err - } - - return true, nil -} - -// FromTomlFile decodes a TOML file into a struct. -func FromTomlFile(file string, v interface{}) error { - _, err := toml.DecodeFile(file, v) - return err -} - -// WriteToFile writes the contents of an io.Reader to a file. -func WriteToFile(source io.Reader, destFile string, mode os.FileMode) error { - err := os.MkdirAll(filepath.Dir(destFile), 0755) - if err != nil { - return err - } - - fh, err := os.OpenFile(destFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) - if err != nil { - return err - } - defer fh.Close() - - _, err = io.Copy(fh, source) - if err != nil { - return err - } - - return nil -} - -func extractTar(src io.Reader, destDir string, stripComponents int) error { - tr := tar.NewReader(src) - - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } - - pathComponents := strings.Split(hdr.Name, string(filepath.Separator)) - if len(pathComponents) <= stripComponents { - continue - } - - path := filepath.Join(append([]string{destDir}, pathComponents[stripComponents:]...)...) - fi := hdr.FileInfo() - - if fi.IsDir() { - err = os.MkdirAll(path, hdr.FileInfo().Mode()) - } else if fi.Mode()&os.ModeSymlink != 0 { - target := hdr.Linkname - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return err - } - if err = os.Symlink(target, path); err != nil { - return err - } - } else { - err = WriteToFile(tr, path, hdr.FileInfo().Mode()) - } - - if err != nil { - return err - } - } - return nil -} - -func osArgs(index int) (string, error) { - if len(os.Args) < index+1 { - return "", fmt.Errorf("incorrect number of command line arguments") - } - - return os.Args[index], nil -} diff --git a/util_test.go b/util_test.go deleted file mode 100644 index 9d58ae0..0000000 --- a/util_test.go +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * 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 libjavabuildpack_test - -import ( - "path/filepath" - "testing" - - "github.com/cloudfoundry/libjavabuildpack" - "github.com/cloudfoundry/libjavabuildpack/test" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestExtractTarGz(t *testing.T) { - spec.Run(t, "ExtractTarGz", testExtractTarGz, spec.Report(report.Terminal{})) -} - -func testExtractTarGz(t *testing.T, when spec.G, it spec.S) { - - when("ExtractTarGz", func() { - - it("extracts the archive", func() { - root := test.ScratchDir(t, "util") - - err := libjavabuildpack.ExtractTarGz(test.FixturePath(t, "test-archive.tar.gz"), root, 0) - if err != nil { - t.Fatal(err) - } - - test.BeFileLike(t, filepath.Join(root, "fileA.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "dirA", "fileB.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "dirA", "fileC.txt"), 0644, "") - }) - - it("skips stripped components", func() { - root := test.ScratchDir(t, "util") - - err := libjavabuildpack.ExtractTarGz(test.FixturePath(t, "test-archive.tar.gz"), root, 1) - if err != nil { - t.Fatal(err) - } - - exists, err := libjavabuildpack.FileExists(filepath.Join(root, "fileA.txt")) - if err != nil { - t.Fatal(err) - } - - if exists { - t.Errorf("fileA.txt exists, expected not to") - } - - test.BeFileLike(t, filepath.Join(root, "fileB.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "fileC.txt"), 0644, "") - }) - - }) - - when("ExtractZip", func() { - - it("extracts the archive", func() { - root := test.ScratchDir(t, "util") - - err := libjavabuildpack.ExtractZip(test.FixturePath(t, "test-archive.zip"), root, 0) - if err != nil { - t.Fatal(err) - } - - test.BeFileLike(t, filepath.Join(root, "fileA.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "dirA", "fileB.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "dirA", "fileC.txt"), 0644, "") - }) - - it("skips stripped components", func() { - root := test.ScratchDir(t, "util") - - err := libjavabuildpack.ExtractZip(test.FixturePath(t, "test-archive.zip"), root, 1) - if err != nil { - t.Fatal(err) - } - - exists, err := libjavabuildpack.FileExists(filepath.Join(root, "fileA.txt")) - if err != nil { - t.Fatal(err) - } - - if exists { - t.Errorf("fileA.txt exists, expected not to") - } - - test.BeFileLike(t, filepath.Join(root, "fileB.txt"), 0644, "") - test.BeFileLike(t, filepath.Join(root, "fileC.txt"), 0644, "") - }) - - }) - -}