Skip to content
Snippets Groups Projects
Commit c8642bf2 authored by bryanl's avatar bryanl
Browse files

updating ksonnet-lib with support for new generator


Signed-off-by: default avatarbryanl <bryanliles@gmail.com>
parent 332a9610
No related branches found
No related tags found
No related merge requests found
Showing
with 2478 additions and 4 deletions
......@@ -224,12 +224,15 @@
[[projects]]
name = "github.com/ksonnet/ksonnet-lib"
packages = [
"ksonnet-gen/astext",
"ksonnet-gen/jsonnet",
"ksonnet-gen/ksonnet",
"ksonnet-gen/kubespec",
"ksonnet-gen/kubeversion"
"ksonnet-gen/kubeversion",
"ksonnet-gen/nodemaker",
"ksonnet-gen/printer"
]
revision = "e8077023fa84395ad7976fa750136b39d0aa5f08"
revision = "b13dc1c505011ee838ae45324994dac432233000"
[[projects]]
branch = "master"
......@@ -600,6 +603,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "2d8337f0b0870fb8b2c0a041d7b5e3e77b66b414fe9c2d7962ee5c5842bb94e0"
inputs-digest = "f44fb422720ed1d7e1a654a9c4b6f6d9baa90492bb28c459bf30552527255e83"
solver-name = "gps-cdcl"
solver-version = 1
......@@ -54,7 +54,7 @@
[[constraint]]
name = "github.com/ksonnet/ksonnet-lib"
revision = "e8077023fa84395ad7976fa750136b39d0aa5f08"
revision = "b13dc1c505011ee838ae45324994dac432233000"
[[constraint]]
name = "github.com/mattn/go-isatty"
......
package astext
import "github.com/google/go-jsonnet/ast"
// ObjectFields is a slice of ObjectField.
type ObjectFields []ObjectField
// ObjectField wraps ast.ObjectField and adds commenting and the ability to
// be printed on one line.
type ObjectField struct {
ast.ObjectField
// Comment is a comment for the object field.
Comment *Comment
// Oneline prints this field on a single line.
Oneline bool
}
// Object wraps ast.Object and adds the ability to be printed on one line.
type Object struct {
ast.Object
Fields []ObjectField
// Oneline prints this field on a single line.
Oneline bool
}
package astext
// extensions for ast that could live upstream
// Comment is a comment.
type Comment struct {
Text string // represents a single line comment
}
package ksonnet
import (
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
"github.com/pkg/errors"
)
// APIObject is an API object.
type APIObject struct {
resource Object
renderFieldsFn renderFieldsFn
}
// NewAPIObject creates an instance of APIObject.
func NewAPIObject(resource Object) *APIObject {
ao := &APIObject{
resource: resource,
renderFieldsFn: renderFields,
}
return ao
}
// Kind is the kind of api object this is.
func (a *APIObject) Kind() string {
return FormatKind(a.resource.Kind())
}
// Description is the description of this API object.
func (a *APIObject) Description() string {
return a.resource.Description()
}
// Node returns an AST node for this api object.
func (a *APIObject) Node(catalog *Catalog) (*nm.Object, error) {
return apiObjectNode(catalog, a)
}
func (a *APIObject) initNode(catalog *Catalog) (*nm.Object, error) {
o := nm.NewObject()
if a.resource.IsType() {
kindObject := nm.OnelineObject()
kind := a.resource.Kind()
kindObject.Set(nm.InheritedKey("kind"), nm.NewStringDouble(kind))
o.Set(nm.LocalKey("kind"), kindObject)
ctorBase := []nm.Noder{
nm.NewVar("apiVersion"),
nm.NewVar("kind"),
}
a.setConstructors(o, ctorBase, objectConstructor())
} else {
a.setConstructors(o, nil, nm.OnelineObject())
}
return o, nil
}
func (a *APIObject) setConstructors(parent *nm.Object, ctorBase []nm.Noder, defaultCtorBody nm.Noder) error {
desc := makeDescriptor(a.resource.Codebase(), a.resource.Group(), a.resource.Kind())
ctors := locateConstructors(desc)
if len(ctors) > 0 {
for _, ctor := range ctors {
key, err := ctor.Key()
if err != nil {
return errors.Wrap(err, "generate constructor key")
}
parent.Set(key, ctor.Body(ctorBase...))
}
return nil
}
parent.Set(nm.FunctionKey("new", []string{}), defaultCtorBody)
return nil
}
func objectConstructor() *nm.Binary {
return nm.NewBinary(nm.NewVar("apiVersion"), nm.NewVar("kind"), nm.BopPlus)
}
func apiObjectNode(catalog *Catalog, a *APIObject) (*nm.Object, error) {
if catalog == nil {
return nil, errors.New("catalog is nil")
}
o, err := a.initNode(catalog)
if err != nil {
return nil, err
}
if err := a.renderFieldsFn(catalog, o, "", a.resource.Properties()); err != nil {
return nil, err
}
return o, nil
}
package ksonnet
import (
"strings"
"github.com/blang/semver"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
)
var (
blockedReferences = []string{
"io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta",
"io.k8s.apimachinery.pkg.apis.meta.v1.Status",
}
blockedPropertyNames = []string{
"status",
"apiVersion",
"kind",
}
)
// ExtractFn is a function which extracts properties from a schema.
type ExtractFn func(*Catalog, map[string]spec.Schema, []string) (map[string]Property, error)
// CatalogOpt is an option for configuring Catalog.
type CatalogOpt func(*Catalog)
// CatalogOptExtractProperties is a Catalog option for setting the property
// extractor.
func CatalogOptExtractProperties(fn ExtractFn) CatalogOpt {
return func(c *Catalog) {
c.extractFn = fn
}
}
// CatalogOptChecksum is a Catalog option for setting the checksum of the swagger schema.
func CatalogOptChecksum(checksum string) CatalogOpt {
return func(c *Catalog) {
c.checksum = checksum
}
}
// Catalog is a catalog definitions
type Catalog struct {
apiSpec *spec.Swagger
extractFn ExtractFn
apiVersion semver.Version
paths map[string]Component
checksum string
// memos
typesCache []Type
fieldsCache []Field
}
// NewCatalog creates an instance of Catalog.
func NewCatalog(apiSpec *spec.Swagger, opts ...CatalogOpt) (*Catalog, error) {
if apiSpec == nil {
return nil, errors.New("apiSpec is nil")
}
if apiSpec.Info == nil {
return nil, errors.New("apiSpec Info is nil")
}
parts := strings.SplitN(apiSpec.Info.Version, ".", 3)
parts[0] = strings.TrimPrefix(parts[0], "v")
vers := strings.Join(parts, ".")
apiVersion, err := semver.Parse(vers)
if err != nil {
return nil, errors.Wrap(err, "invalid apiSpec version")
}
paths, err := parsePaths(apiSpec)
if err != nil {
return nil, errors.Wrap(err, "parse apiSpec paths")
}
c := &Catalog{
apiSpec: apiSpec,
extractFn: extractProperties,
apiVersion: apiVersion,
paths: paths,
}
for _, opt := range opts {
opt(c)
}
return c, nil
}
// Checksum returns the checksum of the swagger schema.
func (c *Catalog) Checksum() string {
return c.checksum
}
// Version returns the Kubernetes API version represented by this Catalog.
func (c *Catalog) Version() string {
return c.apiVersion.String()
}
// Types returns a slice of all types.
func (c *Catalog) Types() ([]Type, error) {
if c.typesCache != nil {
return c.typesCache, nil
}
var resources []Type
for name, schema := range c.definitions() {
desc, err := ParseDescription(name)
if err != nil {
return nil, errors.Wrapf(err, "parse description for %s", name)
}
// If there is a path, we can update it as a first class object
// in the API. This makes this schema a type.
component, ok := c.paths[name]
if !ok {
continue
}
props, err := c.extractFn(c, schema.Properties, schema.Required)
if err != nil {
return nil, errors.Wrapf(err, "extract propererties from %s", name)
}
kind := NewType(name, schema.Description, desc.Codebase, desc.Group, component, props)
resources = append(resources, kind)
}
c.typesCache = resources
return resources, nil
}
// Fields returns a slice of all fields.
func (c *Catalog) Fields() ([]Field, error) {
if c.fieldsCache != nil {
return c.fieldsCache, nil
}
var types []Field
for name, schema := range c.definitions() {
desc, err := ParseDescription(name)
if err != nil {
return nil, errors.Wrapf(err, "parse description for %s", name)
}
// If there is a path, this should ot be a hidden object. This
// makes this schema a field.
if _, ok := c.paths[name]; ok {
continue
}
props, err := c.extractFn(c, schema.Properties, schema.Required)
if err != nil {
return nil, errors.Wrapf(err, "extract propererties from %s", name)
}
t := NewField(name, schema.Description, desc.Codebase, desc.Group, desc.Version, desc.Kind, props)
types = append(types, *t)
}
c.fieldsCache = types
return types, nil
}
func (c *Catalog) isFormatRef(name string) (bool, error) {
schema, ok := c.apiSpec.Definitions[name]
if !ok {
return false, errors.Errorf("%s was not found", name)
}
if schema.Format != "" {
return true, nil
}
return false, nil
}
// Field returns a field by definition id. If the type cannot be found, it returns an error.
func (c *Catalog) Field(name string) (*Field, error) {
types, err := c.Fields()
if err != nil {
return nil, err
}
for _, ty := range types {
if ty.Identifier() == name {
return &ty, nil
}
}
return nil, errors.Errorf("%s was not found", name)
}
// Resource returns a resource by group, version, kind. If the field cannot be found,
// it returns an error
func (c *Catalog) Resource(group, version, kind string) (*Type, error) {
resources, err := c.Types()
if err != nil {
return nil, err
}
for _, resource := range resources {
if group == resource.Group() &&
version == resource.Version() &&
kind == resource.Kind() {
return &resource, nil
}
}
return nil, errors.Errorf("unable to find %s.%s.%s",
group, version, kind)
}
// TypeByID returns a type by identifier.
func (c *Catalog) TypeByID(id string) (*Type, error) {
resources, err := c.Types()
if err != nil {
return nil, err
}
for _, resource := range resources {
if resource.Identifier() == id {
return &resource, nil
}
}
return nil, errors.Errorf("unable to find type %q", id)
}
// TypesWithDescendant returns types who have the specified definition as a descendant.
// This list does not include List types (e.g. DeploymentList).
func (c *Catalog) TypesWithDescendant(definition string) ([]Type, error) {
types, err := c.Types()
if err != nil {
return nil, errors.Wrap(err, "retrieve types")
}
var out []Type
for _, ty := range types {
if strings.HasSuffix(ty.Kind(), "List") {
continue
}
tf, err := c.descend(definition, ty.Properties())
if err != nil {
return nil, err
}
if tf {
out = append(out, ty)
}
}
return out, nil
}
func (c *Catalog) find(id string) (Object, error) {
f, err := c.Field(id)
if err == nil {
return f, nil
}
t, err := c.TypeByID(id)
if err != nil {
return nil, errors.Errorf("unable to find object %q", id)
}
return t, nil
}
func (c *Catalog) descend(definition string, m map[string]Property) (bool, error) {
for _, prop := range m {
if ref := prop.Ref(); ref != "" {
if ref == definition {
return true, nil
}
// NOTE: if this is a reference to json schema, bail out because this is recursive.
if ref == "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps" {
continue
}
f, err := c.find(ref)
if err != nil {
return false, errors.Wrapf(err, "find field %s", ref)
}
tf, err := c.descend(definition, f.Properties())
if err != nil {
return false, err
}
if tf {
return true, nil
}
}
}
return false, nil
}
func isValidDefinition(name string, ver semver.Version) bool {
checkVer := semver.Version{Major: 1, Minor: 7}
if ver.GTE(checkVer) {
return !strings.HasPrefix(name, "io.k8s.kubernetes.pkg.api")
}
return true
}
// extractRef extracts a ref from a schema.
func extractRef(schema spec.Schema) string {
return strings.TrimPrefix(schema.Ref.String(), "#/definitions/")
}
func (c *Catalog) definitions() spec.Definitions {
out := spec.Definitions{}
for name, schema := range c.apiSpec.Definitions {
if isValidDefinition(name, c.apiVersion) {
out[name] = schema
}
}
return out
}
package ksonnet
import (
"fmt"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
)
const (
extensionGroupVersionKind = "x-kubernetes-group-version-kind"
)
// Component is resource information provided in the k8s swagger schema
// which contains the group, kind, and version for a definition.
type Component struct {
Group string
Kind string
Version string
}
// NewComponent extracts component information from a schema.
func NewComponent(s spec.Schema) (*Component, error) {
re := componentExtractor{schema: s}
group := re.extract("group")
kind := re.extract("kind")
version := re.extract("version")
if re.err != nil {
return nil, re.err
}
return &Component{
Group: group,
Kind: kind,
Version: version,
}, nil
}
func (c *Component) String() string {
group := c.Group
if group == "" {
group = "core"
}
return fmt.Sprintf("%s.%s.%s", group, c.Version, c.Kind)
}
type componentExtractor struct {
err error
schema spec.Schema
}
func (re *componentExtractor) extract(key string) string {
if re.err != nil {
return ""
}
i, ok := re.schema.Extensions[extensionGroupVersionKind]
if !ok {
re.err = errors.New("no group/kind/version extension")
return ""
}
s, ok := i.([]interface{})
if ok {
m, ok := s[0].(map[string]interface{})
if ok {
str, ok := m[key].(string)
if ok {
return str
}
}
}
return ""
}
package ksonnet
import (
"regexp"
"sort"
"github.com/google/go-jsonnet/ast"
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
"github.com/pkg/errors"
)
var (
// reCtorSetter is a regex that matches function names. It'll successfully
// match `withName`, `foo.withName`, and `foo.bar.withName`.
reCtorSetter = regexp.MustCompile(`((^.*?)\.)*(with\w+)$`)
)
func matchCtorSetter(in string) (string, string, error) {
match := reCtorSetter.FindAllStringSubmatch(in, -1)
if len(match) == 0 {
return "", "", errors.New("no match")
}
cur := match[0]
if cur[1] == "" {
return "self", cur[3], nil
}
return "self." + cur[2], cur[3], nil
}
type constructor struct {
name string
params []constructorParam
}
func newConstructor(name string, params ...constructorParam) *constructor {
return &constructor{
name: name,
params: params,
}
}
// Key creates an object key for the constructor.
func (c *constructor) Key() (nm.Key, error) {
var args []nm.OptionalArg
for _, param := range c.params {
option, err := param.Option()
if err != nil {
return nm.Key{}, errors.Wrap(err, "unable to create key from param")
}
args = append(args, option)
}
key := nm.FunctionKey(c.name, []string{}, nm.KeyOptNamedParams(args...))
return key, nil
}
func (c *constructor) Body(baseNodes ...nm.Noder) nm.Noder {
var items []nm.Noder
for _, node := range baseNodes {
items = append(items, node)
}
// collection functions so they can be de-duplicated.
funs := make(map[string][]argRef)
for _, param := range c.params {
path, fn, err := matchCtorSetter(param.function)
if err != nil {
// TODO should we handle this error?
continue
}
if _, ok := funs[path]; !ok {
funs[path] = make([]argRef, 0)
}
funs[path] = append(funs[path], argRef{name: param.name, fn: fn})
}
var funNames []string
for funName := range funs {
funNames = append(funNames, funName)
}
sort.Strings(funNames)
for _, funName := range funNames {
call := nm.NewCall(funName)
var curApply *ctorApply
var addedCall bool
ars := funs[funName]
sort.Slice(ars, func(i, j int) bool {
return ars[i].fn < ars[j].fn
})
for _, ar := range ars {
indexID := ast.Identifier(ar.fn)
index := &ast.Index{Id: &indexID}
if !addedCall {
index.Target = call.Node()
addedCall = true
} else {
index.Target = curApply.Node()
}
arg := &ast.Var{Id: ast.Identifier(ar.name)}
apply := ast.Apply{
Arguments: ast.Arguments{Positional: ast.Nodes{arg}},
Target: index,
}
curApply = &ctorApply{Apply: apply}
}
items = append(items, curApply)
}
return nm.Combine(items...)
}
type ctorApply struct {
ast.Apply
}
func (ca *ctorApply) Node() ast.Node {
return &ca.Apply
}
type argRef struct {
name string
fn string
}
type constructorParam struct {
name string
function string
defaultValue interface{}
}
func newConstructorParam(name, function string, defaultValue interface{}) *constructorParam {
if defaultValue == nil {
defaultValue = ""
}
return &constructorParam{
name: name,
function: function,
defaultValue: defaultValue,
}
}
func (cp *constructorParam) Option() (nm.OptionalArg, error) {
var node nm.Noder
var err error
switch t := cp.defaultValue.(type) {
case string:
node = nm.NewStringDouble(t)
case map[string]interface{}:
node, err = nm.KVFromMap(t)
if err != nil {
return nm.OptionalArg{}, errors.Wrap(err, "invalid parameter")
}
case []string:
var items []nm.Noder
for _, item := range t {
items = append(items, nm.NewStringDouble(item))
}
node = nm.NewArray(items)
case float64:
node = nm.NewFloat(t)
case int:
node = nm.NewInt(t)
case bool:
node = nm.NewBoolean(t)
default:
return nm.OptionalArg{}, errors.Errorf("unable to use type %T in param", t)
}
return nm.OptionalArg{Name: cp.name, Default: node}, nil
}
package ksonnet
// NOTE: custom constructors will be removed at ksonnet 0.11
func locateConstructors(desc Description) []constructor {
ctors, ok := customConstructors[desc]
if !ok {
return nil
}
return ctors
}
func makeDescriptor(codebase, group, kind string) Description {
return Description{
Codebase: codebase,
Group: group,
Kind: kind,
}
}
var (
customConstructors = map[Description][]constructor{
makeDescriptor("api", "apps", "Deployment"): deploymentCtor,
makeDescriptor("api", "apps", "DeploymentList"): objectList,
makeDescriptor("api", "apps", "DeploymentRollback"): deploymentRollbackCtor,
makeDescriptor("api", "apps", "Scale"): scaleCtor,
makeDescriptor("api", "apps", "StatefulSet"): statefulSetCtor,
makeDescriptor("api", "apps", "StatefulSetList"): objectList,
makeDescriptor("api", "extensions", "Deployment"): deploymentCtor,
makeDescriptor("api", "extensions", "DeploymentList"): objectList,
makeDescriptor("api", "extensions", "DeploymentRollback"): deploymentRollbackCtor,
makeDescriptor("api", "extensions", "Scale"): scaleCtor,
makeDescriptor("api", "extensions", "StatefulSet"): statefulSetCtor,
makeDescriptor("api", "extensions", "StatefulSetList"): objectList,
makeDescriptor("api", "authentication", "TokenReview"): []constructor{
*newConstructor(
"new",
*newConstructorParam("token", "mixin.spec.withToken", nil),
),
},
makeDescriptor("api", "autoscaling", "HorizontalPodAutoscalerList"): objectList,
makeDescriptor("api", "autoscaling", "Scale"): scaleCtor,
makeDescriptor("api", "batch", "JobList"): objectList,
makeDescriptor("api", "batch", "CronJobList"): objectList,
makeDescriptor("api", "certificates", "CertificateSigningRequestList"): objectList,
makeDescriptor("api", "core", "ConfigMap"): []constructor{
*newConstructor(
"new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
),
},
makeDescriptor("api", "core", "ConfigMapList"): objectList,
makeDescriptor("api", "core", "Container"): []constructor{
*newConstructor(
"new",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("image", "withImage", nil),
),
},
makeDescriptor("api", "core", "ContainerPort"): []constructor{
*newConstructor("new", *newConstructorParam("containerPort", "withContainerPort", nil)),
*newConstructor("newNamed",
*newConstructorParam("containerPort", "withContainerPort", nil),
*newConstructorParam("name", "withName", nil),
),
},
makeDescriptor("api", "core", "EndpointsList"): objectList,
makeDescriptor("api", "core", "EnvVar"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("value", "withValue", nil)),
*newConstructor("fromSecretRef",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("secretRefName", "mixin.valueFrom.secretKeyRef.withName", nil),
*newConstructorParam("secretRefKey", "mixin.valueFrom.secretKeyRef.withKey", nil)),
*newConstructor("fromFieldPath",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("fieldPath", "mixin.valueFrom.fieldRef.withFieldPath", nil)),
},
makeDescriptor("api", "core", "EventList"): objectList,
makeDescriptor("api", "core", "KeyToPath"): []constructor{
*newConstructor("new",
*newConstructorParam("key", "withKey", nil),
*newConstructorParam("path", "withPath", nil)),
},
makeDescriptor("api", "core", "LimitRangeList"): objectList,
makeDescriptor("api", "core", "Namespace"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "withName", nil)),
},
makeDescriptor("api", "core", "NamespaceList"): objectList,
makeDescriptor("api", "core", "NodeList"): objectList,
makeDescriptor("api", "core", "PersistentVolumeClaimList"): objectList,
makeDescriptor("api", "core", "PersistentVolumeList"): objectList,
makeDescriptor("api", "core", "PodList"): objectList,
makeDescriptor("api", "core", "PodTemplateList"): objectList,
makeDescriptor("api", "core", "ReplicationControllerList"): objectList,
makeDescriptor("api", "core", "ResourceQuotaList"): objectList,
makeDescriptor("api", "core", "Secret"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("data", "withData", nil),
*newConstructorParam("type", "withType", "Opaque")),
*newConstructor("new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("stringDate", "withStringData", nil),
*newConstructorParam("type", "withType", "Opaque")),
},
makeDescriptor("api", "core", "SecretList"): objectList,
makeDescriptor("api", "core", "Service"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("selector", "mixin.spec.withSelector", nil),
*newConstructorParam("ports", "mixin.spec.withPorts", nil)),
},
makeDescriptor("api", "core", "ServiceAccount"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "mixin.metadata.withName", nil)),
},
makeDescriptor("api", "core", "ServiceAccountList"): objectList,
makeDescriptor("api", "core", "ServiceList"): objectList,
makeDescriptor("api", "core", "ServicePort"): []constructor{
*newConstructor("new",
*newConstructorParam("port", "withPort", nil),
*newConstructorParam("targetPort", "withTargetPort", nil)),
*newConstructor("newNamed",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("port", "withPort", nil),
*newConstructorParam("targetPort", "withTargetPort", nil)),
},
makeDescriptor("api", "core", "Volume"): []constructor{
*newConstructor(
"fromConfigMap",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("configMapName", "mixin.configMap.withName", nil),
*newConstructorParam("configMapItems", "mixin.configMap.withItems", nil)),
*newConstructor("fromEmptyDir",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("emptyDir", "mixin.emptyDir.mixinInstance",
map[string]interface{}{})),
*newConstructor("fromPersistentVolumeClaim",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("emptyDir", "mixin.persistentVolumeClaim.withClaimName", nil)),
*newConstructor("fromHostPath",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("hostPath", "mixin.hostPath.withPath", nil)),
*newConstructor("fromSecret",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("secretName", "mixin.secret.withSecretName", nil)),
},
makeDescriptor("api", "core", "VolumeMount"): []constructor{
*newConstructor("new",
*newConstructorParam("name", "withName", nil),
*newConstructorParam("mountPath", "withMountPath", nil),
*newConstructorParam("readOnly", "withReadOnly", false)),
},
}
// customConstructor definitions
deploymentCtor = []constructor{
*newConstructor(
"new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("replicas", "mixin.spec.withReplicas", 1),
*newConstructorParam("containers", "mixin.spec.template.spec.withContainers", nil),
*newConstructorParam("podLabels", "mixin.spec.template.metadata.withLabels",
map[string]interface{}{"app": "name"}),
),
}
deploymentRollbackCtor = []constructor{
*newConstructor(
"new",
*newConstructorParam("name", "withName", nil),
),
}
objectList = []constructor{
*newConstructor(
"new",
*newConstructorParam("items", "withItems", nil),
),
}
scaleCtor = []constructor{
*newConstructor(
"new",
*newConstructorParam("replicas", "mixin.spec.withReplicas", 1),
),
}
statefulSetCtor = []constructor{
*newConstructor(
"new",
*newConstructorParam("name", "mixin.metadata.withName", nil),
*newConstructorParam("replicas", "mixin.spec.withReplicas", 1),
*newConstructorParam("containers", "mixin.spec.template.spec.withContainers", nil),
*newConstructorParam("volumeClaims", "mixin.spec.withVolumeClaimTemplates", nil),
*newConstructorParam("podLabels", "mixin.spec.template.metadata.withLabels", map[string]interface{}{
"app": "name",
}),
),
}
tokenReviewCtor = []constructor{
*newConstructor(
"new",
*newConstructorParam("token", "mixin.spec.withToken", nil),
),
}
)
package ksonnet
import (
"fmt"
"regexp"
)
const (
groupCore = "core"
)
var (
reNames = []*regexp.Regexp{
// Core API, pre-1.8 Kubernetes OR non-Kubernetes codebase APIs
regexp.MustCompile(`io\.k8s\.(?P<codebase>\S+)\.pkg\.api\.(?P<version>\S+)\.(?P<kind>\S+)`),
// Core API, 1.8+ Kubernetes
regexp.MustCompile(`io\.k8s\.api\.(?P<packageType>core)\.(?P<version>\S+)\.(?P<kind>\S+)`),
// Other APIs, pre-1.8 Kubernetes OR non-Kubernetes codebase APIs
regexp.MustCompile(`io\.k8s\.(?P<codebase>\S+)\.pkg\.(?P<packageType>apis)\.(?P<group>\S+)\.(?P<version>\S+)\.(?P<kind>\S+)`),
// Other APIs, 1.8+ Kubernetes
regexp.MustCompile(`io\.k8s\.api\.(?P<group>\S+)\.(?P<version>\S+)\.(?P<kind>\S+)`),
// Util packageType
regexp.MustCompile(`io\.k8s\.(?P<codebase>\S+)\.pkg\.(?P<packageType>util)\.(?P<version>\S+)\.(?P<kind>\S+)`),
// Version packageType
regexp.MustCompile(`io\.k8s\.(?P<codebase>\S+)\.pkg\.(?P<packageType>version)\.(?P<kind>\S+)`),
// Runtime packageType
regexp.MustCompile(`io\.k8s\.(?P<codebase>\S+)\.pkg\.(?P<packageType>runtime)\.(?P<kind>\S+)`),
}
)
// UnknownDefinitionError is an error signifying an unknown definition.
type UnknownDefinitionError struct {
name string
}
var _ error = (*UnknownDefinitionError)(nil)
// NewUnknownDefinitionError creates an instance of UnknownDefinitionError.
func NewUnknownDefinitionError(name string) *UnknownDefinitionError {
return &UnknownDefinitionError{
name: name,
}
}
func (e *UnknownDefinitionError) Error() string {
return fmt.Sprintf("%q is not a known definition name", e.name)
}
// Description is a description of a Kubernetes definition name.
type Description struct {
Name string
Version string
Kind string
Group string
Codebase string
}
// Validate validates the Description. A description is valid if it has a version.
func (d *Description) Validate() error {
if d.Version == "" {
return fmt.Errorf("version is nil for %q", d.Name)
}
return nil
}
// ParseDescription takes a definition name and returns a Description.
func ParseDescription(name string) (*Description, error) {
for _, r := range reNames {
if match := r.FindStringSubmatch(name); len(match) > 0 {
result := make(map[string]string)
for i, name := range r.SubexpNames() {
if i != 0 {
result[name] = match[i]
}
}
codebase := result["codebase"]
if codebase == "" {
codebase = "api"
}
d := &Description{
Name: name,
Version: result["version"],
Kind: result["kind"],
Group: result["group"],
Codebase: codebase,
}
return d, nil
}
}
return nil, &UnknownDefinitionError{name: name}
}
package ksonnet
import (
"sort"
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
"github.com/pkg/errors"
)
type renderNodeFn func(c *Catalog, a *APIObject) (*nm.Object, error)
// Document represents a ksonnet lib document.
type Document struct {
catalog *Catalog
// these are defined to aid testing Document
typesFn func() ([]Type, error)
fieldsFn func() ([]Field, error)
renderFn func(fn renderNodeFn, c *Catalog, o *nm.Object, groups []Group) error
renderGroups func(doc *Document, container *nm.Object) error
renderHiddenGroups func(doc *Document, container *nm.Object) error
objectNodeFn func(c *Catalog, a *APIObject) (*nm.Object, error)
}
// NewDocument creates an instance of Document.
func NewDocument(catalog *Catalog) (*Document, error) {
if catalog == nil {
return nil, errors.New("catalog is nil")
}
return &Document{
catalog: catalog,
typesFn: catalog.Types,
fieldsFn: catalog.Fields,
renderFn: render,
renderGroups: renderGroups,
renderHiddenGroups: renderHiddenGroups,
objectNodeFn: apiObjectNode,
}, nil
}
// Groups returns an alphabetically sorted list of groups.
func (d *Document) Groups() ([]Group, error) {
resources, err := d.typesFn()
if err != nil {
return nil, errors.Wrap(err, "retrieve resources")
}
var nodeObjects []Object
for _, resource := range resources {
res := resource
nodeObjects = append(nodeObjects, &res)
}
return d.groups(nodeObjects)
}
// HiddenGroups returns an alphabetically sorted list of hidden groups.
func (d *Document) HiddenGroups() ([]Group, error) {
resources, err := d.fieldsFn()
if err != nil {
return nil, errors.Wrap(err, "retrieve types")
}
var nodeObjects []Object
for _, resource := range resources {
res := resource
nodeObjects = append(nodeObjects, &res)
}
return d.groups(nodeObjects)
}
func (d *Document) groups(resources []Object) ([]Group, error) {
gMap := make(map[string]*Group)
for i := range resources {
res := resources[i]
name := res.Group()
g, ok := gMap[name]
if !ok {
g = NewGroup(name)
gMap[name] = g
}
g.AddResource(res)
gMap[name] = g
}
var groupNames []string
for name := range gMap {
groupNames = append(groupNames, name)
}
sort.Strings(groupNames)
var groups []Group
for _, name := range groupNames {
g := gMap[name]
groups = append(groups, *g)
}
return groups, nil
}
// Node converts a document to a node.
func (d *Document) Node() (*nm.Object, error) {
out := nm.NewObject()
metadata := map[string]interface{}{
"kubernetesVersion": d.catalog.Version(),
"checksum": d.catalog.Checksum(),
}
metadataObj, err := nm.KVFromMap(metadata)
if err != nil {
return nil, errors.Wrap(err, "create metadata key")
}
out.Set(nm.InheritedKey("__ksonnet"), metadataObj)
if err := d.renderGroups(d, out); err != nil {
return nil, err
}
hidden := nm.NewObject()
if err := d.renderHiddenGroups(d, hidden); err != nil {
return nil, err
}
out.Set(nm.LocalKey("hidden"), hidden)
return out, nil
}
func render(fn renderNodeFn, catalog *Catalog, o *nm.Object, groups []Group) error {
for _, group := range groups {
groupNode := group.Node()
for _, version := range group.Versions() {
versionNode := version.Node()
for _, apiObject := range version.APIObjects() {
objectNode, err := fn(catalog, &apiObject)
if err != nil {
return errors.Wrapf(err, "create node %s", apiObject.Kind())
}
versionNode.Set(
nm.NewKey(apiObject.Kind(), nm.KeyOptComment(apiObject.Description())),
objectNode)
}
groupNode.Set(nm.NewKey(version.Name()), versionNode)
}
o.Set(nm.NewKey(group.Name()), groupNode)
}
return nil
}
func renderGroups(d *Document, container *nm.Object) error {
groups, err := d.Groups()
if err != nil {
return errors.Wrap(err, "retrieve groups")
}
if err = d.renderFn(d.objectNodeFn, d.catalog, container, groups); err != nil {
return errors.Wrap(err, "render groups")
}
return nil
}
func renderHiddenGroups(d *Document, container *nm.Object) error {
groups, err := d.HiddenGroups()
if err != nil {
return errors.Wrap(err, "retrieve hidden groups")
}
if err = d.renderFn(d.objectNodeFn, d.catalog, container, groups); err != nil {
return errors.Wrap(err, "render hidden groups")
}
return nil
}
package ksonnet
import (
"fmt"
"sort"
"strings"
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
"github.com/pkg/errors"
)
const (
localK8s = "k8s"
)
// Extension represents a ksonnet lib extension document.
type Extension struct {
catalog *Catalog
}
// NewExtension creates an an instance of Extension.
func NewExtension(catalog *Catalog) *Extension {
return &Extension{
catalog: catalog,
}
}
// Node converts an extension to a node.
func (e *Extension) Node() (nm.Noder, error) {
ext, err := e.genK8sExtension()
if err != nil {
return nil, err
}
extBinary := nm.NewBinary(nm.NewVar(localK8s), ext, nm.BopPlus)
fns := genMapContainers(extBinary)
k8sImportFile := nm.NewImport("k8s.libsonnet")
k8sImport := nm.NewLocal(localK8s, k8sImportFile, fns)
return k8sImport, nil
}
func (e *Extension) genK8sExtension() (*nm.Object, error) {
gi := makeGroupItems()
e.listExtension(gi)
if err := e.mapContainersExtension(gi); err != nil {
return nil, errors.Wrap(err, "map container extensions")
}
return gi.Node(), nil
}
func (e *Extension) mapContainersExtension(gi *groupItems) error {
types, err := e.catalog.TypesWithDescendant("io.k8s.api.core.v1.PodSpec")
if err != nil {
return errors.Wrap(err, "find types with PodSec")
}
mapping := nm.NewObject()
mapping.Set(
nm.FunctionKey("mapContainers", []string{"f"}),
nm.ApplyCall("fn.mapContainers", nm.NewVar("f")),
)
mapping.Set(
nm.FunctionKey("mapContainersWithName", []string{"names", "f"}),
nm.ApplyCall("fn.mapContainersWithName", nm.NewVar("names"), nm.NewVar("f")),
)
for _, ty := range types {
parts := strings.Split(ty.component.String(), ".")
gi.add(parts[0], parts[1], FormatKind(parts[2]), mapping, true)
}
return nil
}
func (e *Extension) listExtension(gi *groupItems) {
apiVersion := nm.NewObject()
apiVersion.Set(nm.InheritedKey("apiVersion"), nm.NewStringDouble("v1"))
kind := nm.NewObject()
kind.Set(nm.InheritedKey("kind"), nm.NewStringDouble("List"))
items := nm.ApplyCall("self.items", nm.NewVar("items"))
o := nm.NewObject()
o.Set(
nm.FunctionKey("new", []string{"items"}),
nm.Combine(apiVersion, kind, items),
)
o.Set(
nm.FunctionKey("items", []string{"items"}),
convertToArray("items", "", true),
)
gi.add("core", "v1", "list", o, false)
}
type nodeMixin struct {
node nm.Noder
isMixin bool
}
type groupItems struct {
groups map[string]map[string]map[string]nodeMixin
}
func makeGroupItems() *groupItems {
return &groupItems{
groups: make(map[string]map[string]map[string]nodeMixin),
}
}
func (gi *groupItems) add(group, version, key string, node nm.Noder, isMixin bool) {
g, ok := gi.groups[group]
if !ok {
g = make(map[string]map[string]nodeMixin)
gi.groups[group] = g
}
v, ok := g[version]
if !ok {
v = make(map[string]nodeMixin)
g[version] = v
}
v[key] = nodeMixin{node: node, isMixin: isMixin}
}
func (gi *groupItems) Node() *nm.Object {
var groupNames []string
for name := range gi.groups {
groupNames = append(groupNames, name)
}
sort.Strings(groupNames)
o := nm.NewObject()
for _, groupName := range groupNames {
group := gi.groups[groupName]
groupObject := nm.NewObject()
var versionNames []string
for name := range group {
versionNames = append(versionNames, name)
}
sort.Strings(versionNames)
for _, versionName := range versionNames {
version := group[versionName]
versionObject := nm.NewObject()
var keyNames []string
for name := range version {
keyNames = append(keyNames, name)
}
sort.Strings(keyNames)
for _, keyName := range keyNames {
node := version[keyName].node
isMixin := version[keyName].isMixin
if isMixin {
parent := nm.NewCall(fmt.Sprintf("k8s.%s.%s.%s", groupName, versionName, keyName))
node = nm.NewBinary(parent, node, nm.BopPlus)
}
versionObject.Set(nm.NewKey(keyName), node)
}
parent := nm.NewCall(fmt.Sprintf("k8s.%s.%s", groupName, versionName))
groupObject.Set(nm.NewKey(versionName), nm.NewBinary(parent, versionObject, nm.BopPlus))
}
parent := nm.NewCall(fmt.Sprintf("k8s.%s", groupName))
o.Set(nm.NewKey(groupName), nm.NewBinary(parent, groupObject, nm.BopPlus))
}
return o
}
func genMapContainers(body nm.Noder) *nm.Local {
o := nm.NewObject()
o.Set(
nm.FunctionKey("mapContainers", []string{"f"}),
createMapContainersFn(),
)
o.Set(
nm.FunctionKey("mapContainersWithName", []string{"names", "f"}),
createMapContainersWithName(),
)
return nm.NewLocal("fn", o, body)
}
func createMapContainersFn() *nm.Object {
o := nm.NewObject()
o.Set(
nm.LocalKey("podContainers"),
nm.NewCall("super.spec.template.spec.containers"),
)
templateSpecObject := nm.NewObject()
templateSpecObject.Set(
nm.InheritedKey("containers"),
nm.ApplyCall("std.map", nm.NewVar("f"), nm.NewVar("podContainers")),
)
templateObject := nm.NewObject()
templateObject.Set(
nm.InheritedKey("spec", nm.KeyOptMixin(true)),
templateSpecObject,
)
specObject := nm.NewObject()
specObject.Set(
nm.InheritedKey("template", nm.KeyOptMixin(true)),
templateObject,
)
o.Set(
nm.InheritedKey("spec", nm.KeyOptMixin(true)),
specObject,
)
return o
}
func createMapContainersWithName() *nm.Local {
c1Binary := nm.NewBinary(
nm.ApplyCall("std.objectHas", nm.NewVar("c"), nm.NewStringDouble("name")),
nm.ApplyCall("inNameSet", nm.NewCall("c.name")),
nm.BopAnd,
)
c1True := nm.ApplyCall("f", nm.NewVar("c"))
c1False := nm.NewVar("c")
c1 := nm.NewConditional(c1Binary, c1True, c1False)
apply := nm.NewApply(c1, []nm.Noder{nm.NewVar("c")}, nil)
runMap := nm.ApplyCall("self.mapContainers", apply)
a := nm.NewVar("nameSet")
b := nm.ApplyCall("std.set", nm.NewArray([]nm.Noder{nm.NewVar("name")}))
inNameSet := nm.NewLocal(
"inNameSet",
nm.NewFunction([]string{"name"}, genIsIntersection(a, b)),
runMap,
)
nameSet := nm.NewLocal("nameSet", setArray("names"), inNameSet)
return nameSet
}
func setArray(varName string) *nm.Conditional {
bin := nm.NewBinary(
nm.ApplyCall("std.type", nm.NewVar(varName)),
nm.NewStringDouble("array"),
nm.BopEqual,
)
tBranch := nm.ApplyCall("std.set", nm.NewVar(varName))
fBranch := nm.ApplyCall("std.set", nm.NewArray([]nm.Noder{nm.NewVar(varName)}))
return nm.NewConditional(bin, tBranch, fBranch)
}
func genIsIntersection(a, b nm.Noder) *nm.Binary {
intersection := nm.ApplyCall("std.Inter", a, b)
checkLen := nm.ApplyCall("std.length", intersection)
return nm.NewBinary(
checkLen,
nm.NewInt(0),
nm.BopGreater,
)
}
package ksonnet
// Field is a Kubernetes field.
type Field struct {
kind string
description string
properties map[string]Property
version string
group string
codebase string
identifier string
}
var _ Object = (*Field)(nil)
// NewField creates an instance of Field.
func NewField(id, desc, codebase, group, ver, kind string, props map[string]Property) *Field {
return &Field{
identifier: id,
description: desc,
group: group,
codebase: codebase,
version: ver,
kind: kind,
properties: props,
}
}
// Kind is the kind for this field.
func (f *Field) Kind() string {
return f.kind
}
// Version is the version for this field.
func (f *Field) Version() string {
return f.version
}
// Codebase is the codebase for this field.
func (f *Field) Codebase() string {
return f.codebase
}
// Group is the group for this field.
func (f *Field) Group() string {
if f.group == "" {
return "core"
}
return f.group
}
// QualifiedGroup is the group for this field.
func (f *Field) QualifiedGroup() string {
return f.Group()
}
// Description is the description for this field.
func (f *Field) Description() string {
return f.description
}
// Identifier is the identifier for this field.
func (f *Field) Identifier() string {
return f.identifier
}
// IsType returns if this item is a type. It always returns false.
func (f *Field) IsType() bool {
return false
}
// Properties are the properties for this field.
func (f *Field) Properties() map[string]Property {
return f.properties
}
package ksonnet
import (
"sort"
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
)
// Group is group of definitions.
type Group struct {
versions map[string]*Version
name string
}
// NewGroup creates an instance of Group.
func NewGroup(name string) *Group {
return &Group{
versions: make(map[string]*Version),
name: name,
}
}
// Name is the name of the group.
func (g *Group) Name() string {
return g.name
}
// Versions returns the versions available for this group.
func (g *Group) Versions() []Version {
var names []string
for name := range g.versions {
names = append(names, name)
}
sort.Strings(names)
var versions []Version
for _, name := range names {
versions = append(versions, *g.versions[name])
}
return versions
}
// AddResource adds a resource to a version.
func (g *Group) AddResource(r Object) {
name := r.Version()
if name == "" {
return
}
v, ok := g.versions[name]
if !ok {
v = NewVersion(name, r.QualifiedGroup())
g.versions[name] = v
}
v.AddResource(r)
}
// Node returns an ast node for this group.
func (g *Group) Node() *nm.Object {
return nm.NewObject()
}
package ksonnet
import (
"bytes"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubespec"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/printer"
"github.com/pkg/errors"
)
// Lib is a ksonnet lib.
type Lib struct {
K8s []byte
Extensions []byte
Version string
}
// GenerateLib generates ksonnet lib.
func GenerateLib(source string) (*Lib, error) {
apiSpec, checksum, err := kubespec.Import(source)
if err != nil {
return nil, errors.Wrap(err, "import Kubernetes spec")
}
c, err := NewCatalog(apiSpec, CatalogOptChecksum(checksum))
if err != nil {
return nil, errors.Wrap(err, "create ksonnet catalog")
}
k8s, err := createK8s(c)
if err != nil {
return nil, errors.Wrap(err, "create k8s.libsonnet")
}
k, err := createK(c)
if err != nil {
return nil, errors.Wrap(err, "create k.libsonnet")
}
lib := &Lib{
K8s: k8s,
Extensions: k,
Version: c.apiVersion.String(),
}
return lib, nil
}
func createK8s(c *Catalog) ([]byte, error) {
doc, err := NewDocument(c)
if err != nil {
return nil, errors.Wrapf(err, "create document")
}
node, err := doc.Node()
if err != nil {
return nil, errors.Wrapf(err, "build document node")
}
var buf bytes.Buffer
if err := printer.Fprint(&buf, node.Node()); err != nil {
return nil, errors.Wrap(err, "print AST")
}
return buf.Bytes(), nil
}
func createK(c *Catalog) ([]byte, error) {
e := NewExtension(c)
node, err := e.Node()
if err != nil {
return nil, errors.Wrapf(err, "build extension node")
}
var buf bytes.Buffer
if err := printer.Fprint(&buf, node.Node()); err != nil {
return nil, errors.Wrap(err, "print AST")
}
return buf.Bytes(), nil
}
package ksonnet
// Object is an object that can be turned into a node by APIObject.
type Object interface {
Kind() string
Description() string
IsType() bool
Properties() map[string]Property
Version() string
Group() string
Codebase() string
QualifiedGroup() string
Identifier() string
}
// Property is a field in a resource
type Property interface {
Description() string
Name() string
Ref() string
}
// LiteralField is a literal field. (e.g. string, number, int, array)
type LiteralField struct {
name string
fieldType string
description string
ref string
}
var _ Property = (*LiteralField)(nil)
// NewLiteralField creates an instance of LiteralField.
func NewLiteralField(name, fieldType, description, ref string) *LiteralField {
return &LiteralField{
name: name,
fieldType: fieldType,
description: description,
ref: ref,
}
}
// FieldType returns the field type of the LiteralField.
func (f *LiteralField) FieldType() string {
return f.fieldType
}
// Name returns the name of the LiteralField.
func (f *LiteralField) Name() string {
return f.name
}
// Description returns the description of the LiteralField.
func (f *LiteralField) Description() string {
return f.description
}
// Ref returns the ref of the LiteralField.
func (f *LiteralField) Ref() string {
return f.ref
}
// ReferenceField is a reference field.
type ReferenceField struct {
name string
description string
ref string
}
var _ Property = (*ReferenceField)(nil)
// NewReferenceField creates an instance of ReferenceField.
func NewReferenceField(name, description, ref string) *ReferenceField {
return &ReferenceField{
name: name,
description: description,
ref: ref,
}
}
// Name returns the name of the ReferenceField.
func (f *ReferenceField) Name() string {
return f.name
}
// Description returns the description of the ReferenceField.
func (f *ReferenceField) Description() string {
return f.description
}
// Ref returns the defintion this ReferenceField represents.
func (f *ReferenceField) Ref() string {
return f.ref
}
package ksonnet
import (
"github.com/go-openapi/spec"
"github.com/pkg/errors"
)
func parsePaths(apiSpec *spec.Swagger) (map[string]Component, error) {
m := make(map[string]Component)
if apiSpec.Paths == nil {
return nil, errors.New("api spec has zero paths")
}
paths := apiSpec.Paths.Paths
for _, pathItem := range paths {
verbs := []*spec.Operation{pathItem.Post, pathItem.Patch, pathItem.Put}
for _, verb := range verbs {
if verb == nil {
continue
}
var body *spec.Parameter
for _, param := range verb.Parameters {
if param.Name == "body" {
body = &param
}
}
if body == nil {
continue
}
ref := extractRef(*body.Schema)
component, exists, err := pathExtensionComponent(verb.Extensions)
if err != nil {
return nil, errors.Wrapf(err, "extract component for %s", ref)
}
if exists {
m[ref] = component
}
}
}
return m, nil
}
// pathExtensionComponent generates a component from a method tpe extension
func pathExtensionComponent(extensions spec.Extensions) (Component, bool, error) {
for x, v := range extensions {
if x == extensionGroupVersionKind {
gvk, ok := v.(map[string]interface{})
if !ok {
return Component{}, false, errors.New("gvk extension was invalid")
}
component := Component{
Group: gvk["group"].(string),
Version: gvk["version"].(string),
Kind: gvk["kind"].(string),
}
return component, true, nil
}
}
return Component{}, false, nil
}
package ksonnet
import (
"strings"
"github.com/go-openapi/spec"
"github.com/pkg/errors"
)
var (
recursiveRefs = []string{
"io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.JSONSchemaProps",
}
)
func extractProperties(c *Catalog, properties map[string]spec.Schema, required []string) (map[string]Property, error) {
if c == nil {
return nil, errors.New("catalog is nil")
}
out := make(map[string]Property)
for name, schema := range properties {
if isSkippedProperty(name, schema) {
if !stringInSlice(name, required) {
continue
}
}
ref := extractRef(schema)
if ref != "" && stringInSlice(ref, recursiveRefs) {
out[name] = NewLiteralField(name, "object", schema.Description, ref)
continue
}
// literal
if t := schema.Type; len(t) == 1 {
out[name] = buildLiteralField(t[0], name, schema)
continue
}
ifr, err := c.isFormatRef(ref)
if err != nil {
return nil, errors.Wrap(err, "check for format ref")
}
if ifr {
// don't have to check for existence here because isFormatRef does the same thing
formatSchema := c.apiSpec.Definitions[ref]
out[name] = buildLiteralField(fieldType(formatSchema), name, schema)
continue
}
// must be a mixin
f := NewReferenceField(name, schema.Description, ref)
out[name] = f
}
return out, nil
}
func buildLiteralField(fieldType, name string, schema spec.Schema) *LiteralField {
var itemRef string
if schema.Items != nil && schema.Items.Schema != nil {
itemRef = extractRef(*schema.Items.Schema)
}
return NewLiteralField(name, fieldType, schema.Description, itemRef)
}
func isSkippedProperty(name string, schema spec.Schema) bool {
if stringInSlice(name, blockedPropertyNames) {
return true
}
if strings.Contains(strings.ToLower(schema.Description), "read-only") {
return true
}
ref := extractRef(schema)
if stringInSlice(ref, blockedReferences) {
return true
}
return false
}
func fieldType(schema spec.Schema) string {
if t := schema.Type; len(t) == 1 {
return t[0]
}
return ""
}
package ksonnet
import (
"fmt"
"sort"
"strings"
nm "github.com/ksonnet/ksonnet-lib/ksonnet-gen/nodemaker"
"github.com/pkg/errors"
)
// renderer is an item that can be rendered.
type renderer interface {
Render(parent *nm.Object) error
}
type baseRenderer struct {
name string
description string
parent string
ref string
}
func newBaseRenderer(field Property, parent string) baseRenderer {
return baseRenderer{
name: field.Name(),
description: field.Description(),
parent: parent,
ref: field.Ref(),
}
}
func (r *baseRenderer) setter() string {
return fieldName(r.name, false)
}
func (r *baseRenderer) mixin() string {
return fieldName(r.name, true)
}
// LiteralFieldRenderer renders a literal field.
type LiteralFieldRenderer struct {
lf *LiteralField
parentName string
}
// NewLiteralFieldRenderer creates an instance of LiteralField.
func NewLiteralFieldRenderer(lf *LiteralField, parentName string) *LiteralFieldRenderer {
return &LiteralFieldRenderer{
lf: lf,
parentName: parentName,
}
}
// Render renders the literal field in the container.
func (r *LiteralFieldRenderer) Render(container *nm.Object) error {
var rndr renderer
switch ft := r.lf.FieldType(); ft {
case "array":
rndr = NewArrayRenderer(r.lf, r.parentName)
case "object":
rndr = NewObjectRenderer(r.lf, r.parentName)
case "string", "boolean", "integer", "number":
rndr = NewItemRenderer(r.lf, r.parentName)
default:
return errors.Errorf("unknown literal field type %s", ft)
}
return rndr.Render(container)
}
// ReferenceRenderer renders a reference field.
type ReferenceRenderer struct {
baseRenderer
rf *ReferenceField
tl typeLookup
}
// NewReferenceRenderer creates an instance of ReferenceRenderer.
func NewReferenceRenderer(rf *ReferenceField, tl typeLookup, parent string) *ReferenceRenderer {
return &ReferenceRenderer{
baseRenderer: newBaseRenderer(rf, parent),
tl: tl,
rf: rf,
}
}
// Render renders the reference in the container.
func (r *ReferenceRenderer) Render(container *nm.Object) error {
name := r.rf.Name()
desc := r.rf.Description()
mo := nm.NewObject()
mixinPreamble(mo, r.parent, name)
ref := r.rf.Ref()
ty, err := r.tl.Field(ref)
if err != nil {
return errors.Wrapf(err, "fetch type %s", ref)
}
renderFields(r.tl, mo, name, ty.Properties())
formattedName := FormatKind(r.rf.Name())
container.Set(nm.NewKey(formattedName, nm.KeyOptComment(desc)), mo)
_ = genTypeAliasEntry(container, name, ref)
return nil
}
// ObjectRenderer renders an object field.
type ObjectRenderer struct {
baseRenderer
}
// NewObjectRenderer creates an instance of ObjectRenderer
func NewObjectRenderer(field Property, parent string) *ObjectRenderer {
return &ObjectRenderer{
baseRenderer: newBaseRenderer(field, parent),
}
}
// Render renders the object field in the container.
func (r *ObjectRenderer) Render(container *nm.Object) error {
wrapper := mixinName(r.parent)
setterFn := createObjectWithField(r.name, wrapper, false)
setProperty(container, r.setter(), r.description, []string{FormatKind(r.name)}, setterFn)
mixinFn := createObjectWithField(r.name, wrapper, true)
setProperty(container, r.mixin(), r.description, []string{FormatKind(r.name)}, mixinFn)
_ = genTypeAliasEntry(container, r.name, r.ref)
return nil
}
// ItemRenderer renders items.
type ItemRenderer struct {
baseRenderer
}
var _ renderer = (*ItemRenderer)(nil)
// NewItemRenderer creates an instance of ItemRenderer.
func NewItemRenderer(f Property, parent string) *ItemRenderer {
return &ItemRenderer{baseRenderer: newBaseRenderer(f, parent)}
}
// Render renders an item in its parent object.
func (r *ItemRenderer) Render(parent *nm.Object) error {
noder := createObjectWithField(r.name, mixinName(r.parent), false)
setProperty(parent, r.setter(), r.description, []string{FormatKind(r.name)}, noder)
_ = genTypeAliasEntry(parent, r.name, r.ref)
return nil
}
// ArrayRenderer renders arrays.
type ArrayRenderer struct {
baseRenderer
}
// NewArrayRenderer creates an instance of ArrayRenderer.
func NewArrayRenderer(f Property, parent string) *ArrayRenderer {
return &ArrayRenderer{baseRenderer: newBaseRenderer(f, parent)}
}
// Render renders an item in its parent object.
func (r *ArrayRenderer) Render(container *nm.Object) error {
wrapper := mixinName(r.parent)
setterFn := convertToArray(r.name, wrapper, false)
setProperty(container, r.setter(), r.description, []string{FormatKind(r.name)}, setterFn)
mixinFn := convertToArray(r.name, wrapper, true)
setProperty(container, r.mixin(), r.description, []string{FormatKind(r.name)}, mixinFn)
_ = genTypeAliasEntry(container, r.name, r.ref)
return nil
}
func convertToArray(varName, parent string, mixin bool) nm.Noder {
apply := nm.NewApply(
nm.NewCall("std.type"),
[]nm.Noder{nm.NewVar(FormatKind(varName))},
nil)
test := nm.NewBinary(apply, nm.NewStringDouble("array"), nm.BopEqual)
var trueBranch nm.Noder
var falseBranch nm.Noder
trueO := nm.OnelineObject()
trueO.Set(
nm.InheritedKey(varName, nm.KeyOptMixin(mixin)),
nm.NewVar(FormatKind(varName)))
falseO := nm.OnelineObject()
falseO.Set(
nm.InheritedKey(varName, nm.KeyOptMixin(mixin)),
nm.NewArray([]nm.Noder{nm.NewVar(FormatKind(varName))}))
if parent == "" {
trueBranch = trueO
falseBranch = falseO
} else {
trueBranch = nm.NewApply(nm.NewCall(parent), []nm.Noder{trueO}, nil)
falseBranch = nm.NewApply(nm.NewCall(parent), []nm.Noder{falseO}, nil)
}
return nm.NewConditional(test, trueBranch, falseBranch)
}
// createObjectWithField creates an object with a field. Creates {field: field} or {field+: field}
// if mixin. If it has a parent, it create __parentNameMixin({field: field}).
func createObjectWithField(name, parentName string, mixin bool) nm.Noder {
var noder nm.Noder
io := nm.OnelineObject()
io.Set(nm.InheritedKey(name, nm.KeyOptMixin(mixin)), nm.NewVar(FormatKind(name)))
if parentName == "" {
noder = io
} else {
noder = nm.NewApply(nm.NewCall(parentName), []nm.Noder{io}, nil)
}
return noder
}
func setProperty(o *nm.Object, fnName, desc string, args []string, node nm.Noder) {
node = nm.NewBinary(&nm.Self{}, node, nm.BopPlus)
key := nm.FunctionKey(fnName, args, nm.KeyOptComment(desc))
o.Set(key, node)
}
func mixinPreamble(o *nm.Object, parent, name string) error {
if o == nil {
return errors.New("parent object is nil")
}
name = FormatKind(name)
formattedName := mixinName(name)
var noder nm.Noder
io := nm.OnelineObject()
io.Set(nm.InheritedKey(name, nm.KeyOptMixin(true)), nm.NewVar(name))
if parent == "" {
noder = io
} else {
noder = nm.ApplyCall(mixinName(parent), io)
}
o.Set(nm.LocalKey(formattedName, nm.KeyOptParams([]string{name})), noder)
miFn := nm.NewCall(formattedName)
o.Set(nm.FunctionKey("mixinInstance", []string{name}), nm.NewApply(miFn, []nm.Noder{nm.NewVar(name)}, nil))
return nil
}
func genTypeAliasEntry(container *nm.Object, name, refName string) error {
if refName == "" {
return errors.New("ref name is blank")
}
rd, err := ParseDescription(refName)
if err != nil {
return errors.Wrapf(err, "parse ref name from %q and %q", name, refName)
}
if rd.Group == "" {
rd.Group = "core"
}
if rd.Version == "" {
return errors.Errorf("there is no version in the ref name for %q and %q",
name, refName)
}
kind := FormatKind(rd.Kind)
path := []string{"hidden", rd.Group, rd.Version, kind}
location := strings.Join(path, ".")
typeAliasName := fmt.Sprintf("%sType", name)
c := nm.NewCall(location)
container.Set(nm.NewKey(typeAliasName), c)
return nil
}
// Generates a field name.
func fieldName(name string, isMixin bool) string {
var out string
name = FormatKind(name)
out = fmt.Sprintf("with%s", strings.Title(name))
if isMixin {
return fmt.Sprintf("%s%s", out, "Mixin")
}
return out
}
func mixinName(name string) string {
if name == "" {
return ""
}
name = FormatKind(name)
return fmt.Sprintf("__%sMixin", name)
}
// typeLookup can look up types by id.
type typeLookup interface {
Field(id string) (*Field, error)
}
type renderFieldsFn func(tl typeLookup, parent *nm.Object, parentName string, props map[string]Property) error
// renderFields renders fields from a property map.
func renderFields(tl typeLookup, parent *nm.Object, parentName string, props map[string]Property) error {
container := parent
if parentName == "" {
container = nm.NewObject()
}
var names []string
for name := range props {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
field := props[name]
switch t := field.(type) {
case *LiteralField:
r := NewLiteralFieldRenderer(t, parentName)
if err := r.Render(parent); err != nil {
return errors.Wrap(err, "render literal field")
}
case *ReferenceField:
r := NewReferenceRenderer(t, tl, parentName)
if err := r.Render(container); err != nil {
return errors.Wrap(err, "render reference field")
}
default:
return errors.Errorf("unknown field type %T", t)
}
}
if parentName == "" {
parent.Set(nm.NewKey("mixin"), container)
}
return nil
}
package ksonnet
import (
"bytes"
"strings"
"unicode"
)
var (
jsonnetKeywords = []string{"assert", "else", "error", "false", "for", "function", "if",
"import", "importstr", "in", "null", "tailstrict", "then", "self", "super",
"true"}
)
// camelCase converts a string to camel case.
func camelCase(in string) string {
out := ""
for i, r := range in {
if i == 0 {
out += strings.ToLower(string(r))
continue
}
out += string(r)
}
return out
}
// stringInSlice returns true if the string is in the slice.
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// capitalizer adjusts the case of terms found in a string.
func toLower(b byte) byte {
return byte(unicode.ToLower(rune(b)))
}
func isUpper(b byte) bool {
return unicode.IsUpper(rune(b))
}
// capitalize adjusts the case of terms found in a string. It will convert `HTTPHeader` into
// `HttpHeader`.
func capitalize(in string) string {
l := len(in) - 1
if l == 0 {
// nothing to do when there is a one character strings
return in
}
var b bytes.Buffer
b.WriteByte(in[0])
for i := 1; i <= l; i++ {
if isUpper(in[i-1]) {
if i < l {
if isUpper(in[i+1]) || (isUpper(in[i]) && i+1 == l) {
b.WriteByte(toLower(in[i]))
} else {
b.WriteByte(in[i])
}
} else if i == l && isUpper(in[i]) {
b.WriteByte(toLower(in[i]))
} else {
b.WriteByte(in[i])
}
} else {
b.WriteByte(in[i])
}
}
return b.String()
}
// FormatKind formats a string in kind format. i.e camel case with jsonnet keywords massaged.
func FormatKind(s string) string {
if strings.ToLower(s) == "local" {
return "localStorage"
}
if strings.HasPrefix(s, "$") {
s = "dollar" + strings.Title(strings.TrimPrefix(s, "$"))
return s
}
s = capitalize(s)
s = camelCase(s)
if stringInSlice(s, jsonnetKeywords) {
s = s + "Param"
}
return s
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment