mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00
Merge pull request #2848 from jsternberg/bake-composable-attributes-attests
bake: implement composable attributes for attestations
This commit is contained in:
commit
5c5bc510ac
47
bake/bake.go
47
bake/bake.go
@ -699,7 +699,7 @@ type Target struct {
|
|||||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||||
|
|
||||||
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
||||||
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
Attest buildflags.Attests `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||||
@ -707,8 +707,8 @@ type Target struct {
|
|||||||
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
Args map[string]*string `json:"args,omitempty" hcl:"args,optional" cty:"args"`
|
||||||
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
Labels map[string]*string `json:"labels,omitempty" hcl:"labels,optional" cty:"labels"`
|
||||||
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
Tags []string `json:"tags,omitempty" hcl:"tags,optional" cty:"tags"`
|
||||||
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
CacheFrom buildflags.CacheOptions `json:"cache-from,omitempty" hcl:"cache-from,optional" cty:"cache-from"`
|
||||||
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
CacheTo buildflags.CacheOptions `json:"cache-to,omitempty" hcl:"cache-to,optional" cty:"cache-to"`
|
||||||
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
Target *string `json:"target,omitempty" hcl:"target,optional" cty:"target"`
|
||||||
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
Secrets buildflags.Secrets `json:"secret,omitempty" hcl:"secret,optional" cty:"secret"`
|
||||||
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
SSH buildflags.SSHKeys `json:"ssh,omitempty" hcl:"ssh,optional" cty:"ssh"`
|
||||||
@ -718,8 +718,8 @@ type Target struct {
|
|||||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||||
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
|
NetworkMode *string `json:"network,omitempty" hcl:"network,optional" cty:"network"`
|
||||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||||
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
|
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional" cty:"shm-size"`
|
||||||
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
|
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
|
||||||
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
|
||||||
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
|
||||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
|
// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
|
||||||
@ -737,7 +737,7 @@ var (
|
|||||||
|
|
||||||
func (t *Target) normalize() {
|
func (t *Target) normalize() {
|
||||||
t.Annotations = removeDupesStr(t.Annotations)
|
t.Annotations = removeDupesStr(t.Annotations)
|
||||||
t.Attest = removeAttestDupes(t.Attest)
|
t.Attest = t.Attest.Normalize()
|
||||||
t.Tags = removeDupesStr(t.Tags)
|
t.Tags = removeDupesStr(t.Tags)
|
||||||
t.Secrets = t.Secrets.Normalize()
|
t.Secrets = t.Secrets.Normalize()
|
||||||
t.SSH = t.SSH.Normalize()
|
t.SSH = t.SSH.Normalize()
|
||||||
@ -811,8 +811,7 @@ func (t *Target) Merge(t2 *Target) {
|
|||||||
t.Annotations = append(t.Annotations, t2.Annotations...)
|
t.Annotations = append(t.Annotations, t2.Annotations...)
|
||||||
}
|
}
|
||||||
if t2.Attest != nil { // merge
|
if t2.Attest != nil { // merge
|
||||||
t.Attest = append(t.Attest, t2.Attest...)
|
t.Attest = t.Attest.Merge(t2.Attest)
|
||||||
t.Attest = removeAttestDupes(t.Attest)
|
|
||||||
}
|
}
|
||||||
if t2.Secrets != nil { // merge
|
if t2.Secrets != nil { // merge
|
||||||
t.Secrets = t.Secrets.Merge(t2.Secrets)
|
t.Secrets = t.Secrets.Merge(t2.Secrets)
|
||||||
@ -969,7 +968,11 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
|
|||||||
case "annotations":
|
case "annotations":
|
||||||
t.Annotations = append(t.Annotations, o.ArrValue...)
|
t.Annotations = append(t.Annotations, o.ArrValue...)
|
||||||
case "attest":
|
case "attest":
|
||||||
t.Attest = append(t.Attest, o.ArrValue...)
|
attest, err := parseArrValue[buildflags.Attest](o.ArrValue)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "invalid value for attest")
|
||||||
|
}
|
||||||
|
t.Attest = t.Attest.Merge(attest)
|
||||||
case "no-cache":
|
case "no-cache":
|
||||||
noCache, err := strconv.ParseBool(value)
|
noCache, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1383,11 +1386,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attests, err := buildflags.ParseAttests(t.Attest)
|
bo.Attests = controllerapi.CreateAttestations(t.Attest.ToPB())
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bo.Attests = controllerapi.CreateAttestations(attests)
|
|
||||||
|
|
||||||
bo.SourcePolicy, err = build.ReadSourcePolicy()
|
bo.SourcePolicy, err = build.ReadSourcePolicy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1430,26 +1429,6 @@ func removeDupesStr(s []string) []string {
|
|||||||
return s[:i]
|
return s[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAttestDupes(s []string) []string {
|
|
||||||
res := []string{}
|
|
||||||
m := map[string]int{}
|
|
||||||
for _, v := range s {
|
|
||||||
att, err := buildflags.ParseAttest(v)
|
|
||||||
if err != nil {
|
|
||||||
res = append(res, v)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if i, ok := m[att.Type]; ok {
|
|
||||||
res[i] = v
|
|
||||||
} else {
|
|
||||||
m[att.Type] = len(res)
|
|
||||||
res = append(res, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPushOverride(outputs []*buildflags.ExportEntry, push bool) []*buildflags.ExportEntry {
|
func setPushOverride(outputs []*buildflags.ExportEntry, push bool) []*buildflags.ExportEntry {
|
||||||
if !push {
|
if !push {
|
||||||
// Disable push for any relevant export types
|
// Disable push for any relevant export types
|
||||||
|
@ -1688,7 +1688,7 @@ func TestAttestDuplicates(t *testing.T) {
|
|||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil, &EntitlementConf{})
|
m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil, &EntitlementConf{})
|
||||||
require.Equal(t, []string{"type=sbom,foo=bar", "type=provenance,mode=max"}, m["default"].Attest)
|
require.Equal(t, []string{"type=provenance,mode=max", "type=sbom,foo=bar"}, stringify(m["default"].Attest))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts, err := TargetsToBuildOpt(m, &Input{})
|
opts, err := TargetsToBuildOpt(m, &Input{})
|
||||||
@ -1699,7 +1699,7 @@ func TestAttestDuplicates(t *testing.T) {
|
|||||||
}, opts["default"].Attests)
|
}, opts["default"].Attests)
|
||||||
|
|
||||||
m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil, &EntitlementConf{})
|
m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil, &EntitlementConf{})
|
||||||
require.Equal(t, []string{"type=sbom,disabled=true", "type=provenance,mode=max"}, m["default"].Attest)
|
require.Equal(t, []string{"type=provenance,mode=max", "type=sbom,disabled=true"}, stringify(m["default"].Attest))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
opts, err = TargetsToBuildOpt(m, &Input{})
|
opts, err = TargetsToBuildOpt(m, &Input{})
|
||||||
|
@ -604,6 +604,11 @@ func TestHCLAttrsCustomType(t *testing.T) {
|
|||||||
func TestHCLAttrsCapsuleType(t *testing.T) {
|
func TestHCLAttrsCapsuleType(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
target "app" {
|
target "app" {
|
||||||
|
attest = [
|
||||||
|
{ type = "provenance", mode = "max" },
|
||||||
|
"type=sbom,disabled=true",
|
||||||
|
]
|
||||||
|
|
||||||
cache-from = [
|
cache-from = [
|
||||||
{ type = "registry", ref = "user/app:cache" },
|
{ type = "registry", ref = "user/app:cache" },
|
||||||
"type=local,src=path/to/cache",
|
"type=local,src=path/to/cache",
|
||||||
@ -634,6 +639,7 @@ func TestHCLAttrsCapsuleType(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, []string{"type=provenance,mode=max", "type=sbom,disabled=true"}, stringify(c.Targets[0].Attest))
|
||||||
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
|
require.Equal(t, []string{"type=local,dest=../out", "type=oci,dest=../out.tar"}, stringify(c.Targets[0].Outputs))
|
||||||
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
|
require.Equal(t, []string{"type=local,src=path/to/cache", "user/app:cache"}, stringify(c.Targets[0].CacheFrom))
|
||||||
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
|
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[0].CacheTo))
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package buildflags
|
package buildflags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -10,6 +12,167 @@ import (
|
|||||||
"github.com/tonistiigi/go-csvvalue"
|
"github.com/tonistiigi/go-csvvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Attests []*Attest
|
||||||
|
|
||||||
|
func (a Attests) Merge(other Attests) Attests {
|
||||||
|
if other == nil {
|
||||||
|
a.Normalize()
|
||||||
|
return a
|
||||||
|
} else if a == nil {
|
||||||
|
other.Normalize()
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(a, other...).Normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Attests) Normalize() Attests {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return removeAttestDupes(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Attests) ToPB() []*controllerapi.Attest {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]*controllerapi.Attest, len(a))
|
||||||
|
for i, entry := range a {
|
||||||
|
entries[i] = entry.ToPB()
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attest struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
Attrs map[string]string `json:"attrs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) Equal(other *Attest) bool {
|
||||||
|
if a.Type != other.Type || a.Disabled != other.Disabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return maps.Equal(a.Attrs, other.Attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) String() string {
|
||||||
|
var b csvBuilder
|
||||||
|
if a.Type != "" {
|
||||||
|
b.Write("type", a.Type)
|
||||||
|
}
|
||||||
|
if a.Disabled {
|
||||||
|
b.Write("disabled", "true")
|
||||||
|
}
|
||||||
|
if len(a.Attrs) > 0 {
|
||||||
|
b.WriteAttributes(a.Attrs)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) ToPB() *controllerapi.Attest {
|
||||||
|
var b csvBuilder
|
||||||
|
if a.Type != "" {
|
||||||
|
b.Write("type", a.Type)
|
||||||
|
}
|
||||||
|
if a.Disabled {
|
||||||
|
b.Write("disabled", "true")
|
||||||
|
}
|
||||||
|
b.WriteAttributes(a.Attrs)
|
||||||
|
|
||||||
|
return &controllerapi.Attest{
|
||||||
|
Type: a.Type,
|
||||||
|
Disabled: a.Disabled,
|
||||||
|
Attrs: b.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) MarshalJSON() ([]byte, error) {
|
||||||
|
m := make(map[string]interface{}, len(a.Attrs)+2)
|
||||||
|
for k, v := range m {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
m["type"] = a.Type
|
||||||
|
if a.Disabled {
|
||||||
|
m["disabled"] = true
|
||||||
|
}
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) UnmarshalJSON(data []byte) error {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ, ok := m["type"]; ok {
|
||||||
|
a.Type, ok = typ.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("attest type must be a string")
|
||||||
|
}
|
||||||
|
delete(m, "type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if disabled, ok := m["disabled"]; ok {
|
||||||
|
a.Disabled, ok = disabled.(bool)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("attest disabled attribute must be a boolean")
|
||||||
|
}
|
||||||
|
delete(m, "disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := make(map[string]string, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("attest attribute %q must be a string", k)
|
||||||
|
}
|
||||||
|
attrs[k] = s
|
||||||
|
}
|
||||||
|
a.Attrs = attrs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) UnmarshalText(text []byte) error {
|
||||||
|
in := string(text)
|
||||||
|
fields, err := csvvalue.Fields(in, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Attrs = map[string]string{}
|
||||||
|
for _, field := range fields {
|
||||||
|
key, value, ok := strings.Cut(field, "=")
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("invalid value %s", field)
|
||||||
|
}
|
||||||
|
key = strings.TrimSpace(strings.ToLower(key))
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
a.Type = value
|
||||||
|
case "disabled":
|
||||||
|
disabled, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "invalid value %s", field)
|
||||||
|
}
|
||||||
|
a.Disabled = disabled
|
||||||
|
default:
|
||||||
|
a.Attrs[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attest) validate() error {
|
||||||
|
if a.Type == "" {
|
||||||
|
return errors.Errorf("attestation type not specified")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func CanonicalizeAttest(attestType string, in string) string {
|
func CanonicalizeAttest(attestType string, in string) string {
|
||||||
if in == "" {
|
if in == "" {
|
||||||
return ""
|
return ""
|
||||||
@ -21,21 +184,34 @@ func CanonicalizeAttest(attestType string, in string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseAttests(in []string) ([]*controllerapi.Attest, error) {
|
func ParseAttests(in []string) ([]*controllerapi.Attest, error) {
|
||||||
var out []*controllerapi.Attest
|
var outs []*Attest
|
||||||
found := map[string]struct{}{}
|
for _, s := range in {
|
||||||
for _, in := range in {
|
var out Attest
|
||||||
in := in
|
if err := out.UnmarshalText([]byte(s)); err != nil {
|
||||||
attest, err := ParseAttest(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outs = append(outs, &out)
|
||||||
|
}
|
||||||
|
return ConvertAttests(outs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertAttests converts Attestations for the controller API from
|
||||||
|
// the ones in this package.
|
||||||
|
//
|
||||||
|
// Attestations of the same type will cause an error. Some tools,
|
||||||
|
// like bake, remove the duplicates before calling this function.
|
||||||
|
func ConvertAttests(in []*Attest) ([]*controllerapi.Attest, error) {
|
||||||
|
out := make([]*controllerapi.Attest, 0, len(in))
|
||||||
|
|
||||||
|
// Check for dupplicate attestations while we convert them
|
||||||
|
// to the controller API.
|
||||||
|
found := map[string]struct{}{}
|
||||||
|
for _, attest := range in {
|
||||||
if _, ok := found[attest.Type]; ok {
|
if _, ok := found[attest.Type]; ok {
|
||||||
return nil, errors.Errorf("duplicate attestation field %s", attest.Type)
|
return nil, errors.Errorf("duplicate attestation field %s", attest.Type)
|
||||||
}
|
}
|
||||||
found[attest.Type] = struct{}{}
|
found[attest.Type] = struct{}{}
|
||||||
|
out = append(out, attest.ToPB())
|
||||||
out = append(out, attest)
|
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@ -77,3 +253,17 @@ func ParseAttest(in string) (*controllerapi.Attest, error) {
|
|||||||
|
|
||||||
return &attest, nil
|
return &attest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeAttestDupes(s []*Attest) []*Attest {
|
||||||
|
res := []*Attest{}
|
||||||
|
m := map[string]int{}
|
||||||
|
for _, att := range s {
|
||||||
|
if i, ok := m[att.Type]; ok {
|
||||||
|
res[i] = att
|
||||||
|
} else {
|
||||||
|
m[att.Type] = len(res)
|
||||||
|
res = append(res, att)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
97
util/buildflags/attests_cty.go
Normal file
97
util/buildflags/attests_cty.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package buildflags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var attestType = sync.OnceValue(func() cty.Type {
|
||||||
|
return cty.Map(cty.String)
|
||||||
|
})
|
||||||
|
|
||||||
|
func (e *Attests) FromCtyValue(in cty.Value, p cty.Path) error {
|
||||||
|
got := in.Type()
|
||||||
|
if got.IsTupleType() || got.IsListType() {
|
||||||
|
return e.fromCtyValue(in, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := cty.List(attestType())
|
||||||
|
return p.NewErrorf("%s", convert.MismatchMessage(got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Attests) fromCtyValue(in cty.Value, p cty.Path) error {
|
||||||
|
*e = make([]*Attest, 0, in.LengthInt())
|
||||||
|
for elem := in.ElementIterator(); elem.Next(); {
|
||||||
|
_, value := elem.Element()
|
||||||
|
|
||||||
|
entry := &Attest{}
|
||||||
|
if err := entry.FromCtyValue(value, p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = append(*e, entry)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Attests) ToCtyValue() cty.Value {
|
||||||
|
if len(e) == 0 {
|
||||||
|
return cty.ListValEmpty(attestType())
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make([]cty.Value, len(e))
|
||||||
|
for i, entry := range e {
|
||||||
|
vals[i] = entry.ToCtyValue()
|
||||||
|
}
|
||||||
|
return cty.ListVal(vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Attest) FromCtyValue(in cty.Value, p cty.Path) error {
|
||||||
|
if in.Type() == cty.String {
|
||||||
|
if err := e.UnmarshalText([]byte(in.AsString())); err != nil {
|
||||||
|
return p.NewError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conv, err := convert.Convert(in, cty.Map(cty.String))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Attrs = map[string]string{}
|
||||||
|
for it := conv.ElementIterator(); it.Next(); {
|
||||||
|
k, v := it.Element()
|
||||||
|
switch key := k.AsString(); key {
|
||||||
|
case "type":
|
||||||
|
e.Type = v.AsString()
|
||||||
|
case "disabled":
|
||||||
|
b, err := strconv.ParseBool(v.AsString())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.Disabled = b
|
||||||
|
default:
|
||||||
|
e.Attrs[key] = v.AsString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Attest) ToCtyValue() cty.Value {
|
||||||
|
if e == nil {
|
||||||
|
return cty.NullVal(cty.Map(cty.String))
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make(map[string]cty.Value, len(e.Attrs)+2)
|
||||||
|
for k, v := range e.Attrs {
|
||||||
|
vals[k] = cty.StringVal(v)
|
||||||
|
}
|
||||||
|
vals["type"] = cty.StringVal(e.Type)
|
||||||
|
if e.Disabled {
|
||||||
|
vals["disabled"] = cty.StringVal("true")
|
||||||
|
}
|
||||||
|
return cty.MapVal(vals)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user