github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/loader/load.go (about) 1 /* 2 Copyright 2021 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package loader 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "os/exec" 24 "strings" 25 "time" 26 27 "github.com/docker/distribution/reference" 28 "k8s.io/client-go/tools/clientcmd/api" 29 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubectl" 34 kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 37 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/stringslice" 38 timeutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/time" 39 ) 40 41 type ImageLoader struct { 42 kubeContext string 43 cli *kubectl.CLI 44 } 45 46 type Config interface { 47 kubectl.Config 48 49 GetKubeContext() string 50 LoadImages() bool 51 } 52 53 func NewImageLoader(kubeContext string, cli *kubectl.CLI) *ImageLoader { 54 return &ImageLoader{ 55 kubeContext: kubeContext, 56 cli: cli, 57 } 58 } 59 60 // We only load images that 61 // 1) Were identified as local images by the Runner, and 62 // 2) Are part of the set of images being deployed by a given Deployer, so we don't duplicate effort 63 64 func imagesToLoad(localImages, deployerImages, images []graph.Artifact) []graph.Artifact { 65 local := map[string]bool{} 66 for _, image := range localImages { 67 local[docker.SanitizeImageName(image.ImageName)] = true 68 } 69 70 tracked := map[string]bool{} 71 for _, image := range deployerImages { 72 if local[image.ImageName] { 73 tracked[image.ImageName] = true 74 } 75 } 76 77 var res []graph.Artifact 78 for _, image := range images { 79 if tracked[docker.SanitizeImageName(image.ImageName)] { 80 res = append(res, image) 81 } 82 } 83 return res 84 } 85 86 // LoadImages loads images into a local cluster. 87 // imagesToLoad is used to determine the set of images we should load, based on images that are 88 // marked as local by the Runner, and part of the calling Deployer's set of manifests 89 func (i *ImageLoader) LoadImages(ctx context.Context, out io.Writer, localImages, deployerImages, images []graph.Artifact) error { 90 currentContext, err := i.getCurrentContext() 91 if err != nil { 92 return err 93 } 94 95 artifacts := imagesToLoad(localImages, deployerImages, images) 96 97 if config.IsKindCluster(i.kubeContext) { 98 kindCluster := config.KindClusterName(currentContext.Cluster) 99 100 // With `kind`, docker images have to be loaded with the `kind` CLI. 101 if err := i.loadImagesInKindNodes(ctx, out, kindCluster, artifacts); err != nil { 102 return fmt.Errorf("loading images into kind nodes: %w", err) 103 } 104 } 105 106 if config.IsK3dCluster(i.kubeContext) { 107 k3dCluster := config.K3dClusterName(currentContext.Cluster) 108 109 // With `k3d`, docker images have to be loaded with the `k3d` CLI. 110 if err := i.loadImagesInK3dNodes(ctx, out, k3dCluster, artifacts); err != nil { 111 return fmt.Errorf("loading images into k3d nodes: %w", err) 112 } 113 } 114 115 return nil 116 } 117 118 // loadImagesInKindNodes loads artifact images into every node of a kind cluster. 119 func (i *ImageLoader) loadImagesInKindNodes(ctx context.Context, out io.Writer, kindCluster string, artifacts []graph.Artifact) error { 120 output.Default.Fprintln(out, "Loading images into kind cluster nodes...") 121 return i.loadImages(ctx, out, artifacts, func(tag string) *exec.Cmd { 122 return exec.CommandContext(ctx, "kind", "load", "docker-image", "--name", kindCluster, tag) 123 }) 124 } 125 126 // loadImagesInK3dNodes loads artifact images into every node of a k3s cluster. 127 func (i *ImageLoader) loadImagesInK3dNodes(ctx context.Context, out io.Writer, k3dCluster string, artifacts []graph.Artifact) error { 128 output.Default.Fprintln(out, "Loading images into k3d cluster nodes...") 129 return i.loadImages(ctx, out, artifacts, func(tag string) *exec.Cmd { 130 return exec.CommandContext(ctx, "k3d", "image", "import", "--cluster", k3dCluster, tag) 131 }) 132 } 133 134 func (i *ImageLoader) loadImages(ctx context.Context, out io.Writer, artifacts []graph.Artifact, createCmd func(tag string) *exec.Cmd) error { 135 start := time.Now() 136 137 var knownImages []string 138 139 for _, artifact := range artifacts { 140 output.Default.Fprintf(out, " - %s -> ", artifact.Tag) 141 142 // Only load images that are unknown to the node 143 if knownImages == nil { 144 var err error 145 if knownImages, err = findKnownImages(ctx, i.cli); err != nil { 146 return fmt.Errorf("unable to retrieve node's images: %w", err) 147 } 148 } 149 normalizedImageRef, err := reference.ParseNormalizedNamed(artifact.Tag) 150 if err != nil { 151 return err 152 } 153 if stringslice.Contains(knownImages, normalizedImageRef.String()) { 154 output.Green.Fprintln(out, "Found") 155 continue 156 } 157 158 cmd := createCmd(artifact.Tag) 159 if cmdOut, err := util.RunCmdOut(ctx, cmd); err != nil { 160 output.Red.Fprintln(out, "Failed") 161 return fmt.Errorf("unable to load image %q into cluster: %w, %s", artifact.Tag, err, cmdOut) 162 } 163 164 output.Green.Fprintln(out, "Loaded") 165 } 166 167 output.Default.Fprintln(out, "Images loaded in", timeutil.Humanize(time.Since(start))) 168 return nil 169 } 170 171 func findKnownImages(ctx context.Context, cli *kubectl.CLI) ([]string, error) { 172 nodeGetOut, err := cli.RunOut(ctx, "get", "nodes", `-ojsonpath='{@.items[*].status.images[*].names[*]}'`) 173 if err != nil { 174 return nil, fmt.Errorf("unable to inspect the nodes: %w", err) 175 } 176 177 knownImages := strings.Split(string(nodeGetOut), " ") 178 return knownImages, nil 179 } 180 181 func (i *ImageLoader) getCurrentContext() (*api.Context, error) { 182 currentCfg, err := kubectx.CurrentConfig() 183 if err != nil { 184 return nil, fmt.Errorf("unable to get kubernetes config: %w", err) 185 } 186 187 currentContext, present := currentCfg.Contexts[i.kubeContext] 188 if !present { 189 return nil, fmt.Errorf("unable to get current kubernetes context: %w", err) 190 } 191 return currentContext, nil 192 }