mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00

Broken in 11c84973ef104e48eb88a41b5b23d6a559efe868. The section to skip an empty input was accidentally removed when some code was refactored to fix a separate issue. This skips empty cache entries which allows disabling the `cache-from` and `cache-to` entries from the command line overrides. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
279 lines
6.2 KiB
Go
279 lines
6.2 KiB
Go
package buildflags
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"maps"
|
|
"os"
|
|
"strconv"
|
|
"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 CacheOptions []*CacheOptionsEntry
|
|
|
|
func (o CacheOptions) Merge(other CacheOptions) CacheOptions {
|
|
if other == nil {
|
|
return o.Normalize()
|
|
} else if o == nil {
|
|
return other.Normalize()
|
|
}
|
|
|
|
return append(o, other...).Normalize()
|
|
}
|
|
|
|
func (o CacheOptions) Normalize() CacheOptions {
|
|
if len(o) == 0 {
|
|
return nil
|
|
}
|
|
return removeDupes(o)
|
|
}
|
|
|
|
func (o CacheOptions) ToPB() []*controllerapi.CacheOptionsEntry {
|
|
if len(o) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var outs []*controllerapi.CacheOptionsEntry
|
|
for _, entry := range o {
|
|
pb := entry.ToPB()
|
|
if !isActive(pb) {
|
|
continue
|
|
}
|
|
outs = append(outs, pb)
|
|
}
|
|
return outs
|
|
}
|
|
|
|
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 {
|
|
ci := &controllerapi.CacheOptionsEntry{
|
|
Type: e.Type,
|
|
Attrs: maps.Clone(e.Attrs),
|
|
}
|
|
addGithubToken(ci)
|
|
addAwsCredentials(ci)
|
|
return ci
|
|
}
|
|
|
|
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) 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)
|
|
}
|
|
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) (CacheOptions, error) {
|
|
if len(in) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
opts := make(CacheOptions, 0, len(in))
|
|
for _, in := range in {
|
|
if in == "" {
|
|
continue
|
|
}
|
|
|
|
if !strings.Contains(in, "=") {
|
|
// This is ref only format. Each field in the CSV is its own entry.
|
|
fields, err := csvvalue.Fields(in, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, field := range fields {
|
|
opt := CacheOptionsEntry{}
|
|
if err := opt.UnmarshalText([]byte(field)); err != nil {
|
|
return nil, err
|
|
}
|
|
opts = append(opts, &opt)
|
|
}
|
|
continue
|
|
}
|
|
|
|
var out CacheOptionsEntry
|
|
if err := out.UnmarshalText([]byte(in)); err != nil {
|
|
return nil, err
|
|
}
|
|
opts = append(opts, &out)
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
func addGithubToken(ci *controllerapi.CacheOptionsEntry) {
|
|
if ci.Type != "gha" {
|
|
return
|
|
}
|
|
version, ok := ci.Attrs["version"]
|
|
if !ok {
|
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L19
|
|
if v, ok := os.LookupEnv("ACTIONS_CACHE_SERVICE_V2"); ok {
|
|
if b, err := strconv.ParseBool(v); err == nil && b {
|
|
version = "2"
|
|
}
|
|
}
|
|
}
|
|
if _, ok := ci.Attrs["token"]; !ok {
|
|
if v, ok := os.LookupEnv("ACTIONS_RUNTIME_TOKEN"); ok {
|
|
ci.Attrs["token"] = v
|
|
}
|
|
}
|
|
if _, ok := ci.Attrs["url_v2"]; !ok && version == "2" {
|
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L34-L35
|
|
if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
|
|
ci.Attrs["url_v2"] = v
|
|
}
|
|
}
|
|
if _, ok := ci.Attrs["url"]; !ok {
|
|
// https://github.com/actions/toolkit/blob/2b08dc18f261b9fdd978b70279b85cbef81af8bc/packages/cache/src/internal/config.ts#L28-L33
|
|
if v, ok := os.LookupEnv("ACTIONS_CACHE_URL"); ok {
|
|
ci.Attrs["url"] = v
|
|
} else if v, ok := os.LookupEnv("ACTIONS_RESULTS_URL"); ok {
|
|
ci.Attrs["url"] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
func addAwsCredentials(ci *controllerapi.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
|
|
}
|
|
}
|
|
|
|
func isActive(pb *controllerapi.CacheOptionsEntry) bool {
|
|
// Always active if not gha.
|
|
if pb.Type != "gha" {
|
|
return true
|
|
}
|
|
return pb.Attrs["token"] != "" && (pb.Attrs["url"] != "" || pb.Attrs["url_v2"] != "")
|
|
}
|