github.com/nektos/act@v0.2.63/pkg/container/docker_pull.go (about)

     1  //go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd))
     2  
     3  package container
     4  
     5  import (
     6  	"context"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/docker/distribution/reference"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/registry"
    15  
    16  	"github.com/nektos/act/pkg/common"
    17  )
    18  
    19  // NewDockerPullExecutor function to create a run executor for the container
    20  func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
    21  	return func(ctx context.Context) error {
    22  		logger := common.Logger(ctx)
    23  		logger.Debugf("%sdocker pull %v", logPrefix, input.Image)
    24  
    25  		if common.Dryrun(ctx) {
    26  			return nil
    27  		}
    28  
    29  		pull := input.ForcePull
    30  		if !pull {
    31  			imageExists, err := ImageExistsLocally(ctx, input.Image, input.Platform)
    32  			logger.Debugf("Image exists? %v", imageExists)
    33  			if err != nil {
    34  				return fmt.Errorf("unable to determine if image already exists for image '%s' (%s): %w", input.Image, input.Platform, err)
    35  			}
    36  
    37  			if !imageExists {
    38  				pull = true
    39  			}
    40  		}
    41  
    42  		if !pull {
    43  			return nil
    44  		}
    45  
    46  		imageRef := cleanImage(ctx, input.Image)
    47  		logger.Debugf("pulling image '%v' (%s)", imageRef, input.Platform)
    48  
    49  		cli, err := GetDockerClient(ctx)
    50  		if err != nil {
    51  			return err
    52  		}
    53  		defer cli.Close()
    54  
    55  		imagePullOptions, err := getImagePullOptions(ctx, input)
    56  		if err != nil {
    57  			return err
    58  		}
    59  
    60  		reader, err := cli.ImagePull(ctx, imageRef, imagePullOptions)
    61  
    62  		_ = logDockerResponse(logger, reader, err != nil)
    63  		if err != nil {
    64  			if imagePullOptions.RegistryAuth != "" && strings.Contains(err.Error(), "unauthorized") {
    65  				logger.Errorf("pulling image '%v' (%s) failed with credentials %s retrying without them, please check for stale docker config files", imageRef, input.Platform, err.Error())
    66  				imagePullOptions.RegistryAuth = ""
    67  				reader, err = cli.ImagePull(ctx, imageRef, imagePullOptions)
    68  
    69  				_ = logDockerResponse(logger, reader, err != nil)
    70  			}
    71  			return err
    72  		}
    73  		return nil
    74  	}
    75  }
    76  
    77  func getImagePullOptions(ctx context.Context, input NewDockerPullExecutorInput) (types.ImagePullOptions, error) {
    78  	imagePullOptions := types.ImagePullOptions{
    79  		Platform: input.Platform,
    80  	}
    81  	logger := common.Logger(ctx)
    82  
    83  	if input.Username != "" && input.Password != "" {
    84  		logger.Debugf("using authentication for docker pull")
    85  
    86  		authConfig := registry.AuthConfig{
    87  			Username: input.Username,
    88  			Password: input.Password,
    89  		}
    90  
    91  		encodedJSON, err := json.Marshal(authConfig)
    92  		if err != nil {
    93  			return imagePullOptions, err
    94  		}
    95  
    96  		imagePullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
    97  	} else {
    98  		authConfig, err := LoadDockerAuthConfig(ctx, input.Image)
    99  		if err != nil {
   100  			return imagePullOptions, err
   101  		}
   102  		if authConfig.Username == "" && authConfig.Password == "" {
   103  			return imagePullOptions, nil
   104  		}
   105  		logger.Info("using DockerAuthConfig authentication for docker pull")
   106  
   107  		encodedJSON, err := json.Marshal(authConfig)
   108  		if err != nil {
   109  			return imagePullOptions, err
   110  		}
   111  
   112  		imagePullOptions.RegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
   113  	}
   114  
   115  	return imagePullOptions, nil
   116  }
   117  
   118  func cleanImage(ctx context.Context, image string) string {
   119  	ref, err := reference.ParseAnyReference(image)
   120  	if err != nil {
   121  		common.Logger(ctx).Error(err)
   122  		return ""
   123  	}
   124  
   125  	return ref.String()
   126  }