github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/github/render.go (about) 1 package github 2 3 import ( 4 "context" 5 "encoding/base64" 6 "fmt" 7 "os" 8 "path" 9 "path/filepath" 10 "strings" 11 12 "github.com/go-kit/kit/log" 13 "github.com/go-kit/kit/log/level" 14 "github.com/pkg/errors" 15 "github.com/replicatedhq/libyaml" 16 "github.com/replicatedhq/ship/pkg/api" 17 "github.com/replicatedhq/ship/pkg/constants" 18 "github.com/replicatedhq/ship/pkg/lifecycle/render/root" 19 "github.com/replicatedhq/ship/pkg/specs/apptype" 20 "github.com/replicatedhq/ship/pkg/specs/githubclient" 21 "github.com/replicatedhq/ship/pkg/specs/gogetter" 22 "github.com/replicatedhq/ship/pkg/state" 23 "github.com/replicatedhq/ship/pkg/templates" 24 "github.com/replicatedhq/ship/pkg/util" 25 "github.com/spf13/afero" 26 "github.com/spf13/viper" 27 ) 28 29 // Renderer is something that can render a helm asset as part of a planner.Plan 30 type Renderer interface { 31 Execute( 32 rootFs root.Fs, 33 asset api.GitHubAsset, 34 configGroups []libyaml.ConfigGroup, 35 renderRoot string, 36 meta api.ReleaseMetadata, 37 templateContext map[string]interface{}, 38 ) func(ctx context.Context) error 39 } 40 41 var _ Renderer = &LocalRenderer{} 42 43 // LocalRenderer pulls proxied github files from pg 44 // and pulls no proxy github files directly via git or the git client 45 type LocalRenderer struct { 46 Logger log.Logger 47 Fs afero.Afero 48 BuilderBuilder *templates.BuilderBuilder 49 Viper *viper.Viper 50 StateManager state.Manager 51 } 52 53 func NewRenderer( 54 logger log.Logger, 55 fs afero.Afero, 56 viper *viper.Viper, 57 builderBuilder *templates.BuilderBuilder, 58 stateManager state.Manager, 59 ) Renderer { 60 return &LocalRenderer{ 61 Logger: logger, 62 Fs: fs, 63 Viper: viper, 64 BuilderBuilder: builderBuilder, 65 StateManager: stateManager, 66 } 67 } 68 69 // refactored from planner.plan but I neeeeed tests 70 func (r *LocalRenderer) Execute( 71 rootFs root.Fs, 72 asset api.GitHubAsset, 73 configGroups []libyaml.ConfigGroup, 74 renderRoot string, 75 meta api.ReleaseMetadata, 76 templateContext map[string]interface{}, 77 ) func(ctx context.Context) error { 78 return func(ctx context.Context) error { 79 debug := level.Debug(log.With(r.Logger, "step.type", "render", "render.phase", "execute", "asset.type", "github", "dest", asset.Dest, "description", asset.Description)) 80 81 debug.Log("event", "execute") 82 basePath := filepath.Dir(asset.Dest) 83 debug.Log("event", "mkdirall.attempt", "root", rootFs.RootPath, "dest", asset.Dest, "basePath", basePath) 84 if err := rootFs.MkdirAll(basePath, 0755); err != nil { 85 debug.Log("event", "mkdirall.fail", "err", err, "root", rootFs.RootPath, "dest", asset.Dest, "basePath", basePath) 86 return errors.Wrapf(err, "write directory to %s", asset.Dest) 87 } 88 89 builder, err := r.BuilderBuilder.FullBuilder(meta, configGroups, templateContext) 90 if err != nil { 91 return errors.Wrap(err, "init builder") 92 } 93 94 debug.Log("event", "resolveProxyGithubAssets") 95 files := filterGithubContents(meta.GithubContents, asset) 96 if len(files) == 0 { 97 level.Info(r.Logger).Log("msg", "no proxy files for asset", "repo", asset.Repo, "path", asset.Path) 98 r.debugDumpKnownGithubFiles(meta, asset) 99 100 if asset.Source == "public" || !asset.Proxy { 101 debug.Log("event", "resolveNoProxyGithubAssets") 102 err := r.resolveNoProxyGithubAssets(asset, builder, renderRoot) 103 if err != nil { 104 return errors.Wrap(err, "resolveNoProxyGithubAssets") 105 } 106 } else { 107 return fmt.Errorf("github asset %s returned no files in %s at %s", asset.Repo, asset.Path, asset.Ref) 108 } 109 } 110 111 return r.resolveProxyGithubAssets(asset, builder, rootFs, files) 112 } 113 } 114 115 func (r *LocalRenderer) debugDumpKnownGithubFiles(meta api.ReleaseMetadata, asset api.GitHubAsset) { 116 debugStr := "[" 117 for _, content := range meta.GithubContents { 118 debugStr += fmt.Sprintf("%s, ", content.String()) 119 120 } 121 debugStr += "]" 122 123 level.Debug(r.Logger).Log( 124 "msg", "github contents", 125 "repo", asset.Repo, 126 "path", asset.Path, 127 "releaseMeta", debugStr, 128 ) 129 } 130 131 func filterGithubContents(githubContents []api.GithubContent, asset api.GitHubAsset) []api.GithubFile { 132 var filtered []api.GithubFile 133 for _, c := range githubContents { 134 if c.Repo == asset.Repo && strings.Trim(c.Path, "/") == strings.Trim(asset.Path, "/") && c.Ref == asset.Ref { 135 filtered = c.Files 136 break 137 } 138 } 139 return filtered 140 } 141 142 func (r *LocalRenderer) resolveProxyGithubAssets(asset api.GitHubAsset, builder *templates.Builder, rootFs root.Fs, files []api.GithubFile) error { 143 debug := level.Debug(log.With(r.Logger, "step.type", "render", "render.phase", "execute", "asset.type", "github", "dest", asset.Dest, "description", asset.Description)) 144 145 for _, file := range files { 146 data, err := base64.StdEncoding.DecodeString(file.Data) 147 if err != nil { 148 return errors.Wrapf(err, "decode %s", file.Path) 149 } 150 151 built, err := builder.String(string(data)) 152 if err != nil { 153 return errors.Wrapf(err, "building %s", file.Path) 154 } 155 156 filePath, err := getDestPath(file.Path, asset, builder) 157 if err != nil { 158 return errors.Wrapf(err, "determining destination for %s", file.Path) 159 } 160 161 basePath := filepath.Dir(filePath) 162 debug.Log("event", "mkdirall.attempt", "root", rootFs.RootPath, "dest", filePath, "basePath", basePath) 163 if err := rootFs.MkdirAll(basePath, 0755); err != nil { 164 debug.Log("event", "mkdirall.fail", "err", err, "root", rootFs.RootPath, "dest", filePath, "basePath", basePath) 165 return errors.Wrapf(err, "write directory to %s", filePath) 166 } 167 168 mode := os.FileMode(0644) // TODO: how to get mode info from github? 169 if asset.AssetShared.Mode != os.FileMode(0000) { 170 debug.Log("event", "applying override permissions", "override.filemode", asset.AssetShared.Mode, "override.filemode.int", int(asset.AssetShared.Mode)) 171 mode = asset.AssetShared.Mode 172 } 173 if err := rootFs.WriteFile(filePath, []byte(built), mode); err != nil { 174 debug.Log("event", "execute.fail", "err", err) 175 return errors.Wrapf(err, "Write inline asset to %s", filePath) 176 } 177 } 178 179 return nil 180 } 181 182 func (r *LocalRenderer) resolveNoProxyGithubAssets(asset api.GitHubAsset, builder *templates.Builder, renderRoot string) error { 183 debug := level.Debug(log.With(r.Logger, "step.type", "render", "render.phase", "execute", "asset.type", "github", "dest", asset.Dest, "description", asset.Description)) 184 debug.Log("event", "createUpstream") 185 upstream, err := createUpstreamURL(asset, builder) 186 if err != nil { 187 return errors.Wrapf(err, "create upstream url") 188 } 189 190 var fetcher apptype.FileFetcher 191 localFetchPath := filepath.Join(constants.InstallerPrefixPath, constants.GithubAssetSavePath) 192 fetcher = githubclient.NewGithubClient(r.Fs, r.Logger) 193 if r.Viper.GetBool("prefer-git") { 194 var isSingleFile bool 195 var subdir string 196 upstream, subdir, isSingleFile = gogetter.UntreeGithub(upstream) 197 fetcher = &gogetter.GoGetter{Logger: r.Logger, FS: r.Fs, Subdir: subdir, IsSingleFile: isSingleFile} 198 } 199 200 debug.Log("event", "getFiles", "upstream", upstream) 201 localPath, err := fetcher.GetFiles(context.Background(), upstream, localFetchPath) 202 if err != nil { 203 return errors.Wrap(err, "get files") 204 } 205 206 debug.Log("event", "getDestPath") 207 dest, err := r.getDestPathNoProxy(asset, builder, renderRoot) 208 if err != nil { 209 return errors.Wrap(err, "get dest path") 210 } 211 212 if filepath.Ext(asset.Path) != "" { 213 localPath = filepath.Join(localPath, asset.Path) 214 } 215 216 exists, err := r.Fs.Exists(filepath.Dir(dest)) 217 if err != nil { 218 return errors.Wrap(err, "dest dir exists") 219 } 220 221 if !exists { 222 debug.Log("event", "mkdirall", "dir", filepath.Dir(dest)) 223 if err := r.Fs.MkdirAll(filepath.Dir(dest), 0755); err != nil { 224 return errors.Wrap(err, "mkdir all dest dir") 225 } 226 } 227 228 debug.Log("event", "rename", "from", localPath, "dest", dest) 229 if err := r.Fs.Rename(localPath, dest); err != nil { 230 return errors.Wrap(err, "rename to dest") 231 } 232 233 if err := r.Fs.RemoveAll(localFetchPath); err != nil { 234 return errors.Wrap(err, "remove tmp github asset") 235 } 236 237 if err := templates.BuildDir(dest, &r.Fs, builder); err != nil { 238 return errors.Wrapf(err, "render templates in github asset %s", dest) 239 } 240 241 return nil 242 } 243 244 func getDestPath(githubPath string, asset api.GitHubAsset, builder *templates.Builder) (string, error) { 245 stripPath, err := builder.Bool(asset.StripPath, false) 246 if err != nil { 247 return "", errors.Wrapf(err, "parse boolean from %q", asset.StripPath) 248 } 249 250 destDir, err := builder.String(asset.Dest) 251 if err != nil { 252 return "", errors.Wrapf(err, "get destination directory from %q", asset.Dest) 253 } 254 255 if stripPath { 256 // remove asset.Path's directory from the beginning of githubPath 257 sourcePathDir := filepath.ToSlash(filepath.Dir(asset.Path)) + "/" 258 githubPath = strings.TrimPrefix(githubPath, sourcePathDir) 259 260 // handle cases where the source path was a dir but a trailing slash was not included 261 if !strings.HasSuffix(asset.Path, "/") { 262 sourcePathBase := filepath.Base(asset.Path) + "/" 263 githubPath = strings.TrimPrefix(githubPath, sourcePathBase) 264 } 265 } 266 267 combinedPath := filepath.Join(destDir, githubPath) 268 269 err = util.IsLegalPath(combinedPath) 270 if err != nil { 271 return "", errors.Wrap(err, "write github asset") 272 } 273 274 return combinedPath, nil 275 } 276 277 func (r *LocalRenderer) getDestPathNoProxy(asset api.GitHubAsset, builder *templates.Builder, renderRoot string) (string, error) { 278 279 debug := level.Debug(log.With(r.Logger, "method", "getdestPathNoProxy", "asset.type", "github", "dest", asset.Dest, "renderRoot", renderRoot, "proxy", false)) 280 assetPath := asset.Path 281 stripPath, err := builder.Bool(asset.StripPath, false) 282 if err != nil { 283 return "", errors.Wrapf(err, "parse boolean from %q", asset.StripPath) 284 } 285 286 destDir, err := builder.String(asset.Dest) 287 if err != nil { 288 return "", errors.Wrapf(err, "get destination directory from %q", asset.Dest) 289 } 290 291 if stripPath { 292 if filepath.Ext(assetPath) != "" { 293 assetPath = filepath.Base(assetPath) 294 } else { 295 assetPath = "" 296 } 297 } 298 299 destPath := filepath.Join(renderRoot, destDir, assetPath) 300 debug.Log("event", "destPath.resolve", "path", destPath) 301 return destPath, nil 302 } 303 304 func createUpstreamURL(asset api.GitHubAsset, builder *templates.Builder) (string, error) { 305 var assetType string 306 assetPath, err := builder.String(asset.Path) 307 if err != nil { 308 return "", errors.Wrapf(err, "build asset path %s", asset.Path) 309 } 310 assetBasePath := filepath.Base(assetPath) 311 if filepath.Ext(assetBasePath) != "" { 312 assetType = "blob" 313 } else { 314 assetType = "tree" 315 } 316 317 assetRef := "master" 318 templatedAssetRef, err := builder.String(asset.Ref) 319 if err != nil { 320 return "", errors.Wrapf(err, "build asset ref %s", asset.Ref) 321 } 322 323 if templatedAssetRef != "" { 324 assetRef = templatedAssetRef 325 } 326 327 assetRepo, err := builder.String(asset.Repo) 328 if err != nil { 329 return "", errors.Wrapf(err, "build asset repo %s", asset.Repo) 330 } 331 332 return path.Join("github.com", assetRepo, assetType, assetRef, assetPath), nil 333 }