Files
buildx/util/buildflags/attests.go
T
Jonathan A. Sternberg 1d7cda1232 controller: remove remaining parts of the controller
Removes all references to the controller and moves the remaining
sections of code to other packages.

Processes has been moved to monitor where it is used and the data
structs have been removed so buildflags is used directly. The controller
build function has been moved to the commands package.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
2025-06-05 11:57:03 -05:00

208 lines
3.9 KiB
Go

package buildflags
import (
"encoding/json"
"fmt"
"maps"
"strconv"
"strings"
"github.com/pkg/errors"
"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) ToMap() map[string]*string {
result := map[string]*string{}
for _, attest := range a {
// ignore duplicates
if _, ok := result[attest.Type]; ok {
continue
}
if attest.Disabled {
result[attest.Type] = nil
continue
}
var b csvBuilder
if attest.Type != "" {
b.Write("type", attest.Type)
}
if attest.Disabled {
b.Write("disabled", "true")
}
b.WriteAttributes(attest.Attrs)
s := b.String()
result[attest.Type] = &s
}
return result
}
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) MarshalJSON() ([]byte, error) {
m := make(map[string]any, len(a.Attrs)+2)
for k, v := range a.Attrs {
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]any
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)
}
switch strings.TrimSpace(strings.ToLower(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 {
if in == "" {
return ""
}
if b, err := strconv.ParseBool(in); err == nil {
return fmt.Sprintf("type=%s,disabled=%t", attestType, !b)
}
return fmt.Sprintf("type=%s,%s", attestType, in)
}
func ParseAttests(in []string) (Attests, error) {
var outs []*Attest
for _, s := range in {
var out Attest
if err := out.UnmarshalText([]byte(s)); err != nil {
return nil, err
}
outs = append(outs, &out)
}
return outs, 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
}