Skip to content

Commit

Permalink
feat: implement resolve command
Browse files Browse the repository at this point in the history
  • Loading branch information
thesayyn committed Sep 28, 2023
1 parent b71bae6 commit ad97a58
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 5 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand Down Expand Up @@ -144,3 +144,5 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gotest.tools/v3 v3.1.0 // indirect
)

replace github.com/chainguard-dev/go-apk v0.0.0-20230906161245-0728258ab917 => github.com/thesayyn/go-apk v0.0.0-20230928231027-0ed5c9ad96a8
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chainguard-dev/go-apk v0.0.0-20230906161245-0728258ab917 h1:/FvFggKayia7a3cA/UcD18Dtn53/be/EBhPpQla65g8=
github.com/chainguard-dev/go-apk v0.0.0-20230906161245-0728258ab917/go.mod h1:I7uFl3LBMG1UQONJRQN+3lzZE4tw8myh87FzkDEztHg=
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 h1:9Qh4lJ/KMr5iS1zfZ8I97+3MDpiKjl+0lZVUNBhdvRs=
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08/go.mod h1:MAuu1uDJNOS3T3ui0qmKdPUwm59+bO19BbTph2wZafE=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
Expand Down Expand Up @@ -290,8 +288,8 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down Expand Up @@ -404,6 +402,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/thesayyn/go-apk v0.0.0-20230928231027-0ed5c9ad96a8 h1:TncT9U3OjomiWll0qp/VV2cos/ryf71Srp3ULQl+qL4=
github.com/thesayyn/go-apk v0.0.0-20230928231027-0ed5c9ad96a8/go.mod h1:2N1SnsftH4Bh2Loo+3jT16lnZjAm7oeZfbFW9nBrceU=
github.com/theupdateframework/go-tuf v0.6.1 h1:6J89fGjQf7s0mLmTG7p7pO/MbKOg+bIXhaLyQdmbKuE=
github.com/theupdateframework/go-tuf v0.6.1/go.mod h1:LAFusuQsFNBnEyYoTuA5zZrF7iaQ4TEgBXm8lb6Vj18=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
Expand Down
1 change: 1 addition & 0 deletions internal/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func New() *cobra.Command {
cmd.AddCommand(publish())
cmd.AddCommand(showPackages())
cmd.AddCommand(dotcmd())
cmd.AddCommand(resolve())
cmd.AddCommand(version.Version())

cmd.PersistentFlags().StringVarP(&workDir, "workdir", "C", cwd, "working dir (default is current dir where executed)")
Expand Down
254 changes: 254 additions & 0 deletions internal/cli/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// 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"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

apkfs "github.com/chainguard-dev/go-apk/pkg/fs"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"

"chainguard.dev/apko/pkg/build"
"chainguard.dev/apko/pkg/build/types"
"chainguard.dev/apko/pkg/iocomb"
"chainguard.dev/apko/pkg/log"
)

type lock struct {
Version string `json:"version"`
Contents lockContents `json:"contents"`
}

type lockContents struct {
Keyrings []lockKeyring `json:"keyring"`
Repositories []lockRepo `json:"repositories"`
Packages []lockPkg `json:"packages"`
}

type lockPkg struct {
Name string `json:"name"`
Url string `json:"url"`
Version string `json:"version"`
Architecture string `json:"architecture"`
Signature lockPkg_RangeAndChecksum `json:"signature"`
Control lockPkg_RangeAndChecksum `json:"control"`
Data lockPkg_RangeAndChecksum `json:"data"`
}
type lockPkg_RangeAndChecksum struct {
Range string `json:"range"`
Checksum string `json:"checksum"`
}

type lockRepo struct {
Name string `json:"name"`
Url string `json:"url"`
Architecture string `json:"architecture"`
}

type lockKeyring struct {
Name string `json:"name"`
Url string `json:"url"`
}

func resolve() *cobra.Command {
var extraKeys []string
var extraRepos []string
var archstrs []string
var output string

var logPolicy []string
var debugEnabled bool
var quietEnabled bool

cmd := &cobra.Command{
Use: "resolve",
Short: "TODO",
Long: `TODO`,
Example: `apko resolve <config.yaml>`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if output == "" {
output = fmt.Sprintf("%s.resolved.json", strings.TrimSuffix(args[0], filepath.Ext(args[0])))
}

if len(logPolicy) == 0 {
if quietEnabled {
logPolicy = []string{"builtin:discard"}
} else {
logPolicy = []string{"builtin:stderr"}
}
}

logWriter, err := iocomb.Combine(logPolicy)
if err != nil {
return fmt.Errorf("invalid logging policy: %w", err)
}
logger := log.NewLogger(logWriter)

archs := types.ParseArchitectures(archstrs)

return ResolveCmd(
cmd.Context(),
output,
archs,
[]build.Option{
build.WithLogger(logger),
build.WithConfig(args[0]),
build.WithExtraKeys(extraKeys),
build.WithExtraRepos(extraRepos),
build.WithDebugLogging(debugEnabled),
},
)
},
}

cmd.Flags().StringSliceVarP(&extraKeys, "keyring-append", "k", []string{}, "path to extra keys to include in the keyring")
cmd.Flags().StringSliceVarP(&extraRepos, "repository-append", "r", []string{}, "path to extra repositories to include")
cmd.Flags().StringSliceVar(&archstrs, "arch", nil, "architectures to build for (e.g., x86_64,ppc64le,arm64) -- default is all, unless specified in config. Can also use 'host' to indicate arch of host this is running on")
cmd.Flags().StringVar(&output, "output", "", "path to file where lock file will be written")
cmd.Flags().StringSliceVar(&logPolicy, "log-policy", []string{}, "logging policy to use")
cmd.Flags().BoolVar(&debugEnabled, "debug", false, "enable debug logging")
cmd.Flags().BoolVar(&quietEnabled, "quiet", false, "disable logging")

return cmd
}

func ResolveCmd(ctx context.Context, output string, archs []types.Architecture, opts []build.Option) error {
wd, err := os.MkdirTemp("", "apko-*")
if err != nil {
return fmt.Errorf("failed to create working directory: %w", err)
}
defer os.RemoveAll(wd)

o, ic, err := build.NewOptions(opts...)
if err != nil {
return err
}

// cases:
// - archs set: use those archs
// - archs not set, bc.ImageConfiguration.Archs set: use Config archs
// - archs not set, bc.ImageConfiguration.Archs not set: use all archs
switch {
case len(archs) != 0:
ic.Archs = archs
case len(ic.Archs) != 0:
// do nothing
default:
ic.Archs = types.AllArchs
}
// save the final set we will build
archs = ic.Archs
o.Logger().Infof("Determining packages for %d architectures: %+v", len(ic.Archs), ic.Archs)

// The build context options is sometimes copied in the next functions. Ensure
// we have the directory defined and created by invoking the function early.
defer os.RemoveAll(o.TempDir())

lock := lock{
Version: "v1",
Contents: lockContents{
Packages: []lockPkg{},
Repositories: []lockRepo{},
Keyrings: []lockKeyring{},
},
}

repositories := map[string]bool{}

for _, keyring := range ic.Contents.Keyring {
lock.Contents.Keyrings = append(lock.Contents.Keyrings, lockKeyring{
Name: stripUrlScheme(keyring),
Url: keyring,
})
}

for _, arch := range archs {
arch := arch
// working directory for this architecture
wd := filepath.Join(wd, arch.ToAPK())
bopts := append(slices.Clone(opts), build.WithArch(arch))
fs := apkfs.DirFS(wd, apkfs.WithCreateDir())
bc, err := build.New(ctx, fs, bopts...)
if err != nil {
return err
}

resolvedPkgs, err := bc.Resolve(ctx)

if err != nil {
return fmt.Errorf("failed to get package list for image: %w", err)
}

for _, rpkg := range resolvedPkgs {

lockPkg := lockPkg{
Name: rpkg.Package.Name,
Url: rpkg.Package.Url(),
Architecture: rpkg.Package.Arch,
Version: rpkg.Package.Version,
Control: lockPkg_RangeAndChecksum{
Range: fmt.Sprintf("bytes=%d-%d", rpkg.SignatureSize, rpkg.ControlSize-1),
Checksum: "sha1-" + base64.StdEncoding.EncodeToString(rpkg.ControlHash),
},
Data: lockPkg_RangeAndChecksum{
Range: fmt.Sprintf("bytes=%d-%d", rpkg.ControlSize, rpkg.DataSize),
Checksum: "sha256-" + base64.StdEncoding.EncodeToString(rpkg.DataHash),
},
}

if rpkg.SignatureSize != 0 {
lockPkg.Signature = lockPkg_RangeAndChecksum{
Range: fmt.Sprintf("bytes=0-%d", rpkg.SignatureSize-1),
Checksum: "sha1-" + base64.StdEncoding.EncodeToString(rpkg.SignatureHash),
}
}

lock.Contents.Packages = append(lock.Contents.Packages, lockPkg)

if _, ok := repositories[rpkg.Package.Repository().Uri]; !ok {
lock.Contents.Repositories = append(lock.Contents.Repositories, lockRepo{
Name: stripUrlScheme(rpkg.Package.Repository().Uri),
Url: rpkg.Package.Repository().IndexUri(),
Architecture: arch.ToAPK(),
})
repositories[rpkg.Package.Repository().Uri] = true
}

}

}

jsonb, err := json.MarshalIndent(lock, "", " ")
if err != nil {
return fmt.Errorf("failed to marshall json: %w", err)
}

return os.WriteFile(output, jsonb, os.ModePerm)
}

func stripUrlScheme(url string) string {
return strings.TrimPrefix(
strings.TrimPrefix(url, "https://"),
"http://",
)
}
4 changes: 4 additions & 0 deletions pkg/build/build_implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ func (bc *Context) BuildPackageList(ctx context.Context) (toInstall []*repositor
return toInstall, conflicts, err
}

func (bc *Context) Resolve(ctx context.Context) ([]*apk.APKResolved, error) {
return bc.apk.ResolveAndCalculateWorld(ctx)
}

func (bc *Context) InstalledPackages() ([]*apk.InstalledPackage, error) {
return bc.apk.GetInstalled()
}

0 comments on commit ad97a58

Please sign in to comment.