refactor rudr run to rudr <workload>:run

This commit is contained in:
天元
2020-08-01 13:14:06 +08:00
parent 194bd472dc
commit 728ebb33b2
23 changed files with 747 additions and 329 deletions
+6 -6
View File
@@ -24,12 +24,6 @@ jobs:
run: |
go get -v -t -d ./...
- name: Run Make
run: make
- name: Run Make test
run: make test
- name: Install ginkgo
run: |
sudo apt-get update
@@ -40,6 +34,12 @@ jobs:
with:
version: "v0.7.0"
- name: Run Make
run: make
- name: Run Make test
run: make test
- name: Run e2e tests
run: |
make e2e-setup
@@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package util
package types
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// TemplateSpec defines the desired state of Template
// Template defines the content of a plugin
type Template struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Alias string `json:"alias,omitempty"`
Object unstructured.Unstructured `json:"object,omitempty"`
LastCommandParam string `json:"lastCommandParam,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
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"`
}
type DefinitionType string
const (
TypeWorkload DefinitionType = "workload"
TypeTrait DefinitionType = "trait"
)
type Parameter struct {
Name string `json:"name"`
Short string `json:"short,omitempty"`
+10
View File
@@ -0,0 +1,10 @@
package types
const (
DefaultOAMNS = "oam-system"
DefaultOAMReleaseName = "core-runtime"
DefaultOAMChartName = "crossplane-master/oam-kubernetes-runtime"
DefaultOAMRepoName = "crossplane-master"
DefaultOAMRepoUrl = "https://charts.crossplane.io/master"
DefaultOAMVersion = ">0.0.0-0"
)
-36
View File
@@ -1,36 +0,0 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha2 contains API Schema definitions for the core.oam.dev v1alpha2 API group
// +kubebuilder:object:generate=true
// +groupName=admin.oam.dev
package v1alpha2
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "admin.oam.dev", Version: "v1alpha2"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
+6 -4
View File
@@ -7,6 +7,8 @@ import (
"runtime"
"time"
"github.com/cloud-native-application/rudrx/pkg/cmd/workload"
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
"github.com/spf13/cobra"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
@@ -14,7 +16,6 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
coreoamdevv1alpha2 "github.com/cloud-native-application/rudrx/api/v1alpha2"
"github.com/cloud-native-application/rudrx/pkg/cmd"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/cloud-native-application/rudrx/pkg/utils/logs"
@@ -38,8 +39,6 @@ var (
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = coreoamdevv1alpha2.AddToScheme(scheme)
_ = core.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@@ -82,7 +81,6 @@ func newCommand() *cobra.Command {
}
cmds.AddCommand(
cmd.NewRunCommand(f, client, ioStream, os.Args[1:]),
cmd.NewTraitsCommand(f, client, ioStream, []string{}),
cmd.NewWorkloadsCommand(f, client, ioStream, os.Args[1:]),
cmd.NewBindCommand(f, client, ioStream, []string{}),
@@ -95,6 +93,10 @@ func newCommand() *cobra.Command {
cmd.NewEnvCommand(f, ioStream),
NewVersionCommand(),
)
if err = workload.AddPlugins(cmds, client, ioStream); err != nil {
fmt.Println("Add plugins from workloadDefinition err", err)
os.Exit(1)
}
return cmds
}
@@ -25,7 +25,6 @@ spec:
- containerPort: 6379
protocol: TCP
name: tbd # TODO(zzxwill) A temporary workaround for ContainerizedWorkload
lastCommandParam: image
parameters:
- name: image
short: i
@@ -20,7 +20,6 @@ spec:
- containerPort: 6379
protocol: TCP
name: tbd # TODO(zzxwill) A temporary workaround for ContainerizedWorkload
lastCommandParam: image
parameters:
- name: image
short: i
+1
View File
@@ -5,6 +5,7 @@ go 1.13
require (
github.com/crossplane/crossplane-runtime v0.8.0
github.com/crossplane/oam-kubernetes-runtime v0.0.8
github.com/ghodss/yaml v1.0.0
github.com/go-logr/logr v0.1.0
github.com/gosuri/uitable v0.0.4
github.com/onsi/ginkgo v1.11.0
+1
View File
@@ -185,6 +185,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gertd/go-pluralize v0.1.7 h1:RgvJTJ5W7olOoAks97BOwOlekBFsLEyh00W48Z6ZEZY=
github.com/gertd/go-pluralize v0.1.7/go.mod h1:O4eNeeIf91MHh1GJ2I47DNtaesm66NYvjYgAahcqSDQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+7 -5
View File
@@ -8,6 +8,8 @@ import (
"strconv"
"strings"
"github.com/cloud-native-application/rudrx/api/types"
cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
@@ -18,7 +20,7 @@ import (
type commandOptions struct {
Env *EnvMeta
Template cmdutil.Template
Template types.Template
Component corev1alpha2.Component
AppConfig corev1alpha2.ApplicationConfiguration
Client client.Client
@@ -62,8 +64,8 @@ func NewBindCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStre
}
for _, t := range traitDefinitions.Items {
var traitTemplate cmdutil.Template
traitTemplate, err := cmdutil.ConvertTemplateJson2Object(t.Spec.Extension)
var traitTemplate types.Template
traitTemplate, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
if err != nil {
fmt.Printf("extract template from traitDefinition %v err: %v, ignore it\n", t.Name, err)
continue
@@ -142,13 +144,13 @@ func (o *commandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
cmdutil.PrintErrorMessage(errMsg, 1)
}
traitTemplate, err := cmdutil.ConvertTemplateJson2Object(traitDefinition.Spec.Extension)
traitTemplate, err := types.ConvertTemplateJson2Object(traitDefinition.Spec.Extension)
if err != nil {
return fmt.Errorf("attaching the trait hit an issue: %s", err)
}
pvd := fieldpath.Pave(traitTemplate.Object.Object)
pvd := fieldpath.Pave(traitTemplate.Object)
for _, v := range traitTemplate.Parameters {
flagSet := cmd.Flag(v.Name)
for _, path := range v.FieldPaths {
+25 -32
View File
@@ -1,13 +1,11 @@
package cmd
import (
"github.com/cloud-native-application/rudrx/pkg/cmd/util"
"github.com/cloud-native-application/rudrx/api/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"github.com/cloud-native-application/rudrx/api/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
)
@@ -18,37 +16,33 @@ var (
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = v1alpha2.AddToScheme(scheme)
_ = core.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
// used in testing
var (
workloadTemplateExample = &util.Template{
workloadTemplateExample = &types.Template{
Object: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ContainerizedWorkload",
"metadata": map[string]interface{}{
"name": "pod",
},
"spec": map[string]interface{}{
"containers": "",
},
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ContainerizedWorkload",
"metadata": map[string]interface{}{
"name": "pod",
},
"spec": map[string]interface{}{
"containers": "",
},
},
LastCommandParam: "image",
Parameters: []util.Parameter{
util.Parameter{
Parameters: []types.Parameter{
types.Parameter{
Name: "image",
Short: "i",
Required: true,
Type: "string",
FieldPaths: []string{"spec.containers[0].image"},
},
util.Parameter{
types.Parameter{
Name: "port",
Short: "p",
Required: false,
@@ -58,22 +52,21 @@ var (
},
}
traitTemplateExample = &util.Template{
traitTemplateExample = &types.Template{
Object: unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ManualScalerTrait",
"metadata": map[string]interface{}{
"name": "pod",
},
"spec": map[string]interface{}{
"replicaCount": "2",
},
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ManualScalerTrait",
"metadata": map[string]interface{}{
"name": "pod",
},
"spec": map[string]interface{}{
"replicaCount": "2",
},
},
Parameters: []util.Parameter{
util.Parameter{
Parameters: []types.Parameter{
types.Parameter{
Name: "replicaCount",
Short: "i",
Required: true,
+13 -20
View File
@@ -6,6 +6,8 @@ import (
"io"
"os"
"github.com/cloud-native-application/rudrx/api/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -50,15 +52,6 @@ type initCmd struct {
version string
}
const (
DefaultOAMNS = "oam-system"
DefaultOAMReleaseName = "core-runtime"
DefaultOAMChartName = "crossplane-master/oam-kubernetes-runtime"
DefaultOAMRepoName = "crossplane-master"
DefaultOAMRepoUrl = "https://charts.crossplane.io/master"
DefaultOAMVersion = ">0.0.0-0"
)
var (
defaultObject = []interface{}{
&oamv1.WorkloadDefinition{},
@@ -92,7 +85,7 @@ func NewInitCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStre
Long: initDesc,
RunE: func(cmd *cobra.Command, args []string) error {
i.client = c
i.namespace = DefaultOAMNS
i.namespace = types.DefaultOAMNS
return i.run(ioStreams)
},
}
@@ -109,8 +102,8 @@ func (i *initCmd) run(ioStreams cmdutil.IOStreams) error {
return fmt.Errorf("could not get kubernetes client: %s", err)
}
if !cmdutil.IsNamespaceExist(i.client, DefaultOAMNS) {
if err := cmdutil.NewNamespace(i.client, DefaultOAMNS); err != nil {
if !cmdutil.IsNamespaceExist(i.client, types.DefaultOAMNS) {
if err := cmdutil.NewNamespace(i.client, types.DefaultOAMNS); err != nil {
return err
}
}
@@ -143,8 +136,8 @@ func (i *initCmd) IsOamRuntimeExist() bool {
func InstallOamRuntime(ioStreams cmdutil.IOStreams, version string) error {
if !IsHelmRepositoryExist(DefaultOAMRepoName, DefaultOAMRepoUrl) {
err := AddHelmRepository(DefaultOAMRepoName, DefaultOAMRepoUrl,
if !IsHelmRepositoryExist(types.DefaultOAMRepoName, types.DefaultOAMRepoUrl) {
err := AddHelmRepository(types.DefaultOAMRepoName, types.DefaultOAMRepoUrl,
"", "", "", "", "", false, ioStreams.Out)
if err != nil {
return err
@@ -156,7 +149,7 @@ func InstallOamRuntime(ioStreams cmdutil.IOStreams, version string) error {
return err
}
chartRequested, err := GetChart(chartClient, DefaultOAMChartName)
chartRequested, err := GetChart(chartClient, types.DefaultOAMChartName)
if err != nil {
return err
}
@@ -174,8 +167,8 @@ func NewHelmInstall(version string, ioStreams cmdutil.IOStreams) (*action.Instal
actionConfig := new(action.Configuration)
if err := actionConfig.Init(
kube.GetConfig(cmdutil.GetKubeConfig(), "", DefaultOAMNS),
DefaultOAMNS,
kube.GetConfig(cmdutil.GetKubeConfig(), "", types.DefaultOAMNS),
types.DefaultOAMNS,
os.Getenv("HELM_DRIVER"),
ioStreams.Infof,
); err != nil {
@@ -183,14 +176,14 @@ func NewHelmInstall(version string, ioStreams cmdutil.IOStreams) (*action.Instal
}
client := action.NewInstall(actionConfig)
client.Namespace = DefaultOAMNS
client.ReleaseName = DefaultOAMReleaseName
client.Namespace = types.DefaultOAMNS
client.ReleaseName = types.DefaultOAMReleaseName
if len(version) > 0 {
client.Version = version
return client, nil
}
client.Version = DefaultOAMVersion
client.Version = types.DefaultOAMVersion
return client, nil
}
-204
View File
@@ -1,204 +0,0 @@
package cmd
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"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"
)
type runOptions struct {
Template cmdutil.Template
Env *EnvMeta
Component corev1alpha2.Component
AppConfig corev1alpha2.ApplicationConfiguration
client client.Client
cmdutil.IOStreams
}
func newRunOptions(ioStreams cmdutil.IOStreams) *runOptions {
return &runOptions{IOStreams: ioStreams}
}
// NewRunCommand init new command
func NewRunCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStreams, args []string) *cobra.Command {
cmd := newRunCommand()
// flags pass to new command directly
cmd.DisableFlagParsing = true
cmd.SetArgs(args)
cmd.SetOut(ioStreams.Out)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return runSubRunCommand(cmd, f, c, ioStreams, args)
}
return cmd
}
// runSubRunCommand is init a new command and run independent
func runSubRunCommand(parentCmd *cobra.Command, f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStreams, args []string) error {
ctx := context.Background()
workloadNames := []string{}
o := newRunOptions(ioStreams)
o.client = c
o.Env, _ = GetEnv()
// init fake command and pass args to fake command
// flags and subcommand append to fake comand and parent command
// run fake command only, show tips in parent command only
fakeCommand := newRunCommand()
fakeCommand.SilenceUsage = true
fakeCommand.SilenceErrors = true
fakeCommand.DisableAutoGenTag = true
fakeCommand.DisableFlagsInUseLine = true
fakeCommand.DisableSuggestions = true
// set args from parent
if len(args) > 0 {
fakeCommand.SetArgs(args)
} else {
fakeCommand.SetArgs([]string{})
}
fakeCommand.SetOut(o.Out)
fakeCommand.RunE = func(cmd *cobra.Command, args []string) error {
return errors.New("You must specify a workload, like " + strings.Join(workloadNames, ", ") +
"\nSee 'rudr run -h' for help and examples")
}
fakeCommand.PersistentFlags().StringP("namespace", "n", "default", "namespace for apps")
parentCmd.PersistentFlags().StringP("namespace", "n", "default", "namespace for apps")
var workloadDefs corev1alpha2.WorkloadDefinitionList
err := c.List(ctx, &workloadDefs)
if err != nil {
return fmt.Errorf("listing Workload definition hit an issue: %s", err)
}
for _, wd := range workloadDefs.Items {
var tmp cmdutil.Template
tmp, err := cmdutil.ConvertTemplateJson2Object(wd.Spec.Extension)
if err != nil {
fmt.Printf("extract template from traitDefinition %v err: %v, ignore it\n", wd.Name, err)
continue
}
name := tmp.Alias
workloadNames = append(workloadNames, name)
subcmd := &cobra.Command{
Use: name + " [args]",
DisableFlagsInUseLine: true,
Short: "Run " + name + " workloads",
Long: "Run " + name + " workloads",
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(f, cmd, args, ctx); err != nil {
return err
}
return o.Run(f, cmd)
},
}
subcmd.SetOut(o.Out)
for _, v := range tmp.Parameters {
if tmp.LastCommandParam != v.Name {
subcmd.PersistentFlags().StringP(v.Name, v.Short, v.Default, v.Usage)
}
}
// Comment this line as template content will get mixed when there are more than two WorkloadDefinitions
// tmp.DeepCopyInto(&o.Template)
o.Template = tmp
fakeCommand.AddCommand(subcmd)
parentCmd.AddCommand(subcmd)
}
return fakeCommand.Execute()
}
func (o *runOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, ctx context.Context) error {
argsLength := len(args)
lastCommandParam := o.Template.LastCommandParam
if argsLength < 1 {
return errors.New("must specify name for workload")
} else if argsLength >= 1 {
workloadName := args[0]
switch {
case argsLength < 2 && lastCommandParam != "":
// TODO(zzxwill): Could not determine whether the argument is the workload name or image name if without image tag
return fmt.Errorf("You must specify `%s` as the last command.\nSee 'rudr run -h' for help and examples",
lastCommandParam)
case argsLength == 2:
workloadTemplate := o.Template
pvd := fieldpath.Pave(workloadTemplate.Object.Object)
for _, v := range workloadTemplate.Parameters {
lastCommandValue := args[argsLength-1]
var paraV string
if v.Name == lastCommandParam {
paraV = lastCommandValue
} else {
flagSet := cmd.Flag(v.Name)
paraV = flagSet.Value.String()
}
if paraV == "" {
return fmt.Errorf("Flag `%s` is NOT set, please check and try again. \nSee 'rudr run -h' for help and examples", v.Name)
}
for _, path := range v.FieldPaths {
if v.Type == "int" {
portValue, _ := strconv.ParseFloat(paraV, 64)
pvd.SetNumber(path, portValue)
break
}
pvd.SetString(path, paraV)
}
}
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.AppConfig.Name = args[0]
o.AppConfig.Namespace = namespace
o.AppConfig.Spec.Components = append(o.AppConfig.Spec.Components, corev1alpha2.ApplicationConfigurationComponent{ComponentName: args[0]})
case argsLength > 2:
return fmt.Errorf("there are more commands than needed, please try again")
}
}
return nil
}
func (o *runOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error {
o.Infof("Creating AppConfig %s\n", o.AppConfig.Name)
err := o.client.Create(context.Background(), &o.Component)
if err != nil {
return fmt.Errorf("create component err: %s", err)
}
err = o.client.Create(context.Background(), &o.AppConfig)
if err != nil {
return fmt.Errorf("create appconfig err %s", err)
}
o.Info("SUCCEED")
return nil
}
func newRunCommand() *cobra.Command {
return &cobra.Command{
Use: "run [WORKLOAD_KIND] [args]",
DisableFlagsInUseLine: true,
Short: "Run OAM workloads",
Long: "Create and Run one Workload one AppConfig OAM APP",
Example: `
rudr run containerized frontend -p 80 oam-dev/demo:v1
`}
}
+8 -6
View File
@@ -5,6 +5,8 @@ import (
"os"
"strings"
"github.com/cloud-native-application/rudrx/api/types"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
@@ -172,7 +174,7 @@ func GetTraitDefinitionByAlias(ctx context.Context, c client.Client, traitAlias
err := c.List(ctx, &traitDefinitionList)
if err == nil {
for _, t := range traitDefinitionList.Items {
template, err := ConvertTemplateJson2Object(t.Spec.Extension)
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
if err == nil && strings.EqualFold(template.Alias, traitAlias) {
traitDefinition = t
break
@@ -190,14 +192,14 @@ func GetTraitNameAliasKind(ctx context.Context, c client.Client, namespace strin
t, err := GetTraitDefinitionByName(ctx, c, namespace, name)
if err == nil {
template, err := ConvertTemplateJson2Object(t.Spec.Extension)
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
if err == nil {
tName, tAlias = t.Name, template.Alias
}
} else {
t, err := GetTraitDefinitionByAlias(ctx, c, name)
if err == nil {
template, err := ConvertTemplateJson2Object(t.Spec.Extension)
template, err := types.ConvertTemplateJson2Object(t.Spec.Extension)
if err == nil {
tName, tAlias = t.Name, template.Alias
}
@@ -230,15 +232,15 @@ func GetWorkloadNameAliasKind(ctx context.Context, c client.Client, namespace st
w, err := GetWorkloadDefinitionByName(ctx, c, namespace, workloadName)
if err == nil { // workloadName is complete name
var workloadTemplate Template
workloadTemplate, err := ConvertTemplateJson2Object(w.Spec.Extension)
var workloadTemplate types.Template
workloadTemplate, err := types.ConvertTemplateJson2Object(w.Spec.Extension)
if err == nil {
name, alias = w.Name, workloadTemplate.Alias
}
} else { // workloadName is alias or kind
w, err := GetWorkloadDefinitionByAlias(ctx, c, name)
if err == nil {
workloadTemplate, err := ConvertTemplateJson2Object(w.Spec.Extension)
workloadTemplate, err := types.ConvertTemplateJson2Object(w.Spec.Extension)
if err == nil {
name, alias, kind = w.Name, workloadTemplate.Alias, w.Kind
}
+134
View File
@@ -0,0 +1,134 @@
package workload
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/cloud-native-application/rudrx/pkg/plugins"
"github.com/cloud-native-application/rudrx/pkg/cmd"
"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"
)
type runOptions struct {
Template types.Template
Env *cmd.EnvMeta
Component corev1alpha2.Component
AppConfig corev1alpha2.ApplicationConfiguration
client client.Client
cmdutil.IOStreams
}
func newRunOptions(ioStreams cmdutil.IOStreams) *runOptions {
return &runOptions{IOStreams: ioStreams}
}
func AddPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error {
templates, err := plugins.GetWorkloadsFromCluster(context.TODO(), types.DefaultOAMNS, c)
if err != nil {
return err
}
for _, tmp := range templates {
var name string
if tmp.Alias != "" {
name = tmp.Alias
}
o := newRunOptions(ioStreams)
o.client = c
o.Env, _ = cmd.GetEnv()
pluginCmd := &cobra.Command{
Use: name + ":run [args]",
DisableFlagsInUseLine: true,
Short: "Run " + name + " workloads",
Long: "Run " + name + " workloads",
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(cmd, args, context.TODO()); err != nil {
return err
}
return o.Run(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)
}
}
o.Template = tmp
parentCmd.AddCommand(pluginCmd)
}
return nil
}
func (o *runOptions) Complete(cmd *cobra.Command, args []string, ctx context.Context) error {
argsLength := len(args)
if argsLength < 1 {
return errors.New("must specify name for workload")
}
workloadName := args[0]
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)
}
}
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.AppConfig.Name = args[0]
o.AppConfig.Namespace = namespace
o.AppConfig.Spec.Components = append(o.AppConfig.Spec.Components, corev1alpha2.ApplicationConfigurationComponent{ComponentName: args[0]})
return nil
}
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)
if err != nil {
return fmt.Errorf("create component err: %s", err)
}
err = o.client.Create(context.Background(), &o.AppConfig)
if err != nil {
return fmt.Errorf("create appconfig err %s", err)
}
o.Info("SUCCEED")
return nil
}
@@ -1,4 +1,4 @@
package cmd
package workload
/*
func TestNewRunCommand(t *testing.T) {
+68
View File
@@ -0,0 +1,68 @@
package plugins
import (
"context"
"fmt"
"github.com/cloud-native-application/rudrx/api/types"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"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)
if err != nil {
return nil, err
}
traits, err := GetTraitsFromCluster(ctx, namespace, c)
if err != nil {
return nil, err
}
workloads = append(workloads, traits...)
return workloads, nil
}
func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Client) ([]types.Template, error) {
var templates []types.Template
var workloadDefs corev1alpha2.WorkloadDefinitionList
err := c.List(ctx, &workloadDefs, &client.ListOptions{Namespace: namespace})
if err != nil {
return nil, fmt.Errorf("list WorkloadDefinition err: %s", err)
}
for _, wd := range workloadDefs.Items {
var tmp types.Template
tmp, err := types.ConvertTemplateJson2Object(wd.Spec.Extension)
if err != nil {
fmt.Printf("extract template from workloadDefinition %v err: %v, ignore it\n", wd.Name, err)
continue
}
tmp.Type = types.TypeWorkload
tmp.Name = wd.Name
templates = append(templates, tmp)
}
return templates, nil
}
func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client) ([]types.Template, error) {
var templates []types.Template
var traitDefs corev1alpha2.TraitDefinitionList
err := c.List(ctx, &traitDefs, &client.ListOptions{Namespace: namespace})
if err != nil {
return nil, fmt.Errorf("list TraitDefinition err: %s", err)
}
for _, td := range traitDefs.Items {
var tmp types.Template
tmp, err := types.ConvertTemplateJson2Object(td.Spec.Extension)
if err != nil {
fmt.Printf("extract template from workloadDefinition %v err: %v, ignore it\n", td.Name, err)
continue
}
tmp.Type = types.TypeTrait
tmp.Name = td.Name
templates = append(templates, tmp)
}
return templates, nil
}
+109
View File
@@ -0,0 +1,109 @@
package plugins
import (
"context"
"fmt"
"io/ioutil"
"github.com/cloud-native-application/rudrx/api/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)
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",
},
Parameters: []types.Parameter{
{
Name: "domain",
Short: "d",
Required: true,
FieldPaths: []string{"spec.domain"},
},
},
}
deployment := types.Template{
Name: "deployments.testapps",
Type: types.TypeWorkload,
Alias: "deployment",
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "Deployment",
},
Parameters: []types.Parameter{
{
Name: "image",
Short: "i",
Required: true,
FieldPaths: []string{"spec.containers[0].image"},
},
},
}
BeforeEach(func() {
Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: DefinitionNamespace}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
traitdata, err := ioutil.ReadFile("testdata/traitDef.yaml")
Expect(err).Should(BeNil())
var td v1alpha2.TraitDefinition
Expect(yaml.Unmarshal(traitdata, &td)).Should(BeNil())
td.Namespace = DefinitionNamespace
logf.Log.Info("Creating trait definition", "data", td)
Expect(k8sClient.Create(ctx, &td)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
workloaddata, err := ioutil.ReadFile("testdata/workloadDef.yaml")
Expect(err).Should(BeNil())
var wd v1alpha2.WorkloadDefinition
Expect(yaml.Unmarshal(workloaddata, &wd)).Should(BeNil())
wd.Namespace = DefinitionNamespace
logf.Log.Info("Creating workload definition", "data", wd)
Expect(k8sClient.Create(ctx, &wd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
// Notice!! Definition 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)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting trait definitions %v", traitDefs))
Expect(traitDefs).Should(Equal([]types.Template{route}))
})
// Notice!! Definition 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)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting workload definitions %v", workloadDefs))
Expect(workloadDefs).Should(Equal([]types.Template{deployment}))
})
It("getall", func() {
alldef, err := GetTemplatesFromCluster(context.Background(), DefinitionNamespace, k8sClient)
Expect(err).Should(BeNil())
logf.Log.Info(fmt.Sprintf("Getting all definitions %v", alldef))
Expect(alldef).Should(Equal([]types.Template{deployment, route}))
})
})
+61
View File
@@ -0,0 +1,61 @@
package plugins
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"github.com/cloud-native-application/rudrx/api/types"
)
func GetDefFromLocal(dir string, defType types.DefinitionType) ([]types.Template, error) {
temps, err := LoadTempFromLocal(dir)
if err != nil {
return nil, err
}
var defs []types.Template
for _, t := range temps {
if t.Type != defType {
continue
}
defs = append(defs, t)
}
return defs, nil
}
func SinkTemp2Local(templates []types.Template, dir string) error {
for _, tmp := range templates {
data, err := json.Marshal(tmp)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(dir, tmp.Name), data, 0644)
if err != nil {
return err
}
}
return nil
}
func LoadTempFromLocal(dir string) ([]types.Template, error) {
var tmps []types.Template
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
for _, f := range files {
if f.IsDir() {
continue
}
data, err := ioutil.ReadFile(filepath.Join(dir, f.Name()))
if err != nil {
return nil, err
}
var tmp types.Template
if err = json.Unmarshal(data, &tmp); err != nil {
return nil, err
}
tmps = append(tmps, tmp)
}
return tmps, nil
}
+125
View File
@@ -0,0 +1,125 @@
package plugins
import (
"os"
"testing"
"github.com/cloud-native-application/rudrx/api/types"
"github.com/stretchr/testify/assert"
)
func TestLocalSink(t *testing.T) {
deployment := types.Template{
Name: "deployment",
Type: types.TypeWorkload,
Alias: "deployment",
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
},
Parameters: []types.Parameter{
{
Name: "image",
Short: "i",
Required: true,
FieldPaths: []string{"spec.containers[0].image"},
},
},
}
statefulset := types.Template{
Name: "statefulset",
Type: types.TypeWorkload,
Alias: "stateful",
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Statefulset",
},
Parameters: []types.Parameter{
{
Name: "image",
Short: "i",
Required: true,
FieldPaths: []string{"spec.containers[0].image"},
},
},
}
route := types.Template{
Name: "route",
Type: types.TypeTrait,
Alias: "route",
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Route",
},
Parameters: []types.Parameter{
{
Name: "domain",
Short: "d",
Required: true,
FieldPaths: []string{"spec.domain"},
},
},
}
cases := map[string]struct {
dir string
tmps []types.Template
Type types.DefinitionType
expDef []types.Template
}{
"Test No Templates": {
dir: "rudrx-test1",
tmps: nil,
},
"Test Only Workload": {
dir: "rudrx-test2",
tmps: []types.Template{deployment, statefulset},
Type: types.TypeWorkload,
expDef: []types.Template{deployment, statefulset},
},
"Test Only Trait": {
dir: "rudrx-test3",
tmps: []types.Template{route},
Type: types.TypeTrait,
expDef: []types.Template{route},
},
"Test Only Workload But want trait": {
dir: "rudrx-test3",
tmps: []types.Template{deployment, statefulset},
Type: types.TypeTrait,
expDef: nil,
},
"Test Both have Workload and trait But want Workload": {
dir: "rudrx-test4",
tmps: []types.Template{deployment, route, statefulset},
Type: types.TypeWorkload,
expDef: []types.Template{deployment, statefulset},
},
"Test Both have Workload and trait But want Trait": {
dir: "rudrx-test5",
tmps: []types.Template{deployment, route, statefulset},
Type: types.TypeTrait,
expDef: []types.Template{route},
},
}
for name, c := range cases {
testInDir(t, name, c.dir, c.tmps, c.expDef, c.Type)
}
}
func testInDir(t *testing.T, casename, dir string, tmps, defexp []types.Template, Type types.DefinitionType) {
err := os.MkdirAll(dir, 0755)
assert.NoError(t, err, casename)
defer os.RemoveAll(dir)
err = SinkTemp2Local(tmps, dir)
assert.NoError(t, err, casename)
gottmps, err := LoadTempFromLocal(dir)
assert.NoError(t, err, casename)
assert.Equal(t, tmps, gottmps, casename)
if Type != "" {
gotDef, err := GetDefFromLocal(dir, Type)
assert.NoError(t, err, casename)
assert.Equal(t, defexp, gotDef, casename)
}
}
+122
View File
@@ -0,0 +1,122 @@
package plugins
import (
"context"
"path/filepath"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"CLI Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
By("bootstrapping test environment")
useExistCluster := true
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
UseExistingCluster: &useExistCluster,
}
var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
scheme := runtime.NewScheme()
Expect(corev1alpha2.AddToScheme(scheme)).NotTo(HaveOccurred())
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
Expect(v1beta1.AddToScheme(scheme)).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
crd := v1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "traitdefinitions.core.oam.dev",
},
Spec: v1beta1.CustomResourceDefinitionSpec{
Group: "core.oam.dev",
Names: v1beta1.CustomResourceDefinitionNames{
Kind: "TraitDefinition",
ListKind: "TraitDefinitionList",
Plural: "traitdefinitions",
Singular: "traitdefinition",
},
Versions: []v1beta1.CustomResourceDefinitionVersion{
{
Name: "v1alpha2",
Served: true,
Storage: true,
},
},
},
}
Expect(k8sClient.Create(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
crd = v1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "workloaddefinitions.core.oam.dev",
},
Spec: v1beta1.CustomResourceDefinitionSpec{
Group: "core.oam.dev",
Names: v1beta1.CustomResourceDefinitionNames{
Kind: "WorkloadDefinition",
ListKind: "WorkloadDefinitionList",
Plural: "workloaddefinitions",
Singular: "workloaddefinition",
},
Versions: []v1beta1.CustomResourceDefinitionVersion{
{
Name: "v1alpha2",
Served: true,
Storage: true,
},
},
},
}
Expect(k8sClient.Create(context.Background(), &crd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
close(done)
}, 60)
var DefinitionNamespace = "testdef"
var _ = AfterSuite(func() {
By("tearing down the test environment")
k8sClient.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: DefinitionNamespace}})
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
+18
View File
@@ -0,0 +1,18 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: routes.extend.oam.dev
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"
+18
View File
@@ -0,0 +1,18 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: deployments.testapps
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"