...

Source file src/github.com/redhat-developer/odo/pkg/devfile/image/docker_compatible.go

Documentation: github.com/redhat-developer/odo/pkg/devfile/image

     1  package image
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    11  	"github.com/fatih/color"
    12  	"k8s.io/klog"
    13  
    14  	dfutil "github.com/devfile/library/v2/pkg/util"
    15  
    16  	"github.com/redhat-developer/odo/pkg/log"
    17  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    18  )
    19  
    20  // DockerCompatibleBackend uses a CLI compatible with the docker CLI (at least docker itself and podman)
    21  type DockerCompatibleBackend struct {
    22  	name                string
    23  	globalExtraArgs     []string
    24  	imageBuildExtraArgs []string
    25  }
    26  
    27  var _ Backend = (*DockerCompatibleBackend)(nil)
    28  
    29  func NewDockerCompatibleBackend(name string, globalExtraArgs, imageBuildExtraArgs []string) *DockerCompatibleBackend {
    30  	return &DockerCompatibleBackend{
    31  		name:                name,
    32  		globalExtraArgs:     globalExtraArgs,
    33  		imageBuildExtraArgs: imageBuildExtraArgs,
    34  	}
    35  }
    36  
    37  // Build an image, as defined in devfile, using a Docker compatible CLI
    38  func (o *DockerCompatibleBackend) Build(fs filesystem.Filesystem, image *devfile.ImageComponent, devfilePath string) error {
    39  
    40  	dockerfile, isTemp, err := resolveAndDownloadDockerfile(fs, image.Dockerfile.Uri)
    41  	if isTemp {
    42  		defer func(path string) {
    43  			if e := fs.Remove(path); e != nil {
    44  				klog.V(3).Infof("could not remove temporary Dockerfile at path %q: %v", path, err)
    45  			}
    46  		}(dockerfile)
    47  	}
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	// We use a "No Spin" since we are outputting to stdout / stderr
    53  	buildSpinner := log.SpinnerNoSpin("Building image locally")
    54  	defer buildSpinner.End(false)
    55  
    56  	err = os.Setenv("PROJECTS_ROOT", devfilePath)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	err = os.Setenv("PROJECT_SOURCE", devfilePath)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	shellCmd := getShellCommand(o.name, o.globalExtraArgs, o.imageBuildExtraArgs, image, devfilePath, dockerfile)
    67  	klog.V(4).Infof("Running command: %v", shellCmd)
    68  	for i, cmd := range shellCmd {
    69  		shellCmd[i] = os.ExpandEnv(cmd)
    70  	}
    71  	cmd := exec.Command(shellCmd[0], shellCmd[1:]...)
    72  	cmdEnv := []string{
    73  		"PROJECTS_ROOT=" + devfilePath,
    74  		"PROJECT_SOURCE=" + devfilePath,
    75  	}
    76  	cmd.Env = append(os.Environ(), cmdEnv...)
    77  	cmd.Stdout = log.GetStdout()
    78  	cmd.Stderr = log.GetStderr()
    79  
    80  	// Set all output as italic when doing a push, then return to normal at the end
    81  	color.Set(color.Italic)
    82  	defer color.Unset()
    83  	err = cmd.Run()
    84  	if err != nil {
    85  		return fmt.Errorf("error running %s command: %w", o.name, err)
    86  	}
    87  
    88  	buildSpinner.End(true)
    89  	return nil
    90  }
    91  
    92  // resolveAndDownloadDockerfile resolves and downloads (if needed) the specified Dockerfile URI.
    93  // For now, it only supports resolving HTTP(S) URIs, in which case it downloads the remote file
    94  // to a temporary file. The path to that temporary file is then returned.
    95  //
    96  // In all other cases, the specified URI path is returned as is.
    97  // This means that non-HTTP(S) URIs will *not* get resolved, but will be returned as is.
    98  //
    99  // In addition to the path, a boolean and a potential error are returned. The boolean indicates whether
   100  // the returned path is a temporary one; in such case, it is the caller's responsibility to delete this file
   101  // once it is done working with it.
   102  func resolveAndDownloadDockerfile(fs filesystem.Filesystem, uri string) (string, bool, error) {
   103  	uriLower := strings.ToLower(uri)
   104  	if strings.HasPrefix(uriLower, "http://") || strings.HasPrefix(uriLower, "https://") {
   105  		s := log.Spinner("Downloading Dockerfile")
   106  		defer s.End(false)
   107  		tempFile, err := fs.TempFile("", "odo_*.dockerfile")
   108  		if err != nil {
   109  			return "", false, err
   110  		}
   111  		dockerfile := tempFile.Name()
   112  		err = dfutil.DownloadFile(dfutil.DownloadParams{
   113  			Request: dfutil.HTTPRequestParams{
   114  				URL: uri,
   115  			},
   116  			Filepath: dockerfile,
   117  		})
   118  		s.End(err == nil)
   119  		return dockerfile, true, err
   120  	}
   121  	return uri, false, nil
   122  }
   123  
   124  // getShellCommand creates the docker compatible build command from detected backend,
   125  // container image and devfile path
   126  func getShellCommand(cmdName string, globalExtraArgs []string, buildExtraArgs []string, image *devfile.ImageComponent, devfilePath string, dockerfilePath string) []string {
   127  	imageName := image.ImageName
   128  	dockerfile := dockerfilePath
   129  	if !filepath.IsAbs(dockerfile) {
   130  		dockerfile = filepath.Join(devfilePath, dockerfilePath)
   131  	}
   132  	buildpath := image.Dockerfile.BuildContext
   133  	if buildpath == "" {
   134  		buildpath = devfilePath
   135  	}
   136  
   137  	// +7 because of the other args
   138  	shellCmd := make([]string, 0, len(globalExtraArgs)+len(buildExtraArgs)+len(image.Dockerfile.Args)+7)
   139  	shellCmd = append(shellCmd, cmdName)
   140  	shellCmd = append(shellCmd, globalExtraArgs...)
   141  	shellCmd = append(shellCmd, "build")
   142  	shellCmd = append(shellCmd, buildExtraArgs...)
   143  	shellCmd = append(shellCmd, "-t", imageName, "-f", dockerfile, buildpath)
   144  
   145  	if len(image.Dockerfile.Args) != 0 {
   146  		shellCmd = append(shellCmd, image.Dockerfile.Args...)
   147  	}
   148  	return shellCmd
   149  }
   150  
   151  // Push an image to its registry using a Docker compatible CLI
   152  func (o *DockerCompatibleBackend) Push(image string) error {
   153  
   154  	// We use a "No Spin" since we are outputting to stdout / stderr
   155  	pushSpinner := log.SpinnerNoSpin("Pushing image to container registry")
   156  	defer pushSpinner.End(false)
   157  	klog.V(4).Infof("Running command: %s push %s", o.name, image)
   158  
   159  	cmd := exec.Command(o.name, "push", image)
   160  
   161  	cmd.Stdout = log.GetStdout()
   162  	cmd.Stderr = log.GetStderr()
   163  
   164  	// Set all output as italic when doing a push, then return to normal at the end
   165  	color.Set(color.Italic)
   166  	defer color.Unset()
   167  	err := cmd.Run()
   168  	if err != nil {
   169  		return fmt.Errorf("error running %s command: %w", o.name, err)
   170  	}
   171  
   172  	pushSpinner.End(true)
   173  	return nil
   174  }
   175  
   176  // String return the name of the docker compatible CLI used
   177  func (o *DockerCompatibleBackend) String() string {
   178  	return o.name
   179  }
   180  

View as plain text