github.com/replicatedcom/ship@v0.50.0/pkg/specs/apptype/determine_type.go (about)

     1  package apptype
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/go-kit/kit/log"
    12  	"github.com/go-kit/kit/log/level"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/pkg/errors"
    15  	"github.com/replicatedhq/ship/pkg/constants"
    16  	"github.com/replicatedhq/ship/pkg/specs/githubclient"
    17  	"github.com/replicatedhq/ship/pkg/specs/gogetter"
    18  	"github.com/replicatedhq/ship/pkg/specs/localgetter"
    19  	"github.com/replicatedhq/ship/pkg/specs/stategetter"
    20  	"github.com/replicatedhq/ship/pkg/state"
    21  	"github.com/replicatedhq/ship/pkg/util"
    22  	errors2 "github.com/replicatedhq/ship/pkg/util/errors"
    23  	"github.com/spf13/afero"
    24  	"github.com/spf13/viper"
    25  )
    26  
    27  type LocalAppCopy interface {
    28  	GetType() string
    29  	GetLocalPath() string
    30  	Remove(FS afero.Afero) error
    31  }
    32  
    33  type Inspector interface {
    34  	// DetermineApplicationType loads and application from upstream,
    35  	// returning the app type and the local path where its been downloaded (when applicable),
    36  	DetermineApplicationType(
    37  		ctx context.Context,
    38  		upstream string,
    39  	) (app LocalAppCopy, err error)
    40  }
    41  
    42  func NewInspector(
    43  	logger log.Logger,
    44  	fs afero.Afero,
    45  	v *viper.Viper,
    46  	stateManager state.Manager,
    47  	ui cli.Ui,
    48  ) Inspector {
    49  	return &inspector{
    50  		logger: logger,
    51  		fs:     fs,
    52  		viper:  v,
    53  		state:  stateManager,
    54  		ui:     ui,
    55  		isEdit: v.GetBool("isEdit"),
    56  	}
    57  }
    58  
    59  type inspector struct {
    60  	logger log.Logger
    61  	fs     afero.Afero
    62  	viper  *viper.Viper
    63  	state  state.Manager
    64  	ui     cli.Ui
    65  	isEdit bool
    66  }
    67  
    68  type FileFetcher interface {
    69  	GetFiles(ctx context.Context, upstream, savePath string) (string, error)
    70  }
    71  
    72  func (i *inspector) DetermineApplicationType(ctx context.Context, upstream string) (app LocalAppCopy, err error) {
    73  	// hack hack hack
    74  	isReplicatedApp := strings.HasPrefix(upstream, "replicated.app") ||
    75  		strings.HasPrefix(upstream, "staging.replicated.app") ||
    76  		strings.HasPrefix(upstream, "local.replicated.app")
    77  	if isReplicatedApp {
    78  		return &localAppCopy{AppType: "replicated.app"}, nil
    79  	}
    80  
    81  	parts := strings.SplitN(upstream, "?", 2)
    82  	if _, err := os.Stat(parts[0]); err == nil && gogetter.IsShipYaml(parts[0]) {
    83  		return &localAppCopy{AppType: "runbook.replicated.app", LocalPath: parts[0]}, nil
    84  	}
    85  
    86  	if i.isEdit {
    87  		return i.fetchEditFiles(ctx)
    88  	}
    89  
    90  	i.ui.Info(fmt.Sprintf("Attempting to retrieve upstream %s ...", upstream))
    91  	// use the integrated github client if the url is a github url and does not contain "//", unless perfer-git is set)
    92  	if i.viper.GetBool("prefer-git") == false && util.IsGithubURL(upstream) {
    93  		githubClient := githubclient.NewGithubClient(i.fs, i.logger)
    94  		return i.determineTypeFromContents(ctx, upstream, githubClient)
    95  	}
    96  
    97  	if localgetter.IsLocalFile(&i.fs, upstream) {
    98  		fetcher := localgetter.LocalGetter{Logger: i.logger, FS: i.fs}
    99  		return i.determineTypeFromContents(ctx, upstream, &fetcher)
   100  	}
   101  
   102  	upstream, subdir, isSingleFile := gogetter.UntreeGithub(upstream)
   103  	if !isSingleFile {
   104  		isSingleFile = gogetter.IsShipYaml(upstream)
   105  	}
   106  	if gogetter.IsGoGettable(upstream) {
   107  		// get with go-getter
   108  		fetcher := gogetter.GoGetter{Logger: i.logger, FS: i.fs, Subdir: subdir, IsSingleFile: isSingleFile}
   109  		return i.determineTypeFromContents(ctx, upstream, &fetcher)
   110  	}
   111  
   112  	return nil, errors.Errorf("upstream %s is not a replicated app, a github repo, or compatible with go-getter", upstream)
   113  }
   114  
   115  func (i *inspector) fetchEditFiles(ctx context.Context) (app LocalAppCopy, err error) {
   116  	state, err := i.state.CachedState()
   117  	if err != nil {
   118  		return nil, errors.Wrap(err, "load app state")
   119  	}
   120  	upstreamContents := state.UpstreamContents()
   121  	if upstreamContents == nil {
   122  		return nil, fmt.Errorf("no upstream contents present")
   123  	}
   124  
   125  	if upstreamContents.AppRelease != nil {
   126  		return &localAppCopy{AppType: "replicated.app"}, nil
   127  	}
   128  
   129  	// create a new fetcher class that gets things from the state file
   130  	stateClient := stategetter.NewStateGetter(i.fs, i.logger, upstreamContents)
   131  	return i.determineTypeFromContents(ctx, "ship statefile", stateClient)
   132  }
   133  
   134  func (i *inspector) determineTypeFromContents(ctx context.Context, upstream string, fetcher FileFetcher) (app LocalAppCopy, err error) {
   135  	debug := level.Debug(i.logger)
   136  
   137  	repoSavePath, err := i.fs.TempDir(constants.ShipPathInternalTmp, "repo")
   138  	if err != nil {
   139  		return nil, errors.Wrap(err, "create tmp dir")
   140  	}
   141  
   142  	finalPath, err := fetcher.GetFiles(ctx, upstream, repoSavePath)
   143  	if err != nil {
   144  		if _, ok := err.(errors2.FetchFilesError); ok {
   145  			i.ui.Info(fmt.Sprintf("Failed to retrieve upstream %s", upstream))
   146  
   147  			var retryError = err
   148  			retries := i.viper.GetInt("retries")
   149  			hasSucceeded := false
   150  			for idx := 1; idx <= retries && !hasSucceeded; idx++ {
   151  				debug.Log("event", "retry.getFiles", "attempt", idx)
   152  				i.ui.Info(fmt.Sprintf("Retrying to retrieve upstream %s ...", upstream))
   153  
   154  				time.Sleep(time.Second * 5)
   155  				finalPath, retryError = fetcher.GetFiles(ctx, upstream, repoSavePath)
   156  
   157  				if retryError != nil {
   158  					i.ui.Info(fmt.Sprintf("Retry attempt %v out of %v to fetch upstream failed", idx, retries))
   159  					level.Error(i.logger).Log("event", "getFiles", "err", retryError)
   160  				} else {
   161  					hasSucceeded = true
   162  				}
   163  			}
   164  
   165  			if !hasSucceeded {
   166  				return nil, retryError
   167  			}
   168  		} else {
   169  			return nil, err
   170  		}
   171  	}
   172  
   173  	// if there's a ship.yaml, assume its a replicated.app
   174  	var isReplicatedApp bool
   175  	for _, filename := range []string{"ship.yaml", "ship.yml"} {
   176  		isReplicatedApp, err = i.fs.Exists(path.Join(finalPath, filename))
   177  		if err != nil {
   178  			return nil, errors.Wrapf(err, "check for %s", filename)
   179  		}
   180  		if isReplicatedApp {
   181  			return &localAppCopy{AppType: "inline.replicated.app", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   182  		}
   183  	}
   184  
   185  	// if there's a Chart.yaml, assume its a chart
   186  	isChart, err := i.fs.Exists(path.Join(finalPath, "Chart.yaml"))
   187  	if err != nil {
   188  		isChart = false
   189  	}
   190  	debug.Log("event", "isChart.check", "isChart", isChart)
   191  
   192  	if isChart {
   193  		return &localAppCopy{AppType: "helm", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   194  	}
   195  
   196  	return &localAppCopy{AppType: "k8s", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   197  }
   198  
   199  func NewLocalAppCopy(
   200  	appType string,
   201  	localPath string,
   202  	rootTempDir string,
   203  ) LocalAppCopy {
   204  	return &localAppCopy{
   205  		AppType:     appType,
   206  		LocalPath:   localPath,
   207  		rootTempDir: rootTempDir,
   208  	}
   209  }
   210  
   211  type localAppCopy struct {
   212  	AppType     string
   213  	LocalPath   string
   214  	rootTempDir string
   215  }
   216  
   217  func (c *localAppCopy) GetType() string {
   218  	return c.AppType
   219  }
   220  
   221  func (c *localAppCopy) GetLocalPath() string {
   222  	return c.LocalPath
   223  }
   224  
   225  func (c *localAppCopy) Remove(fs afero.Afero) error {
   226  	if c.rootTempDir == "" {
   227  		return nil
   228  	}
   229  	return fs.RemoveAll(c.rootTempDir)
   230  }