In the Pulumi Cloud, there is no guarantee that two stacks will share the same encryption key. This means that encrypted config can not be shared across stacks (in the Pulumi.yaml) file. To mimic this behavior in the local experience, we now use a unique key per stack. When upgrading an existing project, for any stack with existing secrets, we copy the existing key into this stack. Future stacks will get thier own encryption key. This strikes a balance between expediency of implementation, the end user UX and not having to make a breaking change. As part of this change, I have introduced a CHANGELOG.md file in the root of the repository and added a small note about the change to it. Fixes #769
150 lines
4.7 KiB
Go
150 lines
4.7 KiB
Go
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
// Package pack contains the core LumiPack metadata types.
|
|
package pack
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/pulumi/pulumi/pkg/diag"
|
|
"github.com/pulumi/pulumi/pkg/encoding"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
)
|
|
|
|
// Package is a top-level package definition.
|
|
//
|
|
// We explicitly add yaml tags (instead of using the default behavior from https://github.com/ghodss/yaml which works
|
|
// in terms of the JSON tags) so we can directly marshall and unmarshall this struct using go-yaml an have the fields
|
|
// in the serialized object match the order they are defined in this struct.
|
|
//
|
|
// TODO[pulumi/pulumi#423]: use DOM based marshalling so we can roundtrip the seralized structure perfectly.
|
|
// nolint: lll
|
|
type Package struct {
|
|
Name tokens.PackageName `json:"name" yaml:"name"` // a required fully qualified name.
|
|
Runtime string `json:"runtime" yaml:"runtime"` // a required runtime that executes code.
|
|
Main string `json:"main,omitempty" yaml:"main,omitempty"` // an optional override for the main program location.
|
|
|
|
Description *string `json:"description,omitempty" yaml:"description,omitempty"` // an optional informational description.
|
|
Author *string `json:"author,omitempty" yaml:"author,omitempty"` // an optional author.
|
|
Website *string `json:"website,omitempty" yaml:"website,omitempty"` // an optional website for additional info.
|
|
License *string `json:"license,omitempty" yaml:"license,omitempty"` // an optional license governing this package's usage.
|
|
|
|
Analyzers *Analyzers `json:"analyzers,omitempty" yaml:"analyzers,omitempty"` // any analyzers enabled for this project.
|
|
|
|
EncryptionSalt string `json:"encryptionsalt,omitempty" yaml:"encryptionsalt,omitempty"` // base64 encoded encryption salt.
|
|
NoDefaultIgnores *bool `json:"nodefaultignores,omitempty" yaml:"nodefaultignores,omitempty"` // true if we should only respect .pulumiignore when archiving
|
|
|
|
Config map[tokens.ModuleMember]config.Value `json:"config,omitempty" yaml:"config,omitempty"` // optional config (applies to all stacks).
|
|
|
|
Stacks map[tokens.QName]StackInfo `json:"stacks,omitempty" yaml:"stacks,omitempty"` // optional stack specific information.
|
|
|
|
Doc *diag.Document `json:"-" yaml:"-"` // the document from which this package came.
|
|
}
|
|
|
|
// StackInfo holds stack specific information about a package
|
|
// nolint: lll
|
|
type StackInfo struct {
|
|
EncryptionSalt string `json:"encryptionsalt,omitempty" yaml:"encryptionsalt,omitempty"` // base64 encoded encryption salt.
|
|
Config config.Map `json:"config,omitempty" yaml:"config,omitempty"` // optional config.
|
|
}
|
|
|
|
// IsEmpty returns True if this object contains no information (i.e. all members have their zero values)
|
|
func (s *StackInfo) IsEmpty() bool {
|
|
return len(s.Config) == 0 && s.EncryptionSalt == ""
|
|
}
|
|
|
|
var _ diag.Diagable = (*Package)(nil)
|
|
|
|
func (pkg *Package) Where() (*diag.Document, *diag.Location) {
|
|
return pkg.Doc, nil
|
|
}
|
|
|
|
func (pkg *Package) Validate() error {
|
|
if pkg.Name == "" {
|
|
return errors.New("package is missing a 'name' attribute")
|
|
}
|
|
if pkg.Runtime == "" {
|
|
return errors.New("package is missing a 'runtime' attribute")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pkg *Package) UseDefaultIgnores() bool {
|
|
if pkg.NoDefaultIgnores == nil {
|
|
return true
|
|
}
|
|
|
|
return !(*pkg.NoDefaultIgnores)
|
|
}
|
|
|
|
// Analyzers is a list of analyzers to run on this project.
|
|
type Analyzers []tokens.QName
|
|
|
|
// Load reads a package definition from a file
|
|
func Load(path string) (*Package, error) {
|
|
contract.Require(path != "", "pkg")
|
|
|
|
m, err := marshallerForPath(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pkg Package
|
|
err = m.Unmarshal(b, &pkg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = pkg.Validate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pkg, err
|
|
}
|
|
|
|
// Save writes a package defitiniton to a file
|
|
func Save(path string, pkg *Package) error {
|
|
contract.Require(path != "", "pkg")
|
|
contract.Require(pkg != nil, "pkg")
|
|
contract.Requiref(pkg.Validate() == nil, "pkg", "Validate()")
|
|
|
|
for name, info := range pkg.Stacks {
|
|
if info.IsEmpty() {
|
|
delete(pkg.Stacks, name)
|
|
}
|
|
}
|
|
|
|
m, err := marshallerForPath(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := m.Marshal(pkg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ioutil.WriteFile(path, b, 0644)
|
|
}
|
|
|
|
func marshallerForPath(path string) (encoding.Marshaler, error) {
|
|
ext := filepath.Ext(path)
|
|
m, has := encoding.Marshalers[ext]
|
|
if !has {
|
|
return nil, errors.Errorf("no marshaler found for file format '%v'", ext)
|
|
}
|
|
|
|
return m, nil
|
|
}
|