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 }