Skip to content

Commit

Permalink
Implement text type option (#77)
Browse files Browse the repository at this point in the history
* implement text type option
* fix example
  • Loading branch information
s-dwinter authored May 21, 2024
1 parent 434deac commit d9e37da
Show file tree
Hide file tree
Showing 11 changed files with 694 additions and 26 deletions.
1 change: 1 addition & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ $ aws ssm put-parameter --name '/yashiro/example/secure' --value 'password' --ty

```sh
$ ysr template -c ./yashiro.yaml example.yaml.tmpl
# This is a example of yashiro template.
---
apiVersion: v1
kind: Secret
Expand Down
1 change: 1 addition & 0 deletions example/example.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This is a example of yashiro template.
---
apiVersion: v1
kind: Secret
Expand Down
18 changes: 17 additions & 1 deletion internal/cmd/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/dwango/yashiro/pkg/config"
"github.com/dwango/yashiro/pkg/engine"
Expand All @@ -36,9 +37,19 @@ const example = ` # specify single file.
ysr template ./example/*.tmpl
`

var textTypeValues = []string{
string(engine.TextTypePlane),
string(engine.TextTypeJSON),
string(engine.TextTypeJSONArray),
string(engine.TextTypeYAML),
string(engine.TextTypeYAMLArray),
string(engine.TextTypeYAMLDocs),
}

func newTemplateCommand() *cobra.Command {
var configFile string
var ignoreNotFound bool
var textType string

cmd := cobra.Command{
Use: "template <file>",
Expand All @@ -55,7 +66,9 @@ func newTemplateCommand() *cobra.Command {
return err
}

eng, err := engine.New(cfg, engine.IgnoreNotFound(ignoreNotFound))
eng, err := engine.New(cfg,
engine.IgnoreNotFound(ignoreNotFound), engine.TextType(engine.TextTypeOpt(textType)),
)
if err != nil {
return err
}
Expand All @@ -71,6 +84,9 @@ func newTemplateCommand() *cobra.Command {

f := cmd.Flags()
f.StringVarP(&configFile, "config", "c", config.DefaultConfigFilename, "specify config file.")
f.StringVar(&textType, "text-type", string(engine.TextTypePlane),
fmt.Sprintf("specify text type after rendering. available values: %s", strings.Join(textTypeValues, ", ")),
)
f.BoolVar(&ignoreNotFound, "ignore-not-found", false, "ignore values are not found in the external store.")

return &cmd
Expand Down
60 changes: 60 additions & 0 deletions pkg/engine/encoding/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2024 DWANGO Co., Ltd.
*
* 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
*
* https://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.
*/
package encoding

import (
"errors"
"fmt"
)

type TextType string

// Define text types
const (
TextTypeJSON TextType = "json"
TextTypeJSONArray TextType = "json-array"
TextTypeYAML TextType = "yaml"
TextTypeYAMLArray TextType = "yaml-array"
TextTypeYAMLDocs TextType = "yaml-docs"
)

// Define errors
var (
ErrUnsupportedTextType = errors.New("unsupported text type")
ErrFailedToEncodeAndDecode = errors.New("failed to encode and decode")
)

// EncodeAndDecoder is an interface that provides encoding and decoding functionality.
type EncodeAndDecoder interface {
EncodeAndDecode(b []byte) ([]byte, error)
}

func NewEncodeAndDecoder(t TextType) (EncodeAndDecoder, error) {
switch t {
case TextTypeJSON:
return &jsonEncodeAndDecoder{}, nil
case TextTypeJSONArray:
return &jsonEncodeAndDecoder{isArray: true}, nil
case TextTypeYAML:
return &yamlEncodeAndDecoder{docType: yamlDocTypeSingle}, nil
case TextTypeYAMLArray:
return &yamlEncodeAndDecoder{docType: yamlDocTypeArray}, nil
case TextTypeYAMLDocs:
return &yamlEncodeAndDecoder{docType: yamlDocTypeMulti}, nil
default:
return nil, fmt.Errorf("%w: %s", ErrUnsupportedTextType, t)
}
}
42 changes: 42 additions & 0 deletions pkg/engine/encoding/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2024 DWANGO Co., Ltd.
*
* 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
*
* https://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.
*/
package encoding

import (
"encoding/json"
"fmt"
)

type jsonEncodeAndDecoder struct {
isArray bool
}

func (ed jsonEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) {
if ed.isArray {
v := []any{}
if err := json.Unmarshal(b, &v); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
}
return json.Marshal(v)
}

v := map[string]any{}
if err := json.Unmarshal(b, &v); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
}

return json.Marshal(v)
}
93 changes: 93 additions & 0 deletions pkg/engine/encoding/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright 2024 DWANGO Co., Ltd.
*
* 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
*
* https://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.
*/
package encoding

import (
"reflect"
"testing"
)

func Test_jsonEncodeAndDecoder_EncodeAndDecode(t *testing.T) {
type fields struct {
isArray bool
}
type args struct {
str string
}
tests := []struct {
name string
fields fields
args args
wantStr string
wantErr bool
}{
{
name: "ok",
fields: fields{
isArray: false,
},
args: args{
str: `{"key":"value"}`,
},
wantStr: `{"key":"value"}`,
},
{
name: "ok: array",
fields: fields{
isArray: true,
},
args: args{
str: `[{"key":"value"},{"key2":"value2"}]`,
},
wantStr: `[{"key":"value"},{"key2":"value2"}]`,
},
{
name: "error: invalid json",
fields: fields{
isArray: false,
},
args: args{
str: "invalid json",
},
wantErr: true,
},
{
name: "error: invalid json array",
fields: fields{
isArray: true,
},
args: args{
str: "invalid json",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ed := jsonEncodeAndDecoder{
isArray: tt.fields.isArray,
}
got, err := ed.EncodeAndDecode([]byte(tt.args.str))
if (err != nil) != tt.wantErr {
t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(string(got), tt.wantStr) {
t.Errorf("jsonEncodeAndDecoder.EncodeAndDecode() = %v, want %v", string(got), tt.wantStr)
}
})
}
}
106 changes: 106 additions & 0 deletions pkg/engine/encoding/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Copyright 2024 DWANGO Co., Ltd.
*
* 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
*
* https://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.
*/
package encoding

import (
"bufio"
"bytes"
"fmt"

"sigs.k8s.io/yaml"
)

type yamlDocType int

const (
yamlDocTypeSingle yamlDocType = iota
yamlDocTypeArray
yamlDocTypeMulti
)

type yamlEncodeAndDecoder struct {
docType yamlDocType
}

const (
yamlSeparator = "\n---"
separator = "---\n"
)

func (ed yamlEncodeAndDecoder) EncodeAndDecode(b []byte) ([]byte, error) {
switch ed.docType {
case yamlDocTypeMulti:
buf := bytes.NewBuffer(make([]byte, 0, len(b)))

scn := bufio.NewScanner(bytes.NewReader(b))
scn.Split(splitYAMLDocument)
for scn.Scan() {
v := map[string]any{}
if err := yaml.Unmarshal(scn.Bytes(), &v); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
}
if len(v) == 0 {
continue
}
b, _ := yaml.Marshal(v)
buf.WriteString(separator)
buf.Write(b)
}
return buf.Bytes(), nil
case yamlDocTypeArray:
v := []any{}
if err := yaml.Unmarshal(b, &v); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
}
return yaml.Marshal(v)
default:
v := map[string]any{}
if err := yaml.Unmarshal(b, &v); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFailedToEncodeAndDecode, err)
}
return yaml.Marshal(v)
}
}

// splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents.
func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
sep := len([]byte(yamlSeparator))
if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 {
// We have a potential document terminator
i += sep
after := data[i:]
if len(after) == 0 {
// we can't read any more characters
if atEOF {
return len(data), data[:len(data)-sep], nil
}
return 0, nil, nil
}
if j := bytes.IndexByte(after, '\n'); j >= 0 {
return i + j + 1, data[0 : i-sep], nil
}
return 0, nil, nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
Loading

0 comments on commit d9e37da

Please sign in to comment.