github.com/replicatedhq/ship@v0.55.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") && 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 _, ok := err.(errors2.FetchFilesError); ok {
   144  		i.ui.Info(fmt.Sprintf("Failed to retrieve upstream %s", upstream))
   145  
   146  		var retryError = err
   147  		retries := i.viper.GetInt("retries")
   148  		hasSucceeded := false
   149  		for idx := 1; idx <= retries && !hasSucceeded; idx++ {
   150  			debug.Log("event", "retry.getFiles", "attempt", idx)
   151  			i.ui.Info(fmt.Sprintf("Retrying to retrieve upstream %s ...", upstream))
   152  
   153  			time.Sleep(time.Second * 5)
   154  			finalPath, retryError = fetcher.GetFiles(ctx, upstream, repoSavePath)
   155  
   156  			if retryError != nil {
   157  				i.ui.Info(fmt.Sprintf("Retry attempt %v out of %v to fetch upstream failed", idx, retries))
   158  				level.Error(i.logger).Log("event", "getFiles", "err", retryError)
   159  			} else {
   160  				hasSucceeded = true
   161  			}
   162  		}
   163  
   164  		if !hasSucceeded {
   165  			return nil, retryError
   166  		}
   167  	} else if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	// if there's a ship.yaml, assume its a replicated.app
   172  	var isReplicatedApp bool
   173  	for _, filename := range []string{"ship.yaml", "ship.yml"} {
   174  		isReplicatedApp, err = i.fs.Exists(path.Join(finalPath, filename))
   175  		if err != nil {
   176  			return nil, errors.Wrapf(err, "check for %s", filename)
   177  		}
   178  		if isReplicatedApp {
   179  			return &localAppCopy{AppType: "inline.replicated.app", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   180  		}
   181  	}
   182  
   183  	// if there's a Chart.yaml, assume its a chart
   184  	isChart, err := i.fs.Exists(path.Join(finalPath, "Chart.yaml"))
   185  	if err != nil {
   186  		isChart = false
   187  	}
   188  	debug.Log("event", "isChart.check", "isChart", isChart)
   189  
   190  	if isChart {
   191  		return &localAppCopy{AppType: "helm", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   192  	}
   193  
   194  	return &localAppCopy{AppType: "k8s", LocalPath: finalPath, rootTempDir: repoSavePath}, nil
   195  }
   196  
   197  func NewLocalAppCopy(
   198  	appType string,
   199  	localPath string,
   200  	rootTempDir string,
   201  ) LocalAppCopy {
   202  	return &localAppCopy{
   203  		AppType:     appType,
   204  		LocalPath:   localPath,
   205  		rootTempDir: rootTempDir,
   206  	}
   207  }
   208  
   209  type localAppCopy struct {
   210  	AppType     string
   211  	LocalPath   string
   212  	rootTempDir string
   213  }
   214  
   215  func (c *localAppCopy) GetType() string {
   216  	return c.AppType
   217  }
   218  
   219  func (c *localAppCopy) GetLocalPath() string {
   220  	return c.LocalPath
   221  }
   222  
   223  func (c *localAppCopy) Remove(fs afero.Afero) error {
   224  	if c.rootTempDir == "" {
   225  		return nil
   226  	}
   227  	return fs.RemoveAll(c.rootTempDir)
   228  }