github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/build_problems.go (about)

     1  /*
     2  Copyright 2020 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 build
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"regexp"
    23  
    24  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    27  	sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/errors"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    29  	"github.com/GoogleContainerTools/skaffold/proto/v1"
    30  )
    31  
    32  const (
    33  	PushImageErr = "could not push image"
    34  	// Error Prefix matches error thrown by Docker
    35  	// See https://github.com/moby/moby/blob/master/client/errors.go#L18
    36  	dockerConnectionFailed = ".*(Cannot connect to the Docker daemon.*) Is"
    37  
    38  	// See https://github.com/moby/moby/blob/master/client/errors.go#L20
    39  	// `docker build: error during connect: Post \"https://127.0.0.1:32770/v1.24/build?buildargs=:  globalRepo canceled`
    40  	buildCancelled = ".*context canceled.*"
    41  )
    42  
    43  var (
    44  	unknownProjectErr = fmt.Sprintf(".*(%s.* unknown: Project.*)", PushImageErr)
    45  	nilSuggestions    = func(cfg interface{}) []*proto.Suggestion { return nil }
    46  	// for testing
    47  	getConfigForCurrentContext = config.GetConfigForCurrentKubectx
    48  	problems                   = []sErrors.Problem{
    49  		{
    50  			Regexp:  re(fmt.Sprintf(".*%s.* denied: .*", PushImageErr)),
    51  			ErrCode: proto.StatusCode_BUILD_PUSH_ACCESS_DENIED,
    52  			Description: func(err error) string {
    53  				log.Entry(context.TODO()).Tracef("error building %s", err)
    54  				return "Build Failed. No push access to specified image repository"
    55  			},
    56  			Suggestion: suggestBuildPushAccessDeniedAction,
    57  		},
    58  		{
    59  			Regexp:  re(buildCancelled),
    60  			ErrCode: proto.StatusCode_BUILD_CANCELLED,
    61  			Description: func(error) string {
    62  				return "Build Cancelled"
    63  			},
    64  			Suggestion: nilSuggestions,
    65  		},
    66  		{
    67  			Regexp: re(unknownProjectErr),
    68  			Description: func(err error) string {
    69  				log.Entry(context.TODO()).Tracef("error building %s", err)
    70  				matchExp := re(unknownProjectErr)
    71  				if match := matchExp.FindStringSubmatch(err.Error()); len(match) >= 2 {
    72  					return fmt.Sprintf("Build Failed. %s", match[1])
    73  				}
    74  				return fmt.Sprintf("Build Failed due to %s", err)
    75  			},
    76  			ErrCode: proto.StatusCode_BUILD_PROJECT_NOT_FOUND,
    77  			Suggestion: func(interface{}) []*proto.Suggestion {
    78  				return []*proto.Suggestion{{
    79  					SuggestionCode: proto.SuggestionCode_CHECK_GCLOUD_PROJECT,
    80  					Action:         "Check your GCR project",
    81  				}}
    82  			},
    83  		},
    84  		{
    85  			Regexp:  re(dockerConnectionFailed),
    86  			ErrCode: proto.StatusCode_BUILD_DOCKER_DAEMON_NOT_RUNNING,
    87  			Description: func(err error) string {
    88  				log.Entry(context.TODO()).Tracef("error building %s", err)
    89  				matchExp := re(dockerConnectionFailed)
    90  				if match := matchExp.FindStringSubmatch(err.Error()); len(match) >= 2 {
    91  					return fmt.Sprintf("Build Failed. %s", match[1])
    92  				}
    93  				return "Build Failed. Could not connect to Docker daemon"
    94  			},
    95  			Suggestion: func(interface{}) []*proto.Suggestion {
    96  				return []*proto.Suggestion{{
    97  					SuggestionCode: proto.SuggestionCode_CHECK_DOCKER_RUNNING,
    98  					Action:         "Check if docker is running",
    99  				}}
   100  			},
   101  		},
   102  	}
   103  )
   104  
   105  // re is a shortcut around regexp.MustCompile
   106  func re(s string) *regexp.Regexp {
   107  	return regexp.MustCompile(s)
   108  }
   109  
   110  func init() {
   111  	sErrors.AddPhaseProblems(constants.Build, problems)
   112  }
   113  
   114  func suggestBuildPushAccessDeniedAction(cfg interface{}) []*proto.Suggestion {
   115  	buildCfg, ok := cfg.(Config)
   116  	if !ok {
   117  		return nil
   118  	}
   119  	if defaultRepo := buildCfg.DefaultRepo(); defaultRepo != nil {
   120  		suggestions := []*proto.Suggestion{{
   121  			SuggestionCode: proto.SuggestionCode_CHECK_DEFAULT_REPO,
   122  			Action:         "Check your `--default-repo` value",
   123  		}}
   124  		return append(suggestions, makeAuthSuggestionsForRepo(*defaultRepo))
   125  	}
   126  
   127  	// check if global repo is set
   128  	if gCfg, err := getConfigForCurrentContext(buildCfg.GlobalConfig()); err == nil {
   129  		if defaultRepo := gCfg.DefaultRepo; defaultRepo != "" {
   130  			suggestions := []*proto.Suggestion{{
   131  				SuggestionCode: proto.SuggestionCode_CHECK_DEFAULT_REPO_GLOBAL_CONFIG,
   132  				Action:         "Check your default-repo setting in skaffold config",
   133  			}}
   134  			return append(suggestions, makeAuthSuggestionsForRepo(defaultRepo))
   135  		}
   136  	}
   137  
   138  	return []*proto.Suggestion{{
   139  		SuggestionCode: proto.SuggestionCode_ADD_DEFAULT_REPO,
   140  		Action:         "Try running with `--default-repo` flag",
   141  	}}
   142  }
   143  
   144  func makeAuthSuggestionsForRepo(repo string) *proto.Suggestion {
   145  	// parse off the registry component; should have already been validated so unlikely to fail
   146  	if ref, _ := docker.ParseReference(repo); ref != nil {
   147  		repo = ref.Domain
   148  	}
   149  
   150  	if re(`(.+\.)?gcr\.io`).MatchString(repo) || re(`.+-docker\.pkg\.dev`).MatchString(repo) {
   151  		return &proto.Suggestion{
   152  			SuggestionCode: proto.SuggestionCode_GCLOUD_DOCKER_AUTH_CONFIGURE,
   153  			Action:         fmt.Sprintf("try `gcloud auth configure-docker%s`", withSpace(repo)),
   154  		}
   155  	}
   156  	return &proto.Suggestion{
   157  		SuggestionCode: proto.SuggestionCode_DOCKER_AUTH_CONFIGURE,
   158  		Action:         fmt.Sprintf("try `docker login%s`", withSpace(repo)),
   159  	}
   160  }
   161  
   162  // withSpace returns the given value with a space prepended when not empty.
   163  func withSpace(value string) string {
   164  	if len(value) > 0 {
   165  		return " " + value
   166  	}
   167  	return value
   168  }