Merge branch 'master' into vela-up

This commit is contained in:
天元
2021-01-05 17:15:03 +08:00
25 changed files with 525 additions and 204 deletions
+35 -71
View File
@@ -2,7 +2,8 @@
## About KubeVela
KubeVela project is initialized and maintained by the cloud native community since day 0 with [bootstrapping contributors from 8+ different organizations](https://github.com/oam-dev/kubevela/graphs/contributors). We intend for KubeVela to have a open governance since the very beginning and donate the project to neutral foundation as soon as it's released.
KubeVela project is initialized and maintained by the cloud native community since day 0 with [bootstrapping contributors from 8+ different organizations](https://github.com/oam-dev/kubevela/graphs/contributors).
We intend for KubeVela to have an open governance since the very beginning and donate the project to neutral foundation as soon as it's released.
This doc explains how to set up a development environment, so you can get started
contributing to `kubevela` or build a PoC (Proof of Concept).
@@ -27,15 +28,22 @@ We also recommend you to learn about KubeVela's [design](docs/en/design.md) befo
git clone git@github.com:oam-dev/kubevela.git
```
KubeVela includes two parts, `vela core` and `vela cli`.
- The `vela core` is actually a K8s controller, it will watch OAM Spec CRD and deploy resources.
- The `vela cli` is a command line tool that can build, run apps(with the help of `vela core`).
For local development, we probably need to build both of them.
* Build Vela CLI
```shell script
make
```
* Configure vela to PATH
After the vela cli built successfully, `make` command will create `vela` binary to `bin/` under the project.
after build, make will create `vela` binary to `bin/`, Set this path to PATH.
* Configure `vela` binary to System PATH
```shell script
export PATH=$PATH:/your/path/to/project/kubevela/bin
@@ -51,7 +59,7 @@ make manager
* Run Vela Core
Firstly make sure your cluster has CRDs.
Firstly make sure your cluster has CRDs, below is the command that can help install all CRDs.
```shell script
make core-install
@@ -66,74 +74,19 @@ make core-run
This command will run controller locally, it will use your local KubeConfig which means you need to have a k8s cluster
locally. If you don't have a one, we suggest that you could setup up a cluster with [kind](https://kind.sigs.k8s.io/).
When you're developing `vela-core`, make sure the controller installed by `vela install` is not running.
Otherwise, it will conflict with your local running controller.
You can check and uninstall it by using helm.
```shell script
helm list -A
helm uninstall -n vela-system kubevela
```
### Use
* Create environment
```shell script
vela env init myenv --namespace myenv --email my@email.com --domain kubevela.io
```
* Create Component
For example, use the following command to create and run an application.
```shell script
$ vela svc deploy mysvc -t webservice --image crccheck/hello-world --port 8000 -a abc
App abc deployed
```
* Add Trait
```shell script
$ vela route abc
Adding route for app mysvc
⠋ Deploying ...
✅ Application Deployed Successfully!
- Name: mysvc
Type: webservice
HEALTHY Ready: 1/1
Last Deployment:
Created at: 2020-11-02 11:17:28 +0800 CST
Updated at: 2020-11-02T11:21:23+08:00
Routes:
- route: Visiting URL: http://abc.kubevela.io IP: 47.242.68.137
```
* Check Status
```
$ vela status abc
About:
Name: abc
Namespace: default
Created at: 2020-11-02 11:17:28.067738 +0800 CST
Updated at: 2020-11-02 11:28:13.490986 +0800 CST
Services:
- Name: mysvc
Type: webservice
HEALTHY Ready: 1/1
Last Deployment:
Created at: 2020-11-02 11:17:28 +0800 CST
Updated at: 2020-11-02T11:28:13+08:00
Routes:
- route: Visiting URL: http://abc.kubevela.io IP: 47.242.68.137
```
* Delete App
```shell script
$ vela ls
SERVICE APP TYPE TRAITS STATUS CREATED-TIME
mysvc abc Deployed 2020-11-02 11:17:28 +0800 CST
$ vela delete abc
Deleting Application "abc"
delete apps succeed abc from default
```
You can try use your local built binaries follow [the documentation](https://kubevela.io/#/en/quick-start).
## Testing
@@ -158,7 +111,18 @@ make e2e-test
```
## Make a pull request
Remember to write unit-test and e2e test before making a pull request.
Remember to write unit-test and e2e-test after you have finished your code.
Run following checks before making a pull request.
```shell script
make reviewable
```
The command will do some lint checks and clean code.
After that, check in all changes and send a pull request.
## Merge Regulations
+2
View File
@@ -94,6 +94,8 @@ func main() {
flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.")
flag.BoolVar(&controllerArgs.ApplyOnceOnly, "apply-once-only", false,
"For the purpose of some production environment that workload or trait should not be affected if no spec change")
flag.StringVar(&controllerArgs.CustomRevisionHookURL, "custom-revision-hook-url", "",
"custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision")
flag.StringVar(&disableCaps, "disable-caps", "", "To be disabled builtin capability list.")
flag.Parse()
+55 -18
View File
@@ -1,31 +1,68 @@
# Vela Dashboard
# KubeVela Dashboard
## Quick start
In the root folder of this project, run `make start-dashboard` to start backend OpenAPI server and Dashboard at the same time.
```shell
➜ xxx/src/github.com/oam-dev/kubevela $ make start-dashboard
go run pkg/server/main/startAPIServer.go &
cd dashboard && yarn && yarn start && cd ..
yarn install v1.22.4
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/5] 🔍 Validating package.json...
[2/5] 🔍 Resolving packages...
success Already up-to-date.
$ umi g tmp
✨ Done in 5.89s.
yarn run v1.22.4
$ umi dev
Starting the development server...
I1230 10:37:54.157092 14236 request.go:621] Throttling request took 1.04915427s, request: GET:https://47.242.145.141:6443/apis/split.smi-spec.io/v1alpha2?timeout=32s
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] POST /api/envs/ --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).CreateEnv-fm (6 handlers)
[GIN-debug] PUT /api/envs/:envName --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).UpdateEnv-fm (6 handlers)
...
[GIN-debug] GET /api/version --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).GetVersion-fm (6 handlers)
[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (7 handlers)
✔ Webpack
Compiled successfully in 26.86s
DONE Compiled successfully in 26865ms 10:38:22 AM
## Environment Prepare
App running at:
- Local: http://localhost:8000 (copied to clipboard)
- Network: http://192.168.31.114:8000
```
Install `node_modules`:
## Development
### Install dependencies
```bash
yarn
```
## Provided Scripts
Scripts provided in `package.json`. It's safe to modify or add additional script:
### Start project
```bash
yarn start
```
### Build project
### Build
```bash
yarn build
```
### Check code style
### Start up
```bash
yarn start
```
### Lint and Test
- Check code style
```bash
yarn lint
@@ -34,11 +71,11 @@ yarn lint
You can also use script to auto fix some lint error:
```bash
yarn lint:fix
yarn prettier
```
### Test code
- Test code
```bash
yarn test
```
```
@@ -31,14 +31,14 @@ export default ({ name, parameters }: ShowParameters) => {
dataIndex: 'name',
key: 'name',
width: 200,
render: (text, row) => [
render: (text, row) => (
<Paragraph copyable={{ text: row.name }}>
<Space size="small" align="center">
{row.name}
{!row.required ? undefined : <Tag color="geekblue">request</Tag>}
</Space>
</Paragraph>,
],
</Paragraph>
),
},
{
title: 'Short',
@@ -46,11 +46,11 @@ export default ({ name, parameters }: ShowParameters) => {
key: 'short',
width: 100,
responsive: ['md'],
render: (text, row) => [
render: (text, row) => (
<Space size="small" align="center">
{!row.short ? undefined : <Tag color="magenta">{text}</Tag>}
</Space>,
],
</Space>
),
},
{
title: 'Usage',
@@ -71,11 +71,11 @@ export default ({ name, parameters }: ShowParameters) => {
key: 'default',
width: 200,
responsive: ['md'],
render: (text, row) => [
render: (text, row) => (
<Space size="small" align="center">
{!row.default ? undefined : <Text code>{text}</Text>}
</Space>,
],
{!row.default ? undefined : <Text code>{text.toString()}</Text>}
</Space>
),
},
]}
dataSource={parameters}
@@ -27,13 +27,13 @@ export default (): React.ReactNode => {
<ShowComponent name={showTraits.name} parameters={showTraits.parameters} />
<Card>
<ProList<any>
rowKey={(record) => record.name}
rowKey="name"
headerTitle="Type"
pagination={{
defaultPageSize: 5,
showSizeChanger: false,
}}
loading={loading ? { delay: 300 } : undefined}
loading={loading ? { delay: 60 } : undefined}
dataSource={traitsList ?? []}
split
metas={{
@@ -49,22 +49,15 @@ export default (): React.ReactNode => {
);
},
},
subTitle: {
render: (text, row) => {
return (
<Space size={0}>
<Tag color="green">{row.crdName}</Tag>
</Space>
);
},
},
content: {
render: (text, row) => {
return (
<Space size={0}>
<Text strong>applies&nbsp;to:&nbsp;</Text>
{row.appliesTo.map((item: string) => (
<Tag color="processing">{item}</Tag>
<Tag key={item} color="processing">
{item}
</Tag>
))}
</Space>
);
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Button, Card, Space, Tag, Typography } from 'antd';
import { Button, Card, Typography } from 'antd';
import { useModel } from 'umi';
import { PageContainer } from '@ant-design/pro-layout';
import { FileWordTwoTone, SnippetsTwoTone } from '@ant-design/icons';
@@ -50,15 +50,6 @@ export default (): React.ReactNode => {
);
},
},
subTitle: {
render: (text, row) => {
return (
<Space size={0}>
{!row.required ? undefined : <Tag color="#5BD8A6">Required</Tag>}
</Space>
);
},
},
actions: {
render: (text, row) => [
<Button
+28
View File
@@ -34,6 +34,15 @@
insert-css "^2.0.0"
rc-util "^5.0.1"
"@ant-design/pro-card@^1.8.3":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@ant-design/pro-card/-/pro-card-1.8.3.tgz#735796b47f7986e31242a7463506396234373472"
integrity sha512-bUEDPGBVNlczllOHGQU3a6s6QiF3Lw9yI6FYOzTAuPKl5tlN1FSICX9GmM+gqibGYYgr0xLXJHh05nA9BA0bPg==
dependencies:
"@ant-design/icons" "^4.2.1"
"@ant-design/pro-utils" "1.6.2"
classnames "^2.2.6"
"@ant-design/pro-cli@^2.0.2":
version "2.0.3"
resolved "https://registry.npmjs.org/@ant-design/pro-cli/-/pro-cli-2.0.3.tgz"
@@ -193,6 +202,13 @@
dependencies:
rc-util "^5.0.1"
"@ant-design/pro-provider@1.2.7":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@ant-design/pro-provider/-/pro-provider-1.2.7.tgz#aed6f0a52fb5f8d948c7087ed964febc97fcee91"
integrity sha512-SBV4zihM5Yg6COgP7tjRhozJbOHZSVippUjCF1Ouuy4JcabF5Maunx1mAOkMGZGMJ3Cwoz6EoX5dSyUrajU0Sw==
dependencies:
rc-util "^5.0.1"
"@ant-design/pro-skeleton@1.0.0-beta.10":
version "1.0.0-beta.10"
resolved "https://registry.npmjs.org/@ant-design/pro-skeleton/-/pro-skeleton-1.0.0-beta.10.tgz"
@@ -265,6 +281,18 @@
moment "^2.27.0"
rc-util "^5.0.6"
"@ant-design/pro-utils@1.6.2":
version "1.6.2"
resolved "https://registry.yarnpkg.com/@ant-design/pro-utils/-/pro-utils-1.6.2.tgz#57d587eb5b74fd9d1ea3fd29af05cff08a596c91"
integrity sha512-lVDJBIFSqmZ9RKOKGz4V7HlXkbxk1bB2xVDjYuBhNLuIS65S0IqmdaOcDjyttjBR6mbvA4Z4t1ccxzutzsOlWg==
dependencies:
"@ant-design/icons" "^4.3.0"
"@ant-design/pro-provider" "1.2.7"
classnames "^2.2.6"
fast-deep-equal "^3.1.3"
moment "^2.27.0"
rc-util "^5.0.6"
"@ant-design/react-slick@~0.27.0":
version "0.27.14"
resolved "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.27.14.tgz"
@@ -25,4 +25,8 @@ type Args struct {
// ApplyOnceOnly indicates whether workloads and traits should be
// affected if no spec change is made in the ApplicationConfiguration.
ApplyOnceOnly bool
// CustomRevisionHookURL is a webhook which will let oam-runtime to call with AC+Component info
// The webhook server will return a customized component revision for oam-runtime
CustomRevisionHookURL string
}
@@ -74,7 +74,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, gerr error
applog.Info("parse template")
// parse template
appParser := parser.NewParser(template.GetHanler(fclient.NewDefinitionClient(r.Client)))
appParser := parser.NewParser(template.GetHandler(fclient.NewDefinitionClient(r.Client)))
appfile, err := appParser.Parse(app.Name, app)
@@ -8,6 +8,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/template"
"github.com/oam-dev/kubevela/pkg/dsl/definition"
"github.com/oam-dev/kubevela/pkg/dsl/process"
@@ -183,13 +184,10 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload
workload.traits = []*Trait{}
workload.name = comp.Name
workload.typ = comp.WorkloadType
templ, kind, err := pser.templ(workload.typ)
templ, err := pser.templ(workload.typ, types.TypeWorkload)
if err != nil && !kerrors.IsNotFound(err) {
return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name)
}
if kind != template.WorkloadKind {
return nil, errors.Errorf("%s type (%s) invalid", comp.Name, workload.typ)
}
workload.template = templ
settings, err := DecodeJSONMarshaler(comp.Settings)
if err != nil {
@@ -214,14 +212,14 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload
}
func (pser *Parser) parseTrait(name string, properties map[string]interface{}) (*Trait, error) {
templ, kind, err := pser.templ(name)
if err != nil && !kerrors.IsNotFound(err) {
templ, err := pser.templ(name, types.TypeTrait)
if kerrors.IsNotFound(err) {
return nil, errors.Errorf("trait definition of %s not found", name)
}
if err != nil {
return nil, err
}
if kind != template.TraitKind {
return nil, errors.Errorf("kind of %s is not trait", name)
}
trait := new(Trait)
trait.template = templ
trait.name = name
@@ -116,7 +116,7 @@ spec:
o := v1alpha2.Application{}
yaml.Unmarshal([]byte(appfileYaml), &o)
appfile, err := NewParser(template.GetHanler(mock)).Parse("test", &o)
appfile, err := NewParser(template.GetHandler(mock)).Parse("test", &o)
if err != nil {
t.Error(err)
return
@@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"github.com/oam-dev/kubevela/apis/types"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -14,8 +16,8 @@ type manager struct {
defclient.DefinitionClient
}
// GetHanler get template handler
func GetHanler(cli defclient.DefinitionClient) Handler {
// GetHandler get template handler
func GetHandler(cli defclient.DefinitionClient) Handler {
m := &manager{
DefinitionClient: cli,
}
@@ -23,51 +25,46 @@ func GetHanler(cli defclient.DefinitionClient) Handler {
}
// Handler is template handler type
type Handler func(key string) (string, Kind, error)
type Handler func(key string, kind types.CapType) (string, error)
// Kind is template kind
type Kind uint16
const (
// WorkloadKind ...
WorkloadKind Kind = (1 << iota)
// TraitKind ...
TraitKind
// Unkownkind ...
Unkownkind
)
type Kind = types.CapType
// LoadTemplate Get template according to key
func (m *manager) LoadTemplate(key string) (string, Kind, error) {
wd, err := m.GetWorkloadDefinition(key)
if err != nil && !kerrors.IsNotFound(err) {
return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if wd != nil {
func (m *manager) LoadTemplate(key string, kd types.CapType) (string, error) {
switch kd {
case types.TypeWorkload:
wd, err := m.GetWorkloadDefinition(key)
if err != nil {
return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
jsonRaw, err := getTemplate(wd.Spec.Extension.Raw)
if err != nil {
return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key)
return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if jsonRaw != "" {
return jsonRaw, WorkloadKind, nil
if jsonRaw == "" {
return "", errors.New("no template found in definition")
}
return jsonRaw, nil
case types.TypeTrait:
td, err := m.GetTraitDefition(key)
if err != nil && !kerrors.IsNotFound(err) {
return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
}
td, err := m.GetTraitDefition(key)
if err != nil && !kerrors.IsNotFound(err) {
return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if td != nil {
jsonRaw, err := getTemplate(td.Spec.Extension.Raw)
if err != nil {
return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key)
return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key)
}
if jsonRaw != "" {
return jsonRaw, TraitKind, nil
if jsonRaw == "" {
return "", errors.New("no template found in definition")
}
return jsonRaw, nil
case types.TypeScope:
// TODO: add scope template support
}
return "", Unkownkind, nil
return "", fmt.Errorf("kind(%s) of %s not supported", kd, key)
}
func getTemplate(raw []byte) (string, error) {
@@ -5,6 +5,7 @@ import (
"cuelang.org/go/cue"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient"
)
@@ -78,16 +79,11 @@ spec:
m := manager{
mock,
}
temp, kind, err := m.LoadTemplate("worker")
temp, err := m.LoadTemplate("worker", types.TypeWorkload)
if err != nil {
t.Error(err)
return
}
if kind != WorkloadKind {
t.Errorf("template.LoadTemplate kind invalid")
return
}
var r cue.Runtime
inst, err := r.Compile("-", temp)
if err != nil {
@@ -94,9 +94,10 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error {
Named(name).
For(&v1alpha2.ApplicationConfiguration{}).
Watches(&source.Kind{Type: &v1alpha2.Component{}}, &ComponentHandler{
Client: mgr.GetClient(),
Logger: l,
RevisionLimit: args.RevisionLimit,
Client: mgr.GetClient(),
Logger: l,
RevisionLimit: args.RevisionLimit,
CustomRevisionHookURL: args.CustomRevisionHookURL,
}).
Complete(NewReconciler(mgr, dm,
WithLogger(l.WithValues("controller", name)),
@@ -0,0 +1,232 @@
/*
Copyright 2020 The KubeVela Authors.
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 applicationconfiguration
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test Deploying ApplicationConfiguration without TraitDefinition", func() {
const (
namespace = "definition-test"
appName = "hello"
componentName = "backend"
)
var (
ctx = context.Background()
workload v1alpha2.ContainerizedWorkload
component v1alpha2.Component
workloadKey = client.ObjectKey{
Name: componentName,
Namespace: namespace,
}
appConfig v1alpha2.ApplicationConfiguration
appConfigKey = client.ObjectKey{
Name: appName,
Namespace: namespace,
}
req = reconcile.Request{NamespacedName: appConfigKey}
ns = corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
)
BeforeEach(func() {})
It("ManualScalerTrait should work successfully even though its TraitDefinition doesn't exist", func() {
var componentStr = `
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: backend
namespace: definition-test
spec:
workload:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
spec:
containers:
- name: nginx
image: nginx:1.9.4
ports:
- containerPort: 80
name: nginx
env:
- name: TEST_ENV
value: test
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
`
var appConfigStr = `
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: hello
namespace: definition-test
spec:
components:
- componentName: backend
traits:
- trait:
apiVersion: core.oam.dev/v1alpha2
kind: ManualScalerTrait
spec:
replicaCount: 2
workloadRef:
apiVersion: core.oam.dev/v1alpha2
kind: ContainerizedWorkload
name: backend
`
By("Create namespace")
Eventually(
func() error {
return k8sClient.Create(ctx, &ns)
},
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
By("Create Component")
Expect(yaml.Unmarshal([]byte(componentStr), &component)).Should(BeNil())
Expect(k8sClient.Create(ctx, &component)).Should(Succeed())
cmpV1 := &v1alpha2.Component{}
Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, cmpV1)).Should(Succeed())
By("Create ApplicationConfiguration")
Expect(yaml.Unmarshal([]byte(appConfigStr), &appConfig)).Should(BeNil())
Expect(k8sClient.Create(ctx, &appConfig)).Should(Succeed())
By("Reconcile")
reconcileRetry(reconciler, req)
time.Sleep(5)
By("Check workload created successfully")
Eventually(func() error {
return k8sClient.Get(ctx, workloadKey, &workload)
}, time.Second, 300*time.Millisecond).Should(BeNil())
By("Check reconcile again and no error will happen")
reconcileRetry(reconciler, req)
By("Check appConfig condition should not have error")
Eventually(func() string {
By("Reconcile again and should not have error")
reconcileRetry(reconciler, req)
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
if err != nil {
return err.Error()
}
if len(appConfig.Status.Conditions) != 1 {
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
}
return string(appConfig.Status.Conditions[0].Reason)
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
By("Check trait CR is created")
var scaleName string
scaleList := v1alpha2.ManualScalerTraitList{}
labels := &metav1.LabelSelector{
MatchLabels: map[string]string{
"app.oam.dev/component": componentName,
},
}
selector, _ := metav1.LabelSelectorAsSelector(labels)
err := k8sClient.List(ctx, &scaleList, &client.ListOptions{
Namespace: namespace,
LabelSelector: selector,
})
Expect(err).Should(BeNil())
traitNamePrefix := fmt.Sprintf("%s-dummy-", componentName)
var traitExistFlag bool
for _, t := range scaleList.Items {
if strings.HasPrefix(t.Name, traitNamePrefix) {
traitExistFlag = true
scaleName = t.Name
}
}
Expect(traitExistFlag).Should(BeTrue())
By("Update ApplicationConfiguration by changing spec of trait")
newTrait := &v1alpha2.ManualScalerTrait{
TypeMeta: metav1.TypeMeta{
APIVersion: "core.oam.dev/v1alpha2",
Kind: "ManualScalerTrait",
},
Spec: v1alpha2.ManualScalerTraitSpec{
ReplicaCount: 3,
WorkloadReference: v1alpha1.TypedReference{
APIVersion: "core.oam.dev/v1alpha2",
Kind: "ContainerizedWorkload",
Name: componentName,
},
},
}
appConfig.Spec.Components[0].Traits = []v1alpha2.ComponentTrait{{Trait: runtime.RawExtension{Object: newTrait.DeepCopyObject()}}}
Expect(k8sClient.Update(ctx, &appConfig)).Should(BeNil())
By("Reconcile")
reconcileRetry(reconciler, req)
By("Check again that appConfig condition should not have error")
Eventually(func() string {
By("Reconcile again and should not have error")
reconcileRetry(reconciler, req)
err := k8sClient.Get(ctx, appConfigKey, &appConfig)
if err != nil {
return err.Error()
}
if len(appConfig.Status.Conditions) != 1 {
return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions))
}
return string(appConfig.Status.Conditions[0].Reason)
}, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess"))
By("Check new trait CR is applied")
scale := v1alpha2.ManualScalerTrait{}
scaleKey := client.ObjectKey{Name: scaleName, Namespace: namespace}
err = k8sClient.Get(ctx, scaleKey, &scale)
Expect(err).Should(BeNil())
Expect(scale.Spec.ReplicaCount).Should(Equal(int32(3)))
})
AfterEach(func() {
// delete the namespace with all its resources
Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))).
Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
})
})
@@ -77,7 +77,7 @@ func (fn WorkloadApplyFns) Finalize(ctx context.Context, ac *v1alpha2.Applicatio
type workloads struct {
// use patching-apply for creating/updating Workload
patchingClient resource.Applicator
// use updateing-apply for creating/updating Trait
// use updating-apply for creating/updating Trait
updatingClient resource.Applicator
rawClient client.Client
dm discoverymapper.DiscoveryMapper
@@ -91,7 +91,7 @@ func (a *workloads) Apply(ctx context.Context, status []v1alpha2.WorkloadStatus,
if !wl.HasDep {
err := a.patchingClient.Apply(ctx, wl.Workload, ao...)
if err != nil {
// TODO(roywang) use errors.As() insteand of type assertion on error
// TODO(roywang) use errors.As() instead of type assertion on error
if _, ok := err.(*GenerationUnchanged); !ok {
// GenerationUnchanged only aborts applying current workload
// but not blocks the whole reconciliation through returning an error
@@ -105,7 +105,7 @@ func (a *workloads) Apply(ctx context.Context, status []v1alpha2.WorkloadStatus,
}
t := trait.Object
if err := a.updatingClient.Apply(ctx, &trait.Object, ao...); err != nil {
// TODO(roywang) use errors.As() insteand of type assertion on error
// TODO(roywang) use errors.As() instead of type assertion on error
if _, ok := err.(*GenerationUnchanged); !ok {
// GenerationUnchanged only aborts applying current trait
// but not blocks the whole reconciliation through returning an error
@@ -29,10 +29,10 @@ const ControllerRevisionComponentLabel = "controller.oam.dev/component"
// ComponentHandler will watch component change and generate Revision automatically.
type ComponentHandler struct {
Client client.Client
Logger logging.Logger
RevisionLimit int
CustomWebHookURL string
Client client.Client
Logger logging.Logger
RevisionLimit int
CustomRevisionHookURL string
}
// Create implements EventHandler
@@ -148,7 +148,7 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim
reqs := c.getRelatedAppConfig(mt)
// Hook to custom revision service if exist
if err := c.customComponentRevisionHook(reqs, comp); err != nil {
c.Logger.Info(fmt.Sprintf("fail to hook from custom revision service(%s) %v", c.CustomWebHookURL, err), "componentName", mt.GetName())
c.Logger.Info(fmt.Sprintf("fail to hook from custom revision service(%s) %v", c.CustomRevisionHookURL, err), "componentName", mt.GetName())
return nil, false
}
@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@@ -36,7 +37,7 @@ type RevisionHookRequest struct {
}
func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.Request, comp *v1alpha2.Component) error {
if c.CustomWebHookURL == "" {
if c.CustomRevisionHookURL == "" {
return nil
}
req := RevisionHookRequest{
@@ -47,7 +48,7 @@ func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.R
if err != nil {
return err
}
httpRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.CustomWebHookURL, bytes.NewBuffer(data))
httpRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.CustomRevisionHookURL, bytes.NewBuffer(data))
if err != nil {
return err
}
@@ -62,5 +63,8 @@ func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.R
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("httpcode(%d) err: %s", resp.StatusCode, string(respData))
}
return json.Unmarshal(respData, comp)
}
@@ -0,0 +1,77 @@
/*
Copyright 2020 The KubeVela Authors.
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 applicationconfiguration
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
)
func TestCustomRevisionHook(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req RevisionHookRequest
data, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(400)
return
}
err = json.Unmarshal(data, &req)
if err != nil {
w.WriteHeader(401)
return
}
if len(req.RelatedApps) != 1 {
w.WriteHeader(400)
w.Write([]byte("we should have only one relatedApps"))
return
}
if req.Comp.Annotations == nil {
req.Comp.Annotations = make(map[string]string)
}
req.Comp.Annotations["app-name"] = req.RelatedApps[0].Name
req.Comp.Annotations["app-namespace"] = req.RelatedApps[0].Namespace
newdata, err := json.Marshal(req.Comp)
if err != nil {
w.WriteHeader(500)
return
}
w.WriteHeader(200)
w.Write(newdata)
}))
defer srv.Close()
compHandler := ComponentHandler{
CustomRevisionHookURL: srv.URL,
}
comp := &v1alpha2.Component{}
err := compHandler.customComponentRevisionHook([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "app1", Namespace: "default1"}}}, comp)
assert.NoError(t, err)
assert.Equal(t, "app1", comp.Annotations["app-name"])
assert.Equal(t, "default1", comp.Annotations["app-namespace"])
err = compHandler.customComponentRevisionHook([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "app1", Namespace: "default1"}}, {NamespacedName: types.NamespacedName{Name: "app2", Namespace: "default2"}}}, comp)
assert.Equal(t, err.Error(), "httpcode(400) err: we should have only one relatedApps")
}
@@ -226,11 +226,10 @@ func (r *components) renderTrait(ctx context.Context, ct v1alpha2.ComponentTrait
}
traitDef, err := util.FetchTraitDefinition(ctx, r.client, r.dm, t)
if err != nil {
if apierrors.IsNotFound(err) {
t.SetNamespace(ac.GetNamespace())
return t, util.GetDummyTraitDefinition(t), nil
if !apierrors.IsNotFound(err) {
return nil, nil, errors.Wrapf(err, errFmtGetTraitDefinition, t.GetAPIVersion(), t.GetKind(), t.GetName())
}
return nil, nil, errors.Wrapf(err, errFmtGetTraitDefinition, t.GetAPIVersion(), t.GetKind(), t.GetName())
traitDef = util.GetDummyTraitDefinition(t)
}
traitName := getTraitName(ac, componentName, &ct, t, traitDef)
@@ -54,7 +54,7 @@ const (
)
// Setup adds a controller that reconciles HealthScope.
func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error {
func Setup(mgr ctrl.Manager, _ controller.Args, l logging.Logger) error {
name := "oam/" + strings.ToLower(v1alpha2.HealthScopeGroupKind)
return ctrl.NewControllerManagedBy(mgr).
@@ -51,7 +51,7 @@ const (
)
// Setup adds a controller that reconciles ContainerizedWorkload.
func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error {
func Setup(mgr ctrl.Manager, _ controller.Args, _ logging.Logger) error {
dm, err := discoverymapper.New(mgr.GetConfig())
if err != nil {
return err
@@ -49,7 +49,7 @@ const (
)
// Setup adds a controller that reconciles ContainerizedWorkload.
func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error {
func Setup(mgr ctrl.Manager, _ controller.Args, _ logging.Logger) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
log: ctrl.Log.WithName("ContainerizedWorkload"),
@@ -30,23 +30,21 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/common"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/common"
)
// nolint:golint
const (
SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set"
SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`"
SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value"
SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value"
SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value"
SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format"
SpecWarningSumOfStartAndDurationMoreThan24Hour = "the sum of the start hour and the duration hour has to be less than 24 hours."
SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set"
SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`"
SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value"
SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value"
SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value"
SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format"
)
// ReconcileWaitResult is the time to wait between reconciliation.
@@ -59,7 +59,7 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a
}
// try render to validate
appParser := parser.NewParser(template.GetHanler(fclient.NewDefinitionClient(h.Client)))
appParser := parser.NewParser(template.GetHandler(fclient.NewDefinitionClient(h.Client)))
if _, err := appParser.Parse(app.Name, app); err != nil {
return admission.Denied(err.Error())
}