2022-05-23 19:13:21 +00:00
|
|
|
// Copyright 2016-2022, Pulumi Corporation.
|
2018-05-22 19:43:36 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2016-11-16 02:26:21 +00:00
|
|
|
|
2016-11-16 17:29:44 +00:00
|
|
|
package encoding
|
2016-11-16 02:26:21 +00:00
|
|
|
|
|
|
|
import (
|
2022-05-23 19:13:21 +00:00
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
2016-11-16 02:26:21 +00:00
|
|
|
"encoding/json"
|
2021-10-26 15:55:52 +00:00
|
|
|
"fmt"
|
2023-01-06 22:39:16 +00:00
|
|
|
"io"
|
2017-02-22 02:31:43 +00:00
|
|
|
"path/filepath"
|
2016-11-16 02:26:21 +00:00
|
|
|
|
2022-11-23 20:22:57 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/yamlutil"
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
2016-11-16 02:26:21 +00:00
|
|
|
)
|
|
|
|
|
2022-05-23 19:13:21 +00:00
|
|
|
var (
|
|
|
|
JSONExt = ".json"
|
|
|
|
YAMLExt = ".yaml"
|
|
|
|
GZIPExt = ".gz"
|
|
|
|
)
|
2016-12-02 23:00:44 +00:00
|
|
|
|
Support Workspaces
This change adds support for Workspaces, a convenient way of sharing settings
among many Stacks, like default cluster targets, configuration settings, and the
like, which are not meant to be distributed as part of the Stack itself.
The following things are included in this checkin:
* At workspace initialization time, detect and parse the .mu/workspace.yaml
file. This is pretty rudimentary right now and contains just the default
cluster targets. The results are stored in a new ast.Workspace type.
* Rename "target" to "cluster". This impacts many things, including ast.Target
being changed to ast.Cluster, and all related fields, the command line --target
being changed to --cluster, various internal helper functions, and so on. This
helps to reinforce the desired mental model.
* Eliminate the ast.Metadata type. Instead, the metadata moves directly onto
the Stack. This reflects the decision to make Stacks "the thing" that is
distributed, versioned, and is the granularity of dependency.
* During cluster targeting, add the workspace settings into the probing logic.
We still search in the same order: CLI > Stack > Workspace.
2016-11-22 18:41:07 +00:00
|
|
|
// Exts contains a list of all the valid marshalable extension types.
|
|
|
|
var Exts = []string{
|
2016-12-02 23:00:44 +00:00
|
|
|
JSONExt,
|
|
|
|
YAMLExt,
|
Support Workspaces
This change adds support for Workspaces, a convenient way of sharing settings
among many Stacks, like default cluster targets, configuration settings, and the
like, which are not meant to be distributed as part of the Stack itself.
The following things are included in this checkin:
* At workspace initialization time, detect and parse the .mu/workspace.yaml
file. This is pretty rudimentary right now and contains just the default
cluster targets. The results are stored in a new ast.Workspace type.
* Rename "target" to "cluster". This impacts many things, including ast.Target
being changed to ast.Cluster, and all related fields, the command line --target
being changed to --cluster, various internal helper functions, and so on. This
helps to reinforce the desired mental model.
* Eliminate the ast.Metadata type. Instead, the metadata moves directly onto
the Stack. This reflects the decision to make Stacks "the thing" that is
distributed, versioned, and is the granularity of dependency.
* During cluster targeting, add the workspace settings into the probing logic.
We still search in the same order: CLI > Stack > Workspace.
2016-11-22 18:41:07 +00:00
|
|
|
// Although ".yml" is not a sanctioned YAML extension, it is used quite broadly; so we will support it.
|
|
|
|
".yml",
|
|
|
|
}
|
|
|
|
|
2017-02-22 02:31:43 +00:00
|
|
|
// Detect auto-detects a marshaler for the given path.
|
|
|
|
func Detect(path string) (Marshaler, string) {
|
|
|
|
ext := filepath.Ext(path)
|
|
|
|
if ext == "" {
|
Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment. It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!
In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."
By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)
The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag. All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.
As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-22 02:23:32 +00:00
|
|
|
ext = DefaultExt() // default to the first (preferred) marshaler.
|
2017-02-22 02:31:43 +00:00
|
|
|
}
|
|
|
|
return Marshalers[ext], ext
|
|
|
|
}
|
|
|
|
|
2016-11-16 16:19:26 +00:00
|
|
|
// Marshalers is a map of extension to a Marshaler object for that extension.
|
|
|
|
var Marshalers map[string]Marshaler
|
|
|
|
|
Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment. It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!
In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."
By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)
The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag. All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.
As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-22 02:23:32 +00:00
|
|
|
// Default returns the default marshaler object.
|
|
|
|
func Default() Marshaler {
|
|
|
|
return Marshalers[DefaultExt()]
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultExt returns the default extension to use.
|
|
|
|
func DefaultExt() string {
|
|
|
|
return Exts[0]
|
|
|
|
}
|
|
|
|
|
2016-11-16 02:26:21 +00:00
|
|
|
// Marshaler is a type that knows how to marshal and unmarshal data in one format.
|
|
|
|
type Marshaler interface {
|
|
|
|
Marshal(v interface{}) ([]byte, error)
|
|
|
|
Unmarshal(data []byte, v interface{}) error
|
|
|
|
}
|
|
|
|
|
2022-08-19 12:27:34 +00:00
|
|
|
// JSON is a Marshaler that marshals and unmarshals JSON with indented printing.
|
2016-11-25 20:58:29 +00:00
|
|
|
var JSON Marshaler = &jsonMarshaler{}
|
|
|
|
|
2023-03-03 16:36:39 +00:00
|
|
|
type jsonMarshaler struct{}
|
2016-11-16 02:26:21 +00:00
|
|
|
|
|
|
|
func (m *jsonMarshaler) Marshal(v interface{}) ([]byte, error) {
|
2022-08-19 12:27:34 +00:00
|
|
|
var buf bytes.Buffer
|
|
|
|
enc := json.NewEncoder(&buf)
|
|
|
|
enc.SetEscapeHTML(false)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
err := enc.Encode(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
2016-11-16 02:26:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *jsonMarshaler) Unmarshal(data []byte, v interface{}) error {
|
2017-06-06 01:11:51 +00:00
|
|
|
// IDEA: use a "strict" marshaler, so that we can warn on unrecognized keys (avoiding silly mistakes). We should
|
2016-11-19 02:12:26 +00:00
|
|
|
// set aside an officially sanctioned area in the metadata for extensibility by 3rd parties.
|
2016-11-16 02:26:21 +00:00
|
|
|
return json.Unmarshal(data, v)
|
|
|
|
}
|
|
|
|
|
2016-11-25 20:58:29 +00:00
|
|
|
var YAML Marshaler = &yamlMarshaler{}
|
|
|
|
|
2023-03-03 16:36:39 +00:00
|
|
|
type yamlMarshaler struct{}
|
2016-11-16 02:26:21 +00:00
|
|
|
|
|
|
|
func (m *yamlMarshaler) Marshal(v interface{}) ([]byte, error) {
|
2022-11-23 20:22:57 +00:00
|
|
|
if r, ok := v.(yamlutil.HasRawValue); ok {
|
|
|
|
if len(r.RawValue()) > 0 {
|
|
|
|
// Attempt a comment preserving edit:
|
|
|
|
return yamlutil.Edit(r.RawValue(), v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-10 04:49:28 +00:00
|
|
|
return yamlutil.YamlEncode(v)
|
2016-11-16 02:26:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *yamlMarshaler) Unmarshal(data []byte, v interface{}) error {
|
2017-06-06 01:11:51 +00:00
|
|
|
// IDEA: use a "strict" marshaler, so that we can warn on unrecognized keys (avoiding silly mistakes). We should
|
2016-11-19 02:12:26 +00:00
|
|
|
// set aside an officially sanctioned area in the metadata for extensibility by 3rd parties.
|
2017-10-17 18:38:03 +00:00
|
|
|
|
2021-10-26 15:55:52 +00:00
|
|
|
err := yaml.Unmarshal(data, v)
|
|
|
|
if err != nil {
|
|
|
|
// Return type errors directly
|
|
|
|
if _, ok := err.(*yaml.TypeError); ok {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Other errors will be parse errors due to invalid syntax
|
|
|
|
return fmt.Errorf("invalid YAML file: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
2016-11-16 02:26:21 +00:00
|
|
|
}
|
2022-05-23 19:13:21 +00:00
|
|
|
|
|
|
|
type gzipMarshaller struct {
|
|
|
|
inner Marshaler
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *gzipMarshaller) Marshal(v interface{}) ([]byte, error) {
|
|
|
|
b, err := m.inner.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
writer := gzip.NewWriter(&buf)
|
|
|
|
defer writer.Close()
|
|
|
|
_, err = writer.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := writer.Close(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *gzipMarshaller) Unmarshal(data []byte, v interface{}) error {
|
|
|
|
buf := bytes.NewBuffer(data)
|
|
|
|
reader, err := gzip.NewReader(buf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer reader.Close()
|
2023-01-06 22:39:16 +00:00
|
|
|
inflated, err := io.ReadAll(reader)
|
2022-05-23 19:13:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := reader.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return m.inner.Unmarshal(inflated, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsCompressed returns if data is zip compressed.
|
|
|
|
func IsCompressed(buf []byte) bool {
|
|
|
|
// Taken from compress/gzip/gunzip.go
|
|
|
|
return len(buf) >= 3 && buf[0] == 31 && buf[1] == 139 && buf[2] == 8
|
|
|
|
}
|
|
|
|
|
|
|
|
func Gzip(m Marshaler) Marshaler {
|
|
|
|
_, alreadyGZIP := m.(*gzipMarshaller)
|
|
|
|
if alreadyGZIP {
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
return &gzipMarshaller{m}
|
|
|
|
}
|