buildx/util/buildflags/secrets.go
CrazyMax fe17ebda89
correctly remove duplicated secrets and ssh keys
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-02-28 15:24:16 +01:00

172 lines
3.2 KiB
Go

package buildflags
import (
"encoding/json"
"strings"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
)
type Secrets []*Secret
func (s Secrets) Merge(other Secrets) Secrets {
if other == nil {
s.Normalize()
return s
} else if s == nil {
other.Normalize()
return other
}
return append(s, other...).Normalize()
}
func (s Secrets) Normalize() Secrets {
if len(s) == 0 {
return nil
}
return removeSecretDupes(s)
}
func (s Secrets) ToPB() []*controllerapi.Secret {
if len(s) == 0 {
return nil
}
entries := make([]*controllerapi.Secret, len(s))
for i, entry := range s {
entries[i] = entry.ToPB()
}
return entries
}
type Secret struct {
ID string `json:"id,omitempty"`
FilePath string `json:"src,omitempty"`
Env string `json:"env,omitempty"`
}
func (s *Secret) Equal(other *Secret) bool {
return s.ID == other.ID && s.FilePath == other.FilePath && s.Env == other.Env
}
func (s *Secret) String() string {
var b csvBuilder
if s.ID != "" {
b.Write("id", s.ID)
}
if s.FilePath != "" {
b.Write("src", s.FilePath)
}
if s.Env != "" {
b.Write("env", s.Env)
}
return b.String()
}
func (s *Secret) ToPB() *controllerapi.Secret {
return &controllerapi.Secret{
ID: s.ID,
FilePath: s.FilePath,
Env: s.Env,
}
}
func (s *Secret) UnmarshalJSON(data []byte) error {
var v struct {
ID string `json:"id,omitempty"`
FilePath string `json:"src,omitempty"`
Env string `json:"env,omitempty"`
}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
s.ID = v.ID
s.FilePath = v.FilePath
s.Env = v.Env
return nil
}
func (s *Secret) UnmarshalText(text []byte) error {
value := string(text)
fields, err := csvvalue.Fields(value, nil)
if err != nil {
return errors.Wrap(err, "failed to parse csv secret")
}
*s = Secret{}
var typ string
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) != 2 {
return errors.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "type":
if value != "file" && value != "env" {
return errors.Errorf("unsupported secret type %q", value)
}
typ = value
case "id":
s.ID = value
case "source", "src":
s.FilePath = value
case "env":
s.Env = value
default:
return errors.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
if typ == "env" && s.Env == "" {
s.Env = s.FilePath
s.FilePath = ""
}
return nil
}
func ParseSecretSpecs(sl []string) ([]*controllerapi.Secret, error) {
fs := make([]*controllerapi.Secret, 0, len(sl))
for _, v := range sl {
if v == "" {
continue
}
s, err := parseSecret(v)
if err != nil {
return nil, err
}
fs = append(fs, s)
}
return fs, nil
}
func parseSecret(value string) (*controllerapi.Secret, error) {
var s Secret
if err := s.UnmarshalText([]byte(value)); err != nil {
return nil, err
}
return s.ToPB(), nil
}
func removeSecretDupes(s []*Secret) []*Secret {
var res []*Secret
m := map[string]int{}
for _, sec := range s {
if i, ok := m[sec.ID]; ok {
res[i] = sec
} else {
m[sec.ID] = len(res)
res = append(res, sec)
}
}
return res
}