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 }