Do not use os package in filestate backend

Since the filestate backend is now written using the go-cloud blob
abstraction, places where we were using functions from the `os`
package were very unlikely to be correct.

This change removes their uses in favor of APIs provided by
go-cloud (which sometimes requires more work than before).
This commit is contained in:
Matt Ellis 2019-08-14 11:50:03 -07:00
parent 828086d638
commit b14e3d9c35
3 changed files with 52 additions and 16 deletions

View file

@ -32,6 +32,7 @@ import (
_ "gocloud.dev/blob/fileblob" // driver for file://
_ "gocloud.dev/blob/gcsblob" // driver for gs://
_ "gocloud.dev/blob/s3blob" // driver for s3://
"gocloud.dev/gcerrors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend"
@ -252,7 +253,7 @@ func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackRefer
stackName := stackRef.Name()
snapshot, path, err := b.getStack(stackName)
switch {
case os.IsNotExist(errors.Cause(err)):
case gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound:
return nil, nil
case err != nil:
return nil, err
@ -305,12 +306,13 @@ func (b *localBackend) RenameStack(ctx context.Context, stackRef backend.StackRe
}
// Ensure the destination stack does not already exist.
_, err = os.Stat(b.stackPath(newName))
if err == nil {
return errors.Errorf("a stack named %s already exists", newName)
} else if !os.IsNotExist(err) {
hasExisting, err := b.bucket.Exists(ctx, b.stackPath(newName))
if err != nil {
return err
}
if hasExisting {
return errors.Errorf("a stack named %s already exists", newName)
}
// If we have a snapshot, we need to rename the URNs inside it to use the new stack name.
if snap != nil {
@ -328,16 +330,8 @@ func (b *localBackend) RenameStack(ctx context.Context, stackRef backend.StackRe
file := b.stackPath(stackName)
backupTarget(b.bucket, file)
// And move the history over as well, if it exists.
oldHistoryDir := b.historyDirectory(stackName)
if _, err := os.Stat(oldHistoryDir); err == nil {
newHistoryDir := b.historyDirectory(newName)
if err := os.Rename(oldHistoryDir, newHistoryDir); err != nil {
return errors.Wrap(err, "renaming history")
}
}
return nil
// And rename the histoy folder as well.
return b.renameHistory(stackName, newName)
}
func (b *localBackend) GetLatestConfiguration(ctx context.Context,

View file

@ -20,6 +20,7 @@ type Bucket interface {
SignedURL(ctx context.Context, key string, opts *blob.SignedURLOptions) (string, error)
ReadAll(ctx context.Context, key string) (_ []byte, err error)
WriteAll(ctx context.Context, key string, p []byte, opts *blob.WriterOptions) (err error)
Exists(ctx context.Context, key string) (bool, error)
}
// wrappedBucket encapsulates a true gocloud blob.Bucket, but ensures that all paths we send to it
@ -54,6 +55,10 @@ func (b *wrappedBucket) WriteAll(ctx context.Context, key string, p []byte, opts
return b.bucket.WriteAll(ctx, filepath.ToSlash(key), p, opts)
}
func (b *wrappedBucket) Exists(ctx context.Context, key string) (bool, error) {
return b.bucket.Exists(ctx, filepath.ToSlash(key))
}
// listBucket returns a list of all files in the bucket within a given directory. go-cloud sorts the results by key
func listBucket(bucket Bucket, dir string) ([]*blob.ListObject, error) {
bucketIter := bucket.List(&blob.ListOptions{

View file

@ -25,6 +25,7 @@ import (
"time"
"github.com/pkg/errors"
"gocloud.dev/gcerrors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend"
@ -267,7 +268,7 @@ func (b *localBackend) getHistory(name tokens.QName) ([]backend.UpdateInfo, erro
allFiles, err := listBucket(b.bucket, dir)
if err != nil {
// History doesn't exist until a stack has been updated.
if os.IsNotExist(err) {
if gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound {
return nil, nil
}
return nil, err
@ -302,6 +303,42 @@ func (b *localBackend) getHistory(name tokens.QName) ([]backend.UpdateInfo, erro
return updates, nil
}
func (b *localBackend) renameHistory(oldName tokens.QName, newName tokens.QName) error {
contract.Require(oldName != "", "oldName")
contract.Require(newName != "", "newName")
oldHistory := b.historyDirectory(oldName)
newHistory := b.historyDirectory(newName)
allFiles, err := listBucket(b.bucket, oldHistory)
if err != nil {
// if there's nothing there, we don't really need to do a rename.
if gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound {
return nil
}
return err
}
for _, file := range allFiles {
fileName := objectName(file)
oldBlob := path.Join(oldHistory, fileName)
// The filename format is <stack-name>-<timestamp>.[checkpoint|history].json, we need to change
// the stack name part but retain the other parts.
newFileName := string(newName) + fileName[strings.LastIndex(fileName, "-"):]
newBlob := path.Join(newHistory, newFileName)
if err := b.bucket.Copy(context.TODO(), newBlob, oldBlob, nil); err != nil {
return errors.Wrap(err, "copying history file")
}
if err := b.bucket.Delete(context.TODO(), oldBlob); err != nil {
return errors.Wrap(err, "deleting existing history file")
}
}
return nil
}
// addToHistory saves the UpdateInfo and makes a copy of the current Checkpoint file.
func (b *localBackend) addToHistory(name tokens.QName, update backend.UpdateInfo) error {
contract.Require(name != "", "name")