package pkg import ( "context" "embed" "errors" "fmt" "io/fs" "io/ioutil" "os" "path" "path/filepath" "strings" "github.com/gofrs/flock" "github.com/rs/zerolog/log" ) var ( // FS contains the filesystem of the stdlib. //go:embed dagger.io universe.dagger.io FS embed.FS ) var ( DaggerModule = "dagger.io" UniverseModule = "universe.dagger.io" modules = []string{ DaggerModule, UniverseModule, } DaggerPackage = fmt.Sprintf("%s/dagger", DaggerModule) DaggerCorePackage = fmt.Sprintf("%s/core", DaggerPackage) lockFilePath = "dagger.lock" ) func Vendor(ctx context.Context, p string) error { if p == "" { p, _ = GetCueModParent() } cuePkgDir := path.Join(p, "cue.mod", "pkg") if err := os.MkdirAll(cuePkgDir, 0755); err != nil { return err } // Lock this function so no more than 1 process can run it at once. lockFile := path.Join(cuePkgDir, lockFilePath) l := flock.New(lockFile) if err := l.Lock(); err != nil { return err } defer func() { l.Unlock() os.Remove(lockFile) }() // ensure cue module is initialized if err := CueModInit(ctx, p, ""); err != nil { return err } // remove 0.1-style .gitignore files gitignorePath := path.Join(cuePkgDir, ".gitignore") if contents, err := ioutil.ReadFile(gitignorePath); err == nil { if strings.HasPrefix(string(contents), "# generated by dagger") { os.Remove(gitignorePath) } } // generate `.gitattributes` file if err := os.WriteFile( path.Join(cuePkgDir, ".gitattributes"), []byte("# generated by dagger\n** linguist-generated=true\n"), 0600, ); err != nil { return err } log.Ctx(ctx).Debug().Str("mod", p).Msg("vendoring packages") // Unpack modules in a temporary directory unpackDir, err := os.MkdirTemp(cuePkgDir, "vendor-*") if err != nil { return err } defer os.RemoveAll(unpackDir) if err := extractModules(unpackDir); err != nil { return err } for _, module := range modules { // Semi-atomic swap of the module // // The following basically does: // $ rm -rf cue.mod/pkg/MODULE.old // $ mv cue.mod/pkg/MODULE cue.mod/pkg/MODULE.old // $ mv VENDOR/MODULE cue.mod/pkg/MODULE // $ rm -rf cue.mod/pkg/MODULE.old moduleDir := path.Join(cuePkgDir, module) backupModuleDir := moduleDir + ".old" if err := os.RemoveAll(backupModuleDir); err != nil && !errors.Is(err, os.ErrNotExist) { return err } if err := os.Rename(moduleDir, backupModuleDir); err != nil && !errors.Is(err, os.ErrNotExist) { return err } defer os.RemoveAll(backupModuleDir) if err := os.Rename(path.Join(unpackDir, module), moduleDir); err != nil { return err } } return nil } func extractModules(dest string) error { return fs.WalkDir(FS, ".", func(p string, entry fs.DirEntry, err error) error { if err != nil { return err } if !entry.Type().IsRegular() { return nil } // Do not vendor the package's `cue.mod/pkg` if strings.Contains(p, "cue.mod/pkg") { return nil } contents, err := fs.ReadFile(FS, p) if err != nil { return fmt.Errorf("%s: %w", p, err) } overlayPath := path.Join(dest, p) if err := os.MkdirAll(filepath.Dir(overlayPath), 0755); err != nil { return err } // Give exec permission on embedded file to freely use shell script // Exclude permission linter //nolint return os.WriteFile(overlayPath, contents, 0700) }) } // GetCueModParent traverses the directory tree up through ancestors looking for a cue.mod folder func GetCueModParent() (string, bool) { cwd, _ := os.Getwd() parentDir := cwd found := false for { if _, err := os.Stat(path.Join(parentDir, "cue.mod")); !errors.Is(err, os.ErrNotExist) { found = true break // found it! } parentDir = filepath.Dir(parentDir) if parentDir == fmt.Sprintf("%s%s", filepath.VolumeName(parentDir), string(os.PathSeparator)) { // reached the root parentDir = cwd // reset to working directory break } } return parentDir, found } func CueModInit(ctx context.Context, parentDir, module string) error { lg := log.Ctx(ctx) absParentDir, err := filepath.Abs(parentDir) if err != nil { return err } modDir := path.Join(absParentDir, "cue.mod") if err := os.MkdirAll(modDir, 0755); err != nil { if !errors.Is(err, os.ErrExist) { return err } } modFile := path.Join(modDir, "module.cue") if _, err := os.Stat(modFile); err != nil { statErr, ok := err.(*os.PathError) if !ok { return statErr } lg.Debug().Str("mod", parentDir).Msg("initializing cue.mod") contents := fmt.Sprintf(`module: "%s"`, module) if err := os.WriteFile(modFile, []byte(contents), 0600); err != nil { return err } } if err := os.Mkdir(path.Join(modDir, "usr"), 0755); err != nil { if !errors.Is(err, os.ErrExist) { return err } } if err := os.Mkdir(path.Join(modDir, "pkg"), 0755); err != nil { if !errors.Is(err, os.ErrExist) { return err } } return nil }