Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moves carton package in from libpak #58

Merged
merged 2 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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