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  }