| 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 != "" { |
| 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 |
| } |
| |
| // 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() |
| } |