-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add YAML model structs for manifest YAML files
Part of #191. These structs will represent the contents of the new "manifest" file, which contains all the information we'll need in the future to cleanly upgrade a template output.
- Loading branch information
Showing
12 changed files
with
353 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Copyright 2023 The Authors (see AUTHORS file) | ||
// | ||
// 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. | ||
|
||
package render | ||
|
||
import ( | ||
"regexp" | ||
|
||
"golang.org/x/mod/sumdb/dirhash" | ||
) | ||
|
||
var ( | ||
// A golang dirhash looks like "h1:0a1b2c3d...". That is, the letter 'h', followed by one or more digits, | ||
// followed by a colon, followed by a bunch of hex. As of this writing, SHA256 is the only supported | ||
// hash function, but we attempt to generalize, so we don't require exactly 64 hex digits. Maybe a future | ||
// hash algorithm will have a smaller output. | ||
validDirhash = regexp.MustCompile(`h([0-9]+):[0-9a-z]{32,}`) | ||
|
||
knownHashAlgos = map[string]dirhash.Hash{ | ||
"1": dirhash.Hash1, | ||
} | ||
) | ||
|
||
// // DirhashFormatted returns error if the input doesn't look like a valid Go dirhash, | ||
// // which looks like "h1:0a1b2c3d....". | ||
// func DirhashFormatted(s String, fieldName string) error { | ||
// validDirhash. | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2023 The Authors (see AUTHORS file) | ||
// | ||
// 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. | ||
|
||
package manifest | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
|
||
"github.com/abcxyz/abc/templates/model" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
// TODO "... do not modify" comment in the encoded YAML | ||
// TODO validate before marshaling just to be safe | ||
|
||
// Decode unmarshals the YAML Spec from r. This function exists so we can | ||
// validate the Spec model before providing it to the caller; we don't want the | ||
// caller to forget, and thereby introduce bugs. | ||
// | ||
// If the Spec parses successfully but then fails validation, the spec will be | ||
// returned along with the validation error. | ||
func Decode(r io.Reader) (*Manifest, error) { | ||
out := &Manifest{} | ||
if err := model.DecodeAndValidate(r, out); err != nil { | ||
return nil, err | ||
} | ||
return out, nil | ||
} | ||
|
||
func Encode(w io.Writer, m *Manifest) error { | ||
enc := yaml.NewEncoder(w) | ||
enc.Encode() | ||
} | ||
|
||
// TODO differences vs design doc: | ||
// - kv pair rather than map for inputs | ||
// - temlpate_dirhash name | ||
// - template_location name | ||
// - file_hashes | ||
|
||
// Manifest TODO | ||
type Manifest struct { | ||
Pos model.ConfigPos `yaml:"-"` | ||
|
||
APIVersion model.String `yaml:"api_version"` | ||
TemplateLocation model.String `yaml:"template_location"` | ||
TemplateDirhash model.String `yaml:"template_dirhash"` | ||
Inputs []*Input `yaml:"inputs"` | ||
OutputHashes []*FileHash `yaml:"output_hashes"` | ||
} | ||
|
||
// UnmarshalYAML implements yaml.Unmarshaler. | ||
func (m *Manifest) UnmarshalYAML(n *yaml.Node) error { | ||
return model.UnmarshalPlain(n, m, &m.Pos) | ||
} | ||
|
||
// Validate() implements model.Validator. | ||
func (m *Manifest) Validate() error { | ||
// We don't validate the TemplateDirhash here beyond just verifying that | ||
// it's present; it's validated more later when it's used. | ||
// | ||
// Inputs and OutputHashes can legally be empty. | ||
return errors.Join( | ||
model.IsKnownSchemaVersion(&m.Pos, m.APIVersion, "api_version"), | ||
model.NotZeroModel(&m.Pos, m.TemplateLocation, "template_location"), | ||
model.NotZeroModel(&m.Pos, m.TemplateDirhash, "template_dirhash"), | ||
model.ValidateEach(m.Inputs), | ||
model.ValidateEach(m.OutputHashes), | ||
) | ||
} | ||
|
||
// Input TODO | ||
type Input struct { | ||
Pos model.ConfigPos | ||
|
||
Name model.String `yaml:"name"` | ||
Value model.String `yaml:"value"` | ||
} | ||
|
||
// UnmarshalYAML implements yaml.Unmarshaler. | ||
func (i *Input) UnmarshalYAML(n *yaml.Node) error { | ||
return model.UnmarshalPlain(n, i, &i.Pos) | ||
} | ||
|
||
// Validate() implements model.Validator. | ||
func (i *Input) Validate() error { | ||
return errors.Join( | ||
model.NotZeroModel(&i.Pos, i.Name, "name"), | ||
model.NotZeroModel(&i.Pos, i.Value, "value"), | ||
) | ||
} | ||
|
||
// FileHash TODO | ||
type FileHash struct { | ||
Pos model.ConfigPos | ||
|
||
File model.String `yaml:"file"` | ||
Hash model.String `yaml:"hash"` | ||
} | ||
|
||
// UnmarshalYAML implements yaml.Unmarshaler. | ||
func (f *FileHash) UnmarshalYAML(n *yaml.Node) error { | ||
return model.UnmarshalPlain(n, f, &f.Pos) | ||
} | ||
|
||
// Validate() implements model.Validator. | ||
func (f *FileHash) Validate() error { | ||
return errors.Join( | ||
model.NotZeroModel(&f.Pos, f.File, "file"), | ||
model.NotZeroModel(&f.Pos, f.Hash, "hash"), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2023 The Authors (see AUTHORS file) | ||
// | ||
// 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. | ||
|
||
package manifest | ||
|
||
import ( | ||
"io" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/abcxyz/abc/templates/model" | ||
"github.com/abcxyz/pkg/testutil" | ||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
func TestDecode(t *testing.T) { | ||
t.Parallel() | ||
|
||
cases := []struct { | ||
name string | ||
in string | ||
want *Manifest | ||
wantUnmarshalErr string | ||
wantValidateErr string | ||
}{ | ||
{ | ||
name: "simple-success", | ||
in: ` | ||
api_version: 'cli.abcxyz.dev/v1alpha1' | ||
template_location: 'github.com/abcxyz/abc.git//t/rest_server' | ||
template_dirhash: 'h1:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03' | ||
inputs: | ||
- name: 'my_input_1' | ||
value: 'my_value_1' | ||
- name: 'my_input_2' | ||
value: 'my_value_2' | ||
output_hashes: | ||
- file: 'a/b/c.txt' | ||
hash: 'h1:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c' | ||
- file: 'd/e/f.txt' | ||
hash: 'h1:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730' | ||
`, | ||
want: &Manifest{ | ||
APIVersion: model.String{Val: "cli.abcxyz.dev/v1alpha1"}, | ||
TemplateLocation: model.String{Val: "github.com/abcxyz/abc.git//t/rest_server"}, | ||
TemplateDirhash: model.String{Val: "h1:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"}, | ||
Inputs: []*Input{ | ||
{ | ||
Name: model.String{Val: "my_input_1"}, | ||
Value: model.String{Val: "my_value_1"}, | ||
}, | ||
{ | ||
Name: model.String{Val: "my_input_2"}, | ||
Value: model.String{Val: "my_value_2"}, | ||
}, | ||
}, | ||
OutputHashes: []*FileHash{ | ||
{ | ||
File: model.String{Val: "a/b/c.txt"}, | ||
Hash: model.String{Val: "h1:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"}, | ||
}, | ||
{ | ||
File: model.String{Val: "d/e/f.txt"}, | ||
Hash: model.String{Val: "h1:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
tc := tc | ||
|
||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
got := &Manifest{} | ||
dec := newDecoder(strings.NewReader(tc.in)) | ||
err := dec.Decode(got) | ||
|
||
if diff := testutil.DiffErrString(err, tc.wantUnmarshalErr); err != nil { | ||
t.Fatal(diff) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
|
||
err = got.Validate() | ||
if diff := testutil.DiffErrString(err, tc.wantValidateErr); diff != "" { | ||
t.Fatal(diff) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
|
||
opt := cmpopts.IgnoreTypes(&model.ConfigPos{}, model.ConfigPos{}) // don't force test authors to assert the line and column numbers | ||
if diff := cmp.Diff(got, tc.want, opt); diff != "" { | ||
t.Errorf("unmarshaling didn't yield expected struct. Diff (-got +want): %s", diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// newDecoder returns a yaml Decoder with the desired options. | ||
func newDecoder(r io.Reader) *yaml.Decoder { | ||
dec := yaml.NewDecoder(r) | ||
dec.KnownFields(true) // Fail if any unexpected fields are seen. Often doesn't work: https://github.com/go-yaml/yaml/issues/460 | ||
return dec | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.