mirror of
https://github.com/linuxkit/linuxkit.git
synced 2026-03-20 05:10:40 +00:00
Compare commits
7 Commits
v1.6.2
...
pkg-v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c5668116 | ||
|
|
2b4687338b | ||
|
|
940c1b7b3b | ||
|
|
818bccf20f | ||
|
|
50120bce2d | ||
|
|
254aefc953 | ||
|
|
4df360d62d |
@@ -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
|
The read process is smart enough to check each blob in the local cache before downloading
|
||||||
it from a registry.
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get get or initialize the cache
|
// 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
|
// initialize the cache path if needed
|
||||||
p, err := layout.FromPath(cache)
|
|
||||||
if err != nil {
|
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 {
|
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
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/core/content"
|
"github.com/containerd/containerd/v2/core/content"
|
||||||
"github.com/containerd/containerd/v2/plugins/content/local"
|
"github.com/containerd/containerd/v2/plugins/content/local"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
"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
|
// Provider cache implementation of cacheProvider
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
cache layout.Path
|
cache layout.Path
|
||||||
store content.Store
|
store content.Store
|
||||||
|
dir string
|
||||||
|
lock *util.FileLock
|
||||||
|
lockMut sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider create a new CacheProvider based in the provided directory
|
// NewProvider create a new CacheProvider based in the provided directory
|
||||||
func NewProvider(dir string) (*Provider, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -22,5 +32,39 @@ func NewProvider(dir string) (*Provider, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
namepkg "github.com/google/go-containerregistry/pkg/name"
|
namepkg "github.com/google/go-containerregistry/pkg/name"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
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/partial"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/validate"
|
"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)
|
return fmt.Errorf("error getting manifest for trusted image %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the original image name in the annotation
|
// lock the cache so we can write to it
|
||||||
annotations := map[string]string{
|
if err := p.Lock(); err != nil {
|
||||||
imagespec.AnnotationRefName: fullname,
|
return fmt.Errorf("unable to lock cache for writing: %v", err)
|
||||||
}
|
}
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
// first attempt as an index
|
// first attempt as an index
|
||||||
ii, err := desc.ImageIndex()
|
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 {
|
if err := p.cache.WriteIndex(ii); err != nil {
|
||||||
return fmt.Errorf("unable to write index: %v", err)
|
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)
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
}
|
}
|
||||||
if withArchReferences {
|
if withArchReferences {
|
||||||
@@ -206,11 +205,10 @@ func (p *Provider) Pull(name string, withArchReferences bool) error {
|
|||||||
for _, m := range im.Manifests {
|
for _, m := range im.Manifests {
|
||||||
if m.MediaType.IsImage() && m.Platform != nil && m.Platform.Architecture != unknown && m.Platform.OS != unknown {
|
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)
|
archSpecific := fmt.Sprintf("%s-%s", ref.String(), m.Platform.Architecture)
|
||||||
archRef, err := reference.Parse(archSpecific)
|
if _, err := reference.Parse(archSpecific); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to parse arch-specific reference %s: %v", archSpecific, err)
|
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)
|
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)
|
return fmt.Errorf("provided image is neither an image nor an index: %s", name)
|
||||||
}
|
}
|
||||||
log.Debugf("ImageWrite retrieved %s is image, saving", fullname)
|
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)
|
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
|
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/google/go-containerregistry/pkg/v1/mutate"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
log "github.com/sirupsen/logrus"
|
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
|
// it might not have existed, so we can add it locally
|
||||||
// use the original image name in the annotation
|
// use the original image name in the annotation
|
||||||
desc := m.DeepCopy()
|
desc := m.DeepCopy()
|
||||||
if desc.Annotations == nil {
|
if err := p.DescriptorWrite(archTag, *desc); err != nil {
|
||||||
desc.Annotations = map[string]string{}
|
|
||||||
}
|
|
||||||
desc.Annotations[imagespec.AnnotationRefName] = archTag
|
|
||||||
if err := p.cache.AppendDescriptor(*desc); err != nil {
|
|
||||||
return fmt.Errorf("error appending descriptor for %s to layout index: %v", archTag, err)
|
return fmt.Errorf("error appending descriptor for %s to layout index: %v", archTag, err)
|
||||||
}
|
}
|
||||||
img, err = p.cache.Image(m.Digest)
|
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)
|
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) {
|
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/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
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/match"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"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)
|
return fmt.Errorf("error getting manifest for image %s: %v", pullImageName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the original image name in the annotation
|
// get our lock
|
||||||
annotations := map[string]string{
|
if err := p.Lock(); err != nil {
|
||||||
imagespec.AnnotationRefName: image,
|
return fmt.Errorf("unable to lock cache for removing descriptors: %v", err)
|
||||||
}
|
}
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
// first attempt as an index
|
// first attempt as an index
|
||||||
ii, err := desc.ImageIndex()
|
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 {
|
if err := p.cache.WriteIndex(ii); err != nil {
|
||||||
return fmt.Errorf("unable to write index: %v", err)
|
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)
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
return fmt.Errorf("provided image is neither an image nor an index: %s", image)
|
||||||
}
|
}
|
||||||
log.Debugf("ImageWrite retrieved %s is image, saving", pullImageName)
|
log.Debugf("ImageWrite retrieved %s is image, saving", pullImageName)
|
||||||
if err = p.cache.ReplaceImage(im, match.Name(image), layout.WithAnnotations(annotations)); err != nil {
|
if err := p.cache.WriteImage(im); err != nil {
|
||||||
return fmt.Errorf("unable to save image to cache: %v", err)
|
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
|
return nil
|
||||||
@@ -149,6 +152,11 @@ func (p *Provider) ImageLoad(r io.Reader) ([]v1.Descriptor, error) {
|
|||||||
index bytes.Buffer
|
index bytes.Buffer
|
||||||
)
|
)
|
||||||
log.Debugf("ImageWriteTar to cache")
|
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 {
|
for {
|
||||||
header, err := tr.Next()
|
header, err := tr.Next()
|
||||||
if err == io.EOF {
|
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
|
// each of these is either an image or an index
|
||||||
// either way, it gets added directly to the linuxkit cache index.
|
// either way, it gets added directly to the linuxkit cache index.
|
||||||
for _, desc := range im.Manifests {
|
for _, desc := range im.Manifests {
|
||||||
if imgName, ok := desc.Annotations[images.AnnotationImageName]; ok {
|
imgName, ok := desc.Annotations[images.AnnotationImageName]
|
||||||
// remove the old descriptor, if it exists
|
if ok {
|
||||||
if err := p.cache.RemoveDescriptors(match.Name(imgName)); err != nil {
|
if err := p.DescriptorWrite(imgName, desc); err != nil {
|
||||||
return nil, fmt.Errorf("unable to remove old descriptors for %s: %v", imgName, err)
|
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)
|
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)
|
return fmt.Errorf("error parsing index: %v", err)
|
||||||
}
|
}
|
||||||
var im v1.IndexManifest
|
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?
|
// do we update an existing one? Or create a new one?
|
||||||
if len(indexes) > 0 {
|
if len(indexes) > 0 {
|
||||||
// we already had one, so update just the referenced index and return
|
// 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)
|
return fmt.Errorf("error writing new index to json: %v", err)
|
||||||
}
|
}
|
||||||
// finally update the descriptor in the root
|
// 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{
|
desc := v1.Descriptor{
|
||||||
MediaType: types.OCIImageIndex,
|
MediaType: types.OCIImageIndex,
|
||||||
Size: size,
|
Size: size,
|
||||||
@@ -377,36 +378,7 @@ func (p *Provider) IndexWrite(ref *reference.Spec, descriptors ...v1.Descriptor)
|
|||||||
imagespec.AnnotationRefName: image,
|
imagespec.AnnotationRefName: image,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := p.cache.AppendDescriptor(desc); err != nil {
|
return p.DescriptorWrite(ref.String(), desc)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
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
|
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"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pkglibConfig pkglib.PkglibConfig
|
var (
|
||||||
|
pkglibConfig pkglib.PkglibConfig
|
||||||
|
registryCreds []string
|
||||||
|
)
|
||||||
|
|
||||||
func pkgCmd() *cobra.Command {
|
func pkgCmd() *cobra.Command {
|
||||||
var (
|
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(pkgBuilderCmd())
|
||||||
cmd.AddCommand(pkgPushCmd())
|
cmd.AddCommand(pkgPushCmd(buildCmd))
|
||||||
cmd.AddCommand(pkgShowTagCmd())
|
cmd.AddCommand(pkgShowTagCmd())
|
||||||
cmd.AddCommand(pkgManifestCmd())
|
cmd.AddCommand(pkgManifestCmd())
|
||||||
cmd.AddCommand(pkgRemoteTagCmd())
|
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(&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().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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib"
|
"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"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -19,27 +20,28 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// some logic clarification:
|
// some logic clarification:
|
||||||
// pkg build - builds unless is in cache or published in registry
|
// 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 --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 - 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 --force --pull - always builds even if is in cache or published in registry; --pull ignored
|
||||||
// pkg push - always builds unless is in cache
|
// pkg build --push - always builds unless is in cache or published in registry; pushes to registry
|
||||||
// pkg push --force - always builds even if is in cache
|
// pkg build --push --force - always builds even if is in cache
|
||||||
// pkg push --nobuild - skips build; if not in cache, fails
|
// pkg build --push --nobuild - skips build; if not in cache, fails
|
||||||
// pkg push --nobuild --force - nonsensical
|
// pkg build --push --nobuild --force - nonsensical
|
||||||
|
// pkg push - equivalent to pkg build --push
|
||||||
|
|
||||||
// addCmdRunPkgBuildPush adds the RunE function and flags to a cobra.Command
|
func pkgBuildCmd() *cobra.Command {
|
||||||
// for "pkg build" or "pkg push".
|
|
||||||
func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
|
||||||
var (
|
var (
|
||||||
force bool
|
force bool
|
||||||
pull bool
|
pull bool
|
||||||
|
push bool
|
||||||
ignoreCache bool
|
ignoreCache bool
|
||||||
docker bool
|
docker bool
|
||||||
platforms string
|
platforms string
|
||||||
skipPlatforms string
|
skipPlatforms string
|
||||||
builders string
|
builders string
|
||||||
builderImage string
|
builderImage string
|
||||||
|
builderConfig string
|
||||||
builderRestart bool
|
builderRestart bool
|
||||||
release string
|
release string
|
||||||
nobuild bool
|
nobuild bool
|
||||||
@@ -51,179 +53,235 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
|||||||
progress string
|
progress string
|
||||||
ssh []string
|
ssh []string
|
||||||
)
|
)
|
||||||
|
cmd := &cobra.Command{
|
||||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
Use: "build",
|
||||||
pkgs, err := pkglib.NewFromConfig(pkglibConfig, args...)
|
Short: "build an OCI package from a directory with a yaml configuration file",
|
||||||
if err != nil {
|
Long: `Build an OCI package from a directory with a yaml configuration file.
|
||||||
return err
|
'path' specifies the path to the package source directory.
|
||||||
}
|
`,
|
||||||
|
Example: ` linuxkit pkg build [options] pkg/dir/`,
|
||||||
if nobuild && force {
|
Args: cobra.MinimumNArgs(1),
|
||||||
return errors.New("flags -force and -nobuild conflict")
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
}
|
pkgs, err := pkglib.NewFromConfig(pkglibConfig, args...)
|
||||||
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)
|
|
||||||
if err != nil {
|
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
|
if nobuild && force {
|
||||||
skipPlatformsMap := make(map[string]bool)
|
return errors.New("flags -force and -nobuild conflict")
|
||||||
if skipPlatforms != "" {
|
}
|
||||||
for _, platform := range strings.Split(skipPlatforms, ",") {
|
if pull && force {
|
||||||
parts := strings.SplitN(platform, "/", 2)
|
return errors.New("flags -force and -pull conflict")
|
||||||
if len(parts) != 2 || parts[0] == "" || parts[0] != "linux" || parts[1] == "" {
|
}
|
||||||
return fmt.Errorf("invalid target platform specification '%s'", platform)
|
|
||||||
|
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 release != "" {
|
||||||
}
|
opts = append(opts, pkglib.WithRelease(release))
|
||||||
}
|
}
|
||||||
// if requested specific platforms, build those. If not, then we will
|
if manifest {
|
||||||
// retrieve the defaults in the loop over each package.
|
opts = append(opts, pkglib.WithBuildManifest())
|
||||||
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 docker {
|
||||||
|
opts = append(opts, pkglib.WithBuildTargetDockerCache())
|
||||||
|
}
|
||||||
|
|
||||||
// build the builders map
|
if sbomScanner != "false" {
|
||||||
buildersMap := map[string]string{}
|
opts = append(opts, pkglib.WithBuildSbomScanner(sbomScanner))
|
||||||
// look for builders env var
|
}
|
||||||
buildersMap, err = buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
|
opts = append(opts, pkglib.WithDockerfile(dockerfile))
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range pkgs {
|
// read any build arg files
|
||||||
// things we need our own copies of
|
var buildArgs []string
|
||||||
var (
|
for _, filename := range buildArgFiles {
|
||||||
pkgOpts = make([]pkglib.BuildOpt, len(opts))
|
f, err := os.Open(filename)
|
||||||
pkgPlats = make([]imagespec.Platform, len(plats))
|
if err != nil {
|
||||||
)
|
return fmt.Errorf("error opening build args file %s: %w", filename, err)
|
||||||
copy(pkgOpts, opts)
|
}
|
||||||
copy(pkgPlats, plats)
|
defer func() { _ = f.Close() }()
|
||||||
// unless overridden, platforms are specific to a package, so this needs to be inside the for loop
|
scanner := bufio.NewScanner(f)
|
||||||
if len(pkgPlats) == 0 {
|
for scanner.Scan() {
|
||||||
for _, a := range p.Arches() {
|
buildArgs = append(buildArgs, scanner.Text())
|
||||||
if _, ok := skipPlatformsMap[a]; ok {
|
}
|
||||||
continue
|
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.
|
// build the builders map
|
||||||
// note that this is *not* an error; we simply skip it
|
buildersMap := map[string]string{}
|
||||||
if len(pkgPlats) == 0 {
|
// look for builders env var
|
||||||
fmt.Printf("Skipping %s with no architectures to build\n", p.Tag())
|
buildersMap, err = buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
|
||||||
continue
|
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...))
|
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
|
||||||
|
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
|
||||||
var msg, action string
|
opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart))
|
||||||
switch {
|
opts = append(opts, pkglib.WithProgress(progress))
|
||||||
case !withPush:
|
if len(ssh) > 0 {
|
||||||
msg = fmt.Sprintf("Building %q", p.Tag())
|
opts = append(opts, pkglib.WithSSH(ssh))
|
||||||
action = "building"
|
}
|
||||||
case nobuild:
|
if len(registryCreds) > 0 {
|
||||||
msg = fmt.Sprintf("Pushing %q without building", p.Tag())
|
registryCredMap := make(map[string]spec.RegistryAuth)
|
||||||
action = "building and pushing"
|
for _, cred := range registryCreds {
|
||||||
default:
|
parts := strings.SplitN(cred, "=", 2)
|
||||||
msg = fmt.Sprintf("Building and pushing %q", p.Tag())
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||||
action = "building and pushing"
|
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 {
|
// if there are no platforms to build for, do nothing.
|
||||||
return fmt.Errorf("error %s %q: %w", action, p.Tag(), err)
|
// 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(&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(&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(&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().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(&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(&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(&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(&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().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().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")
|
cmd.Flags().StringVar(&release, "release", "", "Release the given version")
|
||||||
@@ -237,18 +295,6 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
|
|||||||
|
|
||||||
return cmd
|
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) {
|
func buildPlatformBuildersMap(inputs string, existing map[string]string) (map[string]string, error) {
|
||||||
if inputs == "" {
|
if inputs == "" {
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import (
|
|||||||
|
|
||||||
func pkgBuilderCmd() *cobra.Command {
|
func pkgBuilderCmd() *cobra.Command {
|
||||||
var (
|
var (
|
||||||
builders string
|
builders string
|
||||||
platforms string
|
platforms string
|
||||||
builderImage string
|
builderImage string
|
||||||
|
builderConfigPath string
|
||||||
)
|
)
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "builder",
|
Use: "builder",
|
||||||
@@ -40,11 +41,11 @@ func pkgBuilderCmd() *cobra.Command {
|
|||||||
platformsToClean := strings.Split(platforms, ",")
|
platformsToClean := strings.Split(platforms, ",")
|
||||||
switch command {
|
switch command {
|
||||||
case "du":
|
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)
|
return fmt.Errorf("unable to print disk usage of builder: %w", err)
|
||||||
}
|
}
|
||||||
case "prune":
|
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)
|
return fmt.Errorf("unable to prune builder: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
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(&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(&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.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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
package main
|
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{
|
cmd := &cobra.Command{
|
||||||
Use: "push",
|
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.
|
Long: `Build and push an OCI package from a directory with a yaml configuration file.
|
||||||
'path' specifies the path to the package source directory.
|
'path' specifies the path to the package source directory.
|
||||||
|
|
||||||
The package may or may not be built first, depending on options
|
The package may or may not be built first, depending on options
|
||||||
`,
|
`,
|
||||||
Example: ` linuxkit pkg push [options] pkg/dir/`,
|
Example: ` linuxkit pkg push [options] pkg/dir/`,
|
||||||
Args: cobra.MinimumNArgs(1),
|
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 {
|
type buildOpts struct {
|
||||||
skipBuild bool
|
skipBuild bool
|
||||||
force bool
|
force bool
|
||||||
pull bool
|
pull bool
|
||||||
ignoreCache bool
|
ignoreCache bool
|
||||||
push bool
|
push bool
|
||||||
release string
|
release string
|
||||||
manifest bool
|
manifest bool
|
||||||
targetDocker bool
|
targetDocker bool
|
||||||
cacheDir string
|
cacheDir string
|
||||||
cacheProvider spec.CacheProvider
|
cacheProvider spec.CacheProvider
|
||||||
platforms []imagespec.Platform
|
platforms []imagespec.Platform
|
||||||
builders map[string]string
|
builders map[string]string
|
||||||
runner dockerRunner
|
runner dockerRunner
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
builderImage string
|
builderImage string
|
||||||
builderRestart bool
|
builderConfigPath string
|
||||||
sbomScan bool
|
builderRestart bool
|
||||||
sbomScannerImage string
|
sbomScan bool
|
||||||
dockerfile string
|
sbomScannerImage string
|
||||||
buildArgs []string
|
dockerfile string
|
||||||
progress string
|
buildArgs []string
|
||||||
ssh []string
|
progress string
|
||||||
|
ssh []string
|
||||||
|
registryAuth map[string]spec.RegistryAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildOpt allows callers to specify options to Build
|
// 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
|
// WithBuildBuilderRestart restart the builder container even if it already is running with the correct image version
|
||||||
func WithBuildBuilderRestart(restart bool) BuildOpt {
|
func WithBuildBuilderRestart(restart bool) BuildOpt {
|
||||||
return func(bo *buildOpts) error {
|
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
|
// Build builds the package
|
||||||
func (p Pkg) Build(bos ...BuildOpt) error {
|
func (p Pkg) Build(bos ...BuildOpt) error {
|
||||||
var bo buildOpts
|
var bo buildOpts
|
||||||
@@ -449,10 +467,11 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imageBuildOpts.SSH = bo.ssh
|
imageBuildOpts.SSH = bo.ssh
|
||||||
|
imageBuildOpts.RegistryAuths = bo.registryAuth
|
||||||
|
|
||||||
// build for each arch and save in the linuxkit cache
|
// build for each arch and save in the linuxkit cache
|
||||||
for _, platform := range platformsToBuild {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.DescriptorWrite(&ref, *desc); err != nil {
|
if err := c.DescriptorWrite(fullRelTag, *desc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.Push(fullRelTag, "", bo.manifest, true); err != nil {
|
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
|
// 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
|
// 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
|
// 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 (
|
var (
|
||||||
tagArch string
|
tagArch string
|
||||||
tag = p.FullTag()
|
tag = p.FullTag()
|
||||||
@@ -671,7 +690,7 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider
|
|||||||
|
|
||||||
imageBuildOpts.Dockerfile = bo.dockerfile
|
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()
|
stdoutCloser()
|
||||||
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
|
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)
|
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")
|
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")
|
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 {
|
if !d.enableBuild {
|
||||||
return errors.New("build disabled")
|
return errors.New("build disabled")
|
||||||
}
|
}
|
||||||
@@ -407,13 +407,12 @@ func (c *cacheMocker) Push(name, remoteName string, withManifest, override bool)
|
|||||||
return nil
|
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 {
|
if !c.enabledDescriptorWrite {
|
||||||
return errors.New("descriptor disabled")
|
return errors.New("descriptor disabled")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
image = ref.String()
|
im = v1.IndexManifest{
|
||||||
im = v1.IndexManifest{
|
|
||||||
MediaType: types.OCIImageIndex,
|
MediaType: types.OCIImageIndex,
|
||||||
Manifests: []v1.Descriptor{desc},
|
Manifests: []v1.Descriptor{desc},
|
||||||
SchemaVersion: 2,
|
SchemaVersion: 2,
|
||||||
|
|||||||
@@ -37,12 +37,16 @@ import (
|
|||||||
|
|
||||||
// golint requires comments on non-main(test)
|
// golint requires comments on non-main(test)
|
||||||
// package for blank import
|
// 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/dockercontainer"
|
||||||
_ "github.com/moby/buildkit/client/connhelper/ssh"
|
_ "github.com/moby/buildkit/client/connhelper/ssh"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/linter"
|
"github.com/moby/buildkit/frontend/dockerfile/linter"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
"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/sshforward/sshprovider"
|
||||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -54,16 +58,17 @@ const (
|
|||||||
buildkitWaitServer = 30 // seconds
|
buildkitWaitServer = 30 // seconds
|
||||||
buildkitCheckInterval = 1 // seconds
|
buildkitCheckInterval = 1 // seconds
|
||||||
sbomFrontEndKey = "attest:sbom"
|
sbomFrontEndKey = "attest:sbom"
|
||||||
|
buildkitConfigPath = "/etc/buildkit/buildkitd.toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dockerRunner interface {
|
type dockerRunner interface {
|
||||||
tag(ref, tag string) error
|
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
|
save(tgt string, refs ...string) error
|
||||||
load(src io.Reader) error
|
load(src io.Reader) error
|
||||||
pull(img string) (bool, error)
|
pull(img string) (bool, error)
|
||||||
contextSupportCheck() 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 {
|
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.
|
// 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.
|
// 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".
|
// 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 we were given a context, we must find a builder and use it, or create one and use it
|
||||||
if dockerContext != "" {
|
if dockerContext != "" {
|
||||||
// does the context exist?
|
// does the context exist?
|
||||||
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err != nil {
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err)
|
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, "/", "-"))
|
dockerContext = fmt.Sprintf("%s-%s", "linuxkit", strings.ReplaceAll(platform, "/", "-"))
|
||||||
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err == nil {
|
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
|
// 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
|
return client, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a generic builder
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error ensuring builder container in default context: %v", err)
|
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
|
// but has the wrong version of buildkit, or not running buildkit at all, remove it and create an appropriate
|
||||||
// one.
|
// one.
|
||||||
// Returns a network connection to the buildkit builder in the container.
|
// 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
|
// if no error, then we have a builder already
|
||||||
// inspect it to make sure it is of the right type
|
// inspect it to make sure it is of the right type
|
||||||
var (
|
var (
|
||||||
@@ -288,6 +293,30 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
|||||||
cid = containerJSON[0].ID
|
cid = containerJSON[0].ID
|
||||||
existingImage := containerJSON[0].Config.Image
|
existingImage := containerJSON[0].Config.Image
|
||||||
isRunning := containerJSON[0].State.Status == "running"
|
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 {
|
switch {
|
||||||
case forceRestart:
|
case forceRestart:
|
||||||
@@ -308,6 +337,11 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
|||||||
recreate = true
|
recreate = true
|
||||||
stop = isRunning
|
stop = isRunning
|
||||||
remove = true
|
remove = true
|
||||||
|
case !configPathCorrect:
|
||||||
|
fmt.Printf("existing container has wrong configPath mount, restarting")
|
||||||
|
recreate = true
|
||||||
|
stop = isRunning
|
||||||
|
remove = true
|
||||||
case isRunning:
|
case isRunning:
|
||||||
// if already running with the right image and permissions, just use it
|
// if already running with the right image and permissions, just use it
|
||||||
fmt.Printf("using existing container %s\n", name)
|
fmt.Printf("using existing container %s\n", name)
|
||||||
@@ -351,7 +385,17 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
|
|||||||
}
|
}
|
||||||
if recreate {
|
if recreate {
|
||||||
// create the builder
|
// 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)
|
msg := fmt.Sprintf("creating builder container '%s' in context '%s'", name, dockerContext)
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
if err := dr.command(nil, nil, io.Discard, args...); err != nil {
|
if err := dr.command(nil, nil, io.Discard, args...); err != nil {
|
||||||
@@ -442,9 +486,9 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
|
|||||||
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
|
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
|
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to ensure builder container: %v", err)
|
return fmt.Errorf("unable to ensure builder container: %v", err)
|
||||||
}
|
}
|
||||||
@@ -495,6 +539,7 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
|
|||||||
attachable := []session.Attachable{}
|
attachable := []session.Attachable{}
|
||||||
localDirs := map[string]string{}
|
localDirs := map[string]string{}
|
||||||
|
|
||||||
|
// Add SSH agent provider if needed
|
||||||
if len(imageBuildOpts.SSH) > 0 {
|
if len(imageBuildOpts.SSH) > 0 {
|
||||||
configs, err := build.ParseSSH(imageBuildOpts.SSH)
|
configs, err := build.ParseSSH(imageBuildOpts.SSH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -515,8 +560,30 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
|
|||||||
} else {
|
} else {
|
||||||
localDirs[dockerui.DefaultLocalNameDockerfile] = pkg
|
localDirs[dockerui.DefaultLocalNameDockerfile] = pkg
|
||||||
localDirs[dockerui.DefaultLocalNameContext] = 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{
|
solveOpts := buildkitClient.SolveOpt{
|
||||||
Frontend: "dockerfile.v0",
|
Frontend: "dockerfile.v0",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -28,8 +27,6 @@ type git struct {
|
|||||||
dir string
|
dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
var gitMutex sync.Mutex
|
|
||||||
|
|
||||||
// Returns git==nil and no error if the path is not within a git repository
|
// Returns git==nil and no error if the path is not within a git repository
|
||||||
func newGit(dir string) (*git, error) {
|
func newGit(dir string) (*git, error) {
|
||||||
g := &git{dir}
|
g := &git{dir}
|
||||||
@@ -183,39 +180,36 @@ func (g git) commitTag(commit string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g git) isDirty(pkg, commit string) (bool, 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" {
|
if commit != "HEAD" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cache, otherwise files which have an updated
|
// 1. Check for changes in tracked files (without using update-index)
|
||||||
// timestamp but no actual changes are marked as changes
|
// --no-ext-diff disables any external diff tool
|
||||||
// because `git diff-index` only uses the `lstat` result and
|
// --exit-code makes it return 1 if differences are found
|
||||||
// not the actual file contents. Running `git update-index
|
err := g.command("diff", "--no-ext-diff", "--exit-code", "--quiet", commit, "--", pkg)
|
||||||
// --refresh` updates the cache.
|
if err != nil {
|
||||||
gitMutex.Lock()
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
if err := g.command("update-index", "-q", "--refresh"); err != nil {
|
// Changes found in tracked files
|
||||||
gitMutex.Unlock()
|
return true, nil
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
gitMutex.Unlock()
|
|
||||||
|
|
||||||
// diff-index works pretty well, except that
|
|
||||||
err := g.command("diff-index", "--quiet", commit, "--", 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
|
|
||||||
}
|
}
|
||||||
return true, nil
|
// Some actual failure
|
||||||
}
|
|
||||||
switch err.(type) {
|
|
||||||
case *exec.ExitError:
|
|
||||||
// diff-index exits with an error if there are differences
|
|
||||||
return true, nil
|
|
||||||
default:
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Check for untracked files
|
||||||
|
_, err = g.commandStdout(nil, "ls-files", "--exclude-standard", "--others", "--error-unmatch", "--", pkg)
|
||||||
|
if err == nil {
|
||||||
|
// Untracked files found
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
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.
|
// 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()
|
_ = 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)
|
p, err := platforms.Parse(platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse platform: %s", err)
|
return nil, fmt.Errorf("failed to parse platform: %s", err)
|
||||||
}
|
}
|
||||||
dr := newDockerRunner(false)
|
dr := newDockerRunner(false)
|
||||||
builderName := getBuilderForPlatform(p.Architecture, buildersMap)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to ensure builder container: %v", err)
|
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
|
// 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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
for _, platform := range platformsToClean {
|
for _, platform := range platformsToClean {
|
||||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot get client: %s", err)
|
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
|
// 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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
total := int64(0)
|
total := int64(0)
|
||||||
for _, platform := range platformsToClean {
|
for _, platform := range platformsToClean {
|
||||||
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform)
|
client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot get client: %s", err)
|
return fmt.Errorf("cannot get client: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
package spec
|
package spec
|
||||||
|
|
||||||
type ImageBuildOptions struct {
|
type RegistryAuth struct {
|
||||||
Labels map[string]string
|
Username string
|
||||||
BuildArgs map[string]*string
|
Password string
|
||||||
NetworkMode string
|
RegistryToken string // base64 encoded auth token
|
||||||
Dockerfile string
|
}
|
||||||
SSH []string
|
|
||||||
|
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)
|
ImageLoad(r io.Reader) ([]v1.Descriptor, error)
|
||||||
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
// DescriptorWrite writes a descriptor to the cache index; it validates that it has a name
|
||||||
// and replaces any existing one
|
// 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.
|
// 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.
|
// 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.
|
// 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