github.com/oam-dev/kubevela@v1.9.11/pkg/builtin/kind/client.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 The code is inspired from the Kind repo at https://github.com/kubernetes-sigs/kind/blob/main/pkg/cmd/kind/load/docker-image/docker-image.go 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package kind 20 21 import ( 22 "fmt" 23 "os" 24 "path/filepath" 25 26 "k8s.io/klog/v2" 27 "sigs.k8s.io/kind/pkg/cluster" 28 "sigs.k8s.io/kind/pkg/cluster/nodes" 29 "sigs.k8s.io/kind/pkg/cluster/nodeutils" 30 "sigs.k8s.io/kind/pkg/errors" 31 "sigs.k8s.io/kind/pkg/exec" 32 "sigs.k8s.io/kind/pkg/fs" 33 ) 34 35 func LoadDockerImage(imageName string) error { 36 return LoadDockerImagesWithFlags([]string{imageName}, "", nil) 37 } 38 39 func LoadDockerImages(imageNames []string) error { 40 return LoadDockerImagesWithFlags(imageNames, "", nil) 41 } 42 43 func LoadDockerImagesWithFlags(imageNames []string, flagName string, flagNodes []string) error { 44 provider := cluster.NewProvider( 45 cluster.ProviderWithDocker(), 46 ) 47 48 // Set cluster context name by default 49 if flagName == "" { 50 flagName = cluster.DefaultName 51 } 52 53 // Check that the image exists locally and gets its ID, if not return error 54 var imageIDs []string 55 for _, imageName := range imageNames { 56 imageID, err := imageID(imageName) 57 if err != nil { 58 return fmt.Errorf("image: %q not present locally", imageName) 59 } 60 imageIDs = append(imageIDs, imageID) 61 } 62 63 // Check if the cluster nodes exist 64 nodeList, err := provider.ListInternalNodes(flagName) 65 if err != nil { 66 return err 67 } 68 if len(nodeList) == 0 { 69 return fmt.Errorf("no nodes found for cluster %q", flagName) 70 } 71 72 // map cluster nodes by their name 73 nodesByName := map[string]nodes.Node{} 74 for _, node := range nodeList { 75 // TODO(bentheelder): this depends on the fact that ListByCluster() 76 // will have name for nameOrId. 77 nodesByName[node.String()] = node 78 } 79 80 // pick only the user selected nodes and ensure they exist 81 // the default is all nodes unless flags.Nodes is set 82 candidateNodes := nodeList 83 if len(flagNodes) > 0 { 84 candidateNodes = []nodes.Node{} 85 for _, name := range flagNodes { 86 node, ok := nodesByName[name] 87 if !ok { 88 return fmt.Errorf("unknown node: %q", name) 89 } 90 candidateNodes = append(candidateNodes, node) 91 } 92 } 93 94 // pick only the nodes that don't have the image 95 selectedNodes := []nodes.Node{} 96 fns := []func() error{} 97 for i, imageName := range imageNames { 98 imageID := imageIDs[i] 99 for _, node := range candidateNodes { 100 id, err := nodeutils.ImageID(node, imageName) 101 if err != nil || id != imageID { 102 selectedNodes = append(selectedNodes, node) 103 } 104 } 105 if len(selectedNodes) == 0 { 106 continue 107 } 108 } 109 110 // Setup the tar path where the images will be saved 111 dir, err := fs.TempDir("", "images-tar") 112 if err != nil { 113 return errors.Wrap(err, "failed to create tempdir") 114 } 115 defer os.RemoveAll(dir) 116 imagesTarPath := filepath.Join(dir, "images.tar") 117 // Save the images into a tar 118 err = save(imageNames, imagesTarPath) 119 if err != nil { 120 return err 121 } 122 123 // Load the images on the selected nodes 124 for _, selectedNode := range selectedNodes { 125 selectedNode := selectedNode // capture loop variable 126 fns = append(fns, func() error { 127 return loadImage(imagesTarPath, selectedNode) 128 }) 129 } 130 return errors.UntilErrorConcurrent(fns) 131 } 132 133 // TODO: we should consider having a cluster method to load images 134 135 // loads an image tarball onto a node 136 func loadImage(imageTarName string, node nodes.Node) error { 137 f, err := os.Open(imageTarName) 138 if err != nil { 139 return errors.Wrap(err, "failed to open image") 140 } 141 defer func() { 142 if err := f.Close(); err != nil { 143 klog.Error(err, "Failed to close file") 144 } 145 }() 146 return nodeutils.LoadImageArchive(node, f) 147 } 148 149 // save saves images to dest, as in `docker save` 150 func save(images []string, dest string) error { 151 commandArgs := append([]string{"save", "-o", dest}, images...) 152 return exec.Command("docker", commandArgs...).Run() 153 } 154 155 // imageID return the Id of the container image 156 func imageID(containerNameOrID string) (string, error) { 157 cmd := exec.Command("docker", "image", "inspect", 158 "-f", "{{ .Id }}", 159 containerNameOrID, // ... against the container 160 ) 161 lines, err := exec.OutputLines(cmd) 162 if err != nil { 163 return "", err 164 } 165 if len(lines) != 1 { 166 return "", errors.Errorf("Docker image ID should only be one line, got %d lines", len(lines)) 167 } 168 return lines[0], nil 169 }