Unverified Commit e4a5b17e authored by Jian.Li's avatar Jian.Li Committed by GitHub
Browse files

Support import K8s native API and CRD in definition (#1211)

* vela builtin package

* add test cases

* add gvk test cases

* lint

* testEnv

* lint

* check-diff

* Optimized code

* test case split

* add docs
parent 276d5447
Showing with 858 additions and 2 deletions
+858 -2
......@@ -377,3 +377,22 @@ output: {
}
}
```
### Import Kube Package
KubeVela generate a cue package named “kube” by reading K8s openapi from K8s cluster.
You can use package "kube" in CUE Template of kubevela
```cue
import ("kube")
parameter: {
name: string
}
output: kube.#Deployment
output: {
metadata: name: parameter.name
}
```
......@@ -35,6 +35,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/appfile"
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
"github.com/oam-dev/kubevela/pkg/dsl/definition"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
apply "github.com/oam-dev/kubevela/pkg/utils/apply"
......@@ -170,6 +171,9 @@ func (r *Reconciler) UpdateStatus(ctx context.Context, app *v1alpha2.Application
// Setup adds a controller that reconciles AppRollout.
func Setup(mgr ctrl.Manager, _ core.Args, _ logging.Logger) error {
dm, err := discoverymapper.New(mgr.GetConfig())
if err := definition.AddImportFromCluster(mgr.GetConfig()); err != nil {
ctrl.Log.Error(err, "use kubernetes cluster openAPI as rendering package")
}
if err != nil {
return fmt.Errorf("create discovery dm fail %w", err)
}
......
......@@ -87,6 +87,25 @@ var _ = Describe("Test Application Controller", func() {
},
}
appImportPkg := &v1alpha2.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app-import-pkg",
},
Spec: v1alpha2.ApplicationSpec{
Components: []v1alpha2.ApplicationComponent{
{
Name: "myweb",
WorkloadType: "worker-import",
Settings: runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\"}")},
},
},
},
}
var getExpDeployment = func(compName, appName string) *v1.Deployment {
return &v1.Deployment{
TypeMeta: metav1.TypeMeta{
......@@ -164,6 +183,9 @@ var _ = Describe("Test Application Controller", func() {
cd := &v1alpha2.ComponentDefinition{}
cDDefJson, _ := yaml.YAMLToJSON([]byte(cDDefYaml))
importWd := &v1alpha2.WorkloadDefinition{}
importWdJson, _ := yaml.YAMLToJSON([]byte(wDImportYaml))
webserverwd := &v1alpha2.ComponentDefinition{}
webserverwdJson, _ := yaml.YAMLToJSON([]byte(webserverYaml))
......@@ -183,6 +205,9 @@ var _ = Describe("Test Application Controller", func() {
Expect(json.Unmarshal(cDDefJson, cd)).Should(BeNil())
Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(importWdJson, importWd)).Should(BeNil())
Expect(k8sClient.Create(ctx, importWd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(tDDefJson, td)).Should(BeNil())
Expect(k8sClient.Create(ctx, td.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
......@@ -191,7 +216,6 @@ var _ = Describe("Test Application Controller", func() {
Expect(json.Unmarshal(webserverwdJson, webserverwd)).Should(BeNil())
Expect(k8sClient.Create(ctx, webserverwd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
AfterEach(func() {
By("[TEST] Clean up resources after an integration test")
......@@ -1141,6 +1165,61 @@ var _ = Describe("Test Application Controller", func() {
Name: utils.ConstructRevisionName(appMix.Name, 1),
}, appConfig)).Should(BeNil())
})
It("app-import-pkg will create workload by import kube package", func() {
expDeployment := getExpDeployment("myweb", appImportPkg.Name)
expDeployment.Labels["workload.oam.dev/type"] = "worker-import"
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-test-app-import-pkg",
},
}
appImportPkg.SetNamespace(ns.Name)
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
Expect(k8sClient.Create(ctx, appImportPkg.DeepCopyObject())).Should(BeNil())
appKey := client.ObjectKey{
Name: appImportPkg.Name,
Namespace: appImportPkg.Namespace,
}
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
By("Check Application Created")
checkApp := &v1alpha2.Application{}
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(Equal(v1alpha2.ApplicationRunning))
By("Check ApplicationConfiguration Created")
appConfig := &v1alpha2.ApplicationConfiguration{}
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: appImportPkg.Namespace,
Name: utils.ConstructRevisionName(appImportPkg.Name, 1),
}, appConfig)).Should(BeNil())
By("Check Component Created with the expected workload spec")
var component v1alpha2.Component
Expect(k8sClient.Get(ctx, client.ObjectKey{
Namespace: appImportPkg.Namespace,
Name: "myweb",
}, &component)).Should(BeNil())
Expect(component.ObjectMeta.Labels).Should(BeEquivalentTo(map[string]string{oam.LabelAppName: "app-import-pkg"}))
Expect(component.ObjectMeta.OwnerReferences[0].Name).Should(BeEquivalentTo("app-import-pkg"))
Expect(component.ObjectMeta.OwnerReferences[0].Kind).Should(BeEquivalentTo("Application"))
Expect(component.ObjectMeta.OwnerReferences[0].APIVersion).Should(BeEquivalentTo("core.oam.dev/v1alpha2"))
Expect(component.ObjectMeta.OwnerReferences[0].Controller).Should(BeEquivalentTo(pointer.BoolPtr(true)))
Expect(component.Status.LatestRevision).ShouldNot(BeNil())
// check that the new appconfig has the correct annotation and labels
Expect(appConfig.GetAnnotations()[oam.AnnotationAppRollout]).Should(BeEmpty())
Expect(appConfig.GetLabels()[oam.LabelAppConfigHash]).ShouldNot(BeEmpty())
// check the workload created should be the same as the raw data in the component
gotD := &v1.Deployment{}
Expect(json.Unmarshal(component.Spec.Workload.Raw, gotD)).Should(BeNil())
fmt.Println(cmp.Diff(expDeployment, gotD))
Expect(assert.ObjectsAreEqual(expDeployment, gotD)).Should(BeEquivalentTo(true))
By("Delete Application, clean the resource")
Expect(k8sClient.Delete(ctx, appImportPkg)).Should(BeNil())
})
})
func reconcileRetry(r reconcile.Reconciler, req reconcile.Request) {
......@@ -1223,6 +1302,66 @@ spec:
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
cmd?: [...string]
}
`
wDImportYaml = `
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: worker-import
namespace: vela-system
annotations:
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
spec:
definitionRef:
name: deployments.apps
extension:
template: |
import "kube"
output: kube.#Deployment & {
metadata: {
annotations: {
if context["config"] != _|_ {
for _, v in context.config {
"\(v.name)" : v.value
}
}
}
}
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
}]
}
}
selector:
matchLabels:
"app.oam.dev/component": context.name
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
......
......@@ -46,6 +46,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration"
"github.com/oam-dev/kubevela/pkg/dsl/definition"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
// +kubebuilder:scaffold:imports
)
......@@ -109,7 +110,6 @@ var _ = BeforeSuite(func(done Done) {
err = scheme.AddToScheme(testScheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: testScheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
......@@ -145,6 +145,7 @@ var _ = BeforeSuite(func(done Done) {
Expect(err).NotTo(HaveOccurred())
definitonNs := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}}
Expect(k8sClient.Create(context.Background(), definitonNs.DeepCopy())).Should(BeNil())
Expect(definition.AddImportFromCluster(cfg)).Should(BeNil())
// start the controller in the background so that new componentRevisions are created
go func() {
err = ctlManager.Start(stop)
......
package definition
import (
"context"
"fmt"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/token"
"cuelang.org/go/encoding/jsonschema"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/rest"
)
var velaBuiltinPkgs []*build.Instance
func addImportsFor(bi *build.Instance) {
bi.Imports = append(bi.Imports, velaBuiltinPkgs...)
}
// AddImportFromCluster use K8s native API and CRD definition as a reference package in template rendering
func AddImportFromCluster(config *rest.Config) error {
copyConfig := *config
apiSchema, err := getClusterOpenAPI(&copyConfig)
if err != nil {
return err
}
kubePkg := newPackage("kube")
if err := kubePkg.addOpenAPI(apiSchema); err != nil {
return err
}
kubePkg.mount()
return nil
}
func getClusterOpenAPI(config *rest.Config) (string, error) {
config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{})
restClient, err := rest.UnversionedRESTClientFor(config)
if err != nil {
return "", err
}
body, err := restClient.Get().AbsPath("/openapi/v2").Do(context.Background()).Raw()
if err != nil {
return "", err
}
return string(body), nil
}
func openAPIMapping(pos token.Pos, a []string) ([]ast.Label, error) {
if len(a) < 2 {
return nil, errors.New("openAPIMapping format invalid")
}
spl := strings.Split(a[1], ".")
name := spl[len(spl)-1]
if name == "JSONSchemaProps" && pos != token.NoPos {
return []ast.Label{ast.NewIdent("_")}, nil
}
return []ast.Label{ast.NewIdent("#" + name)}, nil
}
type pkgInstance struct {
*build.Instance
}
func newPackage(name string) *pkgInstance {
return &pkgInstance{
&build.Instance{
PkgName: name,
ImportPath: name,
},
}
}
func (pkg *pkgInstance) processOpenAPIFile(f *ast.File) {
ast.Walk(f, func(node ast.Node) bool {
if st, ok := node.(*ast.StructLit); ok {
hasEllipsis := false
for index, elt := range st.Elts {
if _, isEllipsis := elt.(*ast.Ellipsis); isEllipsis {
if hasEllipsis {
st.Elts = st.Elts[:index]
return true
}
if index > 0 {
st.Elts = st.Elts[:index]
return true
}
hasEllipsis = true
}
}
}
return true
}, nil)
for _, decl := range f.Decls {
if field, ok := decl.(*ast.Field); ok {
if val, ok := field.Value.(*ast.Ident); ok && val.Name == "string" {
field.Value = ast.NewBinExpr(token.OR, ast.NewIdent("int"), ast.NewIdent("string"))
}
}
}
}
func (pkg *pkgInstance) addOpenAPI(apiSchema string) error {
var r cue.Runtime
oaInst, err := r.Compile("-", apiSchema)
if err != nil {
return err
}
kinds := map[string]metav1.GroupVersionKind{}
pathv := oaInst.Value().Lookup("paths")
if pathv.Exists() {
if st, err := pathv.Struct(); err == nil {
iter := st.Fields()
for iter.Next() {
gvk := iter.Value().Lookup("post",
"x-kubernetes-group-version-kind")
if gvk.Exists() {
if v, err := getGVK(gvk); err == nil {
kinds["#"+v.Kind] = v
}
}
}
}
}
oaFile, err := jsonschema.Extract(oaInst, &jsonschema.Config{
Root: "#/definitions",
Map: openAPIMapping,
})
if err != nil {
return err
}
for k, v := range kinds {
apiversion := v.Version
if v.Group != "" {
apiversion = v.Group + "/" + apiversion
}
def := fmt.Sprintf(`%s: {
kind: "%s"
apiVersion: "%s",
}`, k, v.Kind, apiversion)
if err := pkg.AddFile(k, def); err != nil {
return err
}
}
pkg.processOpenAPIFile(oaFile)
return pkg.AddSyntax(oaFile)
}
func (pkg *pkgInstance) mount() {
for i := range velaBuiltinPkgs {
if velaBuiltinPkgs[i].ImportPath == pkg.ImportPath {
velaBuiltinPkgs[i] = pkg.Instance
return
}
}
velaBuiltinPkgs = append(velaBuiltinPkgs, pkg.Instance)
}
func getGVK(v cue.Value) (metav1.GroupVersionKind, error) {
ret := metav1.GroupVersionKind{}
var err error
ret.Group, err = v.Lookup("group").String()
if err != nil {
return ret, err
}
ret.Version, err = v.Lookup("version").String()
if err != nil {
return ret, err
}
ret.Kind, err = v.Lookup("kind").String()
return ret, err
}
package definition
import (
"fmt"
"testing"
"cuelang.org/go/cue"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/parser"
"cuelang.org/go/cue/token"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/pkg/dsl/model"
)
func TestPackage(t *testing.T) {
var openAPISchema = `
{
"paths": {
"paths...": {
"post":{
"x-kubernetes-group-version-kind": {
"group": "test.io",
"kind": "Bucket",
"version": "v1"
}
}
}
},
"definitions":{
"Bucket":{
"properties":{
"apiVersion": {"type": "string"}
"kind": {"type": "string"}
"acl":{
"default":"private",
"enum":[
"public-read-write",
"public-read",
"private"
],
"type":"string"
},
"dataRedundancyType":{
"default":"LRS",
"enum":[
"LRS",
"ZRS"
],
"type":"string"
},
"dataSourceRef":{
"properties":{
"dsPath":{
"type":"string"
}
},
"required":[
"dsPath"
],
"type":"object"
},
"importRef":{
"properties":{
"importKey":{
"type":"string"
}
},
"required":[
"importKey"
],
"type":"object"
},
"output":{
"additionalProperties":{
"oneOf":[
{
"properties":{
"outRef":{
"type":"string"
}
},
"required":[
"outRef"
]
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
]
}
],
"type":"object"
},
"properties":{
"bucketName":{
"properties":{
"outRef":{
"enum":[
"self.name"
],
"type":"string"
}
},
"required":[
"outRef"
],
"type":"object"
},
"extranetEndpoint":{
"properties":{
"outRef":{
"enum":[
"self.state.extranetEndpoint"
],
"type":"string"
}
},
"required":[
"outRef"
],
"type":"object"
},
"intranetEndpoint":{
"properties":{
"outRef":{
"enum":[
"self.state.intranetEndpoint"
],
"type":"string"
}
},
"required":[
"outRef"
],
"type":"object"
},
"masterUserId":{
"properties":{
"outRef":{
"enum":[
"self.state.masterUserId"
],
"type":"string"
}
},
"required":[
"outRef"
],
"type":"object"
}
},
"required":[
"bucketName",
"extranetEndpoint",
"intranetEndpoint",
"masterUserId"
],
"type":"object"
},
"profile":{
"properties":{
"baasRepo":{
"oneOf":[
{
"type":"string"
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
],
"type":"object"
}
]
},
"cloudProduct":{
"enum":[
"AliCloudOSS"
],
"type":"string"
},
"endpoint":{
"oneOf":[
{
"type":"string"
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
],
"type":"object"
}
]
},
"envType":{
"oneOf":[
{
"enum":[
"testing",
"product"
]
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
],
"type":"object"
}
]
},
"provider":{
"enum":[
"alicloud"
],
"type":"string"
},
"region":{
"oneOf":[
{
"type":"string"
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
],
"type":"object"
}
]
},
"serviceAccount":{
"oneOf":[
{
"type":"string"
},
{
"properties":{
"valueRef":{
"description":"Example: demoVpc.vpcId",
"type":"string"
}
},
"required":[
"valueRef"
],
"type":"object"
}
]
}
},
"required":[
"cloudProduct",
"provider",
"baasRepo",
"region"
],
"type":"object"
},
"storageClass":{
"default":"Standard",
"enum":[
"Standard",
"IA",
"Archive",
"ColdArchive"
],
"type":"string"
},
"type":{
"enum":[
"alicloud_oss_bucket"
],
"type":"string"
}
},
"required":[
"type",
"output",
"profile",
"acl"
],
"type":"object"
}
}
}
`
pkg := newPackage("oss")
assert.NilError(t, pkg.addOpenAPI(openAPISchema))
pkg.mount()
bi := build.NewContext().NewInstance("", nil)
addImportsFor(bi)
bi.AddFile("-", `
import "oss"
output: oss.#Bucket
`)
var r cue.Runtime
inst, err := r.Build(bi)
assert.NilError(t, err)
base, err := model.NewBase(inst.Value())
assert.NilError(t, err)
exceptObj := `output: close({
kind: "Bucket"
apiVersion: "test.io/v1"
type: "alicloud_oss_bucket"
acl: "public-read-write" | "public-read" | *"private"
dataRedundancyType?: "ZRS" | *"LRS"
dataSourceRef?: close({
dsPath: string
})
importRef?: close({
importKey: string
})
output: close({
{[!~"^(bucketName|extranetEndpoint|intranetEndpoint|masterUserId)$"]: {
outRef: string
} | {
// Example: demoVpc.vpcId
valueRef: string
}}
bucketName: close({
outRef: "self.name"
})
extranetEndpoint: close({
outRef: "self.state.extranetEndpoint"
})
intranetEndpoint: close({
outRef: "self.state.intranetEndpoint"
})
masterUserId: close({
outRef: "self.state.masterUserId"
})
})
profile: close({
baasRepo: string | close({
// Example: demoVpc.vpcId
valueRef: string
})
cloudProduct: "AliCloudOSS"
endpoint?: string | close({
// Example: demoVpc.vpcId
valueRef: string
})
envType?: "testing" | "product" | close({
// Example: demoVpc.vpcId
valueRef: string
})
provider: "alicloud"
region: string | close({
// Example: demoVpc.vpcId
valueRef: string
})
serviceAccount?: string | close({
// Example: demoVpc.vpcId
valueRef: string
})
})
storageClass?: "IA" | "Archive" | "ColdArchive" | *"Standard"
})
`
assert.Equal(t, base.String(), exceptObj)
}
func TestProcessFile(t *testing.T) {
srcTmpl := `
#Definition: {
kind?: string
apiVersion?: string
metadata: {
name: string
...
}
...
}
`
file, err := parser.ParseFile("-", srcTmpl)
assert.NilError(t, err)
testPkg := newPackage("foo")
testPkg.processOpenAPIFile(file)
var r cue.Runtime
inst, err := r.CompileFile(file)
assert.NilError(t, err)
testCasesInst, err := r.Compile("-", `
#Definition: {}
case1: #Definition & {additionalProperty: "test"}
case2: #Definition & {
metadata: {
additionalProperty: "test"
}
}
`)
assert.NilError(t, err)
retInst, err := inst.Fill(testCasesInst.Value())
assert.NilError(t, err)
assert.Error(t, retInst.Lookup("case1").Err(), "case1: field \"additionalProperty\" not allowed in closed struct")
assert.Error(t, retInst.Lookup("case2", "metadata").Err(), "case2.metadata: field \"additionalProperty\" not allowed in closed struct")
}
func TestMount(t *testing.T) {
velaBuiltinPkgs = nil
testPkg := newPackage("foo")
testPkg.mount()
assert.Equal(t, len(velaBuiltinPkgs), 1)
testPkg.mount()
assert.Equal(t, len(velaBuiltinPkgs), 1)
assert.Equal(t, velaBuiltinPkgs[0], testPkg.Instance)
}
func TestGetGVK(t *testing.T) {
srcTmpl := `
{
"x-kubernetes-group-version-kind": {
"group": "test.io",
"kind": "Foo",
"version": "v1"
}
}
`
var r cue.Runtime
inst, err := r.Compile("-", srcTmpl)
assert.NilError(t, err)
gvk, err := getGVK(inst.Value().Lookup("x-kubernetes-group-version-kind"))
assert.NilError(t, err)
assert.Equal(t, gvk, metav1.GroupVersionKind{
Group: "test.io",
Version: "v1",
Kind: "Foo",
})
}
func TestOpenAPIMapping(t *testing.T) {
testCases := []struct {
input []string
pos token.Pos
result string
errMsg string
}{
{
input: []string{"definitions", "io.k8s.api.discovery.v1beta1.Endpoint"},
pos: token.NoPos,
result: "[#Endpoint]",
},
{
input: []string{"definitions", "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"},
pos: token.NoPos.Add(1),
result: "[_]",
},
{
input: []string{"definitions", "io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps"},
pos: token.NoPos,
result: "[#JSONSchemaProps]",
},
{
input: []string{"definitions"},
pos: token.NoPos,
errMsg: "openAPIMapping format invalid",
},
}
for _, tCase := range testCases {
labels, err := openAPIMapping(tCase.pos, tCase.input)
if tCase.errMsg != "" {
assert.Error(t, err, tCase.errMsg)
continue
}
assert.NilError(t, err)
assert.Equal(t, len(labels), 1)
assert.Equal(t, tCase.result, fmt.Sprint(labels))
}
}
......@@ -89,6 +89,8 @@ func (wd *workloadDef) Complete(ctx process.Context, abstractTemplate string) er
if err := bi.AddFile("-", ctx.BaseContextFile()); err != nil {
return err
}
addImportsFor(bi)
instances := cue.Build([]*build.Instance{bi})
for _, inst := range instances {
if err := inst.Value().Err(); err != nil {
......@@ -269,6 +271,8 @@ func (td *traitDef) Complete(ctx process.Context, abstractTemplate string) error
if err := bi.AddFile("context", ctx.BaseContextFile()); err != nil {
return errors.WithMessagef(err, "invalid context of trait %s", td.name)
}
addImportsFor(bi)
instances := cue.Build([]*build.Instance{bi})
for _, inst := range instances {
if err := inst.Value().Err(); err != nil {
......
Supports Markdown
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