Skip to content

Commit

Permalink
Moves carton package in from libpak
Browse files Browse the repository at this point in the history
Updates references to use now public packages in libpak, like utils and testing.

Signed-off-by: Daniel Mikusa <dan@mikusa.com>
  • Loading branch information
dmikusa committed Oct 29, 2024
1 parent e7fae51 commit f2c278d
Show file tree
Hide file tree
Showing 32 changed files with 3,076 additions and 406 deletions.
71 changes: 71 additions & 0 deletions carton/build_image_dependency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2018-2020 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
*
* https://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 carton

import (
"fmt"
"os"
"regexp"

"github.com/paketo-buildpacks/libpak/v2/log"
"github.com/paketo-buildpacks/libpak/v2/utils"
)

const (
ImageDependencyPattern = `(?m)(.*build-image[\s]+=[\s]+"[^"]+:)[^"]+(".*)`
ImageDependencySubstitution = "${1}%s${2}"
)

type BuildImageDependency struct {
BuilderPath string
Version string
}

func (i BuildImageDependency) Update(options ...Option) {
config := Config{
exitHandler: utils.NewExitHandler(),
}

for _, option := range options {
config = option(config)
}

logger := log.NewPaketoLogger(os.Stdout)
_, _ = fmt.Fprintf(logger.TitleWriter(), "\n%s\n", log.FormatIdentity("Build Image", i.Version))

c, err := os.ReadFile(i.BuilderPath)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to read %s\n%w", i.BuilderPath, err))
return
}

r := regexp.MustCompile(ImageDependencyPattern)

if !r.Match(c) {
config.exitHandler.Error(fmt.Errorf("unable to match '%s'", r.String()))
return
}

s := fmt.Sprintf(ImageDependencySubstitution, i.Version)
c = r.ReplaceAll(c, []byte(s))

// #nosec G306 - permissions need to be 644 on the builder
if err := os.WriteFile(i.BuilderPath, c, 0644); err != nil {
config.exitHandler.Error(fmt.Errorf("unable to write %s\n%w", i.BuilderPath, err))
return
}
}
74 changes: 74 additions & 0 deletions carton/build_image_dependency_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2018-2020 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
*
* https://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 carton_test

import (
"os"
"testing"

"github.com/buildpacks/libcnb/v2/mocks"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/stretchr/testify/mock"

"github.com/paketo-buildpacks/libpak-tools/carton"
)

func testBuildImageDependency(t *testing.T, _ spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

exitHandler *mocks.ExitHandler
path string
)

it.Before(func() {
var err error

exitHandler = &mocks.ExitHandler{}
exitHandler.On("Error", mock.Anything)

f, err := os.CreateTemp("", "carton-image-dependency")
Expect(err).NotTo(HaveOccurred())

_, err = f.WriteString(`test-prologue
build-image = "image-name:test-version-1"
test-epilogue
`)
Expect(err).To(Succeed())
Expect(f.Close()).To(Succeed())
path = f.Name()
})

it.After(func() {
Expect(os.RemoveAll(path)).To(Succeed())
})

it("updates dependency", func() {
d := carton.BuildImageDependency{
BuilderPath: path,
Version: "test-version-2",
}

d.Update(carton.WithExitHandler(exitHandler))

Expect(os.ReadFile(path)).To(Equal([]byte(`test-prologue
build-image = "image-name:test-version-2"
test-epilogue
`)))
})
}
195 changes: 195 additions & 0 deletions carton/buildmodule_dependency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright 2018-2020 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
*
* https://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 carton

import (
"bytes"
"fmt"
"os"
"regexp"

"github.com/BurntSushi/toml"

"github.com/paketo-buildpacks/libpak/v2/log"
"github.com/paketo-buildpacks/libpak/v2/utils"
)

const (
BuildModuleDependencyPattern = `(?m)([\s]*.*id[\s]+=[\s]+"%s"\n.*\n[\s]*version[\s]+=[\s]+")%s("\n[\s]*uri[\s]+=[\s]+").*("\n[\s]*sha256[\s]+=[\s]+").*(".*)`
BuildModuleDependencySubstitution = "${1}%s${2}%s${3}%s${4}"
)

type BuildModuleDependency struct {
BuildModulePath string
ID string
SHA256 string
URI string
Version string
VersionPattern string
CPE string
CPEPattern string
PURL string
PURLPattern string
}

func (b BuildModuleDependency) Update(options ...Option) {
config := Config{
exitHandler: utils.NewExitHandler(),
}

for _, option := range options {
config = option(config)
}

logger := log.NewPaketoLogger(os.Stdout)
_, _ = fmt.Fprintf(logger.TitleWriter(), "\n%s\n", log.FormatIdentity(b.ID, b.VersionPattern))
logger.Headerf("Version: %s", b.Version)
logger.Headerf("PURL: %s", b.PURL)
logger.Headerf("CPEs: %s", b.CPE)
logger.Headerf("URI: %s", b.URI)
logger.Headerf("SHA256: %s", b.SHA256)

versionExp, err := regexp.Compile(b.VersionPattern)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to compile version regex %s\n%w", b.VersionPattern, err))
return
}

cpeExp, err := regexp.Compile(b.CPEPattern)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to compile cpe regex %s\n%w", b.CPEPattern, err))
return
}

purlExp, err := regexp.Compile(b.PURLPattern)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to compile cpe regex %s\n%w", b.PURLPattern, err))
return
}

c, err := os.ReadFile(b.BuildModulePath)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to read %s\n%w", b.BuildModulePath, err))
return
}

// save any leading comments, this is to preserve license headers
// inline comments will be lost
comments := []byte{}
for i, line := range bytes.SplitAfter(c, []byte("\n")) {
if bytes.HasPrefix(line, []byte("#")) || (i > 0 && len(bytes.TrimSpace(line)) == 0) {
comments = append(comments, line...)
} else {
break // stop on first comment
}
}

md := make(map[string]interface{})
if err := toml.Unmarshal(c, &md); err != nil {
config.exitHandler.Error(fmt.Errorf("unable to decode md%s\n%w", b.BuildModulePath, err))
return
}

metadataUnwrapped, found := md["metadata"]
if !found {
config.exitHandler.Error(fmt.Errorf("unable to find metadata block"))
return
}

metadata, ok := metadataUnwrapped.(map[string]interface{})
if !ok {
config.exitHandler.Error(fmt.Errorf("unable to cast metadata"))
return
}

dependenciesUnwrapped, found := metadata["dependencies"]
if !found {
config.exitHandler.Error(fmt.Errorf("unable to find dependencies block"))
return
}

dependencies, ok := dependenciesUnwrapped.([]map[string]interface{})
if !ok {
config.exitHandler.Error(fmt.Errorf("unable to cast dependencies"))
return
}

for _, dep := range dependencies {
depIDUnwrapped, found := dep["id"]
if !found {
continue
}
depID, ok := depIDUnwrapped.(string)
if !ok {
continue
}

if depID == b.ID {
depVersionUnwrapped, found := dep["version"]
if !found {
continue
}

depVersion, ok := depVersionUnwrapped.(string)
if !ok {
continue
}
if versionExp.MatchString(depVersion) {
dep["version"] = b.Version
dep["uri"] = b.URI
dep["sha256"] = b.SHA256

purlUnwrapped, found := dep["purl"]
if found {
purl, ok := purlUnwrapped.(string)
if ok {
dep["purl"] = purlExp.ReplaceAllString(purl, b.PURL)
}
}

cpesUnwrapped, found := dep["cpes"]
if found {
cpes, ok := cpesUnwrapped.([]interface{})
if ok {
for i := 0; i < len(cpes); i++ {
cpe, ok := cpes[i].(string)
if !ok {
continue
}

cpes[i] = cpeExp.ReplaceAllString(cpe, b.CPE)
}
}
}
}
}
}

c, err = utils.Marshal(md)
if err != nil {
config.exitHandler.Error(fmt.Errorf("unable to encode md %s\n%w", b.BuildModulePath, err))
return
}

c = append(comments, c...)

// #nosec G306 - permissions need to be 644 on the build module
if err := os.WriteFile(b.BuildModulePath, c, 0644); err != nil {
config.exitHandler.Error(fmt.Errorf("unable to write %s\n%w", b.BuildModulePath, err))
return
}
}
Loading

0 comments on commit f2c278d

Please sign in to comment.