package app import ( "archive/zip" "fmt" "io" "io/ioutil" "os" "path/filepath" "strings" "forge.cadoles.com/arcad/edge/pkg/app" "forge.cadoles.com/arcad/edge/pkg/bundle" "github.com/pkg/errors" "github.com/urfave/cli/v2" ) func PackageCommand() *cli.Command { return &cli.Command{ Name: "package", Usage: "Generate a new app package from given directory", Flags: []cli.Flag{ &cli.StringFlag{ Name: "directory", Usage: "use source directory `DIR`", Aliases: []string{"d"}, Required: true, }, &cli.StringFlag{ Name: "output-dir", Aliases: []string{"o"}, Usage: "use `DIR` as generated package destination", }, }, Action: func(ctx *cli.Context) error { appDir := ctx.String("directory") outputDir := ctx.String("output-dir") if outputDir == "" { workdir, err := os.Getwd() if err != nil { return errors.Wrap(err, "could not retrieve current working directory") } outputDir = workdir } bundle := bundle.NewDirectoryBundle(appDir) manifest, err := app.LoadManifest(bundle) if err != nil { return errors.Wrap(err, "could not load app manifest") } if valid, err := manifest.Validate(manifestMetadataValidators...); !valid { return errors.Wrap(err, "invalid app manifest") } if err := os.MkdirAll(outputDir, 0o755); err != nil { return errors.Wrapf(err, "could not create directory ''%s'", outputDir) } archiveName := fmt.Sprintf( "%s_%s%s", strings.ToLower(string(manifest.ID)), string(manifest.Version), ".zip", ) packagePath := filepath.Join(outputDir, archiveName) if err := zipDirectory(appDir, packagePath); err != nil { return errors.Wrapf(err, "could not zip directory ''%s'", appDir) } return nil }, } } func zipDirectory(baseDir string, outputFile string) error { outFile, err := os.Create(outputFile) if err != nil { return errors.WithStack(err) } defer func() { if err := outFile.Close(); err != nil { panic(errors.WithStack(err)) } }() w := zip.NewWriter(outFile) if err := copyDir(w, baseDir+"/", ""); err != nil { return errors.WithStack(err) } if err := w.Close(); err != nil { return errors.WithStack(err) } return nil } func copyDir(writer *zip.Writer, baseDir string, zipBasePath string) error { files, err := ioutil.ReadDir(baseDir) if err != nil { return errors.WithStack(err) } for _, file := range files { if !file.IsDir() { srcPath := baseDir + file.Name() zipPath := zipBasePath + file.Name() if err := copyFile(writer, srcPath, zipPath); err != nil { return errors.WithStack(err) } } else if file.IsDir() { newBase := baseDir + file.Name() + "/" if err := copyDir(writer, newBase, zipBasePath+file.Name()+"/"); err != nil { return errors.WithStack(err) } } } return nil } func copyFile(writer *zip.Writer, srcPath string, zipPath string) error { srcFile, err := os.Open(srcPath) if err != nil { return errors.WithStack(err) } srcStat, err := os.Stat(srcPath) if err != nil { return errors.WithStack(err) } defer func() { if err := srcFile.Close(); err != nil { panic(errors.WithStack(err)) } }() fileHeader := &zip.FileHeader{ Name: zipPath, Modified: srcStat.ModTime().UTC(), Method: zip.Deflate, } file, err := writer.CreateHeader(fileHeader) if err != nil { return errors.WithStack(err) } if _, err = io.Copy(file, srcFile); err != nil { return errors.WithStack(err) } return nil }