mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
use cue template instead of parameter
This commit is contained in:
+31
-13
@@ -20,16 +20,20 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// Template defines the content of a plugin
|
||||
type Template struct {
|
||||
Name string `json:"name"`
|
||||
Type DefinitionType `json:"type"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Object map[string]interface{} `json:"object,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type DefinitionType `json:"type"`
|
||||
Template string `json:"template,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
DefinitionPath string `json:"definition"`
|
||||
}
|
||||
|
||||
type DefinitionType string
|
||||
@@ -40,13 +44,12 @@ const (
|
||||
)
|
||||
|
||||
type Parameter struct {
|
||||
Name string `json:"name"`
|
||||
Short string `json:"short,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
FieldPaths []string `json:"fieldPaths"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Usage string `json:"usage,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Short string `json:"short,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Usage string `json:"usage,omitempty"`
|
||||
Type cue.Kind `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// ConvertTemplateJson2Object convert spec.extension to object
|
||||
@@ -63,6 +66,21 @@ func ConvertTemplateJson2Object(in *runtime.RawExtension) (Template, error) {
|
||||
if err == nil {
|
||||
t = extension
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
func SetFlagBy(cmd *cobra.Command, v Parameter) {
|
||||
switch v.Type {
|
||||
case cue.IntKind:
|
||||
cmd.Flags().Int64P(v.Name, v.Short, v.Default.(int64), v.Usage)
|
||||
case cue.StringKind:
|
||||
cmd.Flags().StringP(v.Name, v.Short, v.Default.(string), v.Usage)
|
||||
case cue.BoolKind:
|
||||
cmd.Flags().BoolP(v.Name, v.Short, v.Default.(bool), v.Usage)
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
cmd.Flags().Float64P(v.Name, v.Short, v.Default.(float64), v.Usage)
|
||||
}
|
||||
if v.Required && v.Name != "name" {
|
||||
cmd.MarkFlagRequired(v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultOAMNS = "oam-system"
|
||||
DefaultOAMReleaseName = "core-runtime"
|
||||
@@ -7,4 +13,115 @@ const (
|
||||
DefaultOAMRepoName = "crossplane-master"
|
||||
DefaultOAMRepoUrl = "https://charts.crossplane.io/master"
|
||||
DefaultOAMVersion = ">0.0.0-0"
|
||||
|
||||
DefaultEnvName = "default"
|
||||
)
|
||||
|
||||
const (
|
||||
Traits = "traits"
|
||||
Scopes = "scopes"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Name string `json:"name"`
|
||||
// key of map is component name
|
||||
Components map[string]map[string]interface{} `json:"components"`
|
||||
Secrets map[string]map[string]interface{} `json:"secrets"`
|
||||
Scopes map[string]map[string]interface{} `json:"appScopes"`
|
||||
}
|
||||
|
||||
func (app *Application) Valid() error {
|
||||
if app.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
if len(app.Components) == 0 {
|
||||
return errors.New("at least one component is required")
|
||||
}
|
||||
for name, comp := range app.Components {
|
||||
lenth := len(comp)
|
||||
if traits, ok := comp[Traits]; ok {
|
||||
lenth--
|
||||
trs, ok := traits.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("format of traits in %s must be map", name)
|
||||
}
|
||||
for traitName, tr := range trs {
|
||||
_, ok := tr.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("trait %s in %s must be map", traitName, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if scopes, ok := comp[Scopes]; ok {
|
||||
lenth--
|
||||
_, ok := scopes.([]string)
|
||||
if !ok {
|
||||
return fmt.Errorf("format of scopes in %s must be string array", name)
|
||||
}
|
||||
}
|
||||
if lenth != 1 {
|
||||
return fmt.Errorf("you must have only one workload in component %s", name)
|
||||
}
|
||||
for workloadType, workload := range comp {
|
||||
if NotWorkload(workloadType) {
|
||||
continue
|
||||
}
|
||||
_, ok := workload.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("format of workload in %s must be map", name)
|
||||
}
|
||||
//TODO(wonderflow) check workload type exists
|
||||
//TODO(wonderflow) check arguments of workload is valid
|
||||
}
|
||||
}
|
||||
//TODO(wonderflow) check scope types
|
||||
return nil
|
||||
}
|
||||
|
||||
func NotWorkload(tp string) bool {
|
||||
if tp == Scopes || tp == Traits {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (app *Application) GetComponents() []string {
|
||||
var components []string
|
||||
for name := range app.Components {
|
||||
components = append(components, name)
|
||||
}
|
||||
sort.Strings(components)
|
||||
return components
|
||||
}
|
||||
|
||||
func (app *Application) GetWorkload(componentName string) (string, map[string]interface{}, error) {
|
||||
comp, ok := app.Components[componentName]
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("%s not exist", componentName)
|
||||
}
|
||||
for tp, workload := range comp {
|
||||
if NotWorkload(tp) {
|
||||
continue
|
||||
}
|
||||
return tp, workload.(map[string]interface{}), nil
|
||||
}
|
||||
return "", nil, fmt.Errorf("workload not exist in %s", componentName)
|
||||
}
|
||||
|
||||
func (app *Application) GetTraits(componentName string) (map[string]interface{}, error) {
|
||||
comp, ok := app.Components[componentName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s not exist", componentName)
|
||||
}
|
||||
t, ok := comp[Traits]
|
||||
if !ok {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
// assume it's valid, use Valid() to check
|
||||
traits := t.(map[string]interface{})
|
||||
return traits, nil
|
||||
}
|
||||
|
||||
type EnvMeta struct {
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestApplication(t *testing.T) {
|
||||
yaml1 := `name: myapp
|
||||
components:
|
||||
frontend:
|
||||
deployment:
|
||||
image: inanimate/echo-server
|
||||
env:
|
||||
PORT: 8080
|
||||
traits:
|
||||
autoscaling:
|
||||
max: 10
|
||||
min: 1
|
||||
rollout:
|
||||
strategy: canary
|
||||
step: 5
|
||||
backend:
|
||||
cloneset:
|
||||
image: "back:v1"
|
||||
`
|
||||
yaml2 := `name: myapp`
|
||||
yaml3 := `components:
|
||||
frontend:
|
||||
deployment:
|
||||
image: inanimate/echo-server
|
||||
env:
|
||||
PORT: 8080`
|
||||
yaml4 := `name: myapp
|
||||
components:
|
||||
frontend:
|
||||
deployment:
|
||||
image: inanimate/echo-server
|
||||
scopes:
|
||||
public-scope: true
|
||||
appScopes:
|
||||
public-scope:
|
||||
networkPolicy: public`
|
||||
yaml5 := `name: myapp
|
||||
components:
|
||||
frontend:
|
||||
traits:
|
||||
rollout:
|
||||
strategy: canary
|
||||
step: 5
|
||||
backend:
|
||||
cloneset:
|
||||
image: "back:v1"
|
||||
`
|
||||
yaml6 := `name: myapp
|
||||
components:
|
||||
frontend:
|
||||
deployment:
|
||||
image: inanimate/echo-server
|
||||
traits:
|
||||
autoscaling: 10`
|
||||
|
||||
cases := map[string]struct {
|
||||
raw string
|
||||
InValid bool
|
||||
InvalidReason error
|
||||
ExpName string
|
||||
ExpComponents []string
|
||||
WantWorkload string
|
||||
ExpWorklaod map[string]interface{}
|
||||
ExpWorkloadType string
|
||||
ExpTraits map[string]interface{}
|
||||
}{
|
||||
"normal case backend": {
|
||||
raw: yaml1,
|
||||
ExpName: "myapp",
|
||||
ExpComponents: []string{"backend", "frontend"},
|
||||
WantWorkload: "backend",
|
||||
ExpWorklaod: map[string]interface{}{
|
||||
"image": "back:v1",
|
||||
},
|
||||
ExpWorkloadType: "cloneset",
|
||||
ExpTraits: map[string]interface{}{},
|
||||
},
|
||||
"normal case frontend": {
|
||||
raw: yaml1,
|
||||
ExpName: "myapp",
|
||||
ExpComponents: []string{"backend", "frontend"},
|
||||
WantWorkload: "frontend",
|
||||
ExpWorklaod: map[string]interface{}{
|
||||
"image": "inanimate/echo-server",
|
||||
"env": map[string]interface{}{
|
||||
"PORT": 8080,
|
||||
},
|
||||
},
|
||||
ExpWorkloadType: "deployment",
|
||||
ExpTraits: map[string]interface{}{
|
||||
"autoscaling": map[string]interface{}{
|
||||
"max": 10,
|
||||
"min": 1,
|
||||
},
|
||||
"rollout": map[string]interface{}{
|
||||
"strategy": "canary",
|
||||
"step": 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
"no component": {
|
||||
raw: yaml2,
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: errors.New("at least one component is required"),
|
||||
},
|
||||
"no name": {
|
||||
raw: yaml3,
|
||||
ExpName: "",
|
||||
InValid: true,
|
||||
InvalidReason: errors.New("name is required"),
|
||||
},
|
||||
"scopes not array": {
|
||||
raw: yaml4,
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: fmt.Errorf("format of scopes in frontend must be string array"),
|
||||
},
|
||||
"workload not exist": {
|
||||
raw: yaml5,
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: fmt.Errorf("you must have only one workload in component frontend"),
|
||||
},
|
||||
"trait must be map": {
|
||||
raw: yaml6,
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: fmt.Errorf("trait autoscaling in frontend must be map"),
|
||||
},
|
||||
}
|
||||
for caseName, c := range cases {
|
||||
var app Application
|
||||
err := yaml.Unmarshal([]byte(c.raw), &app)
|
||||
assert.NoError(t, err, caseName)
|
||||
err = app.Valid()
|
||||
if c.InValid {
|
||||
assert.Equal(t, c.InvalidReason, err)
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, c.ExpName, app.Name, caseName)
|
||||
assert.Equal(t, c.ExpComponents, app.GetComponents(), caseName)
|
||||
workloadType, workload, err := app.GetWorkload(c.WantWorkload)
|
||||
assert.NoError(t, err, caseName)
|
||||
assert.Equal(t, c.ExpWorklaod, workload, caseName)
|
||||
assert.Equal(t, c.ExpWorkloadType, workloadType, caseName)
|
||||
traits, err := app.GetTraits(c.WantWorkload)
|
||||
assert.NoError(t, err, caseName)
|
||||
assert.Equal(t, c.ExpTraits, traits)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
|
||||
"github.com/spf13/cobra"
|
||||
k8sruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -77,6 +79,14 @@ func newCommand() *cobra.Command {
|
||||
fmt.Println("create client from kubeconfig err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := system.InitApplicationDir(); err != nil {
|
||||
fmt.Println("InitApplicationDir err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := system.InitDefinitionDir(); err != nil {
|
||||
fmt.Println("InitDefinitionDir err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cmds.AddCommand(
|
||||
cmd.NewTraitsCommand(f, client, ioStream, []string{}),
|
||||
|
||||
@@ -8,16 +8,15 @@ spec:
|
||||
definitionRef:
|
||||
name: manualscalertraits.core.oam.dev
|
||||
extension:
|
||||
alias: ManualScaler
|
||||
object:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ManualScalerTrait
|
||||
spec:
|
||||
replicaCount: 2
|
||||
parameters:
|
||||
- name: replicaCount
|
||||
required: true
|
||||
type: int
|
||||
default: "5"
|
||||
fieldPaths:
|
||||
- "spec.replicaCount"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: manualscaler.replica
|
||||
}
|
||||
}
|
||||
manualscaler: {
|
||||
//+short=r
|
||||
replica: *2 | int
|
||||
}
|
||||
|
||||
@@ -10,35 +10,18 @@ spec:
|
||||
definitionRef:
|
||||
name: simplerollouttraits.extend.oam.dev
|
||||
extension:
|
||||
alias: SimpleRollout
|
||||
object:
|
||||
apiVersion: extend.oam.dev/v1alpha2
|
||||
kind: SimpleRolloutTrait
|
||||
metadata:
|
||||
name: example-rollout-trait
|
||||
spec:
|
||||
replica: 6
|
||||
maxUnavailable: 2
|
||||
batch: 2
|
||||
parameters:
|
||||
- name: replica
|
||||
required: true
|
||||
type: int
|
||||
default: "6"
|
||||
short: r
|
||||
fieldPaths:
|
||||
- "spec.replica"
|
||||
- name: maxUnavailable
|
||||
required: true
|
||||
type: int
|
||||
default: "2"
|
||||
short: u
|
||||
fieldPaths:
|
||||
- "spec.maxUnavailable"
|
||||
- name: batch
|
||||
required: true
|
||||
type: int
|
||||
default: "2"
|
||||
short: b
|
||||
fieldPaths:
|
||||
- "spec.batch"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "extend.oam.dev/v1alpha2"
|
||||
kind: "SimpleRolloutTrait"
|
||||
spec: {
|
||||
replica: rollout.replica
|
||||
maxUnavailable: rollout.maxUnavailable
|
||||
batch: rollout.batch
|
||||
}
|
||||
}
|
||||
rollout: {
|
||||
replica: *3 | int
|
||||
maxUnavailable: *1 | int
|
||||
batch: *2 | int
|
||||
}
|
||||
|
||||
@@ -11,30 +11,30 @@ spec:
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
extension:
|
||||
alias: containerized
|
||||
object:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: ContainerizedWorkload
|
||||
metadata:
|
||||
name: tbd
|
||||
spec:
|
||||
containers:
|
||||
- image: myrepo/myapp:v1
|
||||
name: master
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
protocol: TCP
|
||||
name: tbd # TODO(zzxwill) A temporary workaround for ContainerizedWorkload
|
||||
parameters:
|
||||
- name: image
|
||||
short: i
|
||||
required: true
|
||||
type: string
|
||||
fieldPaths:
|
||||
- "spec.containers[0].image"
|
||||
- name: port
|
||||
short: p
|
||||
required: false
|
||||
type: int
|
||||
fieldPaths:
|
||||
- "spec.containers[0].ports[0].containerPort"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ContainerizedWorkload"
|
||||
metadata: name: containerized.name
|
||||
spec: {
|
||||
containers: [{
|
||||
image: containerized.image
|
||||
name: containerized.name
|
||||
ports: [{
|
||||
containerPort: containerized.port
|
||||
protocol: "TCP"
|
||||
name: "default"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
containerized: {
|
||||
name: string
|
||||
// +usage=specify app image
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=specify port for container
|
||||
// +short=p
|
||||
port: *6379 | int
|
||||
}
|
||||
|
||||
|
||||
@@ -7,29 +7,31 @@ spec:
|
||||
name: deployments.apps
|
||||
extension:
|
||||
alias: deployment
|
||||
object:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: deployment
|
||||
metadata:
|
||||
name: tbd
|
||||
spec:
|
||||
containers:
|
||||
- image: myrepo/myapp:v1
|
||||
name: master
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
protocol: TCP
|
||||
name: tbd # TODO(zzxwill) A temporary workaround for ContainerizedWorkload
|
||||
parameters:
|
||||
- name: image
|
||||
short: i
|
||||
required: true
|
||||
type: string
|
||||
fieldPaths:
|
||||
- "spec.containers[0].image"
|
||||
- name: port
|
||||
short: p
|
||||
required: false
|
||||
type: int
|
||||
fieldPaths:
|
||||
- "spec.containers[0].ports[0].containerPort"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: deployment.name
|
||||
spec: {
|
||||
containers: [{
|
||||
image: deployment.image
|
||||
name: deployment.name
|
||||
env: deployment.env
|
||||
ports: [{
|
||||
containerPort: deployment.port
|
||||
protocol: "TCP"
|
||||
name: "default"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
deployment: {
|
||||
name: string
|
||||
image: string
|
||||
port: *8080 | int
|
||||
env: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
gotest.tools v2.2.0+incompatible
|
||||
helm.sh/helm/v3 v3.2.4
|
||||
k8s.io/api v0.18.6
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -16,7 +18,7 @@ type deleteOptions struct {
|
||||
AppConfig corev1alpha2.ApplicationConfiguration
|
||||
client client.Client
|
||||
cmdutil.IOStreams
|
||||
Env *EnvMeta
|
||||
Env *types.EnvMeta
|
||||
}
|
||||
|
||||
func newDeleteOptions(ioStreams cmdutil.IOStreams) *deleteOptions {
|
||||
|
||||
+19
-59
@@ -8,15 +8,17 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const DefaultEnvName = "default"
|
||||
|
||||
func NewEnvInitCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
var envArgs EnvMeta
|
||||
var envArgs types.EnvMeta
|
||||
ctx := context.Background()
|
||||
cmd := &cobra.Command{
|
||||
Use: "env:init",
|
||||
@@ -81,10 +83,6 @@ func NewEnvSwitchCommand(f cmdutil.Factory, ioStreams cmdutil.IOStreams) *cobra.
|
||||
return cmd
|
||||
}
|
||||
|
||||
type EnvMeta struct {
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
func ListEnvs(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = 60
|
||||
@@ -99,7 +97,7 @@ func ListEnvs(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) e
|
||||
ioStreams.Infof(table.String())
|
||||
return nil
|
||||
}
|
||||
envDir, err := getEnvDir()
|
||||
envDir, err := system.GetEnvDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -115,7 +113,7 @@ func ListEnvs(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) e
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var envMeta EnvMeta
|
||||
var envMeta types.EnvMeta
|
||||
if err = json.Unmarshal(data, &envMeta); err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -137,7 +135,7 @@ func DeleteEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams)
|
||||
if envname == curEnv {
|
||||
return fmt.Errorf("you can't delete current using env %s", curEnv)
|
||||
}
|
||||
envdir, err := getEnvDir()
|
||||
envdir, err := system.GetEnvDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,29 +146,7 @@ func DeleteEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams)
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitDefaultEnv() error {
|
||||
envDir, err := getEnvDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.MkdirAll(envDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, _ := json.Marshal(&EnvMeta{Namespace: DefaultEnvName})
|
||||
if err = ioutil.WriteFile(filepath.Join(envDir, DefaultEnvName), data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
curEnvPath, err := getCurrentEnvPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(curEnvPath, []byte(DefaultEnvName), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateOrUpdateEnv(ctx context.Context, envArgs *EnvMeta, args []string, ioStreams cmdutil.IOStreams) error {
|
||||
func CreateOrUpdateEnv(ctx context.Context, envArgs *types.EnvMeta, args []string, ioStreams cmdutil.IOStreams) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("you must specify env name for rudr env:init command")
|
||||
}
|
||||
@@ -179,14 +155,14 @@ func CreateOrUpdateEnv(ctx context.Context, envArgs *EnvMeta, args []string, ioS
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envdir, err := getEnvDir()
|
||||
envdir, err := system.GetEnvDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(filepath.Join(envdir, envname), data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
curEnvPath, err := getCurrentEnvPath()
|
||||
curEnvPath, err := system.GetCurrentEnvPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -197,28 +173,12 @@ func CreateOrUpdateEnv(ctx context.Context, envArgs *EnvMeta, args []string, ioS
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentEnvPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ".rudr", "curenv"), nil
|
||||
}
|
||||
|
||||
func getEnvDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ".rudr", "envs"), nil
|
||||
}
|
||||
|
||||
func SwitchEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("you must specify env name for rudr env command")
|
||||
}
|
||||
envname := args[0]
|
||||
currentEnvPath, err := getCurrentEnvPath()
|
||||
currentEnvPath, err := system.GetCurrentEnvPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -234,7 +194,7 @@ func SwitchEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams)
|
||||
}
|
||||
|
||||
func GetCurrentEnvName() (string, error) {
|
||||
currentEnvPath, err := getCurrentEnvPath()
|
||||
currentEnvPath, err := system.GetCurrentEnvPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -245,22 +205,22 @@ func GetCurrentEnvName() (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func GetEnv() (*EnvMeta, error) {
|
||||
func GetEnv() (*types.EnvMeta, error) {
|
||||
envName, err := GetCurrentEnvName()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err = InitDefaultEnv(); err != nil {
|
||||
if err = system.InitDefaultEnv(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envName = DefaultEnvName
|
||||
envName = types.DefaultEnvName
|
||||
}
|
||||
return getEnvByName(envName)
|
||||
}
|
||||
|
||||
func getEnvByName(name string) (*EnvMeta, error) {
|
||||
envdir, err := getEnvDir()
|
||||
func getEnvByName(name string) (*types.EnvMeta, error) {
|
||||
envdir, err := system.GetEnvDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -268,7 +228,7 @@ func getEnvByName(name string) (*EnvMeta, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var meta EnvMeta
|
||||
var meta types.EnvMeta
|
||||
if err = json.Unmarshal(data, &meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+8
-4
@@ -6,6 +6,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -14,7 +18,7 @@ func TestENV(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create Default Env
|
||||
err := InitDefaultEnv()
|
||||
err := system.InitDefaultEnv()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check and compare create default env success
|
||||
@@ -23,12 +27,12 @@ func TestENV(t *testing.T) {
|
||||
assert.Equal(t, "default", curEnvName)
|
||||
gotEnv, err := GetEnv()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &EnvMeta{
|
||||
assert.Equal(t, &types.EnvMeta{
|
||||
Namespace: "default",
|
||||
}, gotEnv)
|
||||
|
||||
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
|
||||
exp := &EnvMeta{
|
||||
exp := &types.EnvMeta{
|
||||
Namespace: "test1",
|
||||
}
|
||||
|
||||
@@ -70,7 +74,7 @@ env1 test1 `, b.String())
|
||||
// check switch success
|
||||
gotEnv, err = GetEnv()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &EnvMeta{
|
||||
assert.Equal(t, &types.EnvMeta{
|
||||
Namespace: "default",
|
||||
}, gotEnv)
|
||||
|
||||
|
||||
+10
-37
@@ -24,55 +24,28 @@ func init() {
|
||||
var (
|
||||
workloadTemplateExample = &types.Template{
|
||||
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ContainerizedWorkload",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"containers": "",
|
||||
},
|
||||
},
|
||||
Parameters: []types.Parameter{
|
||||
types.Parameter{
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
Type: "string",
|
||||
FieldPaths: []string{"spec.containers[0].image"},
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
},
|
||||
types.Parameter{
|
||||
Name: "port",
|
||||
Short: "p",
|
||||
Required: false,
|
||||
Type: "int",
|
||||
FieldPaths: []string{"spec.containers[0].ports[0].containerPort"},
|
||||
Name: "port",
|
||||
Short: "p",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
traitTemplateExample = &types.Template{
|
||||
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ManualScalerTrait",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicaCount": "2",
|
||||
},
|
||||
},
|
||||
|
||||
Parameters: []types.Parameter{
|
||||
types.Parameter{
|
||||
Name: "replicaCount",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
Type: "int",
|
||||
FieldPaths: []string{"spec.replicaCount"},
|
||||
Default: "5",
|
||||
Name: "replicaCount",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
Default: "5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ func printApplicationList(ctx context.Context, c client.Client, appName string,
|
||||
table.MaxColWidth = 60
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("listing Trait Definition hit an issue: %s\n", err)
|
||||
fmt.Printf("listing Trait DefinitionPath hit an issue: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+56
-35
@@ -8,6 +8,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
mycue "github.com/cloud-native-application/rudrx/pkg/cue"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/plugins"
|
||||
@@ -23,13 +29,13 @@ import (
|
||||
)
|
||||
|
||||
type commandOptions struct {
|
||||
Env *EnvMeta
|
||||
Template types.Template
|
||||
Component corev1alpha2.Component
|
||||
AppConfig corev1alpha2.ApplicationConfiguration
|
||||
Client client.Client
|
||||
TraitAlias string
|
||||
Detach bool
|
||||
Env *types.EnvMeta
|
||||
cmdutil.IOStreams
|
||||
}
|
||||
|
||||
@@ -38,13 +44,14 @@ func NewCommandOptions(ioStreams cmdutil.IOStreams) *commandOptions {
|
||||
}
|
||||
|
||||
func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error {
|
||||
templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c)
|
||||
dir, _ := system.GetDefinitionDir()
|
||||
templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, tmp := range templates {
|
||||
var name = tmp.Alias
|
||||
var name = tmp.Name
|
||||
o := NewCommandOptions(ioStreams)
|
||||
o.Client = c
|
||||
o.Env, _ = GetEnv()
|
||||
@@ -63,10 +70,7 @@ func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmduti
|
||||
}
|
||||
pluginCmd.SetOut(o.Out)
|
||||
for _, v := range tmp.Parameters {
|
||||
pluginCmd.Flags().StringP(v.Name, v.Short, v.Default, v.Usage)
|
||||
if v.Required {
|
||||
pluginCmd.MarkFlagRequired(v.Name)
|
||||
}
|
||||
types.SetFlagBy(pluginCmd, v)
|
||||
}
|
||||
|
||||
o.Template = tmp
|
||||
@@ -100,21 +104,39 @@ func (o *commandOptions) Complete(cmd *cobra.Command, args []string, ctx context
|
||||
return err
|
||||
}
|
||||
|
||||
pvd := fieldpath.Pave(o.Template.Object)
|
||||
var traitData = make(map[string]interface{})
|
||||
|
||||
var tp = o.Template.Name
|
||||
|
||||
for _, v := range o.Template.Parameters {
|
||||
flagSet := cmd.Flag(v.Name)
|
||||
for _, path := range v.FieldPaths {
|
||||
fValue := flagSet.Value.String()
|
||||
if v.Type == "int" {
|
||||
portValue, _ := strconv.ParseFloat(fValue, 64)
|
||||
pvd.SetNumber(path, portValue)
|
||||
continue
|
||||
}
|
||||
pvd.SetString(path, fValue)
|
||||
switch v.Type {
|
||||
case cue.IntKind:
|
||||
d, _ := strconv.ParseInt(flagSet.Value.String(), 10, 64)
|
||||
traitData[v.Name] = d
|
||||
case cue.StringKind:
|
||||
traitData[v.Name] = flagSet.Value.String()
|
||||
case cue.BoolKind:
|
||||
d, _ := strconv.ParseBool(flagSet.Value.String())
|
||||
traitData[v.Name] = d
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
d, _ := strconv.ParseFloat(flagSet.Value.String(), 64)
|
||||
traitData[v.Name] = d
|
||||
}
|
||||
}
|
||||
|
||||
jsondata, err := mycue.Eval(o.Template.DefinitionPath, tp, traitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var obj = make(map[string]interface{})
|
||||
if err = json.Unmarshal([]byte(jsondata), &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pvd := fieldpath.Pave(obj)
|
||||
// metadata.name needs to be in lower case.
|
||||
pvd.SetString("metadata.name", strings.ToLower(fmt.Sprintf("%s-%s-trait", appName, o.Template.Alias)))
|
||||
pvd.SetString("metadata.name", strings.ToLower(fmt.Sprintf("%s-%s-trait", appName, o.Template.Name)))
|
||||
curObj := &unstructured.Unstructured{Object: pvd.UnstructuredContent()}
|
||||
var updated bool
|
||||
for ic, c := range o.AppConfig.Spec.Components {
|
||||
@@ -124,7 +146,7 @@ func (o *commandOptions) Complete(cmd *cobra.Command, args []string, ctx context
|
||||
for it, t := range c.Traits {
|
||||
g, v, k := GetGVKFromRawExtension(t.Trait)
|
||||
|
||||
// TODO(wonderflow): we should get GVK from Definition instead of assuming template object contains
|
||||
// TODO(wonderflow): we should get GVK from DefinitionPath instead of assuming template object contains
|
||||
gvk := curObj.GroupVersionKind()
|
||||
if gvk.Group == g && gvk.Version == v && gvk.Kind == k {
|
||||
updated = true
|
||||
@@ -142,13 +164,14 @@ func (o *commandOptions) Complete(cmd *cobra.Command, args []string, ctx context
|
||||
}
|
||||
|
||||
func DetachTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error {
|
||||
templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c)
|
||||
dir, _ := system.GetDefinitionDir()
|
||||
templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, tmp := range templates {
|
||||
var name = tmp.Alias
|
||||
var name = tmp.Name
|
||||
o := NewCommandOptions(ioStreams)
|
||||
o.Client = c
|
||||
o.Env, _ = GetEnv()
|
||||
@@ -186,26 +209,24 @@ func (o *commandOptions) DetachTrait(cmd *cobra.Command, args []string, ctx cont
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, tKind := cmdutil.GetTraitNameAliasKind(ctx, c, namespace, o.TraitAlias)
|
||||
var traitDefinition corev1alpha2.TraitDefinition
|
||||
|
||||
apiVersion, kind, err := cmdutil.GetTraitApiVersionKind(ctx, c, namespace, o.TraitAlias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, com := range o.AppConfig.Spec.Components {
|
||||
traits := com.Traits
|
||||
if com.ComponentName == appName {
|
||||
for j := 0; j < len(traits); j++ {
|
||||
err := json.Unmarshal(traits[j].Trait.Raw, &traitDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(traitDefinition.Kind, tKind) {
|
||||
traits = append(traits[:j], traits[j+1:]...)
|
||||
j--
|
||||
}
|
||||
if com.ComponentName != appName {
|
||||
continue
|
||||
}
|
||||
var traits []corev1alpha2.ComponentTrait
|
||||
for _, tr := range com.Traits {
|
||||
a, k := tr.Trait.Object.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind()
|
||||
if a == apiVersion && k == kind {
|
||||
continue
|
||||
}
|
||||
traits = append(traits, tr)
|
||||
}
|
||||
o.AppConfig.Spec.Components[i].Traits = traits
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ func printTraitList(ctx context.Context, c client.Client, workloadName *string,
|
||||
table.MaxColWidth = 60
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Listing Trait Definition hit an issue: %s", err)
|
||||
return fmt.Errorf("Listing Trait DefinitionPath hit an issue: %s", err)
|
||||
}
|
||||
|
||||
table.AddRow("NAME", "ALIAS", "DEFINITION", "APPLIES TO", "STATUS")
|
||||
|
||||
+9
-23
@@ -203,7 +203,7 @@ func GetTraitDefinitionByAlias(ctx context.Context, c client.Client, traitAlias
|
||||
if err == nil {
|
||||
for _, t := range traitDefinitionList.Items {
|
||||
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
|
||||
if err == nil && strings.EqualFold(template.Alias, traitAlias) {
|
||||
if err == nil && strings.EqualFold(template.Name, traitAlias) {
|
||||
traitDefinition = t
|
||||
break
|
||||
}
|
||||
@@ -214,29 +214,15 @@ func GetTraitDefinitionByAlias(ctx context.Context, c client.Client, traitAlias
|
||||
|
||||
// GetTraitNameAndAlias return the name and alias of a TraitDefinition by a string which might be
|
||||
// the trait name, the trait alias, or invalid name
|
||||
func GetTraitNameAliasKind(ctx context.Context, c client.Client, namespace string, name string) (string, string, string) {
|
||||
var tName, tAlias, tKind string
|
||||
func GetTraitApiVersionKind(ctx context.Context, c client.Client, namespace string, name string) (string, string, error) {
|
||||
|
||||
t, err := GetTraitDefinitionByName(ctx, c, namespace, name)
|
||||
|
||||
if err == nil {
|
||||
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
|
||||
if err == nil {
|
||||
tName, tAlias = t.Spec.Reference.Name, template.Alias
|
||||
tKind = fmt.Sprintf("%v", template.Object["kind"])
|
||||
}
|
||||
} else {
|
||||
t, err := GetTraitDefinitionByAlias(ctx, c, name)
|
||||
if err == nil {
|
||||
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
|
||||
if err == nil {
|
||||
tName, tAlias = t.Spec.Reference.Name, template.Alias
|
||||
tKind = fmt.Sprintf("%v", template.Object["kind"])
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return tName, tAlias, tKind
|
||||
apiVersion := t.Annotations["oam.appengine.info/apiVersion"]
|
||||
kind := t.Annotations["oam.appengine.info/kind"]
|
||||
return apiVersion, kind, nil
|
||||
}
|
||||
|
||||
func GetWorkloadNameAliasKind(ctx context.Context, c client.Client, namespace string, workloadName string) (string, string, string) {
|
||||
@@ -248,14 +234,14 @@ func GetWorkloadNameAliasKind(ctx context.Context, c client.Client, namespace st
|
||||
var workloadTemplate types.Template
|
||||
workloadTemplate, err := types.ConvertTemplateJson2Object(w.Spec.Extension)
|
||||
if err == nil {
|
||||
name, alias = w.Name, workloadTemplate.Alias
|
||||
name, alias = w.Name, workloadTemplate.Name
|
||||
}
|
||||
} else { // workloadName is alias or kind
|
||||
w, err := GetWorkloadDefinitionByAlias(ctx, c, name)
|
||||
if err == nil {
|
||||
workloadTemplate, err := types.ConvertTemplateJson2Object(w.Spec.Extension)
|
||||
if err == nil {
|
||||
name, alias, kind = w.Name, workloadTemplate.Alias, w.Kind
|
||||
name, alias, kind = w.Name, workloadTemplate.Name, w.Kind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+87
-49
@@ -2,33 +2,44 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/plugins"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
|
||||
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
|
||||
mycue "github.com/cloud-native-application/rudrx/pkg/cue"
|
||||
|
||||
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
|
||||
)
|
||||
|
||||
// ComponentWorkloadDefLabel indicate which workloaddefinition generate from
|
||||
const ComponentWorkloadDefLabel = "rudrx.oam.dev/workloadDef"
|
||||
|
||||
type runOptions struct {
|
||||
Template types.Template
|
||||
Env *EnvMeta
|
||||
Component corev1alpha2.Component
|
||||
AppConfig corev1alpha2.ApplicationConfiguration
|
||||
client client.Client
|
||||
Template types.Template
|
||||
Env *types.EnvMeta
|
||||
workloadName string
|
||||
client client.Client
|
||||
app *types.Application
|
||||
cmdutil.IOStreams
|
||||
}
|
||||
|
||||
@@ -37,13 +48,14 @@ func newRunOptions(ioStreams cmdutil.IOStreams) *runOptions {
|
||||
}
|
||||
|
||||
func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error {
|
||||
templates, err := plugins.GetWorkloadsFromCluster(context.TODO(), types.DefaultOAMNS, c)
|
||||
dir, _ := system.GetDefinitionDir()
|
||||
templates, err := plugins.GetWorkloadsFromCluster(context.TODO(), types.DefaultOAMNS, c, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tmp := range templates {
|
||||
var name = tmp.Alias
|
||||
var name = tmp.Name
|
||||
o := newRunOptions(ioStreams)
|
||||
o.client = c
|
||||
o.Env, _ = GetEnv()
|
||||
@@ -62,10 +74,7 @@ func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmd
|
||||
}
|
||||
pluginCmd.SetOut(o.Out)
|
||||
for _, v := range tmp.Parameters {
|
||||
pluginCmd.Flags().StringP(v.Name, v.Short, v.Default, v.Usage)
|
||||
if v.Required {
|
||||
pluginCmd.MarkFlagRequired(v.Name)
|
||||
}
|
||||
types.SetFlagBy(pluginCmd, v)
|
||||
}
|
||||
|
||||
o.Template = tmp
|
||||
@@ -83,50 +92,79 @@ func (o *runOptions) Complete(cmd *cobra.Command, args []string, ctx context.Con
|
||||
}
|
||||
|
||||
workloadName := args[0]
|
||||
// TODO(wonderflow): load application from file
|
||||
var app = &types.Application{Name: workloadName}
|
||||
|
||||
workloadTemplate := o.Template
|
||||
pvd := fieldpath.Pave(workloadTemplate.Object)
|
||||
for _, v := range workloadTemplate.Parameters {
|
||||
var paraV string
|
||||
|
||||
flagSet := cmd.Flag(v.Name)
|
||||
paraV = flagSet.Value.String()
|
||||
|
||||
if paraV == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, path := range v.FieldPaths {
|
||||
if v.Type == "int" {
|
||||
portValue, _ := strconv.ParseFloat(paraV, 64)
|
||||
pvd.SetNumber(path, portValue)
|
||||
break
|
||||
}
|
||||
pvd.SetString(path, paraV)
|
||||
}
|
||||
if app.Components == nil {
|
||||
app.Components = make(map[string]map[string]interface{})
|
||||
}
|
||||
tp, workloadData, err := app.GetWorkload(workloadName)
|
||||
if err != nil {
|
||||
// Not exist
|
||||
tp = o.Template.Name
|
||||
workloadData = make(map[string]interface{})
|
||||
}
|
||||
|
||||
pvd.SetString("metadata.name", strings.ToLower(workloadName))
|
||||
namespace := o.Env.Namespace
|
||||
o.Component.Spec.Workload.Object = &unstructured.Unstructured{Object: pvd.UnstructuredContent()}
|
||||
o.Component.Name = args[0]
|
||||
o.Component.Namespace = namespace
|
||||
o.Component.Labels = map[string]string{ComponentWorkloadDefLabel: workloadName}
|
||||
|
||||
o.AppConfig.Name = args[0]
|
||||
o.AppConfig.Namespace = namespace
|
||||
o.AppConfig.Spec.Components = append(o.AppConfig.Spec.Components, corev1alpha2.ApplicationConfigurationComponent{ComponentName: args[0]})
|
||||
|
||||
return nil
|
||||
for _, v := range o.Template.Parameters {
|
||||
flagSet := cmd.Flag(v.Name)
|
||||
switch v.Type {
|
||||
case cue.IntKind:
|
||||
d, _ := strconv.ParseInt(flagSet.Value.String(), 10, 64)
|
||||
workloadData[v.Name] = d
|
||||
case cue.StringKind:
|
||||
workloadData[v.Name] = flagSet.Value.String()
|
||||
case cue.BoolKind:
|
||||
d, _ := strconv.ParseBool(flagSet.Value.String())
|
||||
workloadData[v.Name] = d
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
d, _ := strconv.ParseFloat(flagSet.Value.String(), 64)
|
||||
workloadData[v.Name] = d
|
||||
}
|
||||
}
|
||||
workloadData["name"] = strings.ToLower(workloadName)
|
||||
app.Components[workloadName] = map[string]interface{}{
|
||||
tp: workloadData,
|
||||
}
|
||||
o.workloadName = workloadName
|
||||
o.app = app
|
||||
appDir, _ := system.GetApplicationDir()
|
||||
out, err := yaml.Marshal(app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filepath.Join(appDir, workloadName), out, 0644)
|
||||
}
|
||||
|
||||
func (o *runOptions) Run(cmd *cobra.Command) error {
|
||||
o.Infof("Creating AppConfig %s\n", o.AppConfig.Name)
|
||||
err := o.client.Create(context.Background(), &o.Component)
|
||||
var component corev1alpha2.Component
|
||||
var appconfig corev1alpha2.ApplicationConfiguration
|
||||
tp, data, _ := o.app.GetWorkload(o.workloadName)
|
||||
jsondata, err := mycue.Eval(o.Template.DefinitionPath, tp, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var obj = make(map[string]interface{})
|
||||
if err = json.Unmarshal([]byte(jsondata), &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
component.Spec.Workload.Object = &unstructured.Unstructured{Object: obj}
|
||||
component.Name = o.workloadName
|
||||
component.Namespace = o.Env.Namespace
|
||||
component.Labels = map[string]string{ComponentWorkloadDefLabel: o.workloadName}
|
||||
|
||||
appconfig.Name = o.workloadName
|
||||
appconfig.Namespace = o.Env.Namespace
|
||||
appconfig.Spec.Components = append(appconfig.Spec.Components, corev1alpha2.ApplicationConfigurationComponent{ComponentName: o.workloadName})
|
||||
|
||||
//TODO(wonderflow): we should also support update here
|
||||
|
||||
o.Infof("Creating AppConfig %s\n", appconfig.Name)
|
||||
err = o.client.Create(context.Background(), &component)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create component err: %s", err)
|
||||
}
|
||||
err = o.client.Create(context.Background(), &o.AppConfig)
|
||||
err = o.client.Create(context.Background(), &appconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create appconfig err %s", err)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func printWorkloadList(ctx context.Context, c client.Client, ioStreams cmdutil.I
|
||||
table.MaxColWidth = 60
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Listing Trait Definition hit an issue: %s", err)
|
||||
return fmt.Errorf("Listing Trait DefinitionPath hit an issue: %s", err)
|
||||
}
|
||||
|
||||
table.AddRow("NAME", "SHORT", "DEFINITION")
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package cue
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/pkg/encoding/json"
|
||||
)
|
||||
|
||||
const Template = "#Template"
|
||||
|
||||
func Eval(templatePath, workloadType string, value map[string]interface{}) (string, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile(templatePath, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tempValue := template.Value()
|
||||
appValue, err := tempValue.Fill(value, workloadType).Eval().Struct()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
final, err := appValue.FieldByName(Template, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := final.Value.Validate(cue.Concrete(true), cue.Final()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.Marshal(final.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetParameters(templatePath string) ([]types.Parameter, string, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile(templatePath, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
tempStruct, err := template.Value().Struct()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var info cue.FieldInfo
|
||||
var found bool
|
||||
for i := 0; i < tempStruct.Len(); i++ {
|
||||
info = tempStruct.Field(i)
|
||||
if info.IsDefinition {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return nil, "", errors.New("arguments not exist")
|
||||
}
|
||||
str, err := info.Value.Struct()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("arguments not defined as struct %v", err)
|
||||
}
|
||||
var workloadType = info.Name
|
||||
var params []types.Parameter
|
||||
for i := 0; i < str.Len(); i++ {
|
||||
fi := str.Field(i)
|
||||
if fi.IsDefinition {
|
||||
continue
|
||||
}
|
||||
var param = types.Parameter{
|
||||
Name: fi.Name,
|
||||
Required: true,
|
||||
}
|
||||
val := fi.Value
|
||||
param.Type = fi.Value.IncompleteKind()
|
||||
if def, ok := val.Default(); ok && def.IsConcrete() {
|
||||
param.Required = false
|
||||
param.Type = def.Kind()
|
||||
param.Default = GetDefault(def)
|
||||
}
|
||||
if param.Default == nil {
|
||||
param.Default = getDefaultByKind(param.Type)
|
||||
}
|
||||
|
||||
short, usage := RetrieveComments(val)
|
||||
if short != "" {
|
||||
param.Short = short
|
||||
}
|
||||
if usage != "" {
|
||||
param.Usage = usage
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
return params, workloadType, nil
|
||||
}
|
||||
|
||||
func getDefaultByKind(k cue.Kind) interface{} {
|
||||
switch k {
|
||||
case cue.IntKind:
|
||||
var d int64
|
||||
return d
|
||||
case cue.StringKind:
|
||||
var d string
|
||||
return d
|
||||
case cue.BoolKind:
|
||||
var d bool
|
||||
return d
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
var d float64
|
||||
return d
|
||||
}
|
||||
// assume other cue kind won't be valid parameter
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDefault(val cue.Value) interface{} {
|
||||
switch val.Kind() {
|
||||
case cue.IntKind:
|
||||
if d, err := val.Int64(); err == nil {
|
||||
return d
|
||||
}
|
||||
case cue.StringKind:
|
||||
if d, err := val.String(); err == nil {
|
||||
return d
|
||||
}
|
||||
case cue.BoolKind:
|
||||
if d, err := val.Bool(); err == nil {
|
||||
return d
|
||||
}
|
||||
case cue.NumberKind, cue.FloatKind:
|
||||
if d, err := val.Float64(); err == nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return getDefaultByKind(val.Kind())
|
||||
}
|
||||
|
||||
const (
|
||||
UsagePrefix = "+usage="
|
||||
ShortPrefix = "+short="
|
||||
)
|
||||
|
||||
func RetrieveComments(value cue.Value) (string, string) {
|
||||
var short, usage string
|
||||
docs := value.Doc()
|
||||
for _, doc := range docs {
|
||||
lines := strings.Split(doc.Text(), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
line = strings.TrimPrefix(line, "//")
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, ShortPrefix) {
|
||||
short = strings.TrimPrefix(line, ShortPrefix)
|
||||
}
|
||||
if strings.HasPrefix(line, UsagePrefix) {
|
||||
usage = strings.TrimPrefix(line, UsagePrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
return short, usage
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/pkg/encoding/json"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEval(t *testing.T) {
|
||||
_, workloadType, err := GetParameters("testdata/workloads/deployment.cue")
|
||||
assert.NoError(t, err)
|
||||
data, err := Eval("testdata/workloads/deployment.cue", workloadType, map[string]interface{}{
|
||||
"image": "nginx:v1",
|
||||
"port": 8080,
|
||||
"name": "myapp",
|
||||
"env": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "MYDB",
|
||||
"value": "true",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"myapp"},"spec":{"containers":[{"name":"myapp","env":[{"name":"MYDB","value":"true"}],"image":"nginx:v1","ports":[{"name":"default","containerPort":8080,"protocol":"TCP"}]}]}}`,
|
||||
data)
|
||||
}
|
||||
|
||||
func TestGetparam(t *testing.T) {
|
||||
params, workloadType, err := GetParameters("testdata/workloads/deployment.cue")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "deployment", workloadType)
|
||||
assert.Equal(t, []types.Parameter{
|
||||
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
|
||||
{Name: "env", Required: false, Default: nil, Type: cue.ListKind},
|
||||
{Name: "image", Short: "i", Required: true, Usage: "specify app image", Default: "", Type: cue.StringKind},
|
||||
{Name: "port", Short: "p", Usage: "specify port for container", Default: int64(8080), Type: cue.IntKind}}, params)
|
||||
|
||||
params, workloadType, err = GetParameters("testdata/workloads/test-param.cue")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "deployment", workloadType)
|
||||
assert.Equal(t, []types.Parameter{
|
||||
{Name: "name", Required: true, Default: "", Type: cue.StringKind},
|
||||
{Name: "env", Required: false, Default: nil, Type: cue.ListKind},
|
||||
{Name: "image", Short: "i", Required: true, Usage: "specify app image", Default: "", Type: cue.StringKind},
|
||||
{Name: "port", Short: "p", Usage: "specify port for container", Default: int64(8080), Type: cue.IntKind},
|
||||
{Name: "enable", Default: false, Type: cue.BoolKind},
|
||||
{Name: "fval", Default: 64.3, Type: cue.FloatKind},
|
||||
{Name: "nval", Default: float64(0), Required: true, Type: cue.NumberKind}}, params)
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
var r cue.Runtime
|
||||
ins, _ := r.Compile("testdata/workloads/deployment.cue", nil)
|
||||
ins.Value()
|
||||
fmt.Println(json.Marshal(ins.Value()))
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package cue
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"cuelang.org/go/pkg/encoding/json"
|
||||
)
|
||||
|
||||
const Template = "#Template"
|
||||
|
||||
func Eval(templatePath, appPath, workloadType string) (string, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile(templatePath, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
app, err := r.Compile(appPath, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tempValue := template.Value()
|
||||
appinfo, err := app.Value().FieldByName(workloadType, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
appValue, err := tempValue.Fill(appinfo.Value, workloadType).Eval().Struct()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
final, err := appValue.FieldByName(Template, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := final.Value.Validate(cue.Concrete(true), cue.Final()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := json.Marshal(final.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type CueParameter struct {
|
||||
Name string
|
||||
Default interface{}
|
||||
}
|
||||
|
||||
func GetParameters(templatePath string) ([]CueParameter, string, error) {
|
||||
r := cue.Runtime{}
|
||||
template, err := r.Compile(templatePath, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
tempStruct, err := template.Value().Struct()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var info cue.FieldInfo
|
||||
var found bool
|
||||
for i := 0; i < tempStruct.Len(); i++ {
|
||||
info = tempStruct.Field(i)
|
||||
if info.IsDefinition {
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
return nil, "", errors.New("arguments not exist")
|
||||
}
|
||||
str, err := info.Value.Struct()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("arguments not defined as struct %v", err)
|
||||
}
|
||||
var workloadType = info.Name
|
||||
var params []CueParameter
|
||||
for i := 0; i < str.Len(); i++ {
|
||||
fi := str.Field(i)
|
||||
if fi.IsDefinition {
|
||||
continue
|
||||
}
|
||||
var param = CueParameter{
|
||||
Name: fi.Name,
|
||||
}
|
||||
|
||||
if def, ok := fi.Value.Default(); ok && def.IsConcrete() {
|
||||
if data, err := def.MarshalJSON(); err == nil {
|
||||
param.Default = string(data)
|
||||
}
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
return params, workloadType, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package cue
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEval(t *testing.T) {
|
||||
_, workloadType, err := GetParameters("testdata/workloads/deployment/deployment.cue")
|
||||
assert.NoError(t, err)
|
||||
data, err := Eval("testdata/workloads/deployment/deployment.cue", "testdata/apps/myapp.cue", workloadType)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"myapp"},"spec":{"containers":[{"name":"myapp","env":[{"name":"MYDB","value":"true"}],"image":"nginx:v1","ports":[{"name":"default","containerPort":8080,"protocol":"TCP"}]}]}}`,
|
||||
data)
|
||||
}
|
||||
|
||||
func TestGetparam(t *testing.T) {
|
||||
params, workloadType, err := GetParameters("testdata/workloads/deployment/deployment.cue")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "deployment", workloadType)
|
||||
assert.Equal(t, []CueParameter{{Name: "name"}, {Name: "env", Default: "[]"}, {Name: "image"}, {Name: "port", Default: "8080"}}, params)
|
||||
}
|
||||
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
deployment: {
|
||||
name: "myapp"
|
||||
port: 8080
|
||||
image: "nginx:v1"
|
||||
env: [{
|
||||
name: "MYDB"
|
||||
value: "true"
|
||||
}]
|
||||
}
|
||||
route: {
|
||||
domain: "www.example.com"
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
name: myapp
|
||||
components:
|
||||
frontend:
|
||||
deployment:
|
||||
image: inanimate/echo-server
|
||||
env:
|
||||
PORT: "8080"
|
||||
traits:
|
||||
autoscaling:
|
||||
max: 10
|
||||
min: 1
|
||||
rollout:
|
||||
strategy: canary
|
||||
step: 5
|
||||
expose:
|
||||
service:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
http:
|
||||
service_port: 80
|
||||
container_port: 8080
|
||||
scopes:
|
||||
- public-scope
|
||||
secrets:
|
||||
secret-foo:
|
||||
key1: 'pass-word'
|
||||
appScopes:
|
||||
public-scope:
|
||||
networkPolicy: public
|
||||
private-scope:
|
||||
networkPolicy: private
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#Template: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ManualScalerTrait"
|
||||
spec: {
|
||||
replicaCount: manualscaler.replica
|
||||
}
|
||||
}
|
||||
manualscaler: {
|
||||
//+short=r
|
||||
replica: *2 | int
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
#Template: {
|
||||
apiVersion: "extend.oam.dev/v1alpha2"
|
||||
kind: "SimpleRolloutTrait"
|
||||
spec: {
|
||||
replica: rollout.replica
|
||||
maxUnavailable: rollout.maxUnavailable
|
||||
batch: rollout.batch
|
||||
}
|
||||
}
|
||||
|
||||
rollout: {
|
||||
replica: *3 | int
|
||||
maxUnavailable: *1 | int
|
||||
batch: *2 | int
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
#Template: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Route"
|
||||
spec: {
|
||||
domain: route.domain
|
||||
}
|
||||
}
|
||||
|
||||
route: {
|
||||
domain: string
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#Template: {
|
||||
apiVersion: "core.oam.dev/v1alpha2"
|
||||
kind: "ContainerizedWorkload"
|
||||
metadata: name: containerized.name
|
||||
spec: {
|
||||
containers: [{
|
||||
image: containerized.image
|
||||
name: containerized.name
|
||||
ports: [{
|
||||
containerPort: containerized.port
|
||||
protocol: "TCP"
|
||||
name: "default"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
containerized: {
|
||||
name: string
|
||||
// +usage=specify app image
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=specify port for container
|
||||
// +short=p
|
||||
port: *6379 | int
|
||||
}
|
||||
Vendored
+6
-2
@@ -17,9 +17,13 @@
|
||||
}
|
||||
|
||||
deployment: {
|
||||
name: string
|
||||
name: string
|
||||
// +usage=specify app image
|
||||
// +short=i
|
||||
image: string
|
||||
port: *8080 | int
|
||||
// +usage=specify port for container
|
||||
// +short=p
|
||||
port: *8080 | int
|
||||
env: [...{
|
||||
name: string
|
||||
value: string
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Template: {
|
||||
}
|
||||
|
||||
deployment: {
|
||||
name: string
|
||||
// +usage=specify app image
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=specify port for container
|
||||
// +short=p
|
||||
port: *8080 | int
|
||||
env: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
enable: *false | bool
|
||||
fval: *64.3 | number
|
||||
nval: number
|
||||
}
|
||||
+37
-17
@@ -2,7 +2,14 @@ package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/cue"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
@@ -10,12 +17,12 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func GetTemplatesFromCluster(ctx context.Context, namespace string, c client.Client) ([]types.Template, error) {
|
||||
workloads, err := GetWorkloadsFromCluster(ctx, namespace, c)
|
||||
func GetTemplatesFromCluster(ctx context.Context, namespace string, c client.Client, syncDir string) ([]types.Template, error) {
|
||||
workloads, err := GetWorkloadsFromCluster(ctx, namespace, c, syncDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
traits, err := GetTraitsFromCluster(ctx, namespace, c)
|
||||
traits, err := GetTraitsFromCluster(ctx, namespace, c, syncDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -23,7 +30,7 @@ func GetTemplatesFromCluster(ctx context.Context, namespace string, c client.Cli
|
||||
return workloads, nil
|
||||
}
|
||||
|
||||
func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Client) ([]types.Template, error) {
|
||||
func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Client, syncDir string) ([]types.Template, error) {
|
||||
var templates []types.Template
|
||||
var workloadDefs corev1alpha2.WorkloadDefinitionList
|
||||
err := c.List(ctx, &workloadDefs, &client.ListOptions{Namespace: namespace})
|
||||
@@ -33,22 +40,18 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Cli
|
||||
|
||||
for _, wd := range workloadDefs.Items {
|
||||
var tmp types.Template
|
||||
tmp, err := types.ConvertTemplateJson2Object(wd.Spec.Extension)
|
||||
tmp, err := HandleTemplate(wd.Spec.Extension, wd.Name, syncDir)
|
||||
if err != nil {
|
||||
fmt.Printf("extract template from workloadDefinition %v err: %v, ignore it\n", wd.Name, err)
|
||||
fmt.Printf("[WARN]handle template %s: %v\n", wd.Name, err)
|
||||
continue
|
||||
}
|
||||
tmp.Type = types.TypeWorkload
|
||||
tmp.Name = wd.Name
|
||||
if tmp.Alias == "" {
|
||||
tmp.Alias = tmp.Name
|
||||
}
|
||||
templates = append(templates, tmp)
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client) ([]types.Template, error) {
|
||||
func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client, syncDir string) ([]types.Template, error) {
|
||||
var templates []types.Template
|
||||
var traitDefs corev1alpha2.TraitDefinitionList
|
||||
err := c.List(ctx, &traitDefs, &client.ListOptions{Namespace: namespace})
|
||||
@@ -58,17 +61,34 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client
|
||||
|
||||
for _, td := range traitDefs.Items {
|
||||
var tmp types.Template
|
||||
tmp, err := types.ConvertTemplateJson2Object(td.Spec.Extension)
|
||||
tmp, err := HandleTemplate(td.Spec.Extension, td.Name, syncDir)
|
||||
if err != nil {
|
||||
fmt.Printf("extract template from workloadDefinition %v err: %v, ignore it\n", td.Name, err)
|
||||
fmt.Printf("[WARN]handle template %s: %v\n", td.Name, err)
|
||||
continue
|
||||
}
|
||||
tmp.Type = types.TypeTrait
|
||||
tmp.Name = td.Name
|
||||
if tmp.Alias == "" {
|
||||
tmp.Alias = tmp.Name
|
||||
}
|
||||
templates = append(templates, tmp)
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Template, error) {
|
||||
tmp, err := types.ConvertTemplateJson2Object(in)
|
||||
if err != nil {
|
||||
return types.Template{}, err
|
||||
}
|
||||
if tmp.Template == "" {
|
||||
return types.Template{}, errors.New("template not exist in definition")
|
||||
}
|
||||
filePath := filepath.Join(syncDir, name+".cue")
|
||||
err = ioutil.WriteFile(filePath, []byte(tmp.Template), 0644)
|
||||
if err != nil {
|
||||
return types.Template{}, err
|
||||
}
|
||||
tmp.DefinitionPath = filePath
|
||||
tmp.Parameters, tmp.Name, err = cue.GetParameters(filePath)
|
||||
if err != nil {
|
||||
return types.Template{}, err
|
||||
}
|
||||
return tmp, nil
|
||||
}
|
||||
|
||||
+50
-30
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -24,36 +26,45 @@ import (
|
||||
var _ = Describe("DefinitionFiles", func() {
|
||||
ctx := context.Background()
|
||||
route := types.Template{
|
||||
Name: "routes.extend.oam.dev",
|
||||
Type: types.TypeTrait,
|
||||
Alias: "route",
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "extend.oam.dev/v1alpha2",
|
||||
"kind": "Route",
|
||||
},
|
||||
Name: "route",
|
||||
Type: types.TypeTrait,
|
||||
Parameters: []types.Parameter{
|
||||
{
|
||||
Name: "domain",
|
||||
Short: "d",
|
||||
Required: true,
|
||||
FieldPaths: []string{"spec.domain"},
|
||||
Name: "domain",
|
||||
Required: true,
|
||||
Default: "",
|
||||
Type: cue.StringKind,
|
||||
},
|
||||
},
|
||||
}
|
||||
deployment := types.Template{
|
||||
Name: "deployments.testapps",
|
||||
Type: types.TypeWorkload,
|
||||
Alias: "deployment",
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "Deployment",
|
||||
},
|
||||
Name: "deployment",
|
||||
Type: types.TypeWorkload,
|
||||
Parameters: []types.Parameter{
|
||||
{
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
FieldPaths: []string{"spec.containers[0].image"},
|
||||
Name: "name",
|
||||
Required: true,
|
||||
Type: cue.StringKind,
|
||||
Default: "",
|
||||
},
|
||||
{
|
||||
Type: cue.ListKind,
|
||||
Name: "env",
|
||||
},
|
||||
{
|
||||
Name: "image",
|
||||
Type: cue.StringKind,
|
||||
Default: "",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
Usage: "specify app image",
|
||||
},
|
||||
{
|
||||
Name: "port",
|
||||
Type: cue.IntKind,
|
||||
Short: "p",
|
||||
Default: int64(8080),
|
||||
Usage: "specify port for container",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -81,29 +92,38 @@ var _ = Describe("DefinitionFiles", func() {
|
||||
|
||||
})
|
||||
|
||||
// Notice!! Definition Object is Cluster Scope object
|
||||
// Notice!! DefinitionPath Object is Cluster Scope object
|
||||
// which means objects created in other DefinitionNamespace will also affect here.
|
||||
It("gettrait", func() {
|
||||
traitDefs, err := GetTraitsFromCluster(context.Background(), DefinitionNamespace, k8sClient)
|
||||
traitDefs, err := GetTraitsFromCluster(context.Background(), DefinitionNamespace, k8sClient, definitionDir)
|
||||
Expect(err).Should(BeNil())
|
||||
logf.Log.Info(fmt.Sprintf("Getting trait definitions %v", traitDefs))
|
||||
|
||||
for i := range traitDefs {
|
||||
traitDefs[i].Template = ""
|
||||
traitDefs[i].DefinitionPath = ""
|
||||
}
|
||||
Expect(traitDefs).Should(Equal([]types.Template{route}))
|
||||
})
|
||||
// Notice!! Definition Object is Cluster Scope object
|
||||
// Notice!! DefinitionPath Object is Cluster Scope object
|
||||
// which means objects created in other DefinitionNamespace will also affect here.
|
||||
It("getworkload", func() {
|
||||
workloadDefs, err := GetWorkloadsFromCluster(context.Background(), DefinitionNamespace, k8sClient)
|
||||
workloadDefs, err := GetWorkloadsFromCluster(context.Background(), DefinitionNamespace, k8sClient, definitionDir)
|
||||
Expect(err).Should(BeNil())
|
||||
logf.Log.Info(fmt.Sprintf("Getting workload definitions %v", workloadDefs))
|
||||
|
||||
for i := range workloadDefs {
|
||||
workloadDefs[i].Template = ""
|
||||
workloadDefs[i].DefinitionPath = ""
|
||||
}
|
||||
Expect(workloadDefs).Should(Equal([]types.Template{deployment}))
|
||||
})
|
||||
It("getall", func() {
|
||||
alldef, err := GetTemplatesFromCluster(context.Background(), DefinitionNamespace, k8sClient)
|
||||
alldef, err := GetTemplatesFromCluster(context.Background(), DefinitionNamespace, k8sClient, definitionDir)
|
||||
Expect(err).Should(BeNil())
|
||||
logf.Log.Info(fmt.Sprintf("Getting all definitions %v", alldef))
|
||||
|
||||
for i := range alldef {
|
||||
alldef[i].Template = ""
|
||||
alldef[i].DefinitionPath = ""
|
||||
}
|
||||
Expect(alldef).Should(Equal([]types.Template{deployment, route}))
|
||||
})
|
||||
})
|
||||
|
||||
+15
-33
@@ -11,53 +11,35 @@ import (
|
||||
|
||||
func TestLocalSink(t *testing.T) {
|
||||
deployment := types.Template{
|
||||
Name: "deployment",
|
||||
Type: types.TypeWorkload,
|
||||
Alias: "deployment",
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
},
|
||||
Name: "deployment",
|
||||
Type: types.TypeWorkload,
|
||||
Parameters: []types.Parameter{
|
||||
{
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
FieldPaths: []string{"spec.containers[0].image"},
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
statefulset := types.Template{
|
||||
Name: "statefulset",
|
||||
Type: types.TypeWorkload,
|
||||
Alias: "stateful",
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Statefulset",
|
||||
},
|
||||
Name: "statefulset",
|
||||
Type: types.TypeWorkload,
|
||||
Parameters: []types.Parameter{
|
||||
{
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
FieldPaths: []string{"spec.containers[0].image"},
|
||||
Name: "image",
|
||||
Short: "i",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
route := types.Template{
|
||||
Name: "route",
|
||||
Type: types.TypeTrait,
|
||||
Alias: "route",
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Route",
|
||||
},
|
||||
Name: "route",
|
||||
Type: types.TypeTrait,
|
||||
Parameters: []types.Parameter{
|
||||
{
|
||||
Name: "domain",
|
||||
Short: "d",
|
||||
Required: true,
|
||||
FieldPaths: []string{"spec.domain"},
|
||||
Name: "domain",
|
||||
Short: "d",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/pkg/utils/system"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
@@ -32,6 +35,7 @@ import (
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var definitionDir string
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
@@ -108,6 +112,9 @@ var _ = BeforeSuite(func(done Done) {
|
||||
},
|
||||
},
|
||||
}
|
||||
definitionDir, err = system.GetDefinitionDir()
|
||||
Expect(err).Should(BeNil())
|
||||
os.MkdirAll(definitionDir, 0755)
|
||||
Expect(k8sClient.Create(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
Vendored
+12
-10
@@ -6,13 +6,15 @@ spec:
|
||||
definitionRef:
|
||||
name: routes.extend.oam.dev
|
||||
extension:
|
||||
alias: route
|
||||
object:
|
||||
apiVersion: extend.oam.dev/v1alpha2
|
||||
kind: Route
|
||||
parameters:
|
||||
- name: domain
|
||||
required: true
|
||||
short: d
|
||||
fieldPaths:
|
||||
- "spec.domain"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Route"
|
||||
spec: {
|
||||
domain: route.domain
|
||||
}
|
||||
}
|
||||
|
||||
route: {
|
||||
domain: string
|
||||
}
|
||||
|
||||
+32
-10
@@ -6,13 +6,35 @@ spec:
|
||||
definitionRef:
|
||||
name: deployments.testapps
|
||||
extension:
|
||||
alias: deployment
|
||||
object:
|
||||
apiVersion: core.oam.dev/v1alpha2
|
||||
kind: Deployment
|
||||
parameters:
|
||||
- name: image
|
||||
short: i
|
||||
required: true
|
||||
fieldPaths:
|
||||
- "spec.containers[0].image"
|
||||
template: |
|
||||
#Template: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: deployment.name
|
||||
spec: {
|
||||
containers: [{
|
||||
image: deployment.image
|
||||
name: deployment.name
|
||||
env: deployment.env
|
||||
ports: [{
|
||||
containerPort: deployment.port
|
||||
protocol: "TCP"
|
||||
name: "default"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
deployment: {
|
||||
name: string
|
||||
// +usage=specify app image
|
||||
// +short=i
|
||||
image: string
|
||||
// +usage=specify port for container
|
||||
// +short=p
|
||||
port: *8080 | int
|
||||
env: [...{
|
||||
name: string
|
||||
value: string
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloud-native-application/rudrx/api/types"
|
||||
)
|
||||
|
||||
const rudrHome = ".rudr"
|
||||
|
||||
func GetRudrHomeDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, rudrHome), nil
|
||||
}
|
||||
|
||||
func GetApplicationDir() (string, error) {
|
||||
home, err := GetRudrHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, "applications"), nil
|
||||
}
|
||||
|
||||
func GetDefinitionDir() (string, error) {
|
||||
home, err := GetRudrHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, "definitions"), nil
|
||||
}
|
||||
|
||||
func GetEnvDir() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, rudrHome, "envs"), nil
|
||||
}
|
||||
|
||||
func GetCurrentEnvPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, rudrHome, "curenv"), nil
|
||||
}
|
||||
|
||||
func InitDefinitionDir() error {
|
||||
dir, err := GetDefinitionDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
func InitApplicationDir() error {
|
||||
dir, err := GetApplicationDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
func InitDefaultEnv() error {
|
||||
envDir, err := GetEnvDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.MkdirAll(envDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, _ := json.Marshal(&types.EnvMeta{Namespace: types.DefaultEnvName})
|
||||
if err = ioutil.WriteFile(filepath.Join(envDir, types.DefaultEnvName), data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
curEnvPath, err := GetCurrentEnvPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ioutil.WriteFile(curEnvPath, []byte(types.DefaultEnvName), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user