2018-08-11 15:04:13 -04:00
|
|
|
package apicaps
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
pb "github.com/moby/buildkit/util/apicaps/pb"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type PBCap = pb.APICap
|
|
|
|
|
|
|
|
// ExportedProduct is the name of the product using this package.
|
|
|
|
// Users vendoring this library may override it to provide better versioning hints
|
|
|
|
// for their users (or set it with a flag to buildkitd).
|
|
|
|
var ExportedProduct string
|
|
|
|
|
|
|
|
// CapStatus defines the stability properties of a capability
|
|
|
|
type CapStatus int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// CapStatusStable refers to a capability that should never be changed in
|
|
|
|
// backwards incompatible manner unless there is a serious security issue.
|
|
|
|
CapStatusStable CapStatus = iota
|
|
|
|
// CapStatusExperimental refers to a capability that may be removed in the future.
|
|
|
|
// If incompatible changes are made the previous ID is disabled and new is added.
|
|
|
|
CapStatusExperimental
|
|
|
|
// CapStatusPrerelease is same as CapStatusExperimental that can be used for new
|
|
|
|
// features before they move to stable.
|
|
|
|
CapStatusPrerelease
|
|
|
|
)
|
|
|
|
|
|
|
|
// CapID is type for capability identifier
|
|
|
|
type CapID string
|
|
|
|
|
|
|
|
// Cap describes an API feature
|
|
|
|
type Cap struct {
|
|
|
|
ID CapID
|
|
|
|
Name string // readable name, may contain spaces but keep in one sentence
|
|
|
|
Status CapStatus
|
|
|
|
Enabled bool
|
|
|
|
Deprecated bool
|
|
|
|
SupportedHint map[string]string
|
|
|
|
DisabledReason string
|
|
|
|
DisabledReasonMsg string
|
|
|
|
DisabledAlternative string
|
|
|
|
}
|
|
|
|
|
|
|
|
// CapList is a collection of capability definitions
|
|
|
|
type CapList struct {
|
|
|
|
m map[CapID]Cap
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init initializes definition for a new capability.
|
|
|
|
// Not safe to be called concurrently with other methods.
|
|
|
|
func (l *CapList) Init(cc ...Cap) {
|
|
|
|
if l.m == nil {
|
|
|
|
l.m = make(map[CapID]Cap, len(cc))
|
|
|
|
}
|
|
|
|
for _, c := range cc {
|
|
|
|
l.m[c.ID] = c
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All reports the configuration of all known capabilities
|
|
|
|
func (l *CapList) All() []pb.APICap {
|
|
|
|
out := make([]pb.APICap, 0, len(l.m))
|
|
|
|
for _, c := range l.m {
|
|
|
|
out = append(out, pb.APICap{
|
|
|
|
ID: string(c.ID),
|
|
|
|
Enabled: c.Enabled,
|
|
|
|
Deprecated: c.Deprecated,
|
|
|
|
DisabledReason: c.DisabledReason,
|
|
|
|
DisabledReasonMsg: c.DisabledReasonMsg,
|
|
|
|
DisabledAlternative: c.DisabledAlternative,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
sort.Slice(out, func(i, j int) bool {
|
|
|
|
return out[i].ID < out[j].ID
|
|
|
|
})
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// CapSet returns a CapSet for an capability configuration
|
|
|
|
func (l *CapList) CapSet(caps []pb.APICap) CapSet {
|
|
|
|
m := make(map[string]*pb.APICap, len(caps))
|
|
|
|
for _, c := range caps {
|
|
|
|
if c.ID != "" {
|
|
|
|
c := c // capture loop iterator
|
|
|
|
m[c.ID] = &c
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CapSet{
|
|
|
|
list: l,
|
|
|
|
set: m,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CapSet is a configuration for detecting supported capabilities
|
|
|
|
type CapSet struct {
|
|
|
|
list *CapList
|
|
|
|
set map[string]*pb.APICap
|
|
|
|
}
|
|
|
|
|
|
|
|
// Supports returns an error if capability is not supported
|
|
|
|
func (s *CapSet) Supports(id CapID) error {
|
|
|
|
err := &CapError{ID: id}
|
|
|
|
c, ok := s.list.m[id]
|
|
|
|
if !ok {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
err.Definition = &c
|
|
|
|
state, ok := s.set[string(id)]
|
|
|
|
if !ok {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
err.State = state
|
|
|
|
if !state.Enabled {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
return nil
|
2021-06-22 04:17:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Contains checks if cap set contains cap. Note that unlike Supports() this
|
|
|
|
// function only checks capability existence in remote set, not if cap has been initialized.
|
|
|
|
func (s *CapSet) Contains(id CapID) bool {
|
|
|
|
_, ok := s.set[string(id)]
|
|
|
|
return ok
|
2018-08-11 15:04:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// CapError is an error for unsupported capability
|
|
|
|
type CapError struct {
|
|
|
|
ID CapID
|
|
|
|
Definition *Cap
|
|
|
|
State *pb.APICap
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e CapError) Error() string {
|
|
|
|
if e.Definition == nil {
|
|
|
|
return fmt.Sprintf("unknown API capability %s", e.ID)
|
|
|
|
}
|
|
|
|
typ := ""
|
|
|
|
if e.Definition.Status == CapStatusExperimental {
|
|
|
|
typ = "experimental "
|
|
|
|
}
|
|
|
|
if e.Definition.Status == CapStatusPrerelease {
|
|
|
|
typ = "prerelease "
|
|
|
|
}
|
|
|
|
name := ""
|
|
|
|
if e.Definition.Name != "" {
|
|
|
|
name = "(" + e.Definition.Name + ")"
|
|
|
|
}
|
|
|
|
b := &strings.Builder{}
|
|
|
|
fmt.Fprintf(b, "requested %sfeature %s %s", typ, e.ID, name)
|
|
|
|
if e.State == nil {
|
|
|
|
fmt.Fprint(b, " is not supported by build server")
|
|
|
|
if hint, ok := e.Definition.SupportedHint[ExportedProduct]; ok {
|
|
|
|
fmt.Fprintf(b, " (added in %s)", hint)
|
|
|
|
}
|
|
|
|
fmt.Fprintf(b, ", please update %s", ExportedProduct)
|
|
|
|
} else {
|
|
|
|
fmt.Fprint(b, " has been disabled on the build server")
|
|
|
|
if e.State.DisabledReasonMsg != "" {
|
|
|
|
fmt.Fprintf(b, ": %s", e.State.DisabledReasonMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return b.String()
|
|
|
|
}
|