pulumi/pkg/workspace/workspace.go
Pat Gavlin a23b10a9bf
Update the copyright end date to 2018. (#1068)
Just what it says on the tin.
2018-03-21 12:43:21 -07:00

194 lines
5.2 KiB
Go

// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
package workspace
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// W offers functionality for interacting with Pulumi workspaces.
type W interface {
Settings() *Settings // returns a mutable pointer to the optional workspace settings info.
Repository() *Repository // returns the repository this project belongs to.
StackPath(stack tokens.QName) string // returns the path to store stack information.
BackupDirectory() (string, error) // returns the directory to store backup stack files.
HistoryDirectory(stack tokens.QName) string // returns the directory to store a stack's history information.
Save() error // saves any modifications to the workspace.
}
type projectWorkspace struct {
name tokens.PackageName // the package this workspace is associated with.
project string // the path to the Pulumi.[yaml|json] file for this project.
settings *Settings // settings for this workspace.
repo *Repository // the repo this workspace is associated with.
}
// New creates a new workspace using the current working directory.
func New() (W, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
return NewFrom(cwd)
}
// NewFrom creates a new Pulumi workspace in the given directory. Requires a Pulumi.yaml file be present in the
// folder hierarchy between dir and the .pulumi folder.
func NewFrom(dir string) (W, error) {
repo, err := GetRepository(dir)
if err != nil {
return nil, err
}
path, err := DetectProjectPathFrom(dir)
if err != nil {
return nil, err
} else if path == "" {
return nil, errors.New("no Pulumi.yaml project file found")
}
proj, err := LoadProject(path)
if err != nil {
return nil, err
}
w := projectWorkspace{
name: proj.Name,
project: path,
repo: repo,
}
err = w.readSettings()
if err != nil {
return nil, err
}
if w.settings.ConfigDeprecated == nil {
w.settings.ConfigDeprecated = make(map[tokens.QName]config.Map)
}
return &w, nil
}
func (pw *projectWorkspace) Settings() *Settings {
return pw.settings
}
func (pw *projectWorkspace) Repository() *Repository {
return pw.repo
}
func (pw *projectWorkspace) Save() error {
// let's remove all the empty entries from the config array
for k, v := range pw.settings.ConfigDeprecated {
if len(v) == 0 {
delete(pw.settings.ConfigDeprecated, k)
}
}
settingsFile := pw.settingsPath()
// ensure the path exists
err := os.MkdirAll(filepath.Dir(settingsFile), 0700)
if err != nil {
return err
}
b, err := json.MarshalIndent(pw.settings, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(settingsFile, b, 0600)
}
func (pw *projectWorkspace) StackPath(stack tokens.QName) string {
path := filepath.Join(pw.Repository().Root, StackDir, pw.name.String())
if stack != "" {
path = filepath.Join(path, qnamePath(stack)+".json")
}
return path
}
func (pw *projectWorkspace) BackupDirectory() (string, error) {
user, err := user.Current()
if user == nil || err != nil {
return "", errors.New("failed to get current user")
}
projectDir := filepath.Dir(pw.project)
projectBackupDirName := filepath.Base(projectDir) + "-" + sha1HexString(projectDir)
return filepath.Join(user.HomeDir, BookkeepingDir, BackupDir, projectBackupDirName), nil
}
func (pw *projectWorkspace) HistoryDirectory(stack tokens.QName) string {
path := filepath.Join(pw.Repository().Root, HistoryDir, pw.name.String())
if stack != "" {
return filepath.Join(path, qnamePath(stack))
}
return path
}
func (pw *projectWorkspace) readSettings() error {
settingsPath := pw.settingsPath()
b, err := ioutil.ReadFile(settingsPath)
if err != nil && os.IsNotExist(err) {
// not an error to not have an existing settings file.
pw.settings = &Settings{}
return nil
} else if err != nil {
return err
}
var settings Settings
err = json.Unmarshal(b, &settings)
if err != nil {
return err
}
pw.settings = &settings
return nil
}
func (pw *projectWorkspace) settingsPath() string {
return filepath.Join(pw.Repository().Root, WorkspaceDir, pw.name.String(), WorkspaceFile)
}
// sha1HexString returns a hex string of the sha1 hash of value.
func sha1HexString(value string) string {
h := sha1.New()
_, err := h.Write([]byte(value))
contract.AssertNoError(err)
return hex.EncodeToString(h.Sum(nil))
}
// qnameFileName takes a qname and cleans it for use as a filename (by replacing tokens.QNameDelimter with a dash)
func qnameFileName(nm tokens.QName) string {
return strings.Replace(string(nm), tokens.QNameDelimiter, "-", -1)
}
// qnamePath just cleans a name and makes sure it's appropriate to use as a path.
func qnamePath(nm tokens.QName) string {
return stringNamePath(string(nm))
}
// stringNamePart cleans a string component of a name and makes sure it's appropriate to use as a path.
func stringNamePath(nm string) string {
return strings.Replace(nm, tokens.QNameDelimiter, string(os.PathSeparator), -1)
}