Skip to content

Commit

Permalink
Create a command similar to build-minirootfs for CPIO
Browse files Browse the repository at this point in the history
This makes it significantly simpler to produce `cpio` files from an apko file.

Marking the command as hidden for now, since this is still somewhat experimental.

Signed-off-by: Matt Moore <mattmoor@chainguard.dev>
  • Loading branch information
mattmoor committed Jul 24, 2024
1 parent 82d9f55 commit ed96c73
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 3 deletions.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ require (
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/tmc/dot v0.0.0-20210901225022-f9bc17da75c0
github.com/u-root/u-root v0.14.0
go.lsp.dev/uri v0.3.0
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/trace v1.28.0
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/sync v0.7.0
golang.org/x/sys v0.22.0
golang.org/x/time v0.5.0
Expand Down Expand Up @@ -92,6 +93,7 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand All @@ -104,6 +106,7 @@ require (
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
Expand Down
10 changes: 8 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs=
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down Expand Up @@ -236,6 +238,10 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
github.com/tmc/dot v0.0.0-20210901225022-f9bc17da75c0 h1:hwIpbdjckSFqmZ6hod7WZgGR7tVVrSUzZrBfNZl7AOg=
github.com/tmc/dot v0.0.0-20210901225022-f9bc17da75c0/go.mod h1:DV83s9TfD0rgoKcqvDmM+aYdz6BXmTkquwd+bI/8tlo=
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a h1:BH1SOPEvehD2kVrndDnGJiUF0TrBpNs+iyYocu6h0og=
github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
Expand Down Expand Up @@ -264,8 +270,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand Down
100 changes: 100 additions & 0 deletions internal/cli/build-cpio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2022, 2023 Chainguard, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cli

import (
"context"
"fmt"
"os"
"runtime"

"github.com/spf13/cobra"

"github.com/chainguard-dev/clog"

apkfs "chainguard.dev/apko/pkg/apk/fs"
"chainguard.dev/apko/pkg/build"
"chainguard.dev/apko/pkg/build/types"
"chainguard.dev/apko/pkg/cpio"
)

func buildCPIO() *cobra.Command {
var buildDate string
var buildArch string
var sbomPath string

cmd := &cobra.Command{
Use: "build-cpio",
Short: "Build a cpio file from a YAML configuration file",
Long: "Build a cpio file from a YAML configuration file",
Example: ` apko build-cpio <config.yaml> <output.cpio>`,
Hidden: true,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return BuildCPIOCmd(cmd.Context(), args[1],
build.WithConfig(args[0], []string{}),
build.WithBuildDate(buildDate),
build.WithSBOM(sbomPath),
build.WithArch(types.ParseArchitecture(buildArch)),
)
},
}

cmd.Flags().StringVar(&buildDate, "build-date", "", "date used for the timestamps of the files inside the image")
cmd.Flags().StringVar(&buildArch, "build-arch", runtime.GOARCH, "architecture to build for -- default is Go runtime architecture")
cmd.Flags().StringVar(&sbomPath, "sbom-path", "", "generate an SBOM")

return cmd
}

func BuildCPIOCmd(ctx context.Context, dest string, opts ...build.Option) error {
log := clog.FromContext(ctx)
wd, err := os.MkdirTemp("", "apko-*")
if err != nil {
return fmt.Errorf("failed to create working directory: %w", err)
}
defer os.RemoveAll(wd)

fs := apkfs.DirFS(wd, apkfs.WithCreateDir())
bc, err := build.New(ctx, fs, opts...)
if err != nil {
return err
}

ic := bc.ImageConfiguration()

if len(ic.Archs) != 0 {
log.Warnf("ignoring archs in config, only building for current arch (%s)", bc.Arch())
}

_, layer, err := bc.BuildLayer(ctx)
if err != nil {
return fmt.Errorf("failed to build layer image: %w", err)
}
log.Debugf("converting layer to cpio %s", dest)

// Create the CPIO file, and set up a deduplicating writer
// to produce the gzip-compressed CPIO archive.
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()

// TODO(mattmoor): Consider wrapping in a gzip writer if the filename
// ends in .gz

return cpio.FromLayer(layer, f)
}
1 change: 1 addition & 0 deletions internal/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func New() *cobra.Command {
cmd.AddCommand(cranecmd.NewCmdAuthLogin("apko")) // apko login
cmd.AddCommand(buildCmd())
cmd.AddCommand(buildMinirootFS())
cmd.AddCommand(buildCPIO())
cmd.AddCommand(showConfig())
cmd.AddCommand(publish())
cmd.AddCommand(showPackages())
Expand Down
97 changes: 97 additions & 0 deletions pkg/cpio/layer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 Chainguard, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cpio

import (
"archive/tar"
"bytes"
"fmt"
"io"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/u-root/u-root/pkg/cpio"
)

func FromLayer(layer v1.Layer, dest io.Writer) error {
// Open the filesystem layer to walk through the file.
u, err := layer.Uncompressed()
if err != nil {
return err
}
defer u.Close()
tarReader := tar.NewReader(u)

w := cpio.NewDedupWriter(cpio.Newc.Writer(dest))

// Iterate through the tar archive entries
for {
header, err := tarReader.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
fmt.Println("Error reading tar entry:", err)
return err
}

// Determine CPIO file mode based on TAR typeflag
switch header.Typeflag {
case tar.TypeDir:
if err := cpio.WriteRecordsAndDirs(w, []cpio.Record{
cpio.Directory(header.Name, uint64(header.Mode)),
}); err != nil {
return err
}

case tar.TypeSymlink:
if err := cpio.WriteRecordsAndDirs(w, []cpio.Record{
cpio.Symlink(header.Name, header.Linkname),
}); err != nil {
return err
}

case tar.TypeReg:
var original bytes.Buffer
// TODO(mattmoor): Do something better here, but unfortunately the
// cpio stuff wants a seekable reader, so coming from a tar reader
// I'm not sure how much leeway we have to do something better
// than buffering.
//nolint:gosec
if _, err := io.Copy(&original, tarReader); err != nil {
fmt.Println("Error reading file content:", err)
return err
}

if err := cpio.WriteRecordsAndDirs(w, []cpio.Record{
cpio.StaticFile(header.Name, original.String(), uint64(header.Mode)),
}); err != nil {
return err
}

case tar.TypeChar:
if err := cpio.WriteRecordsAndDirs(w, []cpio.Record{
cpio.CharDev(header.Name, uint64(header.Mode), uint64(header.Devmajor), uint64(header.Devminor)),
}); err != nil {
return err
}

default:
fmt.Printf("Unsupported TAR typeflag: %c for %s\n", header.Typeflag, header.Name)
continue // Skip unsupported types
}
}

return w.WriteRecord(cpio.TrailerRecord)
}

0 comments on commit ed96c73

Please sign in to comment.