Implement box collection

This commit is contained in:
sophia 2021-11-11 15:25:06 -06:00 committed by Paul Hinze
parent 79b1b046a4
commit c48afe423d
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0

View File

@ -0,0 +1,180 @@
package core
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
"google.golang.org/protobuf/types/known/emptypb"
)
const (
TempPrefix = "vagrant-box-add-temp-"
VagrantSlash = "-VAGRANTSLASH-"
VagrantColon = "-VAGRANTCOLON-"
)
type BoxCollection struct {
basis *Basis
directory string
m sync.Mutex
logger hclog.Logger
}
// This adds a new box to the system.
// There are some exceptional cases:
// * BoxAlreadyExists - The box you're attempting to add already exists.
// * BoxProviderDoesntMatch - If the given box provider doesn't match the
// actual box provider in the untarred box.
// * BoxUnpackageFailure - An invalid tar file.
func (b *BoxCollection) Add(path, name, version string, force bool, providers ...string) (box *Box, err error) {
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("Could not add box, unable to find path %s", path)
}
exists, err := b.Find(name, version, providers...)
if err != nil {
return nil, err
}
if exists != nil && !force {
return nil, fmt.Errorf("Box already exits, can't add %s v%s", name, version)
} else {
// If the box already exists but force is enabled, then delete the box
exists.Destroy()
}
tempDir := b.basis.dir.TempDir().String()
// delete tempdir when finished
defer os.RemoveAll(tempDir)
b.logger.Debug("Unpacking box")
boxFile, err := os.Open(path)
if err != nil {
return nil, err
}
reader := tar.NewReader(boxFile)
for {
header, err := reader.Next()
if err != nil {
return nil, err
}
dest := filepath.Join(tempDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
// create directory if it doesn't already exist
if fi, _ := os.Stat(dest); fi != nil {
err = os.MkdirAll(dest, 0755)
if err != nil {
return nil, err
}
}
case tar.TypeReg:
// copy the file
f, err := os.OpenFile(dest, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return nil, err
}
if _, err := io.Copy(f, reader); err != nil {
return nil, err
}
f.Close()
}
}
newBox, err := NewBox(
BoxWithBasis(b.basis),
BoxWithBox(&vagrant_server.Box{
Name: name,
Version: version,
Directory: tempDir,
}),
)
newBox.Save()
if err != nil {
return nil, err
}
provider := newBox.box.Provider
foundProvider := false
for _, p := range providers {
if p == provider {
foundProvider = true
break
}
}
if !foundProvider {
return nil, fmt.Errorf("could not add box %s, provider '%s' does not match the expected providers %s", path, provider, providers)
}
destDir := b.generateDirectoryName(filepath.Join(b.directory, name, version, provider))
b.logger.Debug("Box directory: %s", destDir)
os.Rename(tempDir, destDir)
return newBox, nil
}
// This returns an array of all the boxes on the system
func (b *BoxCollection) All() (boxes []*Box, err error) {
resp, err := b.basis.client.ListBoxes(
b.basis.ctx,
&emptypb.Empty{},
)
boxes = []*Box{}
for _, boxRef := range resp.Boxes {
box, err := NewBox(
BoxWithBasis(b.basis),
BoxWithRef(boxRef, b.basis.ctx),
)
if err != nil {
return nil, err
}
boxes = append(boxes, box)
}
return
}
// Find a box in the collection with the given name, version and provider.
func (b *BoxCollection) Find(name, version string, providers ...string) (box *Box, err error) {
// If no providers are spcified then search for any provider
if len(providers) == 0 {
providers = append(providers, "")
}
for _, provider := range providers {
resp, err := b.basis.client.FindBox(
b.basis.ctx,
&vagrant_server.FindBoxRequest{
Box: &vagrant_plugin_sdk.Ref_Box{
Name: name, Version: version, Provider: provider,
},
},
)
if err != nil {
return nil, err
}
if resp != nil {
// Return the first box that is found
return NewBox(
BoxWithBasis(b.basis),
BoxWithBox(resp.Box),
)
}
}
return
}
// Cleans the directory for a box by removing the folders that are
// empty.
func (b *BoxCollection) Clean(name string) (err error) {
path := filepath.Join(b.directory, name)
return os.RemoveAll(path)
}
func (b *BoxCollection) generateDirectoryName(path string) (out string) {
out = strings.ReplaceAll(path, ":", VagrantColon)
return strings.ReplaceAll(out, "/", VagrantSlash)
}