2021-04-09 14:20:26 +08:00
|
|
|
package buildflags
|
2019-04-18 14:07:01 +08:00
|
|
|
|
|
|
|
import (
|
2022-08-30 23:00:10 +08:00
|
|
|
"context"
|
2024-11-22 02:06:14 +08:00
|
|
|
"encoding/json"
|
|
|
|
"maps"
|
2021-02-06 16:17:50 +08:00
|
|
|
"os"
|
2019-04-18 14:07:01 +08:00
|
|
|
"strings"
|
|
|
|
|
2022-08-30 23:00:10 +08:00
|
|
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
2023-02-09 20:03:58 +08:00
|
|
|
controllerapi "github.com/docker/buildx/controller/pb"
|
2019-04-18 14:07:01 +08:00
|
|
|
"github.com/pkg/errors"
|
2024-06-28 11:31:23 +08:00
|
|
|
"github.com/tonistiigi/go-csvvalue"
|
2024-11-22 02:06:14 +08:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
jsoncty "github.com/zclconf/go-cty/cty/json"
|
2019-04-18 14:07:01 +08:00
|
|
|
)
|
|
|
|
|
2024-11-22 02:06:14 +08:00
|
|
|
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)
|
|
|
|
}
|
2023-02-09 20:03:58 +08:00
|
|
|
|
2024-11-22 02:06:14 +08:00
|
|
|
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
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
key := strings.ToLower(parts[0])
|
|
|
|
value := parts[1]
|
|
|
|
switch key {
|
|
|
|
case "type":
|
|
|
|
e.Type = value
|
|
|
|
default:
|
|
|
|
e.Attrs[key] = value
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2021-02-06 16:17:50 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
return errors.Errorf("type required form> %q", string(text))
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
return nil
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
|
|
|
|
2024-11-22 02:06:14 +08:00
|
|
|
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
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
|
|
|
|
if !out.IsActive() {
|
|
|
|
// Skip inactive cache entries.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
outs = append(outs, out.ToPB())
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2024-11-22 02:06:14 +08:00
|
|
|
return outs, nil
|
2019-04-18 14:07:01 +08:00
|
|
|
}
|
2021-02-06 16:17:50 +08:00
|
|
|
|
2024-11-22 02:06:14 +08:00
|
|
|
func addGithubToken(ci *CacheOptionsEntry) {
|
2021-02-06 16:17:50 +08:00
|
|
|
if ci.Type != "gha" {
|
2024-11-22 02:06:14 +08:00
|
|
|
return
|
2021-02-06 16:17:50 +08:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-30 23:00:10 +08:00
|
|
|
|
2024-11-22 02:06:14 +08:00
|
|
|
func addAwsCredentials(ci *CacheOptionsEntry) {
|
2022-08-30 23:00:10 +08:00
|
|
|
if ci.Type != "s3" {
|
|
|
|
return
|
|
|
|
}
|
2023-05-19 21:37:14 +08:00
|
|
|
_, 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
|
|
|
|
}
|
2022-08-30 23:00:10 +08:00
|
|
|
ctx := context.TODO()
|
|
|
|
awsConfig, err := awsconfig.LoadDefaultConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
credentials, err := awsConfig.Credentials.Retrieve(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-05-19 21:37:14 +08:00
|
|
|
if !okAccessKeyID && credentials.AccessKeyID != "" {
|
2022-08-30 23:00:10 +08:00
|
|
|
ci.Attrs["access_key_id"] = credentials.AccessKeyID
|
|
|
|
}
|
2023-05-19 21:37:14 +08:00
|
|
|
if !okSecretAccessKey && credentials.SecretAccessKey != "" {
|
2022-08-30 23:00:10 +08:00
|
|
|
ci.Attrs["secret_access_key"] = credentials.SecretAccessKey
|
|
|
|
}
|
|
|
|
if _, ok := ci.Attrs["session_token"]; !ok && credentials.SessionToken != "" {
|
|
|
|
ci.Attrs["session_token"] = credentials.SessionToken
|
|
|
|
}
|
|
|
|
}
|