mirror of
https://github.com/linuxkit/linuxkit.git
synced 2026-03-19 16:24:12 +00:00
Compare commits
9 Commits
v1.6.1
...
pkg-v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c5668116 | ||
|
|
2b4687338b | ||
|
|
940c1b7b3b | ||
|
|
818bccf20f | ||
|
|
50120bce2d | ||
|
|
254aefc953 | ||
|
|
4df360d62d | ||
|
|
3f54a80824 | ||
|
|
d45d3e8c6e |
@@ -59,3 +59,31 @@ is provided, it always will pull, independent of what is in the cache.
|
||||
|
||||
The read process is smart enough to check each blob in the local cache before downloading
|
||||
it from a registry.
|
||||
|
||||
## Imports from local Docker instance
|
||||
|
||||
To import an image from your local Docker daemon into LinuxKit, you’ll need to ensure the image is exported in the [OCI image format](https://docs.docker.com/build/exporters/oci-docker/), which LinuxKit understands.
|
||||
|
||||
This requires using a `docker-container` [buildx driver](https://docs.docker.com/build/builders/drivers/docker-container/), rather than the default.
|
||||
|
||||
Set it up like so:
|
||||
|
||||
```shell
|
||||
docker buildx create --driver docker-container --driver-opt image=moby/buildkit:latest --name=ocibuilder --bootstrap
|
||||
```
|
||||
|
||||
Then build and export your image using the OCI format:
|
||||
|
||||
```shell
|
||||
docker buildx build --builder=ocibuilder --output type=oci,name=foo . > foo.tar
|
||||
```
|
||||
|
||||
You can now import it into LinuxKit with:
|
||||
|
||||
```shell
|
||||
linuxkit cache import foo.tar
|
||||
```
|
||||
|
||||
Note that this process, as described, will only produce images for the platform/architecture you're currently on. To produce multi-platform images requires extra docker build flags and external builder or QEMU support - see [here](https://docs.docker.com/build/building/multi-platform/).
|
||||
|
||||
This workaround is only necessary when working with the local Docker daemon. If you’re pulling from Docker Hub or another registry, you don’t need to do any of this.
|
||||
|
||||
5
src/cmd/linuxkit/cache/const.go
vendored
Normal file
5
src/cmd/linuxkit/cache/const.go
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package cache
|
||||
|
||||
const (
|
||||
lockfile = ".lock"
|
||||
)
|
||||
34
src/cmd/linuxkit/cache/open.go
vendored
34
src/cmd/linuxkit/cache/open.go
vendored
@@ -2,20 +2,44 @@ package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
)
|
||||
|
||||
// Get get or initialize the cache
|
||||
func Get(cache string) (layout.Path, error) {
|
||||
func (p *Provider) Get(cache string) (layout.Path, error) {
|
||||
// ensure the dir exists
|
||||
if err := os.MkdirAll(cache, os.ModePerm); err != nil {
|
||||
return "", fmt.Errorf("unable to create cache directory %s: %v", cache, err)
|
||||
}
|
||||
|
||||
// first try to read the layout from the path
|
||||
// if it exists, we can use it
|
||||
// if it does not exist, we will initialize it
|
||||
//
|
||||
// do not lock for first read, because we do not need the lock except for initialization
|
||||
// and future writes, so why slow down reads?
|
||||
l, err := layout.FromPath(cache)
|
||||
|
||||
// initialize the cache path if needed
|
||||
p, err := layout.FromPath(cache)
|
||||
if err != nil {
|
||||
p, err = layout.Write(cache, empty.Index)
|
||||
if err := p.Lock(); err != nil {
|
||||
return "", fmt.Errorf("unable to lock cache %s: %v", cache, err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
|
||||
// after lock, try to read the layout again
|
||||
// in case another process initialized it while we were waiting for the lock
|
||||
// if it still does not exist, we will initialize it
|
||||
l, err = layout.FromPath(cache)
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("could not initialize cache at path %s: %v", cache, err)
|
||||
l, err = layout.Write(cache, empty.Index)
|
||||
if err != nil {
|
||||
return l, fmt.Errorf("could not initialize cache at path %s: %v", cache, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
return l, nil
|
||||
}
|
||||
|
||||
52
src/cmd/linuxkit/cache/provider.go
vendored
52
src/cmd/linuxkit/cache/provider.go
vendored
@@ -1,20 +1,30 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/content"
|
||||
"github.com/containerd/containerd/v2/plugins/content/local"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Provider cache implementation of cacheProvider
|
||||
type Provider struct {
|
||||
cache layout.Path
|
||||
store content.Store
|
||||
cache layout.Path
|
||||
store content.Store
|
||||
dir string
|
||||
lock *util.FileLock
|
||||
lockMut sync.Mutex
|
||||
}
|
||||
|
||||
// NewProvider create a new CacheProvider based in the provided directory
|
||||
func NewProvider(dir string) (*Provider, error) {
|
||||
p, err := Get(dir)
|
||||
p := &Provider{dir: dir, lockMut: sync.Mutex{}}
|
||||
layout, err := p.Get(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -22,5 +32,39 @@ func NewProvider(dir string) (*Provider, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Provider{p, store}, nil
|
||||
p.cache = layout
|
||||
p.store = store
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Lock locks the cache directory to prevent concurrent access
|
||||
func (p *Provider) Lock() error {
|
||||
// if the lock is already set, we do not need to do anything
|
||||
if p.lock != nil {
|
||||
return nil
|
||||
}
|
||||
p.lockMut.Lock()
|
||||
defer p.lockMut.Unlock()
|
||||
var lockFile = filepath.Join(p.dir, lockfile)
|
||||
lock, err := util.Lock(lockFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve cache lock %s: %v", lockFile, err)
|
||||
}
|
||||
p.lock = lock
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock releases the lock on the cache directory
|
||||
func (p *Provider) Unlock() {
|
||||
p.lockMut.Lock()
|
||||
defer p.lockMut.Unlock()
|
||||
// if the lock is not set, we do not need to do anything
|
||||
if p.lock == nil {
|
||||
return
|
||||
}
|
||||
var lockFile = filepath.Join(p.dir, lockfile)
|
||||
if err := p.lock.Unlock(); err != nil {
|
||||
log.Errorf("unable to close lock for cache %s: %v", lockFile, err)
|
||||
}
|
||||
p.lock = nil
|
||||
}
|
||||
|
||||
21
src/cmd/linuxkit/cache/pull.go
vendored
21
src/cmd/linuxkit/cache/pull.go
vendored
@@ -9,8 +9,6 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
namepkg "github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/validate"
|
||||
@@ -182,10 +180,11 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
||||
return fmt.Errorf("error getting manifest for trusted image %s: %v", name, err)
|
||||
}
|
||||
|
||||
// use the original image name in the annotation
|
||||
annotations := map[string]string{
|
||||
imagespec.AnnotationRefName: fullname,
|
||||
// lock the cache so we can write to it
|
||||
if err := p.Lock(); err != nil {
|
||||
return fmt.Errorf("unable to lock cache for writing: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
|
||||
// first attempt as an index
|
||||
ii, err := desc.ImageIndex()
|
||||
@@ -195,7 +194,7 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
||||
if err := p.cache.WriteIndex(ii); err != nil {
|
||||
return fmt.Errorf("unable to write index: %v", err)
|
||||
}
|
||||
if err := p.DescriptorWrite(&v1ref, desc.Descriptor); err != nil {
|
||||
if err := p.DescriptorWrite(v1ref.String(), desc.Descriptor); err != nil {
|
||||
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||
}
|
||||
if withArchReferences {
|
||||
@@ -206,11 +205,10 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
||||
for _, m := range im.Manifests {
|
||||
if m.MediaType.IsImage() && m.Platform != nil && m.Platform.Architecture != unknown && m.Platform.OS != unknown {
|
||||
archSpecific := fmt.Sprintf("%s-%s", ref.String(), m.Platform.Architecture)
|
||||
archRef, err := reference.Parse(archSpecific)
|
||||
if err != nil {
|
||||
if _, err := reference.Parse(archSpecific); err != nil {
|
||||
return fmt.Errorf("unable to parse arch-specific reference %s: %v", archSpecific, err)
|
||||
}
|
||||
if err := p.DescriptorWrite(&archRef, m); err != nil {
|
||||
if err := p.DescriptorWrite(archSpecific, m); err != nil {
|
||||
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -224,9 +222,12 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
||||
return fmt.Errorf("provided image is neither an image nor an index: %s", name)
|
||||
}
|
||||
log.Debugf("ImageWrite retrieved %s is image, saving", fullname)
|
||||
if err = p.cache.ReplaceImage(im, match.Name(fullname), layout.WithAnnotations(annotations)); err != nil {
|
||||
if err = p.cache.WriteImage(im); err != nil {
|
||||
return fmt.Errorf("unable to save image to cache: %v", err)
|
||||
}
|
||||
if err = p.DescriptorWrite(fullname, desc.Descriptor); err != nil {
|
||||
return fmt.Errorf("unable to write updated descriptor to cache: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
7
src/cmd/linuxkit/cache/push.go
vendored
7
src/cmd/linuxkit/cache/push.go
vendored
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -131,11 +130,7 @@ func (p *Provider) Push(name, remoteName string, withArchSpecificTags, override
|
||||
// it might not have existed, so we can add it locally
|
||||
// use the original image name in the annotation
|
||||
desc := m.DeepCopy()
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = map[string]string{}
|
||||
}
|
||||
desc.Annotations[imagespec.AnnotationRefName] = archTag
|
||||
if err := p.cache.AppendDescriptor(*desc); err != nil {
|
||||
if err := p.DescriptorWrite(archTag, *desc); err != nil {
|
||||
return fmt.Errorf("error appending descriptor for %s to layout index: %v", archTag, err)
|
||||
}
|
||||
img, err = p.cache.Image(m.Digest)
|
||||
|
||||
2
src/cmd/linuxkit/cache/remove.go
vendored
2
src/cmd/linuxkit/cache/remove.go
vendored
@@ -65,7 +65,7 @@ func (p *Provider) Remove(name string) error {
|
||||
log.Warnf("unable to remove blob %s for %s: %v", blob, name, err)
|
||||
}
|
||||
}
|
||||
return p.cache.RemoveDescriptors(match.Name(name))
|
||||
return p.RemoveDescriptors(match.Name(name))
|
||||
}
|
||||
|
||||
func blobsForImage(img v1.Image) ([]v1.Hash, error) {
|
||||
|
||||
121
src/cmd/linuxkit/cache/write.go
vendored
121
src/cmd/linuxkit/cache/write.go
vendored
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
@@ -77,10 +76,11 @@ func (p *Provider) ImagePull(ref *reference.Spec, platforms []imagespec.Platform
|
||||
return fmt.Errorf("error getting manifest for image %s: %v", pullImageName, err)
|
||||
}
|
||||
|
||||
// use the original image name in the annotation
|
||||
annotations := map[string]string{
|
||||
imagespec.AnnotationRefName: image,
|
||||
// get our lock
|
||||
if err := p.Lock(); err != nil {
|
||||
return fmt.Errorf("unable to lock cache for removing descriptors: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
|
||||
// first attempt as an index
|
||||
ii, err := desc.ImageIndex()
|
||||
@@ -120,7 +120,7 @@ func (p *Provider) ImagePull(ref *reference.Spec, platforms []imagespec.Platform
|
||||
if err := p.cache.WriteIndex(ii); err != nil {
|
||||
return fmt.Errorf("unable to write index: %v", err)
|
||||
}
|
||||
if err := p.DescriptorWrite(ref, desc.Descriptor); err != nil {
|
||||
if err := p.DescriptorWrite(ref.String(), desc.Descriptor); err != nil {
|
||||
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||
}
|
||||
} else {
|
||||
@@ -131,8 +131,11 @@ func (p *Provider) ImagePull(ref *reference.Spec, platforms []imagespec.Platform
|
||||
return fmt.Errorf("provided image is neither an image nor an index: %s", image)
|
||||
}
|
||||
log.Debugf("ImageWrite retrieved %s is image, saving", pullImageName)
|
||||
if err = p.cache.ReplaceImage(im, match.Name(image), layout.WithAnnotations(annotations)); err != nil {
|
||||
return fmt.Errorf("unable to save image to cache: %v", err)
|
||||
if err := p.cache.WriteImage(im); err != nil {
|
||||
return fmt.Errorf("error writing image %s to cache: %v", pullImageName, err)
|
||||
}
|
||||
if err := p.DescriptorWrite(image, desc.Descriptor); err != nil {
|
||||
return fmt.Errorf("unable to write image descriptor to cache: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -149,6 +152,11 @@ func (p *Provider) ImageLoad(r io.Reader) ([]v1.Descriptor, error) {
|
||||
index bytes.Buffer
|
||||
)
|
||||
log.Debugf("ImageWriteTar to cache")
|
||||
// get our lock
|
||||
if err := p.Lock(); err != nil {
|
||||
return nil, fmt.Errorf("unable to lock cache: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
@@ -208,20 +216,11 @@ func (p *Provider) ImageLoad(r io.Reader) ([]v1.Descriptor, error) {
|
||||
// each of these is either an image or an index
|
||||
// either way, it gets added directly to the linuxkit cache index.
|
||||
for _, desc := range im.Manifests {
|
||||
if imgName, ok := desc.Annotations[images.AnnotationImageName]; ok {
|
||||
// remove the old descriptor, if it exists
|
||||
if err := p.cache.RemoveDescriptors(match.Name(imgName)); err != nil {
|
||||
return nil, fmt.Errorf("unable to remove old descriptors for %s: %v", imgName, err)
|
||||
imgName, ok := desc.Annotations[images.AnnotationImageName]
|
||||
if ok {
|
||||
if err := p.DescriptorWrite(imgName, desc); err != nil {
|
||||
return nil, fmt.Errorf("error writing descriptor for %s: %v", imgName, err)
|
||||
}
|
||||
// save the image name under our proper annotation
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = map[string]string{}
|
||||
}
|
||||
desc.Annotations[imagespec.AnnotationRefName] = imgName
|
||||
}
|
||||
log.Debugf("appending descriptor %#v", desc)
|
||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||
return nil, fmt.Errorf("error appending descriptor to layout index: %v", err)
|
||||
}
|
||||
descs = append(descs, desc)
|
||||
}
|
||||
@@ -256,6 +255,11 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
||||
return fmt.Errorf("error parsing index: %v", err)
|
||||
}
|
||||
var im v1.IndexManifest
|
||||
// get our lock
|
||||
if err := p.Lock(); err != nil {
|
||||
return fmt.Errorf("unable to lock cache: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
// do we update an existing one? Or create a new one?
|
||||
if len(indexes) > 0 {
|
||||
// we already had one, so update just the referenced index and return
|
||||
@@ -366,9 +370,6 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
||||
return fmt.Errorf("error writing new index to json: %v", err)
|
||||
}
|
||||
// finally update the descriptor in the root
|
||||
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
||||
return fmt.Errorf("unable to remove old descriptor from index.json: %v", err)
|
||||
}
|
||||
desc := v1.Descriptor{
|
||||
MediaType: types.OCIImageIndex,
|
||||
Size: size,
|
||||
@@ -377,36 +378,7 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
||||
imagespec.AnnotationRefName: image,
|
||||
},
|
||||
}
|
||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||
return fmt.Errorf("unable to append new descriptor to index.json: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||
// and replaces any existing one
|
||||
func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) error {
|
||||
if ref == nil {
|
||||
return errors.New("cannot write descriptor without reference name")
|
||||
}
|
||||
image := ref.String()
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = map[string]string{}
|
||||
}
|
||||
desc.Annotations[imagespec.AnnotationRefName] = image
|
||||
log.Debugf("writing descriptor for image %s", image)
|
||||
|
||||
// do we update an existing one? Or create a new one?
|
||||
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
||||
return fmt.Errorf("unable to remove old descriptors for %s: %v", image, err)
|
||||
}
|
||||
|
||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||
return fmt.Errorf("unable to append new descriptor for %s: %v", image, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return p.DescriptorWrite(ref.String(), desc)
|
||||
}
|
||||
|
||||
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
||||
@@ -496,3 +468,46 @@ func (p *Provider) ImageInRegistry(ref *reference.Spec, trustedRef, architecture
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||
// and replaces any existing one
|
||||
func (p *Provider) DescriptorWrite(image string, desc v1.Descriptor) error {
|
||||
if image == "" {
|
||||
return errors.New("cannot write descriptor without reference name")
|
||||
}
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = map[string]string{}
|
||||
}
|
||||
desc.Annotations[imagespec.AnnotationRefName] = image
|
||||
log.Debugf("writing descriptor for image %s", image)
|
||||
|
||||
// get our lock
|
||||
if err := p.Lock(); err != nil {
|
||||
return fmt.Errorf("unable to lock cache for writing descriptors: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
|
||||
// get our lock
|
||||
// do we update an existing one? Or create a new one?
|
||||
if err := p.cache.RemoveDescriptors(match.Name(image)); err != nil {
|
||||
return fmt.Errorf("unable to remove old descriptors for %s: %v", image, err)
|
||||
}
|
||||
|
||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
||||
return fmt.Errorf("unable to append new descriptor for %s: %v", image, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDescriptors removes all descriptors that match the provided matcher.
|
||||
// It does so in a parallel-access-safe way
|
||||
func (p *Provider) RemoveDescriptors(matcher match.Matcher) error {
|
||||
// get our lock
|
||||
if err := p.Lock(); err != nil {
|
||||
return fmt.Errorf("unable to lock cache for removing descriptors: %v", err)
|
||||
}
|
||||
defer p.Unlock()
|
||||
|
||||
return p.cache.RemoveDescriptors(matcher)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var pkglibConfig pkglib.PkglibConfig
|
||||
var (
|
||||
pkglibConfig pkglib.PkglibConfig
|
||||
registryCreds []string
|
||||
)
|
||||
|
||||
func pkgCmd() *cobra.Command {
|
||||
var (
|
||||
@@ -72,9 +75,11 @@ func pkgCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(pkgBuildCmd())
|
||||
// because there is an alias 'pkg push' for 'pkg build --push', we need to add the build command first
|
||||
buildCmd := pkgBuildCmd()
|
||||
cmd.AddCommand(buildCmd)
|
||||
cmd.AddCommand(pkgBuilderCmd())
|
||||
cmd.AddCommand(pkgPushCmd())
|
||||
cmd.AddCommand(pkgPushCmd(buildCmd))
|
||||
cmd.AddCommand(pkgShowTagCmd())
|
||||
cmd.AddCommand(pkgManifestCmd())
|
||||
cmd.AddCommand(pkgRemoteTagCmd())
|
||||
@@ -96,5 +101,6 @@ func pkgCmd() *cobra.Command {
|
||||
cmd.PersistentFlags().BoolVar(&dirty, "force-dirty", false, "Force the pkg(s) to be considered dirty")
|
||||
cmd.PersistentFlags().BoolVar(&devMode, "dev", false, "Force org and hash to $USER and \"dev\" respectively")
|
||||
|
||||
cmd.PersistentFlags().StringSliceVar(®istryCreds, "registry-creds", nil, "Registry auths to use for building images, format is <registry>=<username>:<password> OR <registry>=<registry-token>. If no username is provided, it is treated as a registry token. <registry> must be a URL, e.g. 'https://index.docker.io/'. May be provided as many times as desired. Will override anything in your default.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib"
|
||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -19,27 +20,28 @@ const (
|
||||
)
|
||||
|
||||
// some logic clarification:
|
||||
// pkg build - builds unless is in cache or published in registry
|
||||
// pkg build --pull - builds unless is in cache or published in registry; pulls from registry if not in cache
|
||||
// pkg build --force - always builds even if is in cache or published in registry
|
||||
// pkg build --force --pull - always builds even if is in cache or published in registry; --pull ignored
|
||||
// pkg push - always builds unless is in cache
|
||||
// pkg push --force - always builds even if is in cache
|
||||
// pkg push --nobuild - skips build; if not in cache, fails
|
||||
// pkg push --nobuild --force - nonsensical
|
||||
// pkg build - builds unless is in cache or published in registry
|
||||
// pkg build --pull - builds unless is in cache or published in registry; pulls from registry if not in cache
|
||||
// pkg build --force - always builds even if is in cache or published in registry
|
||||
// pkg build --force --pull - always builds even if is in cache or published in registry; --pull ignored
|
||||
// pkg build --push - always builds unless is in cache or published in registry; pushes to registry
|
||||
// pkg build --push --force - always builds even if is in cache
|
||||
// pkg build --push --nobuild - skips build; if not in cache, fails
|
||||
// pkg build --push --nobuild --force - nonsensical
|
||||
// pkg push - equivalent to pkg build --push
|
||||
|
||||
// addCmdRunPkgBuildPush adds the RunE function and flags to a cobra.Command
|
||||
// for "pkg build" or "pkg push".
|
||||
func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
||||
func pkgBuildCmd() *cobra.Command {
|
||||
var (
|
||||
force bool
|
||||
pull bool
|
||||
push bool
|
||||
ignoreCache bool
|
||||
docker bool
|
||||
platforms string
|
||||
skipPlatforms string
|
||||
builders string
|
||||
builderImage string
|
||||
builderConfig string
|
||||
builderRestart bool
|
||||
release string
|
||||
nobuild bool
|
||||
@@ -51,179 +53,235 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
||||
progress string
|
||||
ssh []string
|
||||
)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
pkgs, err := pkglib.NewFromConfig(pkglibConfig, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nobuild && force {
|
||||
return errors.New("flags -force and -nobuild conflict")
|
||||
}
|
||||
if pull && force {
|
||||
return errors.New("flags -force and -pull conflict")
|
||||
}
|
||||
|
||||
var opts []pkglib.BuildOpt
|
||||
if force {
|
||||
opts = append(opts, pkglib.WithBuildForce())
|
||||
}
|
||||
if ignoreCache {
|
||||
opts = append(opts, pkglib.WithBuildIgnoreCache())
|
||||
}
|
||||
if pull {
|
||||
opts = append(opts, pkglib.WithBuildPull())
|
||||
}
|
||||
|
||||
opts = append(opts, pkglib.WithBuildCacheDir(cacheDir.String()))
|
||||
|
||||
if withPush {
|
||||
opts = append(opts, pkglib.WithBuildPush())
|
||||
if nobuild {
|
||||
opts = append(opts, pkglib.WithBuildSkip())
|
||||
}
|
||||
if release != "" {
|
||||
opts = append(opts, pkglib.WithRelease(release))
|
||||
}
|
||||
if manifest {
|
||||
opts = append(opts, pkglib.WithBuildManifest())
|
||||
}
|
||||
}
|
||||
if docker {
|
||||
opts = append(opts, pkglib.WithBuildTargetDockerCache())
|
||||
}
|
||||
|
||||
if sbomScanner != "false" {
|
||||
opts = append(opts, pkglib.WithBuildSbomScanner(sbomScanner))
|
||||
}
|
||||
opts = append(opts, pkglib.WithDockerfile(dockerfile))
|
||||
|
||||
// read any build arg files
|
||||
var buildArgs []string
|
||||
for _, filename := range buildArgFiles {
|
||||
f, err := os.Open(filename)
|
||||
cmd := &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "build an OCI package from a directory with a yaml configuration file",
|
||||
Long: `Build an OCI package from a directory with a yaml configuration file.
|
||||
'path' specifies the path to the package source directory.
|
||||
`,
|
||||
Example: ` linuxkit pkg build [options] pkg/dir/`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
pkgs, err := pkglib.NewFromConfig(pkglibConfig, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening build args file %s: %w", filename, err)
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
buildArgs = append(buildArgs, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("error reading build args file %s: %w", filename, err)
|
||||
}
|
||||
}
|
||||
opts = append(opts, pkglib.WithBuildArgs(buildArgs))
|
||||
|
||||
// skipPlatformsMap contains platforms that should be skipped
|
||||
skipPlatformsMap := make(map[string]bool)
|
||||
if skipPlatforms != "" {
|
||||
for _, platform := range strings.Split(skipPlatforms, ",") {
|
||||
parts := strings.SplitN(platform, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[0] != "linux" || parts[1] == "" {
|
||||
return fmt.Errorf("invalid target platform specification '%s'", platform)
|
||||
if nobuild && force {
|
||||
return errors.New("flags -force and -nobuild conflict")
|
||||
}
|
||||
if pull && force {
|
||||
return errors.New("flags -force and -pull conflict")
|
||||
}
|
||||
|
||||
var opts []pkglib.BuildOpt
|
||||
if force {
|
||||
opts = append(opts, pkglib.WithBuildForce())
|
||||
}
|
||||
if ignoreCache {
|
||||
opts = append(opts, pkglib.WithBuildIgnoreCache())
|
||||
}
|
||||
if pull {
|
||||
opts = append(opts, pkglib.WithBuildPull())
|
||||
}
|
||||
|
||||
opts = append(opts, pkglib.WithBuildCacheDir(cacheDir.String()))
|
||||
|
||||
if push {
|
||||
opts = append(opts, pkglib.WithBuildPush())
|
||||
if nobuild {
|
||||
opts = append(opts, pkglib.WithBuildSkip())
|
||||
}
|
||||
skipPlatformsMap[strings.Trim(parts[1], " ")] = true
|
||||
}
|
||||
}
|
||||
// if requested specific platforms, build those. If not, then we will
|
||||
// retrieve the defaults in the loop over each package.
|
||||
var plats []imagespec.Platform
|
||||
// don't allow the use of --skip-platforms with --platforms
|
||||
if platforms != "" && skipPlatforms != "" {
|
||||
return errors.New("--skip-platforms and --platforms may not be used together")
|
||||
}
|
||||
// process the platforms if provided
|
||||
if platforms != "" {
|
||||
for _, p := range strings.Split(platforms, ",") {
|
||||
parts := strings.SplitN(p, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
fmt.Fprintf(os.Stderr, "invalid target platform specification '%s'\n", p)
|
||||
os.Exit(1)
|
||||
if release != "" {
|
||||
opts = append(opts, pkglib.WithRelease(release))
|
||||
}
|
||||
if manifest {
|
||||
opts = append(opts, pkglib.WithBuildManifest())
|
||||
}
|
||||
plats = append(plats, imagespec.Platform{OS: parts[0], Architecture: parts[1]})
|
||||
}
|
||||
}
|
||||
if docker {
|
||||
opts = append(opts, pkglib.WithBuildTargetDockerCache())
|
||||
}
|
||||
|
||||
// build the builders map
|
||||
buildersMap := map[string]string{}
|
||||
// look for builders env var
|
||||
buildersMap, err = buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in environment variable %s: %w", buildersEnvVar, err)
|
||||
}
|
||||
// any CLI options override env var
|
||||
buildersMap, err = buildPlatformBuildersMap(builders, buildersMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in --builders flag: %w", err)
|
||||
}
|
||||
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
|
||||
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
|
||||
opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart))
|
||||
opts = append(opts, pkglib.WithProgress(progress))
|
||||
if len(ssh) > 0 {
|
||||
opts = append(opts, pkglib.WithSSH(ssh))
|
||||
}
|
||||
if sbomScanner != "false" {
|
||||
opts = append(opts, pkglib.WithBuildSbomScanner(sbomScanner))
|
||||
}
|
||||
opts = append(opts, pkglib.WithDockerfile(dockerfile))
|
||||
|
||||
for _, p := range pkgs {
|
||||
// things we need our own copies of
|
||||
var (
|
||||
pkgOpts = make([]pkglib.BuildOpt, len(opts))
|
||||
pkgPlats = make([]imagespec.Platform, len(plats))
|
||||
)
|
||||
copy(pkgOpts, opts)
|
||||
copy(pkgPlats, plats)
|
||||
// unless overridden, platforms are specific to a package, so this needs to be inside the for loop
|
||||
if len(pkgPlats) == 0 {
|
||||
for _, a := range p.Arches() {
|
||||
if _, ok := skipPlatformsMap[a]; ok {
|
||||
continue
|
||||
// read any build arg files
|
||||
var buildArgs []string
|
||||
for _, filename := range buildArgFiles {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening build args file %s: %w", filename, err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
buildArgs = append(buildArgs, scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("error reading build args file %s: %w", filename, err)
|
||||
}
|
||||
}
|
||||
opts = append(opts, pkglib.WithBuildArgs(buildArgs))
|
||||
|
||||
// skipPlatformsMap contains platforms that should be skipped
|
||||
skipPlatformsMap := make(map[string]bool)
|
||||
if skipPlatforms != "" {
|
||||
for _, platform := range strings.Split(skipPlatforms, ",") {
|
||||
parts := strings.SplitN(platform, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[0] != "linux" || parts[1] == "" {
|
||||
return fmt.Errorf("invalid target platform specification '%s'", platform)
|
||||
}
|
||||
pkgPlats = append(pkgPlats, imagespec.Platform{OS: "linux", Architecture: a})
|
||||
skipPlatformsMap[strings.Trim(parts[1], " ")] = true
|
||||
}
|
||||
}
|
||||
// if requested specific platforms, build those. If not, then we will
|
||||
// retrieve the defaults in the loop over each package.
|
||||
var plats []imagespec.Platform
|
||||
// don't allow the use of --skip-platforms with --platforms
|
||||
if platforms != "" && skipPlatforms != "" {
|
||||
return errors.New("--skip-platforms and --platforms may not be used together")
|
||||
}
|
||||
// process the platforms if provided
|
||||
if platforms != "" {
|
||||
for _, p := range strings.Split(platforms, ",") {
|
||||
parts := strings.SplitN(p, "/", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
fmt.Fprintf(os.Stderr, "invalid target platform specification '%s'\n", p)
|
||||
os.Exit(1)
|
||||
}
|
||||
plats = append(plats, imagespec.Platform{OS: parts[0], Architecture: parts[1]})
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no platforms to build for, do nothing.
|
||||
// note that this is *not* an error; we simply skip it
|
||||
if len(pkgPlats) == 0 {
|
||||
fmt.Printf("Skipping %s with no architectures to build\n", p.Tag())
|
||||
continue
|
||||
// build the builders map
|
||||
buildersMap := map[string]string{}
|
||||
// look for builders env var
|
||||
buildersMap, err = buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in environment variable %s: %w", buildersEnvVar, err)
|
||||
}
|
||||
// any CLI options override env var
|
||||
buildersMap, err = buildPlatformBuildersMap(builders, buildersMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in --builders flag: %w", err)
|
||||
}
|
||||
if builderConfig != "" {
|
||||
if _, err := os.Stat(builderConfig); err != nil {
|
||||
return fmt.Errorf("error reading builder config file %s: %w", builderConfig, err)
|
||||
}
|
||||
opts = append(opts, pkglib.WithBuildBuilderConfig(builderConfig))
|
||||
}
|
||||
|
||||
pkgOpts = append(pkgOpts, pkglib.WithBuildPlatforms(pkgPlats...))
|
||||
|
||||
var msg, action string
|
||||
switch {
|
||||
case !withPush:
|
||||
msg = fmt.Sprintf("Building %q", p.Tag())
|
||||
action = "building"
|
||||
case nobuild:
|
||||
msg = fmt.Sprintf("Pushing %q without building", p.Tag())
|
||||
action = "building and pushing"
|
||||
default:
|
||||
msg = fmt.Sprintf("Building and pushing %q", p.Tag())
|
||||
action = "building and pushing"
|
||||
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
|
||||
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
|
||||
opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart))
|
||||
opts = append(opts, pkglib.WithProgress(progress))
|
||||
if len(ssh) > 0 {
|
||||
opts = append(opts, pkglib.WithSSH(ssh))
|
||||
}
|
||||
if len(registryCreds) > 0 {
|
||||
registryCredMap := make(map[string]spec.RegistryAuth)
|
||||
for _, cred := range registryCreds {
|
||||
parts := strings.SplitN(cred, "=", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return fmt.Errorf("invalid registry auth specification '%s'", cred)
|
||||
}
|
||||
registryPart := strings.TrimSpace(parts[0])
|
||||
authPart := strings.TrimSpace(parts[1])
|
||||
var auth spec.RegistryAuth
|
||||
// if the auth is a token, we don't need a username
|
||||
credParts := strings.SplitN(authPart, ":", 2)
|
||||
var userPart, credPart string
|
||||
userPart = strings.TrimSpace(credParts[0])
|
||||
if len(credParts) == 2 {
|
||||
credPart = strings.TrimSpace(credParts[1])
|
||||
}
|
||||
switch {
|
||||
case len(registryPart) == 0:
|
||||
return fmt.Errorf("invalid registry auth specification '%s', registry must not be blank", cred)
|
||||
case len(credParts) == 2 && (len(userPart) == 0 || len(credPart) == 0):
|
||||
return fmt.Errorf("invalid registry auth specification '%s', username and password must not be blank", cred)
|
||||
case len(credParts) == 1 && len(userPart) == 0:
|
||||
return fmt.Errorf("invalid registry auth specification '%s', token must not be blank", cred)
|
||||
case len(credParts) == 2:
|
||||
auth = spec.RegistryAuth{
|
||||
Username: userPart,
|
||||
Password: credPart,
|
||||
}
|
||||
case len(credParts) == 1:
|
||||
auth = spec.RegistryAuth{
|
||||
RegistryToken: authPart,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid registry auth specification '%s'", cred)
|
||||
}
|
||||
registryCredMap[registryPart] = auth
|
||||
}
|
||||
opts = append(opts, pkglib.WithRegistryAuth(registryCredMap))
|
||||
}
|
||||
|
||||
fmt.Println(msg)
|
||||
for _, p := range pkgs {
|
||||
// things we need our own copies of
|
||||
var (
|
||||
pkgOpts = make([]pkglib.BuildOpt, len(opts))
|
||||
pkgPlats = make([]imagespec.Platform, len(plats))
|
||||
)
|
||||
copy(pkgOpts, opts)
|
||||
copy(pkgPlats, plats)
|
||||
// unless overridden, platforms are specific to a package, so this needs to be inside the for loop
|
||||
if len(pkgPlats) == 0 {
|
||||
for _, a := range p.Arches() {
|
||||
if _, ok := skipPlatformsMap[a]; ok {
|
||||
continue
|
||||
}
|
||||
pkgPlats = append(pkgPlats, imagespec.Platform{OS: "linux", Architecture: a})
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.Build(pkgOpts...); err != nil {
|
||||
return fmt.Errorf("error %s %q: %w", action, p.Tag(), err)
|
||||
// if there are no platforms to build for, do nothing.
|
||||
// note that this is *not* an error; we simply skip it
|
||||
if len(pkgPlats) == 0 {
|
||||
fmt.Printf("Skipping %s with no architectures to build\n", p.Tag())
|
||||
continue
|
||||
}
|
||||
|
||||
pkgOpts = append(pkgOpts, pkglib.WithBuildPlatforms(pkgPlats...))
|
||||
|
||||
var msg, action string
|
||||
switch {
|
||||
case !push:
|
||||
msg = fmt.Sprintf("Building %q", p.Tag())
|
||||
action = "building"
|
||||
case nobuild:
|
||||
msg = fmt.Sprintf("Pushing %q without building", p.Tag())
|
||||
action = "building and pushing"
|
||||
default:
|
||||
msg = fmt.Sprintf("Building and pushing %q", p.Tag())
|
||||
action = "building and pushing"
|
||||
}
|
||||
|
||||
fmt.Println(msg)
|
||||
|
||||
if err := p.Build(pkgOpts...); err != nil {
|
||||
return fmt.Errorf("error %s %q: %w", action, p.Tag(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&force, "force", false, "Force rebuild even if image is in local cache")
|
||||
cmd.Flags().BoolVar(&pull, "pull", false, "Pull image if in registry but not in local cache; conflicts with --force")
|
||||
cmd.Flags().BoolVar(&push, "push", false, "After building, if successful, push the image to the registry; if --nobuild is set, just push")
|
||||
cmd.Flags().BoolVar(&ignoreCache, "ignore-cached", false, "Ignore cached intermediate images, always pulling from registry")
|
||||
cmd.Flags().BoolVar(&docker, "docker", false, "Store the built image in the docker image cache instead of the default linuxkit cache")
|
||||
cmd.Flags().StringVar(&platforms, "platforms", "", "Which platforms to build for, defaults to all of those for which the package can be built")
|
||||
cmd.Flags().StringVar(&skipPlatforms, "skip-platforms", "", "Platforms that should be skipped, even if present in build.yml")
|
||||
cmd.Flags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
|
||||
cmd.Flags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use")
|
||||
cmd.Flags().StringVar(&builderConfig, "builder-config", "", "path to buildkit builder config.toml file to use, overrides the default config.toml in the builder image; USE WITH CAUTION")
|
||||
cmd.Flags().BoolVar(&builderRestart, "builder-restart", false, "force restarting builder, even if container with correct name and image exists")
|
||||
cmd.Flags().Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
|
||||
cmd.Flags().StringVar(&release, "release", "", "Release the given version")
|
||||
@@ -237,18 +295,6 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
func pkgBuildCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "build an OCI package from a directory with a yaml configuration file",
|
||||
Long: `Build an OCI package from a directory with a yaml configuration file.
|
||||
'path' specifies the path to the package source directory.
|
||||
`,
|
||||
Example: ` linuxkit pkg build [options] pkg/dir/`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
}
|
||||
return addCmdRunPkgBuildPush(cmd, false)
|
||||
}
|
||||
|
||||
func buildPlatformBuildersMap(inputs string, existing map[string]string) (map[string]string, error) {
|
||||
if inputs == "" {
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
|
||||
func pkgBuilderCmd() *cobra.Command {
|
||||
var (
|
||||
builders string
|
||||
platforms string
|
||||
builderImage string
|
||||
builders string
|
||||
platforms string
|
||||
builderImage string
|
||||
builderConfigPath string
|
||||
)
|
||||
cmd := &cobra.Command{
|
||||
Use: "builder",
|
||||
@@ -40,11 +41,11 @@ func pkgBuilderCmd() *cobra.Command {
|
||||
platformsToClean := strings.Split(platforms, ",")
|
||||
switch command {
|
||||
case "du":
|
||||
if err := pkglib.DiskUsage(buildersMap, builderImage, platformsToClean, verbose); err != nil {
|
||||
if err := pkglib.DiskUsage(buildersMap, builderImage, builderConfigPath, platformsToClean, verbose); err != nil {
|
||||
return fmt.Errorf("unable to print disk usage of builder: %w", err)
|
||||
}
|
||||
case "prune":
|
||||
if err := pkglib.PruneBuilder(buildersMap, builderImage, platformsToClean, verbose); err != nil {
|
||||
if err := pkglib.PruneBuilder(buildersMap, builderImage, builderConfigPath, platformsToClean, verbose); err != nil {
|
||||
return fmt.Errorf("unable to prune builder: %w", err)
|
||||
}
|
||||
default:
|
||||
@@ -57,6 +58,7 @@ func pkgBuilderCmd() *cobra.Command {
|
||||
cmd.PersistentFlags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
|
||||
cmd.PersistentFlags().StringVar(&platforms, "platforms", fmt.Sprintf("linux/%s", runtime.GOARCH), "Which platforms we built images for")
|
||||
cmd.PersistentFlags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use")
|
||||
cmd.Flags().StringVar(&builderConfigPath, "builder-config", "", "path to buildkit builder config.toml file to use, overrides the default config.toml in the builder image; USE WITH CAUTION")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
package main
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
func pkgPushCmd() *cobra.Command {
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func pkgPushCmd(buildCmd *cobra.Command) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push",
|
||||
Short: "build and push an OCI package from a directory with a yaml configuration file",
|
||||
Short: "Alias for 'pkg build --push'",
|
||||
Long: `Build and push an OCI package from a directory with a yaml configuration file.
|
||||
'path' specifies the path to the package source directory.
|
||||
|
||||
The package may or may not be built first, depending on options
|
||||
`,
|
||||
Example: ` linuxkit pkg push [options] pkg/dir/`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Example: ` linuxkit pkg push [options] pkg/dir/`,
|
||||
SuggestFor: []string{"build"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Deprecated: "use 'pkg build --push' instead",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Create a copy of buildCmd with push=true
|
||||
if err := buildCmd.Flags().Set("push", "true"); err != nil {
|
||||
return fmt.Errorf("'pkg push' unable to set 'pkg build --push': %w", err)
|
||||
}
|
||||
|
||||
// Pass the args to the build command
|
||||
buildCmd.SetArgs(args)
|
||||
return buildCmd.RunE(buildCmd, args)
|
||||
},
|
||||
}
|
||||
return addCmdRunPkgBuildPush(cmd, true)
|
||||
cmd.Flags().AddFlagSet(buildCmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -24,28 +24,30 @@ import (
|
||||
)
|
||||
|
||||
type buildOpts struct {
|
||||
skipBuild bool
|
||||
force bool
|
||||
pull bool
|
||||
ignoreCache bool
|
||||
push bool
|
||||
release string
|
||||
manifest bool
|
||||
targetDocker bool
|
||||
cacheDir string
|
||||
cacheProvider spec.CacheProvider
|
||||
platforms []imagespec.Platform
|
||||
builders map[string]string
|
||||
runner dockerRunner
|
||||
writer io.Writer
|
||||
builderImage string
|
||||
builderRestart bool
|
||||
sbomScan bool
|
||||
sbomScannerImage string
|
||||
dockerfile string
|
||||
buildArgs []string
|
||||
progress string
|
||||
ssh []string
|
||||
skipBuild bool
|
||||
force bool
|
||||
pull bool
|
||||
ignoreCache bool
|
||||
push bool
|
||||
release string
|
||||
manifest bool
|
||||
targetDocker bool
|
||||
cacheDir string
|
||||
cacheProvider spec.CacheProvider
|
||||
platforms []imagespec.Platform
|
||||
builders map[string]string
|
||||
runner dockerRunner
|
||||
writer io.Writer
|
||||
builderImage string
|
||||
builderConfigPath string
|
||||
builderRestart bool
|
||||
sbomScan bool
|
||||
sbomScannerImage string
|
||||
dockerfile string
|
||||
buildArgs []string
|
||||
progress string
|
||||
ssh []string
|
||||
registryAuth map[string]spec.RegistryAuth
|
||||
}
|
||||
|
||||
// BuildOpt allows callers to specify options to Build
|
||||
@@ -164,6 +166,14 @@ func WithBuildBuilderImage(image string) BuildOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithBuildBuilderConfig set the contents of the
|
||||
func WithBuildBuilderConfig(builderConfigPath string) BuildOpt {
|
||||
return func(bo *buildOpts) error {
|
||||
bo.builderConfigPath = builderConfigPath
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithBuildBuilderRestart restart the builder container even if it already is running with the correct image version
|
||||
func WithBuildBuilderRestart(restart bool) BuildOpt {
|
||||
return func(bo *buildOpts) error {
|
||||
@@ -223,6 +233,14 @@ func WithSSH(ssh []string) BuildOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithRegistryAuth stores registry credentials
|
||||
func WithRegistryAuth(creds map[string]spec.RegistryAuth) BuildOpt {
|
||||
return func(bo *buildOpts) error {
|
||||
bo.registryAuth = creds
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds the package
|
||||
func (p Pkg) Build(bos ...BuildOpt) error {
|
||||
var bo buildOpts
|
||||
@@ -449,10 +467,11 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
||||
}
|
||||
|
||||
imageBuildOpts.SSH = bo.ssh
|
||||
imageBuildOpts.RegistryAuths = bo.registryAuth
|
||||
|
||||
// build for each arch and save in the linuxkit cache
|
||||
for _, platform := range platformsToBuild {
|
||||
builtDescs, err := p.buildArch(ctx, d, c, bo.builderImage, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts)
|
||||
builtDescs, err := p.buildArch(ctx, d, c, bo.builderImage, bo.builderConfigPath, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err)
|
||||
}
|
||||
@@ -561,7 +580,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.DescriptorWrite(&ref, *desc); err != nil {
|
||||
if err := c.DescriptorWrite(fullRelTag, *desc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Push(fullRelTag, "", bo.manifest, true); err != nil {
|
||||
@@ -602,7 +621,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
||||
// C - manifest, saved in cache as is, referenced by the index (E), and returned as a descriptor
|
||||
// D - attestations (if any), saved in cache as is, referenced by the index (E), and returned as a descriptor
|
||||
// E - index, saved in cache as is, stored in cache as tag "image:tag-arch", *not* returned as a descriptor
|
||||
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider, builderImage, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts spec.ImageBuildOptions) ([]registry.Descriptor, error) {
|
||||
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider, builderImage, builderConfigPath, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts spec.ImageBuildOptions) ([]registry.Descriptor, error) {
|
||||
var (
|
||||
tagArch string
|
||||
tag = p.FullTag()
|
||||
@@ -671,7 +690,7 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider
|
||||
|
||||
imageBuildOpts.Dockerfile = bo.dockerfile
|
||||
|
||||
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil {
|
||||
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, builderConfigPath, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil {
|
||||
stdoutCloser()
|
||||
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
|
||||
return nil, fmt.Errorf("buildkit was unable to emulate %s. check binfmt has been set up and works for this platform: %v", platform, err)
|
||||
|
||||
@@ -53,10 +53,10 @@ func (d *dockerMocker) contextSupportCheck() error {
|
||||
}
|
||||
return errors.New("contexts not supported")
|
||||
}
|
||||
func (d *dockerMocker) builder(_ context.Context, _, _, _ string, _ bool) (*buildkitClient.Client, error) {
|
||||
func (d *dockerMocker) builder(_ context.Context, _, _, _, _ string, _ bool) (*buildkitClient.Client, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progress string, imageBuildOpts spec.ImageBuildOptions) error {
|
||||
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, builderConfigPath, platform string, builderRestart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progress string, imageBuildOpts spec.ImageBuildOptions) error {
|
||||
if !d.enableBuild {
|
||||
return errors.New("build disabled")
|
||||
}
|
||||
@@ -407,13 +407,12 @@ func (c *cacheMocker) Push(name, remoteName string, withManifest, override bool)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cacheMocker) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) error {
|
||||
func (c *cacheMocker) DescriptorWrite(image string, desc v1.Descriptor) error {
|
||||
if !c.enabledDescriptorWrite {
|
||||
return errors.New("descriptor disabled")
|
||||
}
|
||||
var (
|
||||
image = ref.String()
|
||||
im = v1.IndexManifest{
|
||||
im = v1.IndexManifest{
|
||||
MediaType: types.OCIImageIndex,
|
||||
Manifests: []v1.Descriptor{desc},
|
||||
SchemaVersion: 2,
|
||||
|
||||
@@ -37,12 +37,16 @@ import (
|
||||
|
||||
// golint requires comments on non-main(test)
|
||||
// package for blank import
|
||||
dockerconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
dockerconfigtypes "github.com/docker/cli/cli/config/types"
|
||||
_ "github.com/moby/buildkit/client/connhelper/dockercontainer"
|
||||
_ "github.com/moby/buildkit/client/connhelper/ssh"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/linter"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -54,16 +58,17 @@ const (
|
||||
buildkitWaitServer = 30 // seconds
|
||||
buildkitCheckInterval = 1 // seconds
|
||||
sbomFrontEndKey = "attest:sbom"
|
||||
buildkitConfigPath = "/etc/buildkit/buildkitd.toml"
|
||||
)
|
||||
|
||||
type dockerRunner interface {
|
||||
tag(ref, tag string) error
|
||||
build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, platformType string, imageBuildOpts spec.ImageBuildOptions) error
|
||||
build(ctx context.Context, tag, pkg, dockerContext, builderImage, builderConfigPath, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, platformType string, imageBuildOpts spec.ImageBuildOptions) error
|
||||
save(tgt string, refs ...string) error
|
||||
load(src io.Reader) error
|
||||
pull(img string) (bool, error)
|
||||
contextSupportCheck() error
|
||||
builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error)
|
||||
builder(ctx context.Context, dockerContext, builderImage, builderConfigPath, platform string, restart bool) (*buildkitClient.Client, error)
|
||||
}
|
||||
|
||||
type dockerRunnerImpl struct {
|
||||
@@ -218,14 +223,14 @@ func (dr *dockerRunnerImpl) contextSupportCheck() error {
|
||||
// 1. if dockerContext is provided, try to create a builder with that context; if it succeeds, we are done; if not, return an error.
|
||||
// 2. try to find an existing named runner with the pattern; if it succeeds, we are done; if not, try next.
|
||||
// 3. try to create a generic builder using the default context named "linuxkit".
|
||||
func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error) {
|
||||
func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderImage, builderConfigPath, platform string, restart bool) (*buildkitClient.Client, error) {
|
||||
// if we were given a context, we must find a builder and use it, or create one and use it
|
||||
if dockerContext != "" {
|
||||
// does the context exist?
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err != nil {
|
||||
return nil, fmt.Errorf("provided docker context '%s' not found", dockerContext)
|
||||
}
|
||||
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart)
|
||||
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, platform, dockerContext, restart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err)
|
||||
}
|
||||
@@ -236,13 +241,13 @@ func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderI
|
||||
dockerContext = fmt.Sprintf("%s-%s", "linuxkit", strings.ReplaceAll(platform, "/", "-"))
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err == nil {
|
||||
// we found an appropriately named context, so let us try to use it or error out
|
||||
if client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart); err == nil {
|
||||
if client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, platform, dockerContext, restart); err == nil {
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
// create a generic builder
|
||||
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, "", "default", restart)
|
||||
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, "", "default", restart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error ensuring builder container in default context: %v", err)
|
||||
}
|
||||
@@ -254,7 +259,7 @@ func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderI
|
||||
// but has the wrong version of buildkit, or not running buildkit at all, remove it and create an appropriate
|
||||
// one.
|
||||
// Returns a network connection to the buildkit builder in the container.
|
||||
func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, image, platform, dockerContext string, forceRestart bool) (*buildkitClient.Client, error) {
|
||||
func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, image, configPath, platform, dockerContext string, forceRestart bool) (*buildkitClient.Client, error) {
|
||||
// if no error, then we have a builder already
|
||||
// inspect it to make sure it is of the right type
|
||||
var (
|
||||
@@ -276,6 +281,7 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
||||
)
|
||||
for range buildKitCheckRetryCount {
|
||||
var b bytes.Buffer
|
||||
var cid string
|
||||
if err := dr.command(nil, &b, io.Discard, "--context", dockerContext, "container", "inspect", name); err == nil {
|
||||
// we already have a container named "linuxkit-builder" in the provided context.
|
||||
// get its state and config
|
||||
@@ -284,8 +290,33 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
||||
return nil, fmt.Errorf("unable to read results of 'container inspect %s': %v", name, err)
|
||||
}
|
||||
|
||||
cid = containerJSON[0].ID
|
||||
existingImage := containerJSON[0].Config.Image
|
||||
isRunning := containerJSON[0].State.Status == "running"
|
||||
// need to check for mounts, in case the builder-config is provided
|
||||
// by default, we assume the configPath is correct
|
||||
var configPathCorrect = true
|
||||
if configPath != "" {
|
||||
// if it is provided, we assume it is false until proven true
|
||||
configPathCorrect = false
|
||||
for _, mount := range containerJSON[0].Mounts {
|
||||
// if this mount is not the buildkit config path, we can ignore it
|
||||
if mount.Destination != buildkitConfigPath {
|
||||
continue
|
||||
}
|
||||
// if the mount source does not match the provided configPath,
|
||||
// we should restart it
|
||||
// Just break. Since configPathCorrect is set to false, the switch statement below
|
||||
// will catch it
|
||||
if mount.Source != configPath {
|
||||
fmt.Printf("existing container %s has config mounted from %s instead of expected %s, replacing\n", name, mount.Source, configPath)
|
||||
} else {
|
||||
configPathCorrect = true
|
||||
}
|
||||
// no need to cheak any more, we found the specific mount
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case forceRestart:
|
||||
@@ -306,6 +337,11 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
||||
recreate = true
|
||||
stop = isRunning
|
||||
remove = true
|
||||
case !configPathCorrect:
|
||||
fmt.Printf("existing container has wrong configPath mount, restarting")
|
||||
recreate = true
|
||||
stop = isRunning
|
||||
remove = true
|
||||
case isRunning:
|
||||
// if already running with the right image and permissions, just use it
|
||||
fmt.Printf("using existing container %s\n", name)
|
||||
@@ -326,18 +362,40 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
||||
// if we made it here, we need to stop and remove the container, either because of a config mismatch,
|
||||
// or because we received the CLI option
|
||||
if stop {
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "--context", dockerContext, "container", "stop", name); err != nil {
|
||||
return nil, fmt.Errorf("failed to stop existing container %s", name)
|
||||
if cid == "" {
|
||||
// we don't have a container ID, so we can't stop it
|
||||
return nil, fmt.Errorf("unable to stop existing container %s, no ID found", name)
|
||||
}
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "--context", dockerContext, "container", "stop", cid); err != nil {
|
||||
// if we failed, do a retry; maybe it does not even exist anymore
|
||||
time.Sleep(buildkitCheckInterval)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if remove {
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "--context", dockerContext, "container", "rm", name); err != nil {
|
||||
return nil, fmt.Errorf("failed to remove existing container %s", name)
|
||||
if cid == "" {
|
||||
// we don't have a container ID, so we can't remove it
|
||||
return nil, fmt.Errorf("unable to remove existing container %s, no ID found", name)
|
||||
}
|
||||
if err := dr.command(nil, io.Discard, io.Discard, "--context", dockerContext, "container", "rm", cid); err != nil {
|
||||
// if we failed, do a retry; maybe it does not even exist anymore
|
||||
time.Sleep(buildkitCheckInterval)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if recreate {
|
||||
// create the builder
|
||||
args := []string{"--context", dockerContext, "container", "run", "-d", "--name", name, "--privileged", image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug"}
|
||||
args := []string{"--context", dockerContext, "container", "run", "-d", "--name", name, "--privileged"}
|
||||
// was a config file provided?
|
||||
if configPath != "" {
|
||||
// if so, we need to pass it as a buildkitd config file
|
||||
args = append(args, "-v", fmt.Sprintf("%s:%s:ro", configPath, buildkitConfigPath))
|
||||
}
|
||||
args = append(args, image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug")
|
||||
if configPath != "" {
|
||||
// set the config path explicitly
|
||||
args = append(args, "--config", buildkitConfigPath)
|
||||
}
|
||||
msg := fmt.Sprintf("creating builder container '%s' in context '%s'", name, dockerContext)
|
||||
fmt.Println(msg)
|
||||
if err := dr.command(nil, nil, io.Discard, args...); err != nil {
|
||||
@@ -428,9 +486,9 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
|
||||
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
|
||||
}
|
||||
|
||||
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progressType string, imageBuildOpts spec.ImageBuildOptions) error {
|
||||
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, builderConfigPath, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progressType string, imageBuildOpts spec.ImageBuildOptions) error {
|
||||
// ensure we have a builder
|
||||
client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart)
|
||||
client, err := dr.builder(ctx, dockerContext, builderImage, builderConfigPath, platform, restart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to ensure builder container: %v", err)
|
||||
}
|
||||
@@ -481,6 +539,7 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
|
||||
attachable := []session.Attachable{}
|
||||
localDirs := map[string]string{}
|
||||
|
||||
// Add SSH agent provider if needed
|
||||
if len(imageBuildOpts.SSH) > 0 {
|
||||
configs, err := build.ParseSSH(imageBuildOpts.SSH)
|
||||
if err != nil {
|
||||
@@ -501,8 +560,30 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
|
||||
} else {
|
||||
localDirs[dockerui.DefaultLocalNameDockerfile] = pkg
|
||||
localDirs[dockerui.DefaultLocalNameContext] = pkg
|
||||
|
||||
}
|
||||
// add credentials
|
||||
var cf *configfile.ConfigFile
|
||||
if len(imageBuildOpts.RegistryAuths) > 0 {
|
||||
// if static ones were provided, use those
|
||||
cf = configfile.New("custom")
|
||||
// merge imageBuildOpts.RegistryAuths into dockercfg
|
||||
for registry, auth := range imageBuildOpts.RegistryAuths {
|
||||
bareRegistry := strings.TrimPrefix(registry, "https://")
|
||||
bareRegistry = strings.TrimPrefix(bareRegistry, "http://")
|
||||
cf.AuthConfigs[bareRegistry] = dockerconfigtypes.AuthConfig{
|
||||
ServerAddress: bareRegistry,
|
||||
Username: auth.Username,
|
||||
Password: auth.Password,
|
||||
RegistryToken: auth.RegistryToken,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Else use Docker authentication provider so BuildKit can use ~/.docker/config.json or OS-specific credential helpers.
|
||||
cf = dockerconfig.LoadDefaultConfigFile(io.Discard)
|
||||
}
|
||||
attachable = append(attachable,
|
||||
authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{ConfigFile: cf}),
|
||||
)
|
||||
|
||||
solveOpts := buildkitClient.SolveOpt{
|
||||
Frontend: "dockerfile.v0",
|
||||
|
||||
@@ -180,36 +180,36 @@ func (g git) commitTag(commit string) (string, error) {
|
||||
}
|
||||
|
||||
func (g git) isDirty(pkg, commit string) (bool, error) {
|
||||
// If it isn't HEAD it can't be dirty
|
||||
// Only makes sense to check for HEAD
|
||||
if commit != "HEAD" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Update cache, otherwise files which have an updated
|
||||
// timestamp but no actual changes are marked as changes
|
||||
// because `git diff-index` only uses the `lstat` result and
|
||||
// not the actual file contents. Running `git update-index
|
||||
// --refresh` updates the cache.
|
||||
if err := g.command("update-index", "-q", "--refresh"); err != nil {
|
||||
// 1. Check for changes in tracked files (without using update-index)
|
||||
// --no-ext-diff disables any external diff tool
|
||||
// --exit-code makes it return 1 if differences are found
|
||||
err := g.command("diff", "--no-ext-diff", "--exit-code", "--quiet", commit, "--", pkg)
|
||||
if err != nil {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
// Changes found in tracked files
|
||||
return true, nil
|
||||
}
|
||||
// Some actual failure
|
||||
return false, err
|
||||
}
|
||||
|
||||
// diff-index works pretty well, except that
|
||||
err := g.command("diff-index", "--quiet", commit, "--", pkg)
|
||||
// 2. Check for untracked files
|
||||
_, err = g.commandStdout(nil, "ls-files", "--exclude-standard", "--others", "--error-unmatch", "--", pkg)
|
||||
if err == nil {
|
||||
// this returns an error if there are *no* untracked files, which is strange, but we can work with it
|
||||
if _, err := g.commandStdout(nil, "ls-files", "--exclude-standard", "--others", "--error-unmatch", "--", pkg); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
// Untracked files found
|
||||
return true, nil
|
||||
}
|
||||
switch err.(type) {
|
||||
case *exec.ExitError:
|
||||
// diff-index exits with an error if there are differences
|
||||
return true, nil
|
||||
default:
|
||||
return false, err
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
// No untracked files — clean
|
||||
return false, nil
|
||||
}
|
||||
// Unexpected error
|
||||
return false, err
|
||||
}
|
||||
|
||||
// goPkgVersion return a version that is compliant with go package versioning.
|
||||
|
||||
@@ -93,14 +93,14 @@ func printVerbose(tw *tabwriter.Writer, du []*buildkitClient.UsageInfo) {
|
||||
_ = tw.Flush()
|
||||
}
|
||||
|
||||
func getClientForPlatform(ctx context.Context, buildersMap map[string]string, builderImage, platform string) (*buildkitClient.Client, error) {
|
||||
func getClientForPlatform(ctx context.Context, buildersMap map[string]string, builderImage, builderConfigPath, platform string) (*buildkitClient.Client, error) {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse platform: %s", err)
|
||||
}
|
||||
dr := newDockerRunner(false)
|
||||
builderName := getBuilderForPlatform(p.Architecture, buildersMap)
|
||||
client, err := dr.builder(ctx, builderName, builderImage, platform, false)
|
||||
client, err := dr.builder(ctx, builderName, builderImage, builderConfigPath, platform, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ensure builder container: %v", err)
|
||||
}
|
||||
@@ -108,11 +108,11 @@ func getClientForPlatform(ctx context.Context, buildersMap map[string]string, bu
|
||||
}
|
||||
|
||||
// DiskUsage of builder
|
||||
func DiskUsage(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error {
|
||||
func DiskUsage(buildersMap map[string]string, builderImage, builderConfigPath string, platformsToClean []string, verbose bool) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
for _, platform := range platformsToClean {
|
||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get client: %s", err)
|
||||
}
|
||||
@@ -143,12 +143,12 @@ func DiskUsage(buildersMap map[string]string, builderImage string, platformsToCl
|
||||
}
|
||||
|
||||
// PruneBuilder clean build cache of builder
|
||||
func PruneBuilder(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error {
|
||||
func PruneBuilder(buildersMap map[string]string, builderImage, builderConfigPath string, platformsToClean []string, verbose bool) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
total := int64(0)
|
||||
for _, platform := range platformsToClean {
|
||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get client: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package spec
|
||||
|
||||
type ImageBuildOptions struct {
|
||||
Labels map[string]string
|
||||
BuildArgs map[string]*string
|
||||
NetworkMode string
|
||||
Dockerfile string
|
||||
SSH []string
|
||||
type RegistryAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
RegistryToken string // base64 encoded auth token
|
||||
}
|
||||
|
||||
type ImageBuildOptions struct {
|
||||
Labels map[string]string
|
||||
BuildArgs map[string]*string
|
||||
NetworkMode string
|
||||
Dockerfile string
|
||||
SSH []string
|
||||
RegistryAuths map[string]RegistryAuth
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type CacheProvider interface {
|
||||
ImageLoad(r io.Reader) ([]v1.Descriptor, error)
|
||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||
// and replaces any existing one
|
||||
DescriptorWrite(ref *reference.Spec, descriptors v1.Descriptor) error
|
||||
DescriptorWrite(image string, descriptors v1.Descriptor) error
|
||||
// Push an image along with a multi-arch index from local cache to remote registry.
|
||||
// name is the name as referenced in the local cache, remoteName is the name to give it remotely.
|
||||
// If remoteName is empty, it is the same as name.
|
||||
|
||||
9
src/cmd/linuxkit/util/filelock.go
Normal file
9
src/cmd/linuxkit/util/filelock.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type FileLock struct {
|
||||
file *os.File
|
||||
}
|
||||
19
src/cmd/linuxkit/util/filelock_other.go
Normal file
19
src/cmd/linuxkit/util/filelock_other.go
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build !unix
|
||||
|
||||
package util
|
||||
|
||||
// Lock opens the file (creating it if needed) and sets an exclusive lock.
|
||||
// Returns a FileLock that can later be unlocked.
|
||||
func Lock(path string) (*FileLock, error) {
|
||||
return &FileLock{}, nil
|
||||
}
|
||||
|
||||
// Unlock releases the lock and closes the file.
|
||||
func (l *FileLock) Unlock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckLock attempts to detect if the file is locked by another process.
|
||||
func CheckLock(path string) (locked bool, holderPID int, err error) {
|
||||
return false, 0, nil
|
||||
}
|
||||
108
src/cmd/linuxkit/util/filelock_unix.go
Normal file
108
src/cmd/linuxkit/util/filelock_unix.go
Normal file
@@ -0,0 +1,108 @@
|
||||
//go:build unix
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lock opens the file (creating it if needed) and sets an exclusive lock.
|
||||
// Returns a FileLock that can later be unlocked.
|
||||
func Lock(path string) (*FileLock, error) {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
|
||||
flock := unix.Flock_t{
|
||||
Type: unix.F_WRLCK,
|
||||
Whence: int16(io.SeekStart),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
|
||||
if err := unix.FcntlFlock(f.Fd(), unix.F_SETLKW, &flock); err != nil {
|
||||
_ = f.Close()
|
||||
return nil, fmt.Errorf("set lock: %w", err)
|
||||
}
|
||||
|
||||
return &FileLock{file: f}, nil
|
||||
}
|
||||
|
||||
// Unlock releases the lock and closes the file.
|
||||
func (l *FileLock) Unlock() error {
|
||||
if l == nil || l.file == nil {
|
||||
return fmt.Errorf("unlock: file handle is nil")
|
||||
}
|
||||
flock := unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Whence: int16(io.SeekStart),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
if err := unix.FcntlFlock(l.file.Fd(), unix.F_SETLKW, &flock); err != nil {
|
||||
return fmt.Errorf("unlock: %w", err)
|
||||
}
|
||||
if err := l.file.Close(); err != nil {
|
||||
return fmt.Errorf("close lock file: %w", err)
|
||||
}
|
||||
l.file = nil // Prevent further use of the file handle
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckLock attempts to detect if the file is locked by another process.
|
||||
func CheckLock(path string) (locked bool, holderPID int, err error) {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return false, 0, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
check := unix.Flock_t{
|
||||
Type: unix.F_WRLCK,
|
||||
Whence: int16(io.SeekStart),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
|
||||
if err := unix.FcntlFlock(f.Fd(), unix.F_GETLK, &check); err != nil {
|
||||
return false, 0, fmt.Errorf("get lock: %w", err)
|
||||
}
|
||||
|
||||
if check.Type == unix.F_UNLCK {
|
||||
return false, 0, nil
|
||||
}
|
||||
return true, int(check.Pid), nil
|
||||
}
|
||||
|
||||
// WaitUnlocked waits until the file is unlocked by another process, and uses it for reading but not writing.
|
||||
func WaitUnlocked(path string) error {
|
||||
f, err := os.OpenFile(path, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
flock := unix.Flock_t{
|
||||
Type: unix.F_RDLCK,
|
||||
Whence: int16(io.SeekStart),
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
}
|
||||
|
||||
if err := unix.FcntlFlock(f.Fd(), unix.F_SETLKW, &flock); err != nil {
|
||||
_ = f.Close()
|
||||
return fmt.Errorf("set lock: %w", err)
|
||||
}
|
||||
fileRef := &FileLock{file: f}
|
||||
_ = fileRef.Unlock()
|
||||
return nil
|
||||
}
|
||||
3
test/cases/040_packages/025_auth/.gitignore
vendored
Normal file
3
test/cases/040_packages/025_auth/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Dockerfile
|
||||
buildkitd.toml
|
||||
docker-config/
|
||||
2
test/cases/040_packages/025_auth/Dockerfile.base
Normal file
2
test/cases/040_packages/025_auth/Dockerfile.base
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM alpine:3.21
|
||||
RUN echo hi
|
||||
2
test/cases/040_packages/025_auth/build.yml
Normal file
2
test/cases/040_packages/025_auth/build.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
org: linuxkit
|
||||
image: auth-registry
|
||||
129
test/cases/040_packages/025_auth/test.sh
Normal file
129
test/cases/040_packages/025_auth/test.sh
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/bin/sh
|
||||
# SUMMARY: Check that we can access a registry with auth
|
||||
# LABELS:
|
||||
|
||||
set -e
|
||||
|
||||
# Source libraries. Uncomment if needed/defined
|
||||
#. "${RT_LIB}"
|
||||
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
|
||||
|
||||
clean_up() {
|
||||
docker kill "${REGISTRY_NAME}" || true
|
||||
DOCKER_CONFIG="${DOCKER_CONFIG}" docker buildx rm "${BUILDKIT_NAME}" || true
|
||||
[ -n "${CACHDIR}" ] && rm -rf "${CACHDIR}"
|
||||
[ -n "${DOCKER_CONFIG}" ] && rm -rf "${DOCKER_CONFIG}"
|
||||
[ -n "${REGISTRY_DIR}" ] && rm -rf "${REGISTRY_DIR}"
|
||||
}
|
||||
trap clean_up EXIT
|
||||
|
||||
# determine platform
|
||||
ARCH=$(uname -m)
|
||||
if [ "${ARCH}" = "x86_64" ]; then
|
||||
ARCH="amd64"
|
||||
elif [ "${ARCH}" = "aarch64" ]; then
|
||||
ARCH="arm64"
|
||||
fi
|
||||
PLATFORM="linux/${ARCH}"
|
||||
|
||||
|
||||
# container names
|
||||
REGISTRY_NAME="test-registry-$$"
|
||||
BUILDKIT_NAME="test-buildkitd-$$"
|
||||
|
||||
# start a registry with auth
|
||||
REGISTRY_USER="testuser"
|
||||
REGISTRY_PASS="testpass"
|
||||
REGISTRY_PORT="5000"
|
||||
REGISTRY_DIR=$(mktemp -d)
|
||||
mkdir -p "$REGISTRY_DIR/auth"
|
||||
docker run --rm \
|
||||
--entrypoint htpasswd \
|
||||
httpd:2 -Bbn "${REGISTRY_USER}" "${REGISTRY_PASS}" > "$REGISTRY_DIR/auth/htpasswd"
|
||||
|
||||
# Start registry
|
||||
REGISTRY_CID=$(docker run -d --rm \
|
||||
-p ":${REGISTRY_PORT}" \
|
||||
-v "$REGISTRY_DIR/auth:/auth" \
|
||||
-e "REGISTRY_AUTH=htpasswd" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
|
||||
--name "${REGISTRY_NAME}" \
|
||||
registry:3)
|
||||
|
||||
REGISTRY_IP=$(docker inspect "${REGISTRY_NAME}" \
|
||||
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
|
||||
|
||||
IMAGENAME="${REGISTRY_IP}:${REGISTRY_PORT}/myimage"
|
||||
|
||||
# start an insecure buildkit so we can load an image to the registry
|
||||
cat > buildkitd.toml <<EOF
|
||||
[registry."${REGISTRY_IP}:${REGISTRY_PORT}"]
|
||||
insecure = true
|
||||
http = true
|
||||
EOF
|
||||
|
||||
# save the credentials
|
||||
credsb64=$(printf "%s" "${REGISTRY_USER}:${REGISTRY_PASS}" | base64)
|
||||
|
||||
# DO NOT export DOCKER_CONFIG, as that will cause the thing we are testing to succeed.
|
||||
# we need to be explicit about it.
|
||||
DOCKER_CONFIG=$(pwd)/docker-config
|
||||
rm -rf "${DOCKER_CONFIG}"
|
||||
mkdir -p "${DOCKER_CONFIG}"
|
||||
cat > "${DOCKER_CONFIG}/config.json" <<EOF
|
||||
{
|
||||
"auths": {
|
||||
"${REGISTRY_IP}:5000": {
|
||||
"auth": "${credsb64}"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
DOCKER_CONFIG=${DOCKER_CONFIG} docker buildx create \
|
||||
--name "${BUILDKIT_NAME}" \
|
||||
--driver docker-container \
|
||||
--buildkitd-config "$(pwd)/buildkitd.toml" \
|
||||
--bootstrap
|
||||
|
||||
DOCKER_CONFIG=${DOCKER_CONFIG} docker buildx build \
|
||||
--builder "${BUILDKIT_NAME}" \
|
||||
--file Dockerfile.base \
|
||||
--tag "${IMAGENAME}" \
|
||||
--push \
|
||||
--progress plain \
|
||||
--platform "${PLATFORM}" \
|
||||
.
|
||||
|
||||
# Generate Dockerfile for pkg with FROM
|
||||
cat > Dockerfile <<EOF
|
||||
FROM "${IMAGENAME}"
|
||||
RUN echo SUCCESS
|
||||
EOF
|
||||
|
||||
|
||||
CACHEDIR=$(mktemp -d)
|
||||
|
||||
# 3 tests:
|
||||
# 1. build a package with no auth - should fail
|
||||
# 2. build a package with explicit auth - should succeed
|
||||
# 3. build a package with auth in the config - should succeed
|
||||
if linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
|
||||
--builder-config "$(pwd)/buildkitd.toml" --force \
|
||||
.; then
|
||||
echo "Test 1 failed: build succeeded without auth"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
|
||||
--builder-config "$(pwd)/buildkitd.toml" --force \
|
||||
--registry-creds "${REGISTRY_IP}:${REGISTRY_PORT}=${REGISTRY_USER}:${REGISTRY_PASS}" \
|
||||
.
|
||||
|
||||
DOCKER_CONFIG=${DOCKER_CONFIG} linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
|
||||
--builder-config "$(pwd)/buildkitd.toml" --force \
|
||||
.
|
||||
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user