buildx/util/buildflags/cache.go

204 lines
4.4 KiB
Go
Raw Normal View History

package buildflags
import (
"context"
"encoding/json"
"maps"
"os"
"strings"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/pkg/errors"
"github.com/tonistiigi/go-csvvalue"
"github.com/zclconf/go-cty/cty"
jsoncty "github.com/zclconf/go-cty/cty/json"
)
type CacheOptionsEntry struct {
Type string `json:"type"`
Attrs map[string]string `json:"attrs,omitempty"`
}
func (e *CacheOptionsEntry) Equal(other *CacheOptionsEntry) bool {
if e.Type != other.Type {
return false
}
return maps.Equal(e.Attrs, other.Attrs)
}
func (e *CacheOptionsEntry) String() string {
// Special registry syntax.
if e.Type == "registry" && len(e.Attrs) == 1 {
if ref, ok := e.Attrs["ref"]; ok {
return ref
}
}
var b csvBuilder
if e.Type != "" {
b.Write("type", e.Type)
}
if len(e.Attrs) > 0 {
b.WriteAttributes(e.Attrs)
}
return b.String()
}
func (e *CacheOptionsEntry) ToPB() *controllerapi.CacheOptionsEntry {
return &controllerapi.CacheOptionsEntry{
Type: e.Type,
Attrs: maps.Clone(e.Attrs),
}
}
func (e *CacheOptionsEntry) MarshalJSON() ([]byte, error) {
m := maps.Clone(e.Attrs)
if m == nil {
m = map[string]string{}
}
m["type"] = e.Type
return json.Marshal(m)
}
func (e *CacheOptionsEntry) UnmarshalJSON(data []byte) error {
var m map[string]string
if err := json.Unmarshal(data, &m); err != nil {
return err
}
e.Type = m["type"]
delete(m, "type")
e.Attrs = m
return e.validate(data)
}
func (e *CacheOptionsEntry) IsActive() bool {
// Always active if not gha.
if e.Type != "gha" {
return true
}
return e.Attrs["token"] != "" && e.Attrs["url"] != ""
}
func (e *CacheOptionsEntry) UnmarshalText(text []byte) error {
in := string(text)
fields, err := csvvalue.Fields(in, nil)
if err != nil {
return err
}
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
e.Type = "registry"
e.Attrs = map[string]string{"ref": fields[0]}
return nil
}
e.Type = ""
e.Attrs = map[string]string{}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) != 2 {
return errors.Errorf("invalid value %s", field)
}
key := strings.ToLower(parts[0])
value := parts[1]
switch key {
case "type":
e.Type = value
default:
e.Attrs[key] = value
}
}
if e.Type == "" {
return errors.Errorf("type required form> %q", in)
}
addGithubToken(e)
addAwsCredentials(e)
return e.validate(text)
}
func (e *CacheOptionsEntry) validate(gv interface{}) error {
if e.Type == "" {
var text []byte
switch gv := gv.(type) {
case []byte:
text = gv
case string:
text = []byte(gv)
case cty.Value:
text, _ = jsoncty.Marshal(gv, gv.Type())
default:
text, _ = json.Marshal(gv)
}
return errors.Errorf("type required form> %q", string(text))
}
return nil
}
func ParseCacheEntry(in []string) ([]*controllerapi.CacheOptionsEntry, error) {
outs := make([]*controllerapi.CacheOptionsEntry, 0, len(in))
for _, in := range in {
var out CacheOptionsEntry
if err := out.UnmarshalText([]byte(in)); err != nil {
return nil, err
}
if !out.IsActive() {
// Skip inactive cache entries.
continue
}
outs = append(outs, out.ToPB())
}
return outs, nil
}
func addGithubToken(ci *CacheOptionsEntry) {
if ci.Type != "gha" {
return
}
if _, ok := ci.Attrs["token"]; !ok {
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
ci.Attrs["token"] = v
}
}
if _, ok := ci.Attrs["url"]; !ok {
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
ci.Attrs["url"] = v
}
}
}
func addAwsCredentials(ci *CacheOptionsEntry) {
if ci.Type != "s3" {
return
}
_, okAccessKeyID := ci.Attrs["access_key_id"]
_, okSecretAccessKey := ci.Attrs["secret_access_key"]
// If the user provides access_key_id, secret_access_key, do not override the session token.
if okAccessKeyID && okSecretAccessKey {
return
}
ctx := context.TODO()
awsConfig, err := awsconfig.LoadDefaultConfig(ctx)
if err != nil {
return
}
credentials, err := awsConfig.Credentials.Retrieve(ctx)
if err != nil {
return
}
if !okAccessKeyID && credentials.AccessKeyID != "" {
ci.Attrs["access_key_id"] = credentials.AccessKeyID
}
if !okSecretAccessKey && credentials.SecretAccessKey != "" {
ci.Attrs["secret_access_key"] = credentials.SecretAccessKey
}
if _, ok := ci.Attrs["session_token"]; !ok && credentials.SessionToken != "" {
ci.Attrs["session_token"] = credentials.SessionToken
}
}