diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 9526b6402a..e60a037056 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -182,6 +182,11 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { return publish.NewKindPublisher(namer, po.Tags), nil } + // handle the k3s distros + if repoName == publish.K3sDomain { + return publish.NewK3sPublisher(namer, po.Tags), nil + } + if repoName == "" && po.Push { return nil, errors.New("KO_DOCKER_REPO environment variable is unset") } diff --git a/pkg/publish/k3s.go b/pkg/publish/k3s.go new file mode 100644 index 0000000000..05dfaee1a3 --- /dev/null +++ b/pkg/publish/k3s.go @@ -0,0 +1,90 @@ +// Copyright 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package publish + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/ko/pkg/build" + "github.com/google/ko/pkg/publish/k3s" +) + +const ( + //K3sDomain k3s local sentinel registry where the images get's loaded + K3sDomain = "k3s.local" +) + +type k3sPublisher struct { + namer Namer + tags []string +} + +//NewK3sPublisher returns a new publish.Interface that loads image into k3s clusters +func NewK3sPublisher(namer Namer, tags []string) Interface { + return &k3sPublisher{ + namer: namer, + tags: tags, + } +} + +//Publish implements publish.Interface +func (k *k3sPublisher) Publish(ctx context.Context, br build.Result, s string) (name.Reference, error) { + s = strings.TrimPrefix(s, build.StrictScheme) + s = strings.ToLower(s) + + img, err := toImage(br, s) + if err != nil { + return nil, err + } + + h, err := img.Digest() + if err != nil { + return nil, err + } + + digestTag, err := name.NewTag(fmt.Sprintf("%s:%s", k.namer(K3sDomain, s), h.Hex)) + if err != nil { + return nil, err + } + + log.Printf("Loading %v", digestTag) + if err := k3s.Write(ctx, digestTag, img); err != nil { + return nil, err + } + log.Printf("Loaded %v", digestTag) + + for _, tagName := range k.tags { + tag, err := name.NewTag(fmt.Sprintf("%s:%s", k.namer(K3sDomain, s), tagName)) + if err != nil { + return nil, err + } + + if err := k3s.Tag(ctx, digestTag, tag); err != nil { + return nil, err + } + log.Printf("Added tag %v", tagName) + } + + return &digestTag, nil +} + +//Close implements publish.Interface +func (k *k3sPublisher) Close() error { + return nil +} diff --git a/pkg/publish/k3s/doc.go b/pkg/publish/k3s/doc.go new file mode 100644 index 0000000000..ada5d19ac3 --- /dev/null +++ b/pkg/publish/k3s/doc.go @@ -0,0 +1,16 @@ +// Copyright 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package k3s defines methods for publishing images into k3s clusters using containerd +package k3s diff --git a/pkg/publish/k3s/write.go b/pkg/publish/k3s/write.go new file mode 100644 index 0000000000..12091ffb14 --- /dev/null +++ b/pkg/publish/k3s/write.go @@ -0,0 +1,96 @@ +// Copyright 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package k3s + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "os" + "os/exec" + + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/tarball" + "golang.org/x/sync/errgroup" +) + +const ( + limaInstanceEnvKey = "LIMA_INSTANCE" + rancherDesktopLimaInstanceName = "0" +) + +// Tag adds a tag to an already existent image. +func Tag(ctx context.Context, src, dest name.Tag) error { + nerdctl, li, env := commandWithEnv() + + var stdErr bytes.Buffer + cmd := exec.CommandContext(ctx, nerdctl, "--namespace=k8s.io", "tag", src.String(), dest.String()) + cmd.Env = env + cmd.Stderr = &stdErr + + if err := cmd.Run(); err != nil { + log.Printf("Error while excuting command %s %s", cmd.String(), stdErr.String()) + return fmt.Errorf("failed to tag image to instance %q: %w", li, err) + } + + return nil +} + +// Write saves the image into the k3s nodes as the given tag. +func Write(ctx context.Context, tag name.Tag, img v1.Image) error { + pr, pw := io.Pipe() + + grp := errgroup.Group{} + grp.Go(func() error { + return pw.CloseWithError(tarball.Write(tag, img, pw)) + }) + + nerdctl, li, env := commandWithEnv() + + var stdErr bytes.Buffer + cmd := exec.CommandContext(ctx, nerdctl, "--namespace=k8s.io", "load") + cmd.Stdin = pr + cmd.Env = env + cmd.Stderr = &stdErr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to load image to instance %q: %s", li, stdErr.String()) + } + + if err := grp.Wait(); err != nil { + return fmt.Errorf("failed to write intermediate tarball representation: %w", err) + } + + return nil +} + +//commandWithEnv build the nerdctl command with correct instance name and required environment variables +func commandWithEnv() (string, string, []string) { + nerdctl := "nerdctl.lima" + env := os.Environ() + // If no LIMA_INSTANCE env is defined it defaults to Rancher Desktop "0" + li, ok := os.LookupEnv(limaInstanceEnvKey) + if !ok { + nerdctl = "nerdctl" + li = rancherDesktopLimaInstanceName + env = append(env, + fmt.Sprintf("LIMA_INSTANCE=%s", li)) + } + + return nerdctl, li, env +} diff --git a/pkg/publish/kind.go b/pkg/publish/kind.go index f20303cbf7..251fb5bd96 100644 --- a/pkg/publish/kind.go +++ b/pkg/publish/kind.go @@ -18,11 +18,9 @@ import ( "context" "fmt" "log" - "os" "strings" "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/ko/pkg/build" "github.com/google/ko/pkg/publish/kind" ) @@ -51,44 +49,9 @@ func (t *kindPublisher) Publish(ctx context.Context, br build.Result, s string) // https://github.com/google/go-containerregistry/issues/212 s = strings.ToLower(s) - // There's no way to write an index to a kind, so attempt to downcast it to an image. - var img v1.Image - switch i := br.(type) { - case v1.Image: - img = i - case v1.ImageIndex: - im, err := i.IndexManifest() - if err != nil { - return nil, err - } - goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH") - if goos == "" { - goos = "linux" - } - if goarch == "" { - goarch = "amd64" - } - for _, manifest := range im.Manifests { - if manifest.Platform == nil { - continue - } - if manifest.Platform.OS != goos { - continue - } - if manifest.Platform.Architecture != goarch { - continue - } - img, err = i.Image(manifest.Digest) - if err != nil { - return nil, err - } - break - } - if img == nil { - return nil, fmt.Errorf("failed to find %s/%s image in index for image: %v", goos, goarch, s) - } - default: - return nil, fmt.Errorf("failed to interpret %s result as image: %v", s, br) + img, err := toImage(br, s) + if err != nil { + return nil, err } h, err := img.Digest() diff --git a/pkg/publish/utils.go b/pkg/publish/utils.go new file mode 100644 index 0000000000..5ce5606774 --- /dev/null +++ b/pkg/publish/utils.go @@ -0,0 +1,68 @@ +// Copyright 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package publish + +import ( + "fmt" + "os" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/ko/pkg/build" +) + +//toImage builds the image reference from the build result +func toImage(br build.Result, s string) (v1.Image, error) { + // There's no way to write an index to a kind, so attempt to downcast it to an image. + var img v1.Image + switch i := br.(type) { + case v1.Image: + img = i + case v1.ImageIndex: + im, err := i.IndexManifest() + if err != nil { + return nil, err + } + goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH") + if goos == "" { + goos = "linux" + } + if goarch == "" { + goarch = "amd64" + } + for _, manifest := range im.Manifests { + if manifest.Platform == nil { + continue + } + if manifest.Platform.OS != goos { + continue + } + if manifest.Platform.Architecture != goarch { + continue + } + img, err = i.Image(manifest.Digest) + if err != nil { + return nil, err + } + break + } + if img == nil { + return nil, fmt.Errorf("failed to find %s/%s image in index for image: %v", goos, goarch, s) + } + default: + return nil, fmt.Errorf("failed to interpret %s result as image: %v", s, br) + } + + return img, nil +}