mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-25 15:50:20 +00:00
317 lines
10 KiB
Go
317 lines
10 KiB
Go
|
// Copyright 2018 Google LLC
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
// Package containers defines types and functions for resolving qualified names within a namespace
|
||
|
// or type provided to CEL.
|
||
|
package containers
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// DefaultContainer has an empty container name.
|
||
|
DefaultContainer *Container = nil
|
||
|
|
||
|
// Empty map to search for aliases when needed.
|
||
|
noAliases = make(map[string]string)
|
||
|
)
|
||
|
|
||
|
// NewContainer creates a new Container with the fully-qualified name.
|
||
|
func NewContainer(opts ...ContainerOption) (*Container, error) {
|
||
|
var c *Container
|
||
|
var err error
|
||
|
for _, opt := range opts {
|
||
|
c, err = opt(c)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return c, nil
|
||
|
}
|
||
|
|
||
|
// Container holds a reference to an optional qualified container name and set of aliases.
|
||
|
//
|
||
|
// The program container can be used to simplify variable, function, and type specification within
|
||
|
// CEL programs and behaves more or less like a C++ namespace. See ResolveCandidateNames for more
|
||
|
// details.
|
||
|
type Container struct {
|
||
|
name string
|
||
|
aliases map[string]string
|
||
|
}
|
||
|
|
||
|
// Extend creates a new Container with the existing settings and applies a series of
|
||
|
// ContainerOptions to further configure the new container.
|
||
|
func (c *Container) Extend(opts ...ContainerOption) (*Container, error) {
|
||
|
if c == nil {
|
||
|
return NewContainer(opts...)
|
||
|
}
|
||
|
// Copy the name and aliases of the existing container.
|
||
|
ext := &Container{name: c.Name()}
|
||
|
if len(c.aliasSet()) > 0 {
|
||
|
aliasSet := make(map[string]string, len(c.aliasSet()))
|
||
|
for k, v := range c.aliasSet() {
|
||
|
aliasSet[k] = v
|
||
|
}
|
||
|
ext.aliases = aliasSet
|
||
|
}
|
||
|
// Apply the new options to the container.
|
||
|
var err error
|
||
|
for _, opt := range opts {
|
||
|
ext, err = opt(ext)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return ext, nil
|
||
|
}
|
||
|
|
||
|
// Name returns the fully-qualified name of the container.
|
||
|
//
|
||
|
// The name may conceptually be a namespace, package, or type.
|
||
|
func (c *Container) Name() string {
|
||
|
if c == nil {
|
||
|
return ""
|
||
|
}
|
||
|
return c.name
|
||
|
}
|
||
|
|
||
|
// ResolveCandidateNames returns the candidates name of namespaced identifiers in C++ resolution
|
||
|
// order.
|
||
|
//
|
||
|
// Names which shadow other names are returned first. If a name includes a leading dot ('.'),
|
||
|
// the name is treated as an absolute identifier which cannot be shadowed.
|
||
|
//
|
||
|
// Given a container name a.b.c.M.N and a type name R.s, this will deliver in order:
|
||
|
//
|
||
|
// a.b.c.M.N.R.s
|
||
|
// a.b.c.M.R.s
|
||
|
// a.b.c.R.s
|
||
|
// a.b.R.s
|
||
|
// a.R.s
|
||
|
// R.s
|
||
|
//
|
||
|
// If aliases or abbreviations are configured for the container, then alias names will take
|
||
|
// precedence over containerized names.
|
||
|
func (c *Container) ResolveCandidateNames(name string) []string {
|
||
|
if strings.HasPrefix(name, ".") {
|
||
|
qn := name[1:]
|
||
|
alias, isAlias := c.findAlias(qn)
|
||
|
if isAlias {
|
||
|
return []string{alias}
|
||
|
}
|
||
|
return []string{qn}
|
||
|
}
|
||
|
alias, isAlias := c.findAlias(name)
|
||
|
if isAlias {
|
||
|
return []string{alias}
|
||
|
}
|
||
|
if c.Name() == "" {
|
||
|
return []string{name}
|
||
|
}
|
||
|
nextCont := c.Name()
|
||
|
candidates := []string{nextCont + "." + name}
|
||
|
for i := strings.LastIndex(nextCont, "."); i >= 0; i = strings.LastIndex(nextCont, ".") {
|
||
|
nextCont = nextCont[:i]
|
||
|
candidates = append(candidates, nextCont+"."+name)
|
||
|
}
|
||
|
return append(candidates, name)
|
||
|
}
|
||
|
|
||
|
// aliasSet returns the alias to fully-qualified name mapping stored in the container.
|
||
|
func (c *Container) aliasSet() map[string]string {
|
||
|
if c == nil || c.aliases == nil {
|
||
|
return noAliases
|
||
|
}
|
||
|
return c.aliases
|
||
|
}
|
||
|
|
||
|
// findAlias takes a name as input and returns an alias expansion if one exists.
|
||
|
//
|
||
|
// If the name is qualified, the first component of the qualified name is checked against known
|
||
|
// aliases. Any alias that is found in a qualified name is expanded in the result:
|
||
|
//
|
||
|
// alias: R -> my.alias.R
|
||
|
// name: R.S.T
|
||
|
// output: my.alias.R.S.T
|
||
|
//
|
||
|
// Note, the name must not have a leading dot.
|
||
|
func (c *Container) findAlias(name string) (string, bool) {
|
||
|
// If an alias exists for the name, ensure it is searched last.
|
||
|
simple := name
|
||
|
qualifier := ""
|
||
|
dot := strings.Index(name, ".")
|
||
|
if dot >= 0 {
|
||
|
simple = name[0:dot]
|
||
|
qualifier = name[dot:]
|
||
|
}
|
||
|
alias, found := c.aliasSet()[simple]
|
||
|
if !found {
|
||
|
return "", false
|
||
|
}
|
||
|
return alias + qualifier, true
|
||
|
}
|
||
|
|
||
|
// ContainerOption specifies a functional configuration option for a Container.
|
||
|
//
|
||
|
// Note, ContainerOption implementations must be able to handle nil container inputs.
|
||
|
type ContainerOption func(*Container) (*Container, error)
|
||
|
|
||
|
// Abbrevs configures a set of simple names as abbreviations for fully-qualified names.
|
||
|
//
|
||
|
// An abbreviation (abbrev for short) is a simple name that expands to a fully-qualified name.
|
||
|
// Abbreviations can be useful when working with variables, functions, and especially types from
|
||
|
// multiple namespaces:
|
||
|
//
|
||
|
// // CEL object construction
|
||
|
// qual.pkg.version.ObjTypeName{
|
||
|
// field: alt.container.ver.FieldTypeName{value: ...}
|
||
|
// }
|
||
|
//
|
||
|
// Only one the qualified names above may be used as the CEL container, so at least one of these
|
||
|
// references must be a long qualified name within an otherwise short CEL program. Using the
|
||
|
// following abbreviations, the program becomes much simpler:
|
||
|
//
|
||
|
// // CEL Go option
|
||
|
// Abbrevs("qual.pkg.version.ObjTypeName", "alt.container.ver.FieldTypeName")
|
||
|
// // Simplified Object construction
|
||
|
// ObjTypeName{field: FieldTypeName{value: ...}}
|
||
|
//
|
||
|
// There are a few rules for the qualified names and the simple abbreviations generated from them:
|
||
|
// - Qualified names must be dot-delimited, e.g. `package.subpkg.name`.
|
||
|
// - The last element in the qualified name is the abbreviation.
|
||
|
// - Abbreviations must not collide with each other.
|
||
|
// - The abbreviation must not collide with unqualified names in use.
|
||
|
//
|
||
|
// Abbreviations are distinct from container-based references in the following important ways:
|
||
|
// - Abbreviations must expand to a fully-qualified name.
|
||
|
// - Expanded abbreviations do not participate in namespace resolution.
|
||
|
// - Abbreviation expansion is done instead of the container search for a matching identifier.
|
||
|
// - Containers follow C++ namespace resolution rules with searches from the most qualified name
|
||
|
// to the least qualified name.
|
||
|
// - Container references within the CEL program may be relative, and are resolved to fully
|
||
|
// qualified names at either type-check time or program plan time, whichever comes first.
|
||
|
//
|
||
|
// If there is ever a case where an identifier could be in both the container and as an
|
||
|
// abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
|
||
|
// preserved between compilations even as the container evolves.
|
||
|
func Abbrevs(qualifiedNames ...string) ContainerOption {
|
||
|
return func(c *Container) (*Container, error) {
|
||
|
for _, qn := range qualifiedNames {
|
||
|
ind := strings.LastIndex(qn, ".")
|
||
|
if ind <= 0 || ind >= len(qn)-1 {
|
||
|
return nil, fmt.Errorf(
|
||
|
"invalid qualified name: %s, wanted name of the form 'qualified.name'", qn)
|
||
|
}
|
||
|
alias := qn[ind+1:]
|
||
|
var err error
|
||
|
c, err = aliasAs("abbreviation", qn, alias)(c)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return c, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Alias associates a fully-qualified name with a user-defined alias.
|
||
|
//
|
||
|
// In general, Abbrevs is preferred to Alias since the names generated from the Abbrevs option
|
||
|
// are more easily traced back to source code. The Alias option is useful for propagating alias
|
||
|
// configuration from one Container instance to another, and may also be useful for remapping
|
||
|
// poorly chosen protobuf message / package names.
|
||
|
//
|
||
|
// Note: all of the rules that apply to Abbrevs also apply to Alias.
|
||
|
func Alias(qualifiedName, alias string) ContainerOption {
|
||
|
return aliasAs("alias", qualifiedName, alias)
|
||
|
}
|
||
|
|
||
|
func aliasAs(kind, qualifiedName, alias string) ContainerOption {
|
||
|
return func(c *Container) (*Container, error) {
|
||
|
if len(alias) == 0 || strings.Contains(alias, ".") {
|
||
|
return nil, fmt.Errorf(
|
||
|
"%s must be non-empty and simple (not qualified): %s=%s", kind, kind, alias)
|
||
|
}
|
||
|
|
||
|
if qualifiedName[0:1] == "." {
|
||
|
return nil, fmt.Errorf("qualified name must not begin with a leading '.': %s",
|
||
|
qualifiedName)
|
||
|
}
|
||
|
ind := strings.LastIndex(qualifiedName, ".")
|
||
|
if ind <= 0 || ind == len(qualifiedName)-1 {
|
||
|
return nil, fmt.Errorf("%s must refer to a valid qualified name: %s",
|
||
|
kind, qualifiedName)
|
||
|
}
|
||
|
aliasRef, found := c.aliasSet()[alias]
|
||
|
if found {
|
||
|
return nil, fmt.Errorf(
|
||
|
"%s collides with existing reference: name=%s, %s=%s, existing=%s",
|
||
|
kind, qualifiedName, kind, alias, aliasRef)
|
||
|
}
|
||
|
if strings.HasPrefix(c.Name(), alias+".") || c.Name() == alias {
|
||
|
return nil, fmt.Errorf(
|
||
|
"%s collides with container name: name=%s, %s=%s, container=%s",
|
||
|
kind, qualifiedName, kind, alias, c.Name())
|
||
|
}
|
||
|
if c == nil {
|
||
|
c = &Container{}
|
||
|
}
|
||
|
if c.aliases == nil {
|
||
|
c.aliases = make(map[string]string)
|
||
|
}
|
||
|
c.aliases[alias] = qualifiedName
|
||
|
return c, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Name sets the fully-qualified name of the Container.
|
||
|
func Name(name string) ContainerOption {
|
||
|
return func(c *Container) (*Container, error) {
|
||
|
if len(name) > 0 && name[0:1] == "." {
|
||
|
return nil, fmt.Errorf("container name must not contain a leading '.': %s", name)
|
||
|
}
|
||
|
if c.Name() == name {
|
||
|
return c, nil
|
||
|
}
|
||
|
if c == nil {
|
||
|
return &Container{name: name}, nil
|
||
|
}
|
||
|
c.name = name
|
||
|
return c, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ToQualifiedName converts an expression AST into a qualified name if possible, with a boolean
|
||
|
// 'found' value that indicates if the conversion is successful.
|
||
|
func ToQualifiedName(e *exprpb.Expr) (string, bool) {
|
||
|
switch e.GetExprKind().(type) {
|
||
|
case *exprpb.Expr_IdentExpr:
|
||
|
id := e.GetIdentExpr()
|
||
|
return id.GetName(), true
|
||
|
case *exprpb.Expr_SelectExpr:
|
||
|
sel := e.GetSelectExpr()
|
||
|
// Test only expressions are not valid as qualified names.
|
||
|
if sel.GetTestOnly() {
|
||
|
return "", false
|
||
|
}
|
||
|
if qual, found := ToQualifiedName(sel.GetOperand()); found {
|
||
|
return qual + "." + sel.GetField(), true
|
||
|
}
|
||
|
}
|
||
|
return "", false
|
||
|
}
|