2021-10-13 23:32:06 +02:00
|
|
|
package mod
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-10-23 00:37:28 +02:00
|
|
|
"context"
|
2021-10-13 23:32:06 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/fs"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
)
|
|
|
|
|
2021-10-23 00:37:28 +02:00
|
|
|
const (
|
|
|
|
modFilePath = "./cue.mod/dagger.mod"
|
|
|
|
sumFilePath = "./cue.mod/dagger.sum"
|
|
|
|
lockFilePath = "./cue.mod/dagger.lock"
|
|
|
|
destBasePath = "./cue.mod/pkg"
|
|
|
|
tmpBasePath = "./cue.mod/tmp"
|
|
|
|
)
|
2021-10-13 23:32:06 +02:00
|
|
|
|
|
|
|
// file is the parsed, interpreted form of dagger.mod file.
|
|
|
|
type file struct {
|
|
|
|
requires []*Require
|
|
|
|
workspacePath string
|
|
|
|
}
|
|
|
|
|
|
|
|
func readPath(workspacePath string) (*file, error) {
|
|
|
|
pMod := path.Join(workspacePath, modFilePath)
|
|
|
|
fMod, err := os.Open(pMod)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// dagger.mod doesn't exist, let's create an empty file
|
|
|
|
if fMod, err = os.Create(pMod); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pSum := path.Join(workspacePath, sumFilePath)
|
|
|
|
fSum, err := os.Open(pSum)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// dagger.sum doesn't exist, let's create an empty file
|
|
|
|
if fSum, err = os.Create(pSum); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
modFile, err := read(fMod, fSum)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
modFile.workspacePath = workspacePath
|
|
|
|
|
|
|
|
return modFile, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func read(fMod, fSum io.Reader) (*file, error) {
|
|
|
|
bMod, err := ioutil.ReadAll(fMod)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
bSum, err := ioutil.ReadAll(fSum)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
modLines := nonEmptyLines(bMod)
|
|
|
|
sumLines := nonEmptyLines(bSum)
|
|
|
|
|
|
|
|
if len(modLines) != len(sumLines) {
|
|
|
|
return nil, fmt.Errorf("length of dagger.mod and dagger.sum files differ")
|
|
|
|
}
|
|
|
|
|
|
|
|
requires := make([]*Require, 0, len(modLines))
|
|
|
|
for i := 0; i < len(modLines); i++ {
|
|
|
|
modSplit := strings.Split(modLines[i], " ")
|
|
|
|
if len(modSplit) != 2 {
|
|
|
|
return nil, fmt.Errorf("line in the mod file doesn't contain 2 elements")
|
|
|
|
}
|
|
|
|
|
|
|
|
sumSplit := strings.Split(sumLines[i], " ")
|
|
|
|
if len(sumSplit) != 2 {
|
|
|
|
return nil, fmt.Errorf("line in the sum file doesn't contain 2 elements")
|
|
|
|
}
|
|
|
|
|
|
|
|
if modSplit[0] != sumSplit[0] {
|
|
|
|
return nil, fmt.Errorf("repos in mod and sum line don't match: %s and %s", modSplit[0], sumSplit[0])
|
|
|
|
}
|
|
|
|
|
2021-10-22 02:50:18 +02:00
|
|
|
// FIXME: if we want to add support for version constraints in the mod file, it would be here
|
|
|
|
require, err := newRequire(modSplit[0], "")
|
2021-10-13 23:32:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
require.version = modSplit[1]
|
|
|
|
require.checksum = sumSplit[1]
|
|
|
|
|
|
|
|
requires = append(requires, require)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &file{requires: requires}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var spaceRgx = regexp.MustCompile(`\s+`)
|
|
|
|
|
|
|
|
func nonEmptyLines(b []byte) []string {
|
|
|
|
s := strings.ReplaceAll(string(b), "\r\n", "\n")
|
|
|
|
split := strings.Split(s, "\n")
|
|
|
|
|
|
|
|
lines := make([]string, 0, len(split))
|
|
|
|
for _, l := range split {
|
|
|
|
trimmed := strings.TrimSpace(l)
|
|
|
|
if trimmed == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
trimmed = spaceRgx.ReplaceAllString(trimmed, " ")
|
|
|
|
lines = append(lines, trimmed)
|
|
|
|
}
|
|
|
|
|
|
|
|
return lines
|
|
|
|
}
|
|
|
|
|
2021-10-23 00:37:28 +02:00
|
|
|
func (f *file) install(ctx context.Context, req *Require) error {
|
2021-10-13 23:32:06 +02:00
|
|
|
// cleaning up possible leftovers
|
|
|
|
tmpPath := path.Join(f.workspacePath, tmpBasePath, req.fullPath())
|
|
|
|
defer os.RemoveAll(tmpPath)
|
|
|
|
|
|
|
|
// clone to a tmp directory
|
2021-10-23 00:37:28 +02:00
|
|
|
r, err := clone(ctx, req, tmpPath, viper.GetString("private-key-file"), viper.GetString("private-key-password"))
|
2021-10-13 23:32:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error downloading package %s: %w", req, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
destPath := path.Join(f.workspacePath, destBasePath, req.fullPath())
|
|
|
|
|
|
|
|
// requirement is new, so we should move the cloned files from tmp to pkg and add it to the mod file
|
|
|
|
existing := f.searchInstalledRequire(req)
|
|
|
|
if existing == nil {
|
|
|
|
if err = replace(req, tmpPath, destPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum, err := dirChecksum(destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.checksum = checksum
|
|
|
|
|
|
|
|
f.requires = append(f.requires, req)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkout the cloned repo to that tag, change the version in the existing requirement and
|
|
|
|
// replace the code in the /pkg folder
|
|
|
|
existing.version = req.version
|
2021-10-23 00:37:28 +02:00
|
|
|
if err = r.checkout(ctx, req.version); err != nil {
|
2021-10-13 23:32:06 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = replace(req, tmpPath, destPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum, err := dirChecksum(destPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
existing.checksum = checksum
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-23 00:37:28 +02:00
|
|
|
func (f *file) updateToLatest(ctx context.Context, req *Require) (*Require, error) {
|
2021-10-13 23:32:06 +02:00
|
|
|
// check if it doesn't exist
|
|
|
|
existing := f.searchInstalledRequire(req)
|
|
|
|
if existing == nil {
|
|
|
|
return nil, fmt.Errorf("package %s isn't already installed", req.fullPath())
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleaning up possible leftovers
|
|
|
|
tmpPath := path.Join(f.workspacePath, tmpBasePath, existing.fullPath())
|
|
|
|
defer os.RemoveAll(tmpPath)
|
|
|
|
|
|
|
|
// clone to a tmp directory
|
2021-10-23 00:37:28 +02:00
|
|
|
gitRepo, err := clone(ctx, existing, tmpPath, viper.GetString("private-key-file"), viper.GetString("private-key-password"))
|
2021-10-13 23:32:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error downloading package %s: %w", existing, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkout the latest tag
|
2021-10-23 00:37:28 +02:00
|
|
|
latestTag, err := gitRepo.latestTag(ctx, req.versionConstraint)
|
2021-10-13 23:32:06 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := compareVersions(latestTag, existing.version)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if c < 0 {
|
|
|
|
return nil, fmt.Errorf("latest git tag is less than the current version")
|
|
|
|
}
|
|
|
|
|
|
|
|
existing.version = latestTag
|
2021-10-23 00:37:28 +02:00
|
|
|
if err = gitRepo.checkout(ctx, existing.version); err != nil {
|
2021-10-13 23:32:06 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// move the package from tmp to pkg directory
|
|
|
|
destPath := path.Join(f.workspacePath, destBasePath, existing.fullPath())
|
|
|
|
if err = replace(existing, tmpPath, destPath); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
checksum, err := dirChecksum(destPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.checksum = checksum
|
|
|
|
|
|
|
|
return existing, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *file) searchInstalledRequire(r *Require) *Require {
|
|
|
|
for _, existing := range f.requires {
|
|
|
|
if existing.fullPath() == r.fullPath() {
|
|
|
|
return existing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *file) ensure() error {
|
|
|
|
for _, require := range f.requires {
|
|
|
|
requirePath := path.Join(f.workspacePath, destBasePath, require.fullPath())
|
|
|
|
|
|
|
|
checksum, err := dirChecksum(requirePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if require.checksum != checksum {
|
|
|
|
return fmt.Errorf("wrong checksum for %s", require.fullPath())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *file) write() error {
|
|
|
|
// write dagger.mod file
|
|
|
|
var bMod bytes.Buffer
|
|
|
|
for _, r := range f.requires {
|
|
|
|
bMod.WriteString(fmt.Sprintf("%s %s\n", r.fullPath(), r.version))
|
|
|
|
}
|
|
|
|
|
|
|
|
err := ioutil.WriteFile(path.Join(f.workspacePath, modFilePath), bMod.Bytes(), 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// write dagger.sum file
|
|
|
|
var bSum bytes.Buffer
|
|
|
|
for _, r := range f.requires {
|
|
|
|
bSum.WriteString(fmt.Sprintf("%s %s\n", r.fullPath(), r.checksum))
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(path.Join(f.workspacePath, sumFilePath), bSum.Bytes(), 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|