Compare commits

..

18 Commits

Author SHA1 Message Date
Sun Jianbo
bce8bfd40f Merge pull request #296 from wonderflow/con
temporarily add containerized to chart
2020-09-18 22:12:01 +08:00
天元
66855265c8 temporarily add containerized to chart 2020-09-18 21:59:48 +08:00
Sun Jianbo
63f52ea556 Merge pull request #294 from hanxie-crypto/feature04
Components static page,bugfix,capability delete
2020-09-18 21:56:38 +08:00
Sun Jianbo
90fd0a5055 Merge pull request #295 from wonderflow/production-issuer
add production certificate issuer and fix route trait
2020-09-18 21:45:14 +08:00
天元
3af41f6515 add production certificate issuer and fix route trait 2020-09-18 19:06:51 +08:00
hanxie
fa37bf0284 Components static page,bugfix,capability delete 2020-09-18 17:47:39 +08:00
Yue Wang
ee39054537 get cue temp from remote through URI (#287)
* get cue template of capabilities from remote by URI

Signed-off-by: roy wang <seiwy2010@gmail.com>

* add e2e tests

Signed-off-by: roy wang <seiwy2010@gmail.com>

* fix type conversion

Signed-off-by: roy wang <seiwy2010@gmail.com>
2020-09-18 15:59:36 +08:00
Ryan Zhang
b5218d371a Merge pull request #293 from wonderflow/iamge
use image from docker hub instead of quay.io
2020-09-17 19:23:41 -07:00
天元
7f5298a802 use image from docker hub instead of quay.io 2020-09-17 17:30:33 +08:00
Sun Jianbo
2ffd56a993 Merge pull request #291 from wonderflow/zip
use compressed file for release
2020-09-17 15:07:00 +08:00
天元
e0d7eed9d3 update readme 2020-09-17 14:52:33 +08:00
Sun Jianbo
b0ec26bca0 Merge pull request #290 from wonderflow/image
update image tag to latest and change image pull policy
2020-09-17 14:44:19 +08:00
天元
2116c9ad0e use compressed file for release 2020-09-17 14:42:00 +08:00
天元
d02d6675ab update image tag to latest and change image pull policy 2020-09-17 14:08:33 +08:00
Sun Jianbo
1a43c0e540 Merge pull request #276 from wonderflow/routetrait
add route trait as vela api gateway
2020-09-17 14:06:00 +08:00
Sun Jianbo
e6b46c6c1b Merge pull request #288 from mosesyou/bugfix-comp-run
fix `vela comp run` panic when args empty
2020-09-17 11:57:27 +08:00
mosesyou
387afaa2c2 fix vela comp run panic when args empty 2020-09-17 11:08:55 +08:00
天元
1b9ee5c882 add route trait as vela apigate 2020-09-16 19:42:01 +08:00
69 changed files with 3795 additions and 517 deletions

View File

@@ -40,6 +40,9 @@ jobs:
- name: Run Make
run: make
- name: Run Make Manager
run: make manager
- name: Run e2e tests
run: |
make e2e-setup

View File

@@ -32,6 +32,8 @@ jobs:
run: make generate-source
- name: Run cross-build
run: make cross-build
- name: Run compress binary
run: make compress
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -41,27 +43,55 @@ jobs:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Upload Linux
- name: Upload Linux tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-linux-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64
asset_path: ./_bin/vela-linux-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS
- name: Upload Linux zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-darwin-amd64
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64
asset_path: ./_bin/vela-linux-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-linux-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows
- name: Upload MacOS tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/vela-windows-amd64.exe
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.exe
asset_path: ./_bin/vela-darwin-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload MacOS zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-darwin-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-darwin-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Windows tar.gz
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.tar.gz
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.tar.gz
asset_content_type: binary/octet-stream
- name: Upload Windows zip
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/vela-windows-amd64.zip
asset_name: vela-${{ steps.get_version.outputs.VERSION }}-windows-amd64.zip
asset_content_type: binary/octet-stream
- name: Upload Checksums
uses: actions/upload-release-asset@v1.0.2
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./_bin/sha256sums.txt
asset_name: sha256sums.txt
asset_content_type: text/plain
- name: Publish to Github Docker Package Registry
uses: elgohr/Publish-Docker-Github-Action@2.21
with:

2
.gitignore vendored
View File

@@ -5,6 +5,7 @@
*.so
*.dylib
bin
_bin
e2e/vela
# Test binary, build with `go test -c`
@@ -35,6 +36,7 @@ config/crd/bases
tmp/
cmd/vela/fake/source.go
charts/vela-core/crds/_.yaml
# Dashboard
dashboard/node_modules/

View File

@@ -5,12 +5,10 @@ contributing to `kubevela` or build a PoC (Proof of Concept).
## Prerequisites
1. Golang version 1.12+
2. Kubernetes version v1.15+ with `~/.kube/config` configured.
3. OAM Kubernetes Runtime installed.
4. Kustomize version 3.8+
5. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
6. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
1. Golang version 1.13+
2. Kubernetes version v1.16+ with `~/.kube/config` configured.
3. ginkgo 1.14.0+ (just for [E2E test](https://github.com/oam-dev/kubevela/blob/master/DEVELOPMENT.md#e2e-test))
4. golangci-lint 1.31.0+, it will install automatically if you run `make`, you can [install it manually](https://golangci-lint.run/usage/install/#local-installation) if the installation is too slow.
## Build
* Clone this project
@@ -19,96 +17,89 @@ contributing to `kubevela` or build a PoC (Proof of Concept).
git clone git@github.com:oam-dev/kubevela.git
```
* Install Template CRD into your cluster
* Build Vela CLI
```shell script
make install
make
```
* Install template object
* Configure vela to PATH
after build, make will create `vela` binary to `bin/`, Set this path to PATH.
```shell script
kubectl apply -f config/samples/
export PATH=$PATH:/your/path/to/project/kubevela/bin
```
## Develop & Debug
If you change Template CRD, remember to rerun `make install`.
Use the following command to develop and debug.
* Install Vela Core
```shell script
$ cd cmd/vela
$ go run main.go COMMAND [FLAG]
vela install
```
## Use
* Create ENV
```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
$ go run main.go run containerized app2057 nginx:1.9.4
Creating AppConfig app2057
$ vela comp run mycomp -t webservice --image crccheck/hello-world --port 8000
Creating AppConfig appcomp
SUCCEED
$ kubectl get oam
NAME WORKLOAD-KIND
component.core.oam.dev/app2057 ContainerizedWorkload
NAME AGE
containerizedworkload.core.oam.dev/poc 53m
NAME AGE
applicationconfiguration.core.oam.dev/app2057 69s
NAME DEFINITION-NAME
traitdefinition.core.oam.dev/simplerollouttraits.extend.oam.dev simplerollouttraits.extend.oam.dev
NAME DEFINITION-NAME
workloaddefinition.core.oam.dev/containerizedworkloads.core.oam.dev containerizedworkloads.core.oam.dev
workloaddefinition.core.oam.dev/deployments.apps deployments.apps
workloaddefinition.core.oam.dev/statefulsets.apps statefulsets.apps
```
## E2E test
```
$ make e2e-test
Running Suite: Trait Suite
==========================
Random Seed: 1596559178
Will run 5 of 5 specs
* Add Trait
Trait env init
should print env initiation successful message
kubevela/e2e/commonContext.go:14
Create env succeed, current env is default
------------------------------
Trait env set
should show env set message
kubevela/e2e/commonContext.go:40
Set env succeed, current env is default
------------------------------
Trait run
should print successful creation information
kubevela/e2e/commonContext.go:76
Creating AppConfig app-trait-basic
SUCCEED
------------------------------
Trait kubevela attach trait
should print successful attached information
kubevela/e2e/trait/trait_test.go:24
Applying trait for app
```shell script
$ vela route mycomp
Adding route for app abc
Succeeded!
------------------------------
Trait delete
should print successful deletion information
kubevela/e2e/commonContext.go:85
Deleting AppConfig "app-trait-basic"
DELETE SUCCEED
Ran 5 of 5 Specs in 9.717 seconds
SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
```
* Check Status
```
$ vela comp status abc
Showing status of Component abc deployed in Environment t2
Component Status:
Name: abc Containerized(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind Containerized workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 18:47:09 +0800 CST
Updated at: 2020-09-18T18:47:16+08:00
```
* Delete App
```shell script
$ vela app ls
abc
$ vela app delete abc
Deleting Application "abc"
delete apps succeed abc from t2
```
## Tests
### Unit test
```shell script
make test
```
### E2E test
```
make e2e-test
```
## Make a pull request

View File

@@ -8,6 +8,7 @@ LDFLAGS ?= "-X $(VELA_VERSION_VAR)=$(VELA_VERSION) -X $(VELA_GITVERSION_VAR)=$(G
GOX = go run github.com/mitchellh/gox
TARGETS := darwin/amd64 linux/amd64 windows/amd64
DIST_DIRS := find * -type d -exec
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@@ -36,7 +37,17 @@ generate-source:
go run hack/frontend/source.go
cross-build:
go run hack/chart/generate.go | GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="bin/vela-{{.OS}}-{{.Arch}}" -osarch='$(TARGETS)' ./cmd/vela/
go run hack/chart/generate.go | GO111MODULE=on CGO_ENABLED=0 $(GOX) -ldflags $(LDFLAGS) -parallel=3 -output="_bin/{{.OS}}-{{.Arch}}/vela" -osarch='$(TARGETS)' ./cmd/vela/
compress:
( \
cd _bin && \
$(DIST_DIRS) cp ../LICENSE {} \; && \
$(DIST_DIRS) cp ../README.md {} \; && \
$(DIST_DIRS) tar -zcf vela-{}.tar.gz {} \; && \
$(DIST_DIRS) zip -r vela-{}.zip {} \; && \
sha256sum vela-* > sha256sums.txt \
)
# Run against the configured Kubernetes cluster in ~/.kube/config
run: fmt vet
@@ -51,7 +62,7 @@ vet:
go vet ./...
lint: golangci
$(GOLANGCILINT) run -E golint,goimports ./...
$(GOLANGCILINT) run --timeout 10m -E golint,goimports ./...
# Build the docker image
docker-build: test
@@ -88,7 +99,7 @@ core-test: generate fmt vet manifests
go test ./pkg/... -coverprofile cover.out
# Build manager binary
manager: generate fmt vet
manager: generate fmt vet lint manifests
go build -o bin/manager ./cmd/core/main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
@@ -110,7 +121,7 @@ core-deploy: manifests
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela/crds
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=charts/vela-core/crds
# Generate code
generate: controller-gen

View File

@@ -1,3 +1,7 @@
domain: oam.dev
repo: github.com/oam-dev/kubevela
resources:
- group: standard
kind: Route
version: v1alpha1
version: "2"

View File

@@ -13,64 +13,70 @@ The Open Application Platform based on Kubernetes and OAM.
### Get the Vela CLI
Download the `vela` binary from the [Releases page](https://github.com/oam-dev/kubevela/releases). Change file mod and add it to `$PATH` to get started.
Download the `vela` binary from the [Releases page](https://github.com/oam-dev/kubevela/releases). Unpack the `vela` binary and add it to `$PATH` to get started.
For exmaple:
```shell
chmod a+x vela-v0.0.2-darwin-amd64
sudo mv ./vela-v0.0.2-darwin-amd64 /usr/local/bin/vela
sudo mv ./vela /usr/local/bin/vela
```
### Install Vela Core
```shell script
$ vela install
- Installing Vela Core:
- Installing builtin capabilities:
Successful applied 4 kinds of Workloads and Traits: deployments.apps,containerizedworkloads.core.oam.dev,manualscalertraits.core.oam.dev,simplerollouttraits.extend.oam.dev.
syncing workload definitions from cluster...
[WARN]handle template task: #Template.metadata.name: reference "task" not found
get 5 workload definitions from cluster, syncing...5 workload definitions successfully synced
syncing trait definitions from cluster...
[WARN]handle template metricstraits.standard.oam.dev: #Template.metadata.name: reference "metricstraits" not found
get 2 trait definitions from cluster, syncing...2 trait definitions successfully synced
- Finished.
```
This command will install vela core controller into your K8s cluster, along with built-in workloads and traits.
## Demos
#### Check workloads
```
$ vela workloads
NAME DEFINITION
backend containerizeds.standard.oam.dev
task jobs
webservice containerizeds.standard.oam.dev
```
#### workload run
* Create ENV
```shell script
$ vela comp run -t webservice app123 -p 80 --image nginx:1.9.4
Creating AppConfig app123
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 comp run mycomp -t webservice --image crccheck/hello-world --port 8000
Creating AppConfig appcomp
SUCCEED
$ vela comp status app123
$ vela comp ls
NAME APP WORKLOAD TRAITS STATUS CREATED-TIME
app123 app123 deployment Deployed 2020-08-27 10:56:41 +0800 CST
```
#### app
* Add Trait
```shell script
$ vela route mycomp
Adding route for app abc
Succeeded!
```
* Check Status
```
$ vela comp status abc
Showing status of Component abc deployed in Environment t2
Component Status:
Name: abc Containerized(type) UNKNOWN APIVersion standard.oam.dev/v1alpha1 Kind Containerized workload is unknown for HealthScope
Traits
└─Trait/route
Last Deployment:
Created at: 2020-09-18 18:47:09 +0800 CST
Updated at: 2020-09-18T18:47:16+08:00
```
* Delete App
```shell script
$ vela app ls
app123
abc
$ vela app delete app123
Deleting AppConfig "app123"
DELETE SUCCEED
$ vela app delete abc
Deleting Application "abc"
delete apps succeed abc from t2
```
#### Auto-Completion

View File

@@ -40,6 +40,7 @@ type Capability struct {
Name string `json:"name"`
Type CapType `json:"type"`
CueTemplate string `json:"template,omitempty"`
CueTemplateURI string `json:"templateURI,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
DefinitionPath string `json:"definition"`
CrdName string `json:"crdName,omitempty"`

View File

@@ -27,8 +27,13 @@ const (
type EnvMeta struct {
Name string `json:"name"`
Current string `json:"current,omitempty"`
Namespace string `json:"namespace"`
Email string `json:"email,omitempty"`
Domain string `json:"domain,omitempty"`
// Below are not arguments, should be auto-generated
Issuer string `json:"issuer"`
Current string `json:"current,omitempty"`
}
const (

138
api/v1alpha1/route_types.go Normal file
View File

@@ -0,0 +1,138 @@
/*
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 v1alpha1
import (
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
"k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// RouteSpec defines the desired state of Route
type RouteSpec struct {
// WorkloadReference to the workload whose metrics needs to be exposed
WorkloadReference runtimev1alpha1.TypedReference `json:"workloadRef,omitempty"`
// Host is the host of the route
Host string `json:"host"`
// Path is location Path, default for "/"
Path string `json:"path,omitempty"`
// TLS indicate route trait will create SSL secret using cert-manager with specified issuer
// If this is nil, route trait will use a selfsigned issuer
TLS *TLS `json:"tls,omitempty"`
// DefaultBackend uses serviceName
DefaultBackend *v1beta1.IngressBackend `json:"defaultBackend,omitempty"`
// RewriteTarget will rewrite request from Path to RewriteTarget path.
RewriteTarget string `json:"rewriteTarget,omitempty"`
// CustomHeaders pass a custom list of headers to the backend service.
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
// Backend indicate how to connect backend service
// If it's nil, will auto discovery
Backend *Backend `json:"backend,omitempty"`
}
type TLS struct {
IssuerName string `json:"issuerName,omitempty"`
// Type indicate the issuer is ClusterIssuer or NamespaceIssuer
Type IssuerType `json:"type,omitempty"`
}
type IssuerType string
const (
ClusterIssuer IssuerType = "ClusterIssuer"
NamespaceIssuer IssuerType = "Issuer"
)
// Route will automatically discover podTemplate for Port and SelectLabels if they are not set.
// If Port and SelectLabels are already set, discovery won't work.
// If Port is not set, the first port discovered will be set.
// If SelectLabels are not set, all selectorLabels discovered will be set.
type Backend struct {
// Protocol means backend-protocol, HTTP, HTTPS, GRPC, GRPCS, AJP and FCGI, By default uses HTTP
Protocol string `json:"protocol,omitempty"`
// ReadTimeout used for setting read timeout duration for backend service, the unit is second.
ReadTimeout int `json:"readTimeout,omitempty"`
// SendTimeout used for setting send timeout duration for backend service, the unit is second.
SendTimeout int `json:"sendTimeout,omitempty"`
// Port points to backend service port.
Port intstr.IntOrString `json:"port,omitempty"`
// SelectLabels for backend service.
SelectLabels map[string]string `json:"selectLabels,omitempty"`
}
// RouteStatus defines the observed state of Route
type RouteStatus struct {
Ingress *runtimev1alpha1.TypedReference `json:"ingress,omitempty"`
Service *runtimev1alpha1.TypedReference `json:"service,omitempty"`
runtimev1alpha1.ConditionedStatus `json:",inline"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:categories={oam}
// +kubebuilder:subresource:status
// Route is the Schema for the routes API
type Route struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RouteSpec `json:"spec,omitempty"`
Status RouteStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// RouteList contains a list of Route
type RouteList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Route `json:"items"`
}
func init() {
SchemeBuilder.Register(&Route{}, &RouteList{})
}
var _ oam.Trait = &Route{}
func (r *Route) SetConditions(c ...runtimev1alpha1.Condition) {
r.Status.SetConditions(c...)
}
func (r *Route) GetCondition(c runtimev1alpha1.ConditionType) runtimev1alpha1.Condition {
return r.Status.GetCondition(c)
}
// GetWorkloadReference of this ManualScalerTrait.
func (r *Route) GetWorkloadReference() runtimev1alpha1.TypedReference {
return r.Spec.WorkloadReference
}
// SetWorkloadReference of this ManualScalerTrait.
func (r *Route) SetWorkloadReference(rt runtimev1alpha1.TypedReference) {
r.Spec.WorkloadReference = rt
}

View File

@@ -22,9 +22,33 @@ package v1alpha1
import (
corev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"k8s.io/api/networking/v1beta1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
out.Port = in.Port
if in.SelectLabels != nil {
in, out := &in.SelectLabels, &out.SelectLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend.
func (in *Backend) DeepCopy() *Backend {
if in == nil {
return nil
}
out := new(Backend)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Containerized) DeepCopyInto(out *Containerized) {
*out = *in
@@ -223,6 +247,129 @@ func (in *MetricsTraitStatus) DeepCopy() *MetricsTraitStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
func (in *Route) DeepCopy() *Route {
if in == nil {
return nil
}
out := new(Route)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Route) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteList) DeepCopyInto(out *RouteList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Route, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteList.
func (in *RouteList) DeepCopy() *RouteList {
if in == nil {
return nil
}
out := new(RouteList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RouteList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteSpec) DeepCopyInto(out *RouteSpec) {
*out = *in
out.WorkloadReference = in.WorkloadReference
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLS)
**out = **in
}
if in.DefaultBackend != nil {
in, out := &in.DefaultBackend, &out.DefaultBackend
*out = new(v1beta1.IngressBackend)
(*in).DeepCopyInto(*out)
}
if in.CustomHeaders != nil {
in, out := &in.CustomHeaders, &out.CustomHeaders
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Backend != nil {
in, out := &in.Backend, &out.Backend
*out = new(Backend)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteSpec.
func (in *RouteSpec) DeepCopy() *RouteSpec {
if in == nil {
return nil
}
out := new(RouteSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RouteStatus) DeepCopyInto(out *RouteStatus) {
*out = *in
if in.Ingress != nil {
in, out := &in.Ingress, &out.Ingress
*out = new(corev1alpha1.TypedReference)
**out = **in
}
if in.Service != nil {
in, out := &in.Service, &out.Service
*out = new(corev1alpha1.TypedReference)
**out = **in
}
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteStatus.
func (in *RouteStatus) DeepCopy() *RouteStatus {
if in == nil {
return nil
}
out := new(RouteStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScapeServiceEndPoint) DeepCopyInto(out *ScapeServiceEndPoint) {
*out = *in
@@ -250,3 +397,18 @@ func (in *ScapeServiceEndPoint) DeepCopy() *ScapeServiceEndPoint {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
func (in *TLS) DeepCopy() *TLS {
if in == nil {
return nil
}
out := new(TLS)
in.DeepCopyInto(out)
return out
}

View File

@@ -11,124 +11,190 @@ spec:
group: core.oam.dev
names:
categories:
- crossplane
- oam
- crossplane
- oam
kind: HealthScope
listKind: HealthScopeList
plural: healthscopes
singular: healthscope
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.health
name: HEALTH
type: string
name: v1alpha2
schema:
openAPIV3Schema:
description: A HealthScope determines an aggregate health status based of
the health of components.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
- additionalPrinterColumns:
- jsonPath: .status.health
name: HEALTH
type: string
name: v1alpha2
schema:
openAPIV3Schema:
description: A HealthScope determines an aggregate health status based of
the health of components.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: A HealthScopeSpec defines the desired state of a HealthScope.
properties:
probe-interval:
description: ProbeInterval is the amount of time in seconds between
probing tries.
format: int32
type: integer
probe-timeout:
description: ProbeTimeout is the amount of time in seconds to wait
when receiving a response before marked failure.
format: int32
type: integer
workloadRefs:
description: WorkloadReferences to the workloads that are in this
scope.
items:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
type: string
metadata:
type: object
spec:
description: A HealthScopeSpec defines the desired state of a HealthScope.
properties:
probe-interval:
description: ProbeInterval is the amount of time in seconds between
probing tries.
format: int32
type: integer
probe-timeout:
description: ProbeTimeout is the amount of time in seconds to wait
when receiving a response before marked failure.
format: int32
type: integer
workloadRefs:
description: WorkloadReferences to the workloads that are in this
scope.
items:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: array
required:
- workloadRefs
type: object
status:
description: A HealthScopeStatus represents the observed state of a HealthScope.
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
healthConditions:
description: WorkloadHealthConditions represents health condition
of workloads in the scope
items:
description: WorkloadHealthCondition represents informative health
condition.
properties:
componentName:
description: ComponentName represents the component name if
target is a workload
type: string
diagnosis:
type: string
healthStatus:
description: HealthStatus represents health status strings.
type: string
targetWorkload:
description: A TypedReference refers to an object by Name, Kind,
and APIVersion. It is commonly used to reference cluster-scoped
objects or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
workloadStatus:
description: WorkloadStatus represents status of workloads whose
HealthStatus is UNKNOWN.
type: string
required:
- healthStatus
type: object
type: array
scopeHealthCondition:
description: ScopeHealthCondition represents health condition summary
of the scope
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
healthStatus:
description: HealthStatus represents health status strings.
type: string
healthyWorkloads:
format: int64
type: integer
total:
format: int64
type: integer
unhealthyWorkloads:
format: int64
type: integer
unknownWorkloads:
format: int64
type: integer
required:
- apiVersion
- kind
- name
- healthStatus
type: object
type: array
required:
- workloadRefs
type: object
status:
description: A HealthScopeStatus represents the observed state of a HealthScope.
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
health:
type: string
required:
- health
type: object
type: object
served: true
storage: true
subresources:
status: {}
required:
- scopeHealthCondition
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -0,0 +1,245 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
creationTimestamp: null
name: routes.standard.oam.dev
spec:
group: standard.oam.dev
names:
categories:
- oam
kind: Route
listKind: RouteList
plural: routes
singular: route
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Route is the Schema for the routes API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: RouteSpec defines the desired state of Route
properties:
backend:
description: Backend indicate how to connect backend service If it's
nil, will auto discovery
properties:
port:
anyOf:
- type: integer
- type: string
description: Port points to backend service port.
x-kubernetes-int-or-string: true
protocol:
description: Protocol means backend-protocol, HTTP, HTTPS, GRPC,
GRPCS, AJP and FCGI, By default uses HTTP
type: string
readTimeout:
description: ReadTimeout used for setting read timeout duration
for backend service, the unit is second.
type: integer
selectLabels:
additionalProperties:
type: string
description: SelectLabels for backend service.
type: object
sendTimeout:
description: SendTimeout used for setting send timeout duration
for backend service, the unit is second.
type: integer
type: object
customHeaders:
additionalProperties:
type: string
description: CustomHeaders pass a custom list of headers to the backend
service.
type: object
defaultBackend:
description: DefaultBackend uses serviceName
properties:
resource:
description: Resource is an ObjectRef to another Kubernetes resource
in the namespace of the Ingress object. If resource is specified,
serviceName and servicePort must not be specified.
properties:
apiGroup:
description: APIGroup is the group for the resource being
referenced. If APIGroup is not specified, the specified
Kind must be in the core API group. For any other third-party
types, APIGroup is required.
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- kind
- name
type: object
serviceName:
description: Specifies the name of the referenced service.
type: string
servicePort:
anyOf:
- type: integer
- type: string
description: Specifies the port of the referenced service.
x-kubernetes-int-or-string: true
type: object
host:
description: Host is the host of the route
type: string
path:
description: Path is location Path, default for "/"
type: string
rewriteTarget:
description: RewriteTarget will rewrite request from Path to RewriteTarget
path.
type: string
tls:
description: TLS indicate route trait will create SSL secret using
cert-manager with specified issuer If this is nil, route trait will
use a selfsigned issuer
properties:
issuerName:
type: string
type:
description: Type indicate the issuer is ClusterIssuer or NamespaceIssuer
type: string
type: object
workloadRef:
description: WorkloadReference to the workload whose metrics needs
to be exposed
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
required:
- host
type: object
status:
description: RouteStatus defines the observed state of Route
properties:
conditions:
description: Conditions of the resource.
items:
description: A Condition that may apply to a resource.
properties:
lastTransitionTime:
description: LastTransitionTime is the last time this condition
transitioned from one status to another.
format: date-time
type: string
message:
description: A Message containing details about this condition's
last transition from one status to another, if any.
type: string
reason:
description: A Reason for this condition's last transition from
one status to another.
type: string
status:
description: Status of this condition; is it currently True,
False, or Unknown?
type: string
type:
description: Type of this condition. At most one of each condition
type may apply to a resource at any point in time.
type: string
required:
- lastTransitionTime
- reason
- status
- type
type: object
type: array
ingress:
description: A TypedReference refers to an object by Name, Kind, and
APIVersion. It is commonly used to reference cluster-scoped objects
or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
service:
description: A TypedReference refers to an object by Name, Kind, and
APIVersion. It is commonly used to reference cluster-scoped objects
or objects where the namespace is already known.
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -689,7 +689,7 @@ spec:
serviceAccountName: cert-manager-cainjector
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-cainjector:v0.12.0"
image: "oamdev/cert-manager-cainjector:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -737,7 +737,7 @@ spec:
serviceAccountName: cert-manager
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-controller:v0.12.0"
image: "oamdev/cert-manager-controller:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -793,7 +793,7 @@ spec:
serviceAccountName: cert-manager-webhook
containers:
- name: cert-manager
image: "quay.io/jetstack/cert-manager-webhook:v0.12.0"
image: "oamdev/cert-manager-webhook:v0.12.0"
imagePullPolicy: IfNotPresent
args:
- --v=2

View File

@@ -0,0 +1,46 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: containerizeds.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: "standard.oam.dev/v1alpha1"
definition.oam.dev/kind: "Containerized"
spec:
definitionRef:
name: containerizeds.standard.oam.dev
childResourceKinds:
- apiVersion: apps/v1
kind: Deployment
- apiVersion: v1
kind: Service
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Containerized"
metadata:
name: webservice.name
spec: {
replicas: 1
podSpec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
}]
}]
}
}
}
webservice: {
name: string
// +usage=specify app image
// +short=i
image: string
// +usage=specify port for container
// +short=p
port: *6379 | int
}

View File

@@ -4,7 +4,7 @@ metadata:
name: metricstraits.standard.oam.dev
namespace: default
annotations:
definition.oam.dev/apiVersion: core.oam.dev/v1alpha2
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: MetricsTrait
spec:
appliesToWorkloads:

View File

@@ -0,0 +1,37 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: routes.standard.oam.dev
annotations:
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Route
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- standard.oam.dev/v1alpha1.Containerized
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: routes.standard.oam.dev
extension:
template: |
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
host: route.domain
path: route.path
tls: {
issuerName: route.issuer
}
backend: {
port: route.port
}
}
}
route: {
domain: *"" | string
path: *"" | string
port: *443 | int
issuer: *"" | string
}

View File

@@ -21,18 +21,20 @@ spec:
metadata:
name: webservice.name
spec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
replicas: 1
podSpec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
}]
}]
}]
}
}
}
webservice: {
name: string
// +usage=specify app image

View File

@@ -6,8 +6,8 @@ replicaCount: 1
useWebhook: true
image:
repository: oamdev/vela-core
tag: 0.3
pullPolicy: IfNotPresent
tag: latest
pullPolicy: Always
imagePullSecrets: []
nameOverride: ""

View File

@@ -6,8 +6,11 @@ import (
"os"
"strconv"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/crossplane/crossplane-runtime/pkg/logging"
velacore "github.com/oam-dev/kubevela/api/v1alpha1"
velacontroller "github.com/oam-dev/kubevela/pkg/controller"
"github.com/oam-dev/kubevela/pkg/controller/dependency"
velawebhook "github.com/oam-dev/kubevela/pkg/webhook"
oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core"
oamcontroller "github.com/crossplane/oam-kubernetes-runtime/pkg/controller"
oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/pkg/controller/v1alpha2"
@@ -16,6 +19,10 @@ import (
injectorcontroller "github.com/oam-dev/trait-injector/controllers"
"github.com/oam-dev/trait-injector/pkg/injector"
"github.com/oam-dev/trait-injector/pkg/plugin"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/crossplane/crossplane-runtime/pkg/logging"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -24,11 +31,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
velacore "github.com/oam-dev/kubevela/api/v1alpha1"
velacontroller "github.com/oam-dev/kubevela/pkg/controller"
"github.com/oam-dev/kubevela/pkg/controller/dependency"
velawebhook "github.com/oam-dev/kubevela/pkg/webhook"
)
var scheme = runtime.NewScheme()
@@ -40,6 +42,8 @@ func init() {
_ = monitoring.AddToScheme(scheme)
_ = velacore.AddToScheme(scheme)
_ = injectorv1alpha1.AddToScheme(scheme)
_ = certmanager.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/crossplane/oam-kubernetes-runtime/apis/core"
"github.com/gosuri/uitable"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"github.com/spf13/cobra"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
@@ -34,7 +35,7 @@ var chartTGZSource string
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = certmanager.AddToScheme(scheme)
_ = core.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}

View File

@@ -74,3 +74,23 @@ rules:
- get
- patch
- update
- apiGroups:
- standard.oam.dev
resources:
- routes
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- standard.oam.dev
resources:
- routes/status
verbs:
- get
- patch
- update

View File

@@ -0,0 +1,19 @@
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt-prod
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: wonderflow@icloud.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: example-issuer-account-key
# Add a single challenge solver, HTTP01 using nginx
solvers:
- http01:
ingress:
class: nginx

View File

@@ -1,42 +1,37 @@
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: ingresses.networking.k8s.io
name: routes.standard.oam.dev
annotations:
"definition.oam.dev/apiVersion": "networking.k8s.io/v1beta1"
"definition.oam.dev/kind": "Ingress"
definition.oam.dev/apiVersion: standard.oam.dev/v1alpha1
definition.oam.dev/kind: Route
spec:
revisionEnabled: true
appliesToWorkloads:
- core.oam.dev/v1alpha2.ContainerizedWorkload
- standard.oam.dev/v1alpha1.Containerized
- deployments.apps
workloadRefPath: spec.workloadRef
definitionRef:
name: ingresses.networking.k8s.io
name: routes.standard.oam.dev
extension:
install:
helm:
repo: stable
name: nginx-ingress
url: https://kubernetes-charts.storage.googleapis.com/
version: 1.41.2
template: |
#Template: {
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
rules: [{
host: route.domain
http: paths: [{
backend: {
serviceName: route.service
servicePort: route.port
}}]
}]
host: route.domain
path: route.path
tls: {
issuerName: route.issuer
}
backend: {
port: route.port
}
}
}
route: {
domain: string
port: *80 | int
service: string
domain: *"" | string
path: *"" | string
port: *443 | int
issuer: *"" | string
}

View File

@@ -68,6 +68,18 @@ export default defineConfig({
path: '/ApplicationList/CreateApplication',
component: './ApplicationList/CreateApplication',
},
{
name: 'ApplicationList.Components',
hideInMenu: true,
path: '/ApplicationList/:appName/Components',
component: './ApplicationList/Components',
},
{
name: 'ApplicationList.Components.createComponent',
hideInMenu: true,
path: '/ApplicationList/:appName/createComponent',
component: './ApplicationList/CreateComponent',
},
{
name: 'Workload',
icon: 'table',

View File

@@ -8,7 +8,7 @@
export default {
dev: {
'/api': {
target: 'http://30.11.171.17:38081/',
target: 'http://30.11.171.29:38081/',
changeOrigin: true,
},
},

View File

@@ -4,7 +4,7 @@
* https://github.com/ant-design/ant-design-pro-layout
*/
import ProLayout from '@ant-design/pro-layout';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { Link, useIntl, connect, history } from 'umi';
import RightContent from '@/components/GlobalHeader/RightContent';
import {
@@ -44,6 +44,7 @@ const AddIcon = (menuData) => {
const BasicLayout = (props) => {
const { settings, dispatch, menus } = props;
const [currentSelectKeys, setCurrentSelectedKeys] = useState('');
const timerRef = useRef();
const getCurrentSelectKeys = () => {
const pathnameCur = props.history.location.pathname;
if (pathnameCur) {
@@ -70,9 +71,14 @@ const BasicLayout = (props) => {
type: 'menus/getMenuData',
});
}
props.history.listen((route) => {
timerRef.current = props.history.listen((route) => {
getCurrentSelectKeys(route.pathname);
});
return () => {
if (timerRef.current) {
timerRef.current = null;
}
};
// setCurrentSelectedKeys('applist')
}, []);

View File

@@ -38,18 +38,26 @@ class TableList extends React.Component {
}
componentDidMount() {
this.getInitialData(1);
this.getInitialData();
}
getInitialData = async (times) => {
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
const traitType = _.get(this.props, 'location.state.traitType', '');
getInitialData = async () => {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
if (appName && envName) {
this.setState({
envName,
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
@@ -76,12 +84,6 @@ class TableList extends React.Component {
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
if (traitType && times === 1) {
await this.setState({
visible: true,
});
this.child.setDefaultValue(traitType);
}
}
};

View File

@@ -0,0 +1,108 @@
import React, { useEffect } from 'react';
import G6 from '@antv/g6';
const Topology = () => {
let graph = null;
const data = {
nodes: [
{
id: 'node1',
x: 150,
y: 50,
label: 'node1',
},
{
id: 'node2',
x: 250,
y: 200,
label: 'node2',
},
{
id: 'node3',
x: 100,
y: 350,
label: 'node3',
},
],
edges: [
{
source: 'node1',
target: 'node2',
label: 'edge 1',
},
{
source: 'node2',
target: 'node3',
label: 'edge 2',
},
{
source: 'node3',
target: 'node1',
label: 'edge 3',
},
],
};
const width = 1000;
const height = 400;
useEffect(() => {
if (!graph) {
graph = new G6.Graph({
container: 'container',
width,
height,
// translate the graph to align the canvas's center, support by v3.5.1
fitCenter: true,
defaultNode: {
type: 'circle',
size: [40],
color: '#5B8FF9',
style: {
fill: '#9EC9FF',
lineWidth: 3,
},
labelCfg: {
style: {
fill: '#1890ff',
fontSize: 14,
},
position: 'bottom',
},
},
defaultEdge: {
labelCfg: {
autoRotate: true,
style: {
background: {
fill: '#ffffff',
stroke: '#9EC9FF',
padding: [2, 2, 2, 2],
radius: 2,
},
},
},
},
modes: {
default: ['drag-canvas', 'drag-node'],
},
nodeStateStyles: {
// style configurations for hover state
hover: {
fillOpacity: 0.8,
},
// style configurations for selected state
selected: {
lineWidth: 5,
},
},
});
}
graph.data(data);
graph.render();
}, []);
return <div id="container" style={{ overflow: 'auto', width: '100%', height: '400px' }} />;
};
export default Topology;

View File

@@ -0,0 +1,462 @@
import React, { Fragment } from 'react';
import './index.less';
import { Button, Row, Col, Tabs, Popconfirm, message, Tooltip, Modal, Spin } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import CreateTraitItem from '../../../components/AttachOneTrait/index.jsx';
const { TabPane } = Tabs;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.applist,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
constructor(props) {
super(props);
this.state = {
appDetailData: {},
visible: false,
traitList: [],
availableTraitList: [],
envName: '',
appName: '',
};
}
componentDidMount() {
this.getInitialData();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.appName !== nextProps.appName) {
this.getInitialData(nextProps.appName);
}
}
getInitialData = async (nextAppName) => {
let appName = _.get(this.props, 'appName', '');
const envName = _.get(this.props, 'envName', '');
appName = nextAppName || appName;
if (appName && envName) {
this.setState({
envName,
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
envName,
appName,
},
});
if (res) {
this.setState({
appDetailData: res,
});
}
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
if (traits) {
this.setState({
traitList: traits,
});
}
const workloadType = _.get(res, 'Workload.workload.kind', '');
if (workloadType && workloadType === 'ContainerizedWorkload') {
this.getAcceptTrait('containerized');
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
}
};
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
});
this.setState(() => ({
availableTraitList: res,
}));
};
deleteApp = async (e) => {
e.stopPropagation();
const { envName } = this.state;
const { appDetailData } = this.state;
const appName = _.get(appDetailData, 'Workload.workload.metadata.name', '');
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/deleteApp',
payload: {
appName,
envName,
},
});
if (res) {
message.success(res);
this.props.history.push({ pathname: '/ApplicationList' });
}
}
};
deleteTrait = async (e, item) => {
e.stopPropagation();
const { appName, envName } = this.state;
const traitNameObj = _.get(item, 'trait.metadata.annotations', '');
const traitName = traitNameObj['vela.oam.dev/traitDef'] || traitNameObj['trait.oam.dev/name'];
if (traitName && appName && envName) {
const res = await this.props.dispatch({
type: 'trait/deleteOneTrait',
payload: {
envName,
appName,
traitName,
},
});
if (res) {
message.success(res);
this.getInitialData(2);
}
}
};
cancel = (e) => {
e.stopPropagation();
};
createTrait = async () => {
await this.setState({
visible: true,
});
};
handleOk = async () => {
await this.child.validateFields();
const submitData = this.child.getSelectValue();
if (submitData.name) {
const submitObj = {
name: submitData.name,
flags: [],
};
Object.keys(submitData).forEach((currentKey) => {
if (currentKey !== 'name' && submitData[currentKey]) {
submitObj.flags.push({
name: currentKey,
value: submitData[currentKey].toString(),
});
}
});
const { envName, appName } = this.state;
if (envName && appName) {
const res = await this.props.dispatch({
type: 'trait/attachOneTraits',
payload: {
envName,
appName,
params: submitObj,
},
});
if (res) {
this.setState({
visible: false,
});
message.success(res);
this.getInitialData(2);
}
}
} else {
message.warning('please select a trait type');
}
};
handleCancel = () => {
this.setState({
visible: false,
});
};
hrefClick = (e) => {
e.stopPropagation();
};
gotoWorkloadDetail = (e) => {
e.stopPropagation();
};
gotoTraitDetail = (e) => {
e.stopPropagation();
};
render() {
const status = _.get(this.state.appDetailData, 'Status', '');
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const Traits = _.get(this.state.appDetailData, 'Traits', []);
let containers = {};
containers = _.get(Workload, 'spec.containers[0]', {});
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
const colorObj = {
Deployed: '#4CAF51',
Staging: '#F44337',
UNKNOWN: '#1890ff',
};
return (
<div style={{ margin: '8px' }}>
<Spin spinning={loadingAll}>
<div className="card-container app-detial">
<h2>{_.get(Workload, 'metadata.name')}</h2>
<p style={{ marginBottom: '20px' }}>
{Workload.apiVersion}, Kind={Workload.kind}
</p>
<Tabs>
<TabPane tab="Summary" key="1">
<Row>
<Col span="11">
<div
className="summaryBox1"
onClick={(e) => this.gotoWorkloadDetail(e)}
style={{ background: colorObj[status] || '#1890ff' }}
>
<Row>
<Col span="22">
<p className="title">{Workload.kind}</p>
<p>{Workload.apiVersion}</p>
</Col>
<Col span="2">
<p className="title hasCursor" onClick={this.hrefClick}>
?
</p>
</Col>
</Row>
<p className="title">
Name:<span>{_.get(Workload, 'metadata.name')}</span>
</p>
<p className="title">Settings:</p>
<Row>
{Object.keys(containers).map((currentKey) => {
if (currentKey === 'ports') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].containerPort', '')}</p>
</Col>
</Fragment>
);
// eslint-disable-next-line no-else-return
} else if (currentKey === 'name') {
return <Fragment key={currentKey} />;
// eslint-disable-next-line no-else-return
} else if (currentKey === 'env') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>env</p>
</Col>
<Col span="16">
<p>{_.get(containers[currentKey], '[0].value', '')}</p>
</Col>
</Fragment>
);
}
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{containers[currentKey]}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
<Popconfirm
title="Are you sure delete this app?"
onConfirm={(e) => this.deleteApp(e)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button danger>Delete</Button>
</Popconfirm>
</Col>
<Col span="1" />
<Col span="10">
{Traits.length ? (
Traits.map((item, index) => {
const traitItem = _.get(item, 'trait', {});
const annotations = _.get(traitItem, 'metadata.annotations', {});
let traitType = 1;
const spec = _.get(traitItem, 'spec', {});
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
return (
<div
className="summaryBox"
onClick={(e) => this.gotoTraitDetail(e, traitItem)}
key={index.toString()}
>
<Row>
<Col span="22">
<p className="title">{traitItem.kind}</p>
<p>{traitItem.apiVersion}</p>
</Col>
<Col span="2">
<p
className="title hasCursor"
onClick={(e) => {
e.stopPropagation();
}}
>
?
</p>
</Col>
</Row>
<Row>
{Object.keys(annotations).map((currentKey3) => {
return (
<Fragment key={currentKey3}>
<Col span="8">
<p>{currentKey3}:</p>
</Col>
<Col span="8">
<p>{annotations[currentKey3]}</p>
</Col>
</Fragment>
);
})}
</Row>
<p className="title">Properties:</p>
<Row>
{traitType === 2 ? (
<Fragment>
<Col span="8">
<p>domain</p>
</Col>
<Col span="16">
<p>{_.get(spec, 'rules[0].host', '')}</p>
</Col>
<Col span="8">
<p>service</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.serviceName',
'',
)}
</p>
</Col>
<Col span="8">
<p>port</p>
</Col>
<Col span="16">
<p>
{_.get(
spec,
'rules[0].http.paths[0].backend.servicePort',
'',
)}
</p>
</Col>
</Fragment>
) : (
Object.keys(spec).map((currentKey) => {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}</p>
</Col>
<Col span="16">
<p>{spec[currentKey]}</p>
</Col>
</Fragment>
);
})
)}
</Row>
<div style={{ clear: 'both', height: '32px' }}>
<Popconfirm
title="Are you sure delete this trait?"
onConfirm={(e) => this.deleteTrait(e, item)}
onCancel={this.cancel}
okText="Yes"
cancelText="No"
>
<Button
danger
className="floatRight"
onClick={(e) => {
e.stopPropagation();
}}
>
Delete
</Button>
</Popconfirm>
</div>
</div>
);
})
) : (
<Fragment />
)}
<Tooltip placement="top" title="Attach Trait">
<p
className="hasCursor"
style={{
fontSize: '30px',
display: 'inline-flex',
}}
onClick={this.createTrait}
>
+
</p>
</Tooltip>
</Col>
</Row>
</TabPane>
<TabPane tab="Topology" key="2">
<p>Topology</p>
</TabPane>
</Tabs>
</div>
<Modal
title="Attach a Trait"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleOk}>
Confirm
</Button>,
]}
>
<CreateTraitItem
onRef={(ref) => {
this.child = ref;
}}
availableTraitList={this.state.availableTraitList}
initialValues={{}}
/>
</Modal>
</Spin>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,47 @@
.app-detial {
.ant-tabs {
min-height: 500px;
padding: 10px 20px;
background-color: #fff;
.ant-tabs-content-holder {
background: #fff;
}
}
.summaryBox1,
.summaryBox2,
.summaryBox {
clear: both;
margin-bottom: 20px;
padding: 0 10px 10px;
.title {
font-weight: 500;
font-size: 16px;
line-height: 36px;
}
p {
margin: 0;
font-size: 12px;
line-height: 20px;
}
}
.summaryBox1 {
color: #fff;
background: #0097a7;
cursor: pointer;
}
.summaryBox2 {
color: #fff;
background: #92c47c;
}
.summaryBox {
border: 1px solid black;
cursor: pointer;
}
.hasCursor {
font-size: 20px;
cursor: pointer;
}
.floatRight {
float: right;
}
}

View File

@@ -0,0 +1,126 @@
import React from 'react';
import { Link } from 'umi';
import { Breadcrumb, Button, Menu, Spin } from 'antd';
import { connect } from 'dva';
import _ from 'lodash';
import './index.less';
import ComponentDetail from '../ComponentDetail/index.jsx';
@connect(({ loading }) => ({
loadingAll: loading.models.applist,
}))
class TableList extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
envName: '',
componentName: '',
defaultSelectedKeys: 'a1',
};
}
UNSAFE_componentWillMount() {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
envName,
componentName: appName,
});
if (appName === 'test33') {
this.setState({
defaultSelectedKeys: 'a1',
});
} else if (appName === 'test01') {
this.setState({
defaultSelectedKeys: 'c2',
});
} else {
this.setState({
defaultSelectedKeys: 'c3',
});
}
}
changeComponent = ({ key }) => {
if (key === 'a1') {
this.setState({
componentName: 'test33',
});
} else if (key === 'c2') {
this.setState({
componentName: 'test01',
});
} else {
this.setState({
componentName: 'testoo',
});
}
};
render() {
const { envName, componentName, defaultSelectedKeys } = this.state;
const { loadingAll } = this.props;
return (
<div style={{ height: '100%' }}>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>appname</Breadcrumb.Item>
<Breadcrumb.Item>Components</Breadcrumb.Item>
<Breadcrumb.Item>{componentName}</Breadcrumb.Item>
</Breadcrumb>
</div>
<Spin spinning={loadingAll}>
<div className="appComponent">
<div className="left">
<Menu
mode="inline"
onClick={this.changeComponent}
defaultSelectedKeys={[defaultSelectedKeys]}
>
<Menu.ItemGroup key="g1" title="Components">
<Menu.Item key="a1">a1(containerized)</Menu.Item>
<Menu.Item key="c2">c2(deploy)</Menu.Item>
<Menu.Item key="c3">c3(webserver)</Menu.Item>
</Menu.ItemGroup>
</Menu>
<div className="addComp">
<Link
to={{
// pathname: '/ApplicationList/CreateApplication',
pathname: `/ApplicationList/${componentName}/createComponent`,
state: { appName: componentName, envName },
}}
>
add a new comp
</Link>
</div>
</div>
<div className="right">
<div style={{ margin: '10px', float: 'right' }}>
<Button type="primary">Delete App</Button>
</div>
<ComponentDetail appName={componentName} envName={envName} />
</div>
</div>
</Spin>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,35 @@
.appComponent {
display: flex;
height: calc(100% - 46px);
.left {
width: 200px;
background: #fff;
.ant-menu-item-group-title {
color: #000;
font-weight: 500;
font-size: 18px;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
}
.ant-menu-item-group-list .ant-menu-item,
.ant-menu-item-group-list .ant-menu-submenu-title {
padding-left: 16px !important;
color: #000;
font-size: 16px;
}
.addComp {
padding: 8px 16px;
color: blue;
font-size: 16px;
text-decoration: underline;
background-color: #fff;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
cursor: pointer;
}
}
.right {
flex: 1;
// clear: both;
}
}

View File

@@ -52,13 +52,6 @@ class TableList extends React.Component {
};
}
UNSAFE_componentWillMount() {
const activeStep = _.get(this.props, 'location.state.activeStep', 0);
this.setState(() => ({
current: activeStep,
}));
}
componentDidMount() {
this.getInitalData();
}
@@ -73,20 +66,6 @@ class TableList extends React.Component {
this.setState({
traitList: traits,
});
const traitType = _.get(this.props, 'location.state.TraitType', '');
if (traitType) {
this.setState({
availableTraitList: traits,
traitNum: [
{
refname: null,
initialData: { name: traitType },
uniq: new Date().valueOf(),
},
],
});
}
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
@@ -94,7 +73,13 @@ class TableList extends React.Component {
}),
() => {
if (this.state.current === 0) {
const WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
let WorkloadType = '';
if (this.props.location.state) {
WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
sessionStorage.setItem('WorkloadType', WorkloadType);
} else {
WorkloadType = sessionStorage.getItem('WorkloadType');
}
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
});

View File

@@ -0,0 +1,642 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Button, Row, Col, Form, Input, Select, Steps, message, Breadcrumb } from 'antd';
import { connect } from 'dva';
import { Link } from 'umi';
import _ from 'lodash';
import CreateTraitItem from '../createTrait/index.jsx';
const { Option } = Select;
const { Step } = Steps;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.workload,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
formRefStep1 = React.createRef();
formRefStep2All = React.createRef();
constructor(props) {
super(props);
this.state = {
current: 0,
isShowMore: false,
traitNum: [
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
],
traitList: [],
availableTraitList: [],
workloadList: [],
workloadSettings: [],
step1SubmitObj: {},
step1InitialValues: {
workload_type: '',
},
step1Settings: [],
appName: '',
envName: '',
};
}
componentDidMount() {
this.getInitalData();
}
getInitalData = async () => {
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
const res = await this.props.dispatch({
type: 'workload/getWorkload',
});
const traits = await this.props.dispatch({
type: 'trait/getTraits',
});
this.setState({
traitList: traits,
});
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
workloadList: res,
}),
() => {
if (this.state.current === 0) {
let WorkloadType = '';
if (this.props.location.state) {
WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
sessionStorage.setItem('WorkloadType', WorkloadType);
} else {
WorkloadType = sessionStorage.getItem('WorkloadType');
}
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
});
this.workloadTypeChange(this.state.workloadList[0].name);
}
},
);
}
};
onFinishStep1 = (values) => {
this.setState({
current: 1,
step1InitialValues: values,
isShowMore: false,
});
};
onFinishStep2 = async () => {
const asyncValidateArray = [];
this.state.traitNum.forEach((item) => {
asyncValidateArray.push(item.refname.validateFields());
});
await Promise.all(asyncValidateArray);
const newTraitNum = this.state.traitNum.map((item) => {
// eslint-disable-next-line no-param-reassign
item.initialData = item.refname.getSelectValue();
return item;
});
// 进行trait数据整理便于第三步展示
this.setState(() => ({
traitNum: newTraitNum,
current: 2,
}));
};
gotoStep2 = () => {
this.setState({
current: 1,
isShowMore: false,
});
};
gotoStep1 = () => {
this.setState({
current: 0,
});
};
changeShowMore = () => {
this.setState({
isShowMore: true,
});
};
addMore = (e) => {
e.preventDefault();
this.setState((prev) => ({
traitNum: prev.traitNum.concat([
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
]),
}));
};
createApp = async () => {
const { traitNum } = this.state;
const { step1SubmitObj } = this.state;
if (step1SubmitObj.env_name !== this.props.currentEnv) {
step1SubmitObj.env_name = this.props.currentEnv;
}
const submitObj = _.cloneDeep(step1SubmitObj);
const { workload_name: workloadName } = step1SubmitObj;
submitObj.flags.push({
name: 'name',
value: workloadName.toString(),
});
// 处理数据为提交的格式
if (traitNum.length) {
const { env_name: envName } = step1SubmitObj;
const step2SubmitObj = [];
traitNum.forEach(({ initialData }) => {
if (initialData.name) {
const initialObj = {
name: initialData.name,
env_name: envName,
workload_name: workloadName,
flags: [],
};
Object.keys(initialData).forEach((key) => {
if (key !== 'name' && initialData[key]) {
initialObj.flags.push({
name: key,
value: initialData[key].toString(),
});
}
});
step2SubmitObj.push(initialObj);
}
});
submitObj.traits = step2SubmitObj;
}
const res = await this.props.dispatch({
type: 'workload/createWorkload',
payload: {
params: submitObj,
},
});
if (res) {
message.success(res);
const { appName, envName } = this.state;
this.props.history.push({
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
});
}
};
createWorkload = async () => {
await this.formRefStep1.current.validateFields();
const currentData = this.formRefStep1.current.getFieldsValue();
const submitObj = {
env_name: this.props.currentEnv,
workload_type: currentData.workload_type,
workload_name: currentData.workload_name,
flags: [],
};
Object.keys(currentData).forEach((key) => {
if (key !== 'workload_name' && key !== 'workload_type' && currentData[key]) {
submitObj.flags.push({
name: key,
value: currentData[key].toString(),
});
}
});
this.setState({
current: 1,
step1InitialValues: currentData,
step1Settings: submitObj.flags,
step1SubmitObj: submitObj,
});
this.getAcceptTrait(currentData.workload_type);
};
workloadTypeChange = (value) => {
const content = this.formRefStep1.current.getFieldsValue();
this.formRefStep1.current.resetFields();
const initialObj = {
workload_type: content.workload_type,
workload_name: content.workload_name,
};
this.formRefStep1.current.setFieldsValue(initialObj);
const currentWorkloadSetting = this.state.workloadList.filter((item) => {
return item.name === value;
});
if (currentWorkloadSetting.length) {
this.setState(
{
workloadSettings: currentWorkloadSetting[0].parameters,
},
() => {
this.state.workloadSettings.forEach((item) => {
if (item.default) {
initialObj[item.name] = item.default;
}
});
this.formRefStep1.current.setFieldsValue(initialObj);
},
);
}
this.setState({
traitNum: [
{
refname: null,
initialData: {},
uniq: new Date().valueOf(),
},
],
});
};
getAcceptTrait = (workloadType) => {
const res = this.state.traitList.filter((item) => {
if (item.appliesTo.indexOf(workloadType) !== -1) {
return true;
}
return false;
});
this.setState(() => ({
availableTraitList: res,
}));
};
deleteTraitItem = (uniq) => {
// 删除的时候不要依据数组的index删除,要一个唯一性的值
this.state.traitNum = this.state.traitNum.filter((item) => {
return item.uniq !== uniq;
});
this.setState((prev) => ({
traitNum: prev.traitNum,
}));
};
render() {
const { appName, envName } = this.state;
const { current, step1InitialValues, traitNum, workloadSettings } = this.state;
let { workloadList } = this.state;
workloadList = Array.isArray(workloadList) ? workloadList : [];
let currentDetail;
if (current === 0) {
currentDetail = (
<div>
<div className="minBox">
<Form
initialValues={step1InitialValues}
labelAlign="left"
{...layout}
ref={this.formRefStep1}
name="control-ref"
onFinish={this.onFinishStep1}
style={{ width: '60%' }}
>
<div style={{ padding: '16px 48px 0px 16px' }}>
<Form.Item
name="workload_name"
label="Name"
rules={[
{
pattern: /^[a-z0-9-_]+$/,
message:
'Names can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
{
required: true,
message: 'Please input name!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="workload_type"
label="Workload Type"
rules={[
{
required: true,
message: 'Please select Workload Type!',
},
]}
>
<Select
placeholder="Select a Workload Type"
allowClear
onChange={this.workloadTypeChange}
>
{workloadList.length ? (
workloadList.map((item) => {
return (
<Option value={item.name} key={item.name}>
{item.name}
</Option>
);
})
) : (
<></>
)}
</Select>
</Form.Item>
</div>
<Form.Item
label="Settings"
style={{ background: 'rgba(0, 0, 0, 0.04)', paddingLeft: '16px' }}
/>
<div className="relativeBox">
<p className="hasMore">?</p>
{Array.isArray(workloadSettings) && workloadSettings.length ? (
workloadSettings.map((item) => {
if (item.name === 'name') {
return <Fragment key={item.name} />;
}
return item.type === 4 ? (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[0-9]*$/, message: `${item.name} only use digits(0-9).` },
]}
>
<Input />
</Form.Item>
) : (
<Form.Item
name={item.name}
label={item.name}
key={item.name}
rules={[
{
required: item.required,
message: `Please input ${item.name}!`,
},
{ pattern: /^[^\s]*$/, message: 'Spaces are not allowed!' },
]}
>
<Input />
</Form.Item>
);
})
) : (
<></>
)}
</div>
<div className="buttonBox">
<Button type="primary" className="floatRightGap" onClick={this.createWorkload}>
Next
</Button>
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
<Button className="floatRightGap">Cancle</Button>
</Link>
</div>
</Form>
</div>
</div>
);
} else if (current === 1) {
currentDetail = (
<div>
<div className="minBox" style={{ width: '60%' }}>
<div style={{ padding: '0px 48px 0px 16px', width: '60%' }}>
<p style={{ fontSize: '18px', lineHeight: '32px' }}>
Name:<span>{step1InitialValues.workload_name}</span>
</p>
</div>
<div style={{ border: '1px solid #eee', padding: '16px 48px 16px 16px' }}>
<p className="title">{step1InitialValues.workload_type}</p>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>apps/v1</span>
<span
style={{
color: '#1890ff',
cursor: 'pointer',
display: this.state.isShowMore ? 'none' : 'black',
}}
onClick={this.changeShowMore}
>
more...
</span>
</div>
{this.state.isShowMore ? (
<div>
<p className="title" style={{ marginTop: '16px' }}>
Settings:
</p>
<Row>
{this.state.step1Settings.map((item) => {
return (
<Fragment key={item.name}>
<Col span="8">
<p>{item.name}:</p>
</Col>
<Col span="16">
<p>{item.value}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
) : (
''
)}
</div>
<div ref={this.formRefStep2All}>
{traitNum.map((item) => {
return (
<CreateTraitItem
onRef={(ref) => {
// eslint-disable-next-line no-param-reassign
item.refname = ref;
}}
key={item.uniq.toString()}
availableTraitList={this.state.availableTraitList}
uniq={item.uniq}
initialValues={item.initialData}
deleteTraitItem={this.deleteTraitItem}
/>
);
})}
</div>
<button style={{ marginTop: '16px' }} onClick={this.addMore} type="button">
Add More...
</button>
<div className="buttonBox">
<Button type="primary" className="floatRight" onClick={this.onFinishStep2}>
Next
</Button>
<Button className="floatRightGap" onClick={this.gotoStep1}>
Back
</Button>
</div>
</div>
</div>
);
} else {
currentDetail = (
<div>
<div className="minBox">
<p>
Name:<span>{step1InitialValues.workload_name}</span>
</p>
<Row>
<Col span="11">
<div className="summaryBox1">
<Row>
<Col span="22">
<p className="title">{step1InitialValues.workload_type}</p>
<p>apps/v1</p>
</Col>
</Row>
<p className="title hasMargin">Settings:</p>
<Row>
{this.state.step1Settings.map((item) => {
return (
<Fragment key={item.name}>
<Col span="8">
<p>{item.name}:</p>
</Col>
<Col span="16">
<p>{item.value}</p>
</Col>
</Fragment>
);
})}
</Row>
</div>
</Col>
<Col span="1" />
<Col span="10">
{traitNum.map(({ initialData }, index) => {
if (initialData.name) {
return (
<div className="summaryBox" key={index.toString()}>
<Row>
<Col span="22">
<p className="title">{initialData.name}</p>
<p>core.oam.dev/v1alpha2</p>
</Col>
</Row>
<p className="title hasMargin">Properties:</p>
<Row>
{Object.keys(initialData).map((currentKey) => {
if (currentKey !== 'name') {
return (
<Fragment key={currentKey}>
<Col span="8">
<p>{currentKey}:</p>
</Col>
<Col span="16">
<p>{initialData[currentKey]}</p>
</Col>
</Fragment>
);
}
return <Fragment key={currentKey} />;
})}
</Row>
</div>
);
}
return <Fragment key={index.toString()} />;
})}
</Col>
</Row>
</div>
<div className="buttonBox">
<Button
type="primary"
className="floatRight"
onClick={() => {
this.createApp();
}}
>
Confirm
</Button>
<Button className="floatRightGap" onClick={this.gotoStep2}>
Back
</Button>
</div>
</div>
);
}
return (
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link to="/ApplicationList">Applications</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>
<Link
to={{
pathname: `/ApplicationList/${appName}/Components`,
state: { appName, envName },
}}
>
{' '}
{appName}
</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>createComponent</Breadcrumb.Item>
</Breadcrumb>
</div>
<PageContainer>
<div className="create-container create-app">
<Steps current={current}>
<Step title="Step 1" description="Choose Workload" />
<Step title="Step 2" description="Attach Trait" />
<Step title="Step 3" description="Review and confirm" />
</Steps>
{currentDetail}
</div>
</PageContainer>
</div>
);
}
}
export default TableList;

View File

@@ -0,0 +1,66 @@
.create-container {
padding: 10px;
background: #fff;
}
.create-app {
.minBox {
margin: 20px 10px;
padding: 10px;
border: 1px solid #eee;
.title {
font-size: 16px;
line-height: 36px;
}
}
.buttonBox {
clear: both;
height: 42px;
margin: 0 10px;
padding: 10px;
}
.floatRight {
float: right;
}
.floatRightGap {
float: right;
margin-right: 16px;
}
.relativeBox {
position: relative;
padding: 0 48px 0 16px;
.hasMore {
position: absolute;
top: 0;
right: 16px;
padding: 4px;
color: rgb(24, 144, 255);
font-size: 16px;
cursor: pointer;
}
}
.summaryBox1,
.summaryBox2,
.summaryBox {
clear: both;
margin-bottom: 20px;
padding: 0 10px 10px;
p {
margin: 0;
font-size: 12px;
line-height: 20px;
}
}
.summaryBox1 {
color: #fff;
background: rgb(24, 144, 255);
}
.summaryBox2 {
background: yellow;
}
.summaryBox {
border: 1px solid black;
}
.hasMargin {
padding: 8px 0;
}
}

View File

@@ -1,21 +1,34 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { BranchesOutlined, ApartmentOutlined } from '@ant-design/icons';
import { Button, Card, Row, Col, Form, Spin, Empty, Breadcrumb } from 'antd';
import { Button, Card, Row, Col, Form, Spin, Empty, Breadcrumb, Modal, Input } from 'antd';
import { connect } from 'dva';
import moment from 'moment';
import './index.less';
import { Link } from 'umi';
const layout = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 18,
},
};
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
returnObj: applist.returnObj,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {};
this.state = {
visible: false,
};
}
componentDidMount() {
@@ -43,6 +56,23 @@ class TableList extends React.Component {
return true;
}
showModal = () => {
this.setState({
visible: true,
});
};
handleOk = async () => {
await this.formRef.current.validateFields();
// const submitData = await this.formRef.current.validateFields();
};
handleCancel = () => {
this.setState({
visible: false,
});
};
onFinish = () => {};
handleChange = () => {};
@@ -153,6 +183,40 @@ class TableList extends React.Component {
)}
</Row>
</Spin>
<Modal
title="Create Application"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
Create
</Button>,
]}
>
<Form {...layout} ref={this.formRef} name="control-ref" labelAlign="left">
<Form.Item
name="name"
label="Name"
rules={[
{
required: true,
message: 'Please input application name!',
},
{
pattern: /^[a-z0-9-_]+$/,
message:
'Name can only use digits(0-9),lowercase letters(a-z),and dashes(-),Underline.',
},
]}
>
<Input />
</Form.Item>
<Form.Item name="description" label="Description">
<Input.TextArea />
</Form.Item>
</Form>
</Modal>
</PageContainer>
</div>
);

View File

@@ -1,11 +1,14 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Space, Button, Row, Col, message, Spin, Breadcrumb } from 'antd';
import { Space, Button, Row, Col, message, Spin, Breadcrumb, Modal } from 'antd';
import { Link } from 'umi';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
const { confirm } = Modal;
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.capability,
currentEnv: globalData.currentEnv,
@@ -16,6 +19,7 @@ class TableList extends React.PureComponent {
this.state = {
workloadList: [],
traitList: [],
capabilityCenterName: '',
};
}
@@ -31,7 +35,16 @@ class TableList extends React.PureComponent {
const workloadList = [];
const traitList = [];
if (Array.isArray(res)) {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
let capabilityCenterName = '';
if (this.props.location.state) {
capabilityCenterName = _.get(this.props, 'location.state.name', '');
sessionStorage.setItem('capabilityCenterName', capabilityCenterName);
} else {
capabilityCenterName = sessionStorage.getItem('capabilityCenterName');
}
this.setState({
capabilityCenterName,
});
res.forEach((item) => {
if (item.center === capabilityCenterName) {
if (item.type === 'workload') {
@@ -55,7 +68,7 @@ class TableList extends React.PureComponent {
installSignle = async (e, name) => {
e.stopPropagation();
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
const res = await this.props.dispatch({
type: 'capability/syncOneCapability',
payload: {
@@ -65,7 +78,10 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
@@ -80,13 +96,16 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
syncAllSignle = async () => {
const capabilityCenterName = _.get(this.props, 'location.state.name', '');
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
const res = await this.props.dispatch({
type: 'capability/syncCapability',
@@ -96,13 +115,51 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
window.location.reload();
this.getInitialData();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
}
};
showDeleteConfirm = () => {
message.info('正在开发中...');
// eslint-disable-next-line
const _this = this;
const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
confirm({
title: `Are you sure delete ${capabilityCenterName}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p style={{ margin: '0px' }}>您本次移除 {capabilityCenterName}将会删除的应用列表</p>
<p style={{ margin: '0px' }}>
确认后移除 {capabilityCenterName}并且删除相应的应用
</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName,
},
});
if (res) {
message.success(res);
_this.props.history.push({ pathname: '/Capability' });
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {

View File

@@ -1,13 +1,14 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, message, Spin, Breadcrumb } from 'antd';
import { CopyOutlined } from '@ant-design/icons';
import { CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
const { Column } = Table;
const { confirm } = Modal;
const layout = {
labelCol: {
@@ -31,6 +32,7 @@ class TableList extends React.PureComponent {
this.state = {
visible: false,
capabilityList: [],
isCreateLoading: false,
};
}
@@ -55,14 +57,19 @@ class TableList extends React.PureComponent {
}
};
showModal = () => {
this.setState({
showModal = async () => {
await this.setState({
visible: true,
isCreateLoading: false,
});
this.formRef.current.resetFields();
};
handleOk = async () => {
const submitData = await this.formRef.current.validateFields();
this.setState({
isCreateLoading: true,
});
const res = await this.props.dispatch({
type: 'capability/createCapabilityCenter',
payload: {
@@ -111,7 +118,9 @@ class TableList extends React.PureComponent {
this.setState(() => ({
capabilityList: newList1,
}));
window.location.reload();
await this.props.dispatch({
type: 'menus/getMenuData',
});
}
};
@@ -126,8 +135,40 @@ class TableList extends React.PureComponent {
message.success('copy success');
};
showDeleteConfirm = () => {
message.info('正在开发中...');
showDeleteConfirm = (record) => {
if (record) {
// eslint-disable-next-line
const _this = this;
confirm({
title: `Are you sure delete ${record}?`,
icon: <ExclamationCircleOutlined />,
width: 500,
content: (
<div>
<p>您本次移除 {record}将会删除的应用列表</p>
<p>确认后移除{record}并且删除相应的应用</p>
</div>
),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
const res = await _this.props.dispatch({
type: 'capability/deleteCapability',
payload: {
capabilityCenterName: record,
},
});
if (res) {
message.success(res);
_this.getInitialData();
}
},
onCancel() {
// console.log('Cancel');
},
});
}
};
render() {
@@ -135,6 +176,7 @@ class TableList extends React.PureComponent {
let { loadingList } = this.props;
loadingList = loadingList || false;
capabilityList = Array.isArray(capabilityList) ? capabilityList : [];
const { isCreateLoading } = this.state;
return (
<div>
<div className="breadCrumb">
@@ -160,7 +202,12 @@ class TableList extends React.PureComponent {
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="submit" type="primary" onClick={this.handleOk}>
<Button
loading={isCreateLoading}
key="submit"
type="primary"
onClick={this.handleOk}
>
Create
</Button>,
]}

View File

@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, Tooltip } from 'antd';
import { Button, Table, Space, Modal, Form, Input, Tooltip, Breadcrumb } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import { Link } from 'umi';
import * as _ from 'lodash';
const { confirm } = Modal;
@@ -147,65 +148,76 @@ const TableList = (props) => {
},
];
return (
<PageContainer>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={showModal}>
Create
</Button>
</Space>
<div>
<div className="breadCrumb">
<Breadcrumb>
<Breadcrumb.Item>
<Link to="/ApplicationList">Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>System</Breadcrumb.Item>
<Breadcrumb.Item>Env</Breadcrumb.Item>
</Breadcrumb>
</div>
<Modal
getContainer={false}
title={env && env.envName ? 'Update Env' : 'Create Env'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="submit" type="primary" onClick={handleOk}>
{env && env.envName ? 'Update' : 'Create'}
</Button>,
]}
>
<Form {...layout} form={form} name="control-ref" labelAlign="left">
<Form.Item
name="envName"
label="Env"
rules={[
{
required: true,
message: 'Please input Evn!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input disabled={!!(env && env.envName)} />
</Form.Item>
<Form.Item
name="namespace"
label="Namespace"
rules={[
{
required: true,
message: 'Please specify a Namespace!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table rowKey={(record) => record.envName} columns={columns} dataSource={tableEnvs} />
</PageContainer>
<PageContainer>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={showModal}>
Create
</Button>
</Space>
</div>
<Modal
getContainer={false}
title={env && env.envName ? 'Update Env' : 'Create Env'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="submit" type="primary" onClick={handleOk}>
{env && env.envName ? 'Update' : 'Create'}
</Button>,
]}
>
<Form {...layout} form={form} name="control-ref" labelAlign="left">
<Form.Item
name="envName"
label="Env"
rules={[
{
required: true,
message: 'Please input Evn!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input disabled={!!(env && env.envName)} />
</Form.Item>
<Form.Item
name="namespace"
label="Namespace"
rules={[
{
required: true,
message: 'Please specify a Namespace!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63,should be combination of numbers,alphabets,underline!',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table rowKey={(record) => record.envName} columns={columns} dataSource={tableEnvs} />
</PageContainer>
</div>
);
};
export default connect((env) => {

View File

@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Form, Input, Button, Row, Col, Tabs, Table, Breadcrumb } from 'antd';
import { Form, Input, Button, Row, Col, Tabs, Table, Breadcrumb, Space } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import _ from 'lodash';
import { connect } from 'dva';
@@ -61,6 +61,17 @@ const columns = [
dataIndex: 'Age',
key: 'Age',
},
{
title: 'Action',
dataIndex: 'name',
key: 'name',
render: () => (
<Space>
<Button>logs</Button>
<Button>exec</Button>
</Space>
),
},
];
const data = [
@@ -144,6 +155,7 @@ class TableList extends React.Component {
hasShowEdit2: false,
traitItem: {},
appName: '',
envName: '',
appDetailData: {},
};
}
@@ -153,16 +165,27 @@ class TableList extends React.Component {
}
async getInitialData() {
const traitItem = _.get(this.props, 'location.state.traitItem', {});
let traitItem = '';
let appName = '';
let envName = '';
if (this.props.location.state) {
traitItem = _.get(this.props, 'location.state.traitItem', '');
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('traitItem', traitItem);
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
traitItem = sessionStorage.getItem('traitItem');
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
traitItem,
appName,
envName,
});
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
if (appName && envName) {
this.setState({
appName,
});
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
@@ -203,7 +226,7 @@ class TableList extends React.Component {
};
render() {
const { hasShowEdit, hasShowEdit2 } = this.state;
const { hasShowEdit, hasShowEdit2, envName } = this.state;
const status = _.get(this.state.appDetailData, 'Status', '');
const { traitItem, appName } = this.state;
const metadata = _.get(traitItem, 'metadata', '');
@@ -214,7 +237,6 @@ class TableList extends React.Component {
if (traitItem.kind === 'Ingress') {
traitType = 2;
}
const envName = _.get(this.props, 'location.state.envName', '');
return (
<div>
<div className="breadCrumb">

View File

@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import './index.less';
import { Form, Input, Button, Row, Col, Tabs, Table, Spin, Breadcrumb } from 'antd';
import { Form, Input, Button, Row, Col, Tabs, Table, Spin, Breadcrumb, Space } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
import { connect } from 'dva';
import { Link } from 'umi';
@@ -61,6 +61,17 @@ const columns = [
dataIndex: 'Age',
key: 'Age',
},
{
title: 'Action',
dataIndex: 'name',
key: 'name',
render: () => (
<Space>
<Button>logs</Button>
<Button>exec</Button>
</Space>
),
},
];
const data = [
@@ -143,6 +154,8 @@ class TableList extends React.PureComponent {
hasShowEdit: false,
hasShowEdit2: false,
appDetailData: {},
appName: '',
envName: '',
};
}
@@ -151,8 +164,21 @@ class TableList extends React.PureComponent {
}
async getInitialData() {
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
let appName = '';
let envName = '';
if (this.props.location.state) {
appName = _.get(this.props, 'location.state.appName', '');
envName = _.get(this.props, 'location.state.envName', '');
sessionStorage.setItem('appName', appName);
sessionStorage.setItem('envName', envName);
} else {
appName = sessionStorage.getItem('appName');
envName = sessionStorage.getItem('envName');
}
this.setState({
appName,
envName,
});
if (appName && envName) {
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
@@ -194,7 +220,7 @@ class TableList extends React.PureComponent {
};
render() {
const { hasShowEdit, hasShowEdit2 } = this.state;
const { hasShowEdit, hasShowEdit2, appName, envName } = this.state;
const status = _.get(this.state.appDetailData, 'Status', '');
const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
const metadata = _.get(Workload, 'metadata', {});
@@ -202,8 +228,6 @@ class TableList extends React.PureComponent {
containers = _.get(Workload, 'spec.containers[0]', {});
let { loadingAll } = this.props;
loadingAll = loadingAll || false;
const appName = _.get(this.props, 'location.state.appName', '');
const envName = _.get(this.props, 'location.state.envName', '');
return (
<div>
<div className="breadCrumb">

View File

@@ -31,10 +31,10 @@ export async function syncCapability({ capabilityCenterName }) {
});
}
/*
* Delete /api/capabilities/:capabilityName (删除一个 capability)
* DELETE /capability-centers/:capabilityCenterName/ (Capability Center delete) (删除一个 capabilityCenter)
*/
export async function deleteCapability({ capabilityName }) {
return request(`/api/capabilities/${capabilityName}`, {
export async function deleteCapability({ capabilityCenterName }) {
return request(`/api/capability-centers/${capabilityCenterName}`, {
method: 'delete',
});
}

View File

@@ -58,7 +58,7 @@ var (
cli := fmt.Sprintf("vela env init %s --namespace %s", envName, envName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
expectedOutput := fmt.Sprintf("Create env succeed, current env is %s", envName)
expectedOutput := fmt.Sprintf("ENV %s CREATED,", envName)
gomega.Expect(output).To(gomega.ContainSubstring(expectedOutput))
})
})
@@ -129,7 +129,7 @@ var (
cli := fmt.Sprintf("vela app delete %s", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("DELETE SUCCEED"))
gomega.Expect(output).To(gomega.ContainSubstring("delete apps succeed"))
})
})
}
@@ -214,7 +214,7 @@ var (
err = json.Unmarshal(result, &r)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(http.StatusOK).Should(gomega.Equal(r.Code))
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring("Create env succeed"))
gomega.Expect(r.Data.(string)).To(gomega.ContainSubstring("CREATED"))
})
})
}

3
go.mod
View File

@@ -17,13 +17,13 @@ require (
github.com/google/go-cmp v0.5.2
github.com/google/go-github/v32 v32.1.0
github.com/gosuri/uitable v0.0.4
github.com/jetstack/cert-manager v0.14.3
github.com/mholt/archiver/v3 v3.3.0
github.com/oam-dev/trait-injector v0.0.0-20200331033130-0a27b176ffc4
github.com/onsi/ginkgo v1.13.0
github.com/onsi/gomega v1.10.1
github.com/openservicemesh/osm v0.3.0
github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
@@ -43,7 +43,6 @@ require (
k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 // indirect
k8s.io/kubectl v0.18.6 // indirect
k8s.io/utils v0.0.0-20200414100711-2df71ebbae66
rsc.io/letsencrypt v0.0.3 // indirect
sigs.k8s.io/controller-runtime v0.6.0
)

60
go.sum
View File

@@ -41,6 +41,7 @@ github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiU
github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v34.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v36.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v41.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
@@ -133,6 +134,7 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/Venafi/vcert v0.0.0-20200310111556-eba67a23943f/go.mod h1:9EegQjmRoMqVT/ydgd54mJj5rTd7ym0qMgEfhnPsce0=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
@@ -166,6 +168,7 @@ github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:o
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.24.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
@@ -224,6 +227,7 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.8.5/go.mod h1:8KhU6K+zHUEWOSU++mEQYf7D9UZOcQcibUoSm6vCUz4=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -265,6 +269,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/prometheus-operator v0.41.1 h1:MEhY9syliPlQg+VlFRUfNodUEVXRXJ2n1pFG0aBp+mI=
github.com/coreos/prometheus-operator v0.41.1/go.mod h1:LhLfEBydppl7nvfEA1jIqlF3xJ9myHCnzrU+HHDxRd4=
github.com/cpu/goacmedns v0.0.0-20180701200144-565ecf2a84df/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -277,8 +282,6 @@ github.com/crossplane/crossplane-runtime v0.8.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbB
github.com/crossplane/crossplane-runtime v0.9.0 h1:K6/tLhXKzhsEUUddTvEWWnQLLrawWyw1ptNK7NBDpDU=
github.com/crossplane/crossplane-runtime v0.9.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbBXp8reYRbwY5B/97Kp4tgM=
github.com/crossplane/crossplane-tools v0.0.0-20200219001116-bb8b2ce46330/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
github.com/crossplane/oam-kubernetes-runtime v0.0.9 h1:cZMT7p1jZ6MsJqAuzVIZwvOxVdD+PGEQgCYHHuwR7Pc=
github.com/crossplane/oam-kubernetes-runtime v0.0.9/go.mod h1:f5xqmo0B2WtaOTZh8jhP+0f0XuzqhJG2xRtxfMZR3jA=
github.com/crossplane/oam-kubernetes-runtime v0.1.1-0.20200909070723-78b84f2c4799 h1:424LLFb7C8Qvy3wFZZ7HzmawlCeF32PNRTXXK5rKOk0=
github.com/crossplane/oam-kubernetes-runtime v0.1.1-0.20200909070723-78b84f2c4799/go.mod h1:UZ4eXkl/e4lKrAhK81Pz1sR90wqeuE9PgdwVXr8kDgI=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
@@ -303,6 +306,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/digitalocean/godo v1.29.0/go.mod h1:iJnN9rVu6K5LioLxLimlq0uRI+y/eAQjROUmeU/r0hY=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 h1:FwssHbCDJD025h+BchanCwE1Q8fyMgqDr2mOQAWOLGw=
@@ -535,6 +539,7 @@ github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
@@ -751,6 +756,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -775,6 +781,7 @@ github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoI
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
@@ -802,6 +809,8 @@ github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mq
github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po=
github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jetstack/cert-manager v0.14.3 h1:SSp/aTA3s+Nr8lRH5d9Kg7vF8HT4sf7Q6idHMnRQ+vs=
github.com/jetstack/cert-manager v0.14.3/go.mod h1:xuOMzt8X2TVad3gRzRDBI88JUfuP78YEp7+mCLWWyWM=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
@@ -867,6 +876,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
@@ -910,6 +920,7 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -945,10 +956,9 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/archiver v1.1.2 h1:xukR55YIrnhDHp10lrNtRSsAK5THpWrOCuviweNSBw4=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig=
github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao=
github.com/miekg/dns v0.0.0-20170721150254-0f3adef2e220/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@@ -970,6 +980,7 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
@@ -997,6 +1008,7 @@ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISe
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/munnerz/crd-schema-fuzz v0.0.0-20191114184610-fbd148d44a0a/go.mod h1:fVs1Mso4ZxhlygBEUDgOcyLtp5/DnLuCb8H5GI3CzS4=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -1012,6 +1024,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/exhaustive v0.0.0-20200708172631-8866003e3856/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
@@ -1083,6 +1096,7 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible/go.mod h1:xlUlxe/2ItGlQyMTstqeDv9r3U4obH7xYd26TbDQutY=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
@@ -1319,6 +1333,7 @@ github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E=
@@ -1406,6 +1421,7 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -1466,6 +1482,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1516,6 +1533,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrS
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1554,6 +1572,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190425045458-9f0b1ff7b46a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1706,6 +1725,7 @@ golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200630223951-c138986dd9b9 h1:CrrjeBLnUL9WPTAjIqIPdfEAAs5XNY+8A4/CNxRqmbI=
golang.org/x/tools v0.0.0-20200630223951-c138986dd9b9/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305 h1:yaM5S0KcY0lIoZo7Fl+oi91b/DdlU2zuWpfHrpWbCS0=
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1820,6 +1840,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
@@ -1836,8 +1857,10 @@ gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -1878,12 +1901,17 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58=
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
k8s.io/api v0.0.0-20191016110408-35e52d86657a/go.mod h1:/L5qH+AD540e7Cetbui1tuJeXdmNhO8jM6VkXeDdDhQ=
k8s.io/api v0.0.0-20191115095533-47f6de673b26/go.mod h1:iA/8arsvelvo4IDqIhX4IbjTEKBGgvsf2OraTuRtLFU=
k8s.io/api v0.0.0-20191122220107-b5267f2975e0/go.mod h1:vYpRfxYkMrmPPSesoHEkGNHxNKTk96REAwqm/inQbs0=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
k8s.io/api v0.17.5/go.mod h1:0zV5/ungglgy2Rlm3QK8fbxkXVs+BSJWpJP/+8gUVLY=
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
@@ -1892,13 +1920,21 @@ k8s.io/api v0.18.5/go.mod h1:tN+e/2nbdGKOAH55NMV8oGrMG+3uRlA9GaRfvnCCSNk=
k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65/go.mod h1:5BINdGqggRXXKnDgpwoJ7PyQH8f+Ypp02fvVNcIFy9s=
k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
k8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8=
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
k8s.io/apimachinery v0.0.0-20191115015347-3c7067801da2/go.mod h1:dXFS2zaQR8fyzuvRdJDHw2Aerij/yVGJSre0bZQSVJA=
k8s.io/apimachinery v0.0.0-20191121175448-79c2a76c473a/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/apimachinery v0.17.5/go.mod h1:ioIo1G/a+uONV7Tv+ZmCbMG1/a3kVw5YcDdncd8ugQ0=
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
@@ -1907,7 +1943,11 @@ k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCk
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
k8s.io/apiserver v0.0.0-20191016112112-5190913f932d/go.mod h1:7OqfAolfWxUM/jJ/HBLyE+cdaWFBUoo5Q5pHgJVj2ws=
k8s.io/apiserver v0.0.0-20191122221311-9d521947b1e1/go.mod h1:RbsZY5zzBIWnz4KbctZsTVjwIuOpTp4Z8oCgFHN4kZQ=
k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
k8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
k8s.io/apiserver v0.18.2 h1:fwKxdTWwwYhxvtjo0UUfX+/fsitsNtfErPNegH2x9ic=
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
@@ -1918,12 +1958,20 @@ k8s.io/cli-runtime v0.18.6/go.mod h1:+G/WTNqHgUv636e5y7rhOQ7epUbRXnwmPnhOhD6t9uM
k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
k8s.io/code-generator v0.0.0-20191004115455-8e001e5d1894/go.mod h1:mJUgkl06XV4kstAnLHAIzJPVCOzVR+ZcfPIv4fUsFCY=
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
k8s.io/code-generator v0.18.5/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
k8s.io/component-base v0.0.0-20191016111319-039242c015a9/go.mod h1:SuWowIgd/dtU/m/iv8OD9eOxp3QZBBhTIiWMsBQvKjI=
k8s.io/component-base v0.0.0-20191122220729-2684fb322cb9/go.mod h1:NFuUusy/X4Tk21m21tcNUihnmp4OI7lXU7/xA+rYXkc=
k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
k8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=
k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c=
k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
k8s.io/component-base v0.18.5/go.mod h1:RSbcboNk4B+S8Acs2JaBOVW3XNz1+A637s2jL+QQrlU=
@@ -1940,6 +1988,7 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-aggregator v0.17.3/go.mod h1:1dMwMFQbmH76RKF0614L7dNenMl3dwnUJuOOyZ3GMXA=
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
@@ -1982,9 +2031,11 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/controller-runtime v0.5.1-0.20200307095134-d0de78d9f1c1/go.mod h1:Uojny7gvg55YLQnEGnPzRE3dC4ik2tRlZJgOUCWXAV4=
sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
sigs.k8s.io/controller-tools v0.2.5/go.mod h1:+t0Hz6tOhJQCdd7IYO0mNzimmiM9sqMU0021u6UCF2o=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
@@ -2001,6 +2052,7 @@ sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

View File

@@ -320,7 +320,7 @@ func EvalToObject(capName string, data map[string]interface{}) (*unstructured.Un
return u, nil
}
func (app *Application) GetComponentTraits(componentName string) ([]v1alpha2.ComponentTrait, error) {
func (app *Application) GetComponentTraits(componentName string, env *types.EnvMeta) ([]v1alpha2.ComponentTrait, error) {
var traits []v1alpha2.ComponentTrait
rawTraits, err := app.GetTraits(componentName)
if err != nil {
@@ -338,6 +338,14 @@ func (app *Application) GetComponentTraits(componentName string) ([]v1alpha2.Com
return traits, nil
}
func (app *Application) VelaCoreInjection(obj *unstructured.Unstructured, env *types.EnvMeta, traitType string) {
switch traitType {
case "route":
}
}
func FormatDefaultHealthScopeName(appName string) string {
return appName + "-default-health"
}
@@ -387,7 +395,7 @@ func (app *Application) OAM(env *types.EnvMeta) ([]v1alpha2.Component, v1alpha2.
}})
//TODO(wonderflow): handle component data input/output here
compTraits, err := app.GetComponentTraits(name)
compTraits, err := app.GetComponentTraits(name, env)
if err != nil {
return nil, v1alpha2.ApplicationConfiguration{}, nil, err
}

View File

@@ -58,8 +58,7 @@ func NewCompRunCommands(c types.Args, ioStreams util.IOStreams) *cobra.Command {
Long: "Init and Run workloads",
Example: "vela comp run -t <workload-type>",
RunE: func(cmd *cobra.Command, args []string) error {
if args[0] == "-h" {
if len(args) == 0 || args[0] == "-h" {
err := cmd.Help()
if err != nil {
return err

View File

@@ -43,7 +43,7 @@ func NewDeleteCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
o.AppName = args[0]
ioStreams.Infof("Deleting Application \"%s\"\n", o.AppName)
_, err = o.DeleteApp()
info, err := o.DeleteApp()
if err != nil {
if apierrors.IsNotFound(err) {
ioStreams.Info("Already deleted")
@@ -51,7 +51,7 @@ func NewDeleteCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
}
return err
}
ioStreams.Info("DELETE SUCCEED")
ioStreams.Info(info)
return nil
}
return cmd

View File

@@ -57,7 +57,7 @@ func NewEnvInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
DisableFlagsInUseLine: true,
Short: "Create environments",
Long: "Create environment and set the currently using environment",
Example: `vela env init test --namespace test`,
Example: `vela env init test --namespace test --email my@email.com`,
RunE: func(cmd *cobra.Command, args []string) error {
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
if err != nil {
@@ -71,6 +71,8 @@ func NewEnvInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command
}
cmd.SetOut(ioStreams.Out)
cmd.Flags().StringVar(&envArgs.Namespace, "namespace", "default", "specify K8s namespace for env")
cmd.Flags().StringVar(&envArgs.Email, "email", "", "specify email for production TLS Certificate notification")
cmd.Flags().StringVar(&envArgs.Domain, "domain", "", "specify domain your applications")
return cmd
}
@@ -94,7 +96,6 @@ func NewEnvDeleteCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
}
func NewEnvSetCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "set",
Aliases: []string{"sw"},
@@ -103,7 +104,7 @@ func NewEnvSetCommand(ioStreams cmdutil.IOStreams) *cobra.Command {
Long: "Set an environment as the current using one",
Example: `vela env set test`,
RunE: func(cmd *cobra.Command, args []string) error {
return SetEnv(ctx, args, ioStreams)
return SetEnv(args, ioStreams)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
@@ -149,8 +150,8 @@ func CreateOrUpdateEnv(ctx context.Context, c client.Client, envArgs *types.EnvM
return fmt.Errorf("you must specify env name for vela env init command")
}
envName := args[0]
namespace := envArgs.Namespace
msg, err := oam.CreateOrUpdateEnv(ctx, c, envName, namespace)
envArgs.Name = envName
msg, err := oam.CreateOrUpdateEnv(ctx, c, envName, envArgs)
if err != nil {
return err
}
@@ -158,7 +159,7 @@ func CreateOrUpdateEnv(ctx context.Context, c client.Client, envArgs *types.EnvM
return nil
}
func SetEnv(ctx context.Context, args []string, ioStreams cmdutil.IOStreams) error {
func SetEnv(args []string, ioStreams cmdutil.IOStreams) error {
if len(args) < 1 {
return fmt.Errorf("you must specify env name for vela env command")
}

View File

@@ -77,7 +77,7 @@ func TestENV(t *testing.T) {
assert.Error(t, err)
// set as default env
err = SetEnv(ctx, []string{"default"}, ioStream)
err = SetEnv([]string{"default"}, ioStream)
assert.NoError(t, err)
// check env set success
@@ -93,10 +93,10 @@ func TestENV(t *testing.T) {
assert.NoError(t, err)
// can not set as a non-exist env
err = SetEnv(ctx, []string{"env1"}, ioStream)
err = SetEnv([]string{"env1"}, ioStream)
assert.Error(t, err)
// set success
err = SetEnv(ctx, []string{"default"}, ioStream)
err = SetEnv([]string{"default"}, ioStream)
assert.NoError(t, err)
}

View File

@@ -44,7 +44,7 @@ func AddTraitCommands(parentCmd *cobra.Command, c types.Args, ioStreams cmdutil.
Long: "Attach " + name + " trait to an app",
Example: "vela " + name + " frontend",
RunE: func(cmd *cobra.Command, args []string) error {
o := &commandOptions{IOStreams: ioStreams}
o := &commandOptions{IOStreams: ioStreams, traitType: name}
o.Template = tmp
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
if err != nil {
@@ -103,7 +103,9 @@ func (o *commandOptions) AddOrUpdateTrait(cmd *cobra.Command, args []string) err
if err = o.Prepare(cmd, args); err != nil {
return err
}
if o.app, err = oam.AddOrUpdateTrait(o.Env.Name, o.appName, o.workloadName, cmd.Flags(), o.Template); err != nil {
flags := cmd.Flags()
if o.app, err = oam.AddOrUpdateTrait(o.Env, o.appName, o.workloadName, flags, o.Template); err != nil {
return err
}
return nil
@@ -138,6 +140,7 @@ func (o *commandOptions) Run(ctx context.Context, cmd *cobra.Command) error {
if err != nil {
return err
}
o.Info(msg)
return nil
}

View File

@@ -0,0 +1,7 @@
package common
const (
ErrLocatingWorkload = "failed to locate the workload"
ErrLocatingService = "failed to locate any the services"
ErrCreatingService = "failed to create the services"
)

View File

@@ -0,0 +1,11 @@
package common
import (
"reflect"
v1 "k8s.io/api/core/v1"
)
var ServiceKind = reflect.TypeOf(v1.Service{}).Name()
var ServiceAPIVersion = v1.SchemeGroupVersion.String()

View File

@@ -21,12 +21,13 @@ import (
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/containerized"
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/metrics"
"github.com/oam-dev/kubevela/pkg/controller/v1alpha1/routes"
)
// Setup workload controllers.
func Setup(mgr ctrl.Manager) error {
for _, setup := range []func(ctrl.Manager) error{
metrics.Setup, containerized.Setup,
metrics.Setup, containerized.Setup, routes.Setup,
} {
if err := setup(mgr); err != nil {
return err

View File

@@ -21,6 +21,8 @@ import (
"fmt"
"reflect"
"github.com/oam-dev/kubevela/pkg/controller/common"
monitoring "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/event"
@@ -42,17 +44,12 @@ import (
const (
errApplyServiceMonitor = "failed to apply the service monitor"
errLocatingWorkload = "failed to locate the workload"
errLocatingService = "failed to locate any the services"
errCreatingService = "failed to create the services"
servicePort = 4848
)
var (
serviceMonitorKind = reflect.TypeOf(monitoring.ServiceMonitor{}).Name()
serviceMonitorAPIVersion = monitoring.SchemeGroupVersion.String()
serviceKind = reflect.TypeOf(corev1.Service{}).Name()
serviceAPIVersion = corev1.SchemeGroupVersion.String()
)
var (
@@ -115,27 +112,27 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
mLog.Error(err, "Error while fetching the workload", "workload reference",
metricsTrait.GetWorkloadReference())
r.record.Event(eventObj, event.Warning(errLocatingWorkload, err))
r.record.Event(eventObj, event.Warning(common.ErrLocatingWorkload, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &metricsTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, errLocatingWorkload)))
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
}
// try to see if the workload already has services as child resources
serviceLabel, err := r.fetchServicesLabel(ctx, mLog, workload, metricsTrait.Spec.ScrapeService.TargetPort)
if err != nil && !apierrors.IsNotFound(err) {
r.record.Event(eventObj, event.Warning(errLocatingService, err))
r.record.Event(eventObj, event.Warning(common.ErrLocatingService, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &metricsTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, errLocatingService)))
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingService)))
} else if serviceLabel == nil {
// TODO: use podMonitor instead?
// no service with the targetPort found, we will create a service that talks to the targetPort
serviceLabel, err = r.createService(ctx, mLog, workload, &metricsTrait)
if err != nil {
r.record.Event(eventObj, event.Warning(errCreatingService, err))
r.record.Event(eventObj, event.Warning(common.ErrCreatingService, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &metricsTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, errCreatingService)))
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrCreatingService)))
}
}
// construct the serviceMonitor that hooks the service to the prometheus server
@@ -171,7 +168,7 @@ func (r *Reconciler) fetchServicesLabel(ctx context.Context, mLog logr.Logger,
}
// find the service that has the port
for _, childRes := range resources {
if childRes.GetAPIVersion() == serviceAPIVersion && childRes.GetKind() == serviceKind {
if childRes.GetAPIVersion() == common.ServiceAPIVersion && childRes.GetKind() == common.ServiceKind {
ports, _, _ := unstructured.NestedSlice(childRes.Object, "spec", "ports")
for _, port := range ports {
servicePort, _ := port.(corev1.ServicePort)
@@ -189,8 +186,8 @@ func (r *Reconciler) createService(ctx context.Context, mLog logr.Logger, worklo
metricsTrait *v1alpha1.MetricsTrait) (map[string]string, error) {
oamService := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: serviceKind,
APIVersion: serviceAPIVersion,
Kind: common.ServiceKind,
APIVersion: common.ServiceAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "oam-" + workload.GetName(),

View File

@@ -0,0 +1,470 @@
/*
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 routes
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strconv"
"github.com/oam-dev/kubevela/api/v1alpha1"
standardv1alpha1 "github.com/oam-dev/kubevela/api/v1alpha1"
"github.com/oam-dev/kubevela/pkg/controller/common"
cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/event"
oamutil "github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
"github.com/go-logr/logr"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/networking/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
errApplyNginxIngress = "failed to apply the ingress"
errCreateIssuer = "failed to create cert-manager Issuer"
)
var (
// oamServiceLabel is the pre-defined labels for any serviceMonitor
// created by the RouteTrait
oamServiceLabel = map[string]string{
"k8s-app": "oam",
"controller": "routeTrait",
}
)
// Reconciler reconciles a Route object
type Reconciler struct {
client.Client
Log logr.Logger
record event.Recorder
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=standard.oam.dev,resources=routes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=standard.oam.dev,resources=routes/status,verbs=get;update;patch
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
mLog := r.Log.WithValues("route", req.NamespacedName)
mLog.Info("Reconcile route trait")
// fetch the trait
var routeTrait standardv1alpha1.Route
if err := r.Get(ctx, req.NamespacedName, &routeTrait); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
mLog.Info("Get the route trait",
"host", routeTrait.Spec.Host,
"path", routeTrait.Spec.Path,
"workload reference", routeTrait.Spec.WorkloadReference,
"labels", routeTrait.GetLabels())
// find the resource object to record the event to, default is the parent appConfig.
eventObj, err := oamutil.LocateParentAppConfig(ctx, r.Client, &routeTrait)
if eventObj == nil {
// fallback to workload itself
mLog.Error(err, "add events to route trait itself", "name", routeTrait.Name)
eventObj = &routeTrait
}
// Fetch the workload instance to which we want to do routes
workload, err := oamutil.FetchWorkload(ctx, r, mLog, &routeTrait)
if err != nil {
mLog.Error(err, "Error while fetching the workload", "workload reference",
routeTrait.GetWorkloadReference())
r.record.Event(eventObj, event.Warning(common.ErrLocatingWorkload, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &routeTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingWorkload)))
}
// try to see if the workload already has services as child resources, and match for our route
svc, exist, svcPort, err := r.fetchService(ctx, mLog, workload, &routeTrait)
if err != nil && !apierrors.IsNotFound(err) {
r.record.Event(eventObj, event.Warning(common.ErrLocatingService, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &routeTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrLocatingService)))
}
// Create Services
if !exist {
// no service found, we will create service according to rule
svc, svcPort, err = r.createService(ctx, mLog, workload, &routeTrait)
if err != nil {
r.record.Event(eventObj, event.Warning(common.ErrCreatingService, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &routeTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, common.ErrCreatingService)))
}
r.record.Event(eventObj, event.Normal("Service created",
fmt.Sprintf("successfully automatically created a service `%s`", svc.Name)))
} else {
mLog.Info("workload already has service as child resource, will not create new", "workloadName", workload.GetName())
}
// Create Issuers
var issuer standardv1alpha1.TLS
if routeTrait.Spec.TLS == nil || routeTrait.Spec.TLS.IssuerName == "" {
issuerName, err := r.createSelfsignedIssuer(ctx, &routeTrait)
if err != nil {
r.record.Event(eventObj, event.Warning(errCreateIssuer, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &routeTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, errCreateIssuer)))
}
r.record.Event(eventObj, event.Normal("Issuer created",
fmt.Sprintf("successfully automatically created a Issuer for route TLS `%s`", issuerName)))
issuer.Type = standardv1alpha1.NamespaceIssuer
issuer.IssuerName = issuerName
} else {
issuer = *routeTrait.Spec.TLS
}
// Create Ingress
// construct the serviceMonitor that hooks the service to the prometheus server
ingress := constructNginxIngress(&routeTrait, issuer, svc, svcPort)
// server side apply the serviceMonitor, only the fields we set are touched
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(routeTrait.GetUID())}
if err := r.Patch(ctx, ingress, client.Apply, applyOpts...); err != nil {
mLog.Error(err, "Failed to apply to ingress")
r.record.Event(eventObj, event.Warning(errApplyNginxIngress, err))
return oamutil.ReconcileWaitResult,
oamutil.PatchCondition(ctx, r, &routeTrait,
cpv1alpha1.ReconcileError(errors.Wrap(err, errApplyNginxIngress)))
}
r.record.Event(eventObj, event.Normal("Nginx Ingress created",
fmt.Sprintf("successfully server side patched a route trait `%s`", routeTrait.Name)))
// TODO(wonderflow): GC mechanism for no used ingress, service, issuer
routeTrait.Status.Service = svc
routeTrait.Status.Ingress = &runtimev1alpha1.TypedReference{
APIVersion: v1beta1.SchemeGroupVersion.String(),
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
Name: ingress.Name,
UID: routeTrait.UID,
}
return ctrl.Result{}, oamutil.PatchCondition(ctx, r, &routeTrait)
}
// create a service that targets the exposed workload pod
func (r *Reconciler) createService(ctx context.Context, mLog logr.Logger, workload *unstructured.Unstructured,
routeTrait *v1alpha1.Route) (*runtimev1alpha1.TypedReference, int32, error) {
oamService := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: common.ServiceKind,
APIVersion: common.ServiceAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "route-" + workload.GetName(),
Namespace: workload.GetNamespace(),
Labels: oamServiceLabel,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
UID: routeTrait.GetUID(),
Name: routeTrait.GetName(),
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
},
}
// assign selector
if routeTrait.Spec.Backend != nil && len(routeTrait.Spec.Backend.SelectLabels) != 0 {
oamService.Spec.Selector = routeTrait.Spec.Backend.SelectLabels
}
port, labels, err := DiscoverPortLabel(ctx, mLog, workload, r)
if err == nil {
if len(oamService.Spec.Selector) == 0 {
oamService.Spec.Selector = labels
}
if routeTrait.Spec.Backend == nil {
routeTrait.Spec.Backend = &standardv1alpha1.Backend{Port: port}
} else if routeTrait.Spec.Backend.Port.String() == "0" {
routeTrait.Spec.Backend.Port = port
}
} else {
mLog.Info("[WARN] fail to discovery port and label", "err", err)
}
var servicePort int32 = 443
oamService.Spec.Ports = []corev1.ServicePort{
{
Port: servicePort,
TargetPort: routeTrait.Spec.Backend.Port,
Protocol: corev1.ProtocolTCP,
},
}
// server side apply the service, only the fields we set are touched
applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner(routeTrait.GetUID())}
if err := r.Patch(ctx, oamService, client.Apply, applyOpts...); err != nil {
mLog.Error(err, "Failed to apply to service")
return nil, servicePort, err
}
return &runtimev1alpha1.TypedReference{
APIVersion: common.ServiceAPIVersion,
Kind: common.ServiceKind,
Name: oamService.Name,
UID: routeTrait.UID,
}, servicePort, nil
}
// Assume the workload or it's childResource will always having spec.template as PodTemplate if discoverable
func DiscoverPortLabel(ctx context.Context, mLog logr.Logger, workload *unstructured.Unstructured, r client.Reader) (intstr.IntOrString, map[string]string, error) {
var resources = []*unstructured.Unstructured{workload}
// Fetch the child resources list from the corresponding workload
childResources, err := oamutil.FetchWorkloadChildResources(ctx, mLog, r, workload)
if err == nil {
resources = append(resources, childResources...)
} else {
mLog.Info("[WARN] fail to fetch workload child resource", "name", workload.GetName(), "err", err)
}
var gatherErrs []error
for _, w := range resources {
port, labels, err := discoveryFromObject(w)
if err == nil {
return port, labels, nil
}
gatherErrs = append(gatherErrs, err)
}
return intstr.IntOrString{}, nil, fmt.Errorf("can't discovery port from workload %v %v.%v and it's child resource, errorList: %v", workload.GetName(), workload.GetAPIVersion(), workload.GetKind(), gatherErrs)
}
func discoveryFromObject(w *unstructured.Unstructured) (intstr.IntOrString, map[string]string, error) {
obj, found, _ := unstructured.NestedMap(w.Object, "spec", "template")
if !found {
return intstr.IntOrString{}, nil, fmt.Errorf("not have spec.template in workload %v", w.GetName())
}
data, err := json.Marshal(obj)
if err != nil {
return intstr.IntOrString{}, nil, fmt.Errorf("workload %v convert object err %v", w.GetName(), err)
}
var template corev1.PodTemplate
err = json.Unmarshal(data, &template)
if err != nil {
return intstr.IntOrString{}, nil, fmt.Errorf("workload %v convert object to PodTemplate err %v", w.GetName(), err)
}
port := getFirstPort(template.Template.Spec.Containers)
if port == 0 {
return intstr.IntOrString{}, nil, fmt.Errorf("no port found in workload %v", w.GetName())
}
return intstr.FromInt(int(port)), template.Labels, nil
}
func getFirstPort(cs []corev1.Container) int32 {
//TODO(wonderflow): exclude some sidecars
for _, container := range cs {
for _, p := range container.Ports {
return p.ContainerPort
}
}
return 0
}
func (r *Reconciler) createSelfsignedIssuer(ctx context.Context, routeTrait *v1alpha1.Route) (string, error) {
var selfSigned = "selfsigned"
var issuer = certmanager.Issuer{
TypeMeta: metav1.TypeMeta{
APIVersion: certmanager.SchemeGroupVersion.String(),
Kind: certmanager.IssuerKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: selfSigned,
Namespace: routeTrait.Namespace,
},
}
err := r.Client.Get(ctx, client.ObjectKey{Name: selfSigned, Namespace: routeTrait.Namespace}, &issuer)
if err == nil {
return selfSigned, nil
}
issuer.Spec.SelfSigned = &certmanager.SelfSignedIssuer{}
if apierrors.IsNotFound(err) {
return selfSigned, r.Client.Create(ctx, &issuer)
}
return selfSigned, fmt.Errorf("get %s err %v", selfSigned, err)
}
func constructNginxIngress(routeTrait *standardv1alpha1.Route, issuer standardv1alpha1.TLS, service *runtimev1alpha1.TypedReference, port int32) *v1beta1.Ingress {
var annotations = make(map[string]string)
// Use nginx-ingress as implementation
annotations["kubernetes.io/ingress.class"] = "nginx"
// SSL
var issuerAnn = "cert-manager.io/issuer"
if issuer.Type == standardv1alpha1.ClusterIssuer {
issuerAnn = "cert-manager.io/cluster-issuer"
}
annotations[issuerAnn] = issuer.IssuerName
// Rewrite
if routeTrait.Spec.RewriteTarget != "" {
annotations["ingress.kubernetes.io/rewrite-target"] = routeTrait.Spec.RewriteTarget
}
// Custom headers
var headerSnippet string
for k, v := range routeTrait.Spec.CustomHeaders {
headerSnippet += fmt.Sprintf("more_set_headers \"%s: %s\";\n", k, v)
}
if headerSnippet != "" {
annotations["nginx.ingress.kubernetes.io/configuration-snippet"] = headerSnippet
}
backend := routeTrait.Spec.Backend
if backend != nil {
// Backend protocol
if backend.Protocol != "" {
annotations["nginx.ingress.kubernetes.io/backend-protocol"] = backend.Protocol
}
//Send timeout
if backend.SendTimeout != 0 {
annotations["nginx.ingress.kubernetes.io/proxy-send-timeout"] = strconv.Itoa(backend.SendTimeout)
}
//Read timeout
if backend.ReadTimeout != 0 {
annotations["nginx.ingress.kubernetes.io/proxyreadtimeout"] = strconv.Itoa(backend.ReadTimeout)
}
}
ingress := &v1beta1.Ingress{
TypeMeta: metav1.TypeMeta{
Kind: reflect.TypeOf(v1beta1.Ingress{}).Name(),
APIVersion: v1beta1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: routeTrait.Name,
Namespace: routeTrait.Namespace,
Annotations: annotations,
Labels: oamServiceLabel,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: routeTrait.GetObjectKind().GroupVersionKind().GroupVersion().String(),
Kind: routeTrait.GetObjectKind().GroupVersionKind().Kind,
UID: routeTrait.GetUID(),
Name: routeTrait.GetName(),
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
}
ingress.Spec.TLS = []v1beta1.IngressTLS{
{
Hosts: []string{routeTrait.Spec.Host},
SecretName: routeTrait.Name + "-cert",
},
}
if routeTrait.Spec.DefaultBackend != nil {
ingress.Spec.Backend = routeTrait.Spec.DefaultBackend
}
ingress.Spec.Rules = []v1beta1.IngressRule{
{
Host: routeTrait.Spec.Host,
IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Path: routeTrait.Spec.Path,
Backend: v1beta1.IngressBackend{
ServiceName: service.Name,
ServicePort: intstr.FromInt(int(port)),
},
},
},
}},
},
}
return ingress
}
// fetch the service that is associated with the workload
func (r *Reconciler) fetchService(ctx context.Context, mLog logr.Logger,
workload *unstructured.Unstructured, routeTrait *v1alpha1.Route) (*runtimev1alpha1.TypedReference, bool, int32, error) {
// Fetch the child resources list from the corresponding workload
resources, err := oamutil.FetchWorkloadChildResources(ctx, mLog, r, workload)
if err != nil {
if !apierrors.IsNotFound(err) {
mLog.Error(err, "Error while fetching the workload child resources", "workload kind", workload.GetKind(),
"workload name", workload.GetName())
}
return nil, false, 0, err
}
// find the service that has the port
for _, childRes := range resources {
if childRes.GetAPIVersion() == corev1.SchemeGroupVersion.String() && childRes.GetKind() == reflect.TypeOf(corev1.Service{}).Name() {
svc := &runtimev1alpha1.TypedReference{
APIVersion: common.ServiceAPIVersion,
Kind: common.ServiceKind,
Name: childRes.GetName(),
UID: childRes.GetUID(),
}
ports, _, _ := unstructured.NestedSlice(childRes.Object, "spec", "ports")
for _, port := range ports {
data, _ := json.Marshal(port)
var servicePort corev1.ServicePort
_ = json.Unmarshal(data, &servicePort)
if routeTrait.Spec.Backend == nil || routeTrait.Spec.Backend.Port.IntValue() == 0 || servicePort.TargetPort == routeTrait.Spec.Backend.Port {
return svc, true, servicePort.Port, nil
}
}
}
}
return nil, false, 0, nil
}
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("Route")).
WithAnnotations("controller", "route")
return ctrl.NewControllerManagedBy(mgr).
For(&standardv1alpha1.Route{}).
Complete(r)
}
// Setup adds a controller that reconciles MetricsTrait.
func Setup(mgr ctrl.Manager) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("Route"),
Scheme: mgr.GetScheme(),
}
return reconciler.SetupWithManager(mgr)
}

View File

@@ -0,0 +1,83 @@
/*
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 routes
import (
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"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"
standardv1alpha1 "github.com/oam-dev/kubevela/api/v1alpha1"
// +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,
"Controller 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())
err = standardv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})

View File

@@ -137,7 +137,7 @@ func (o *DeleteOptions) DeleteApp() (string, error) {
err := o.Client.Get(ctx, client.ObjectKey{Name: o.AppName, Namespace: o.Env.Namespace}, &appConfig)
if err != nil {
if apierrors.IsNotFound(err) {
return "", err
return "", nil
}
return "", fmt.Errorf("delete appconfig err %s", err)
}
@@ -155,7 +155,6 @@ func (o *DeleteOptions) DeleteApp() (string, error) {
if err != nil && !apierrors.IsNotFound(err) {
return "", fmt.Errorf("delete appconfig err %s", err)
}
var healthscope corev1alpha2.HealthScope
healthscope.Name = application.FormatDefaultHealthScopeName(o.AppName)
healthscope.Namespace = o.Env.Namespace

View File

@@ -9,6 +9,9 @@ import (
"os"
"path/filepath"
"github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
v1 "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -36,15 +39,40 @@ func GetEnvByName(name string) (*types.EnvMeta, error) {
//Create or update env.
//If it does not exist, create it and set to the new env.
//If it exists, update it and set to the new env.
func CreateOrUpdateEnv(ctx context.Context, c client.Client, envName string, namespace string) (string, error) {
func CreateOrUpdateEnv(ctx context.Context, c client.Client, envName string, envArgs *types.EnvMeta) (string, error) {
var message = ""
var envArgs = types.EnvMeta{
Name: envName,
Namespace: namespace,
}
// Create Namespace
if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil && !apierrors.IsAlreadyExists(err) {
return message, err
}
// Create Issuer For SSL
if envArgs.Email != "" {
issuerName := "oam-env-" + envArgs.Name
if err := c.Create(ctx, &certmanager.Issuer{
ObjectMeta: metav1.ObjectMeta{Name: issuerName, Namespace: envArgs.Namespace},
Spec: certmanager.IssuerSpec{
IssuerConfig: certmanager.IssuerConfig{
ACME: &v1alpha2.ACMEIssuer{
Email: envArgs.Email,
Server: "https://acme-v02.api.letsencrypt.org/directory",
PrivateKey: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "oam-env-" + envArgs.Name + ".key"},
},
Solvers: []v1alpha2.ACMEChallengeSolver{{
HTTP01: &v1alpha2.ACMEChallengeSolverHTTP01{
Ingress: &v1alpha2.ACMEChallengeSolverHTTP01Ingress{Class: GetStringPointer("nginx")},
},
}},
},
},
},
}); err != nil && !apierrors.IsAlreadyExists(err) {
return message, err
}
envArgs.Issuer = issuerName
}
data, err := json.Marshal(envArgs)
if err != nil {
return message, err
@@ -67,42 +95,22 @@ func CreateOrUpdateEnv(ctx context.Context, c client.Client, envName string, nam
if err = ioutil.WriteFile(curEnvPath, []byte(envName), 0644); err != nil {
return message, err
}
message = fmt.Sprintf("Create env succeed, current env is " + envName + " namespace is " + envArgs.Namespace + ", use --namespace=<namespace> to specify namespace with env init")
message = fmt.Sprintf("ENV %s CREATED, Namespace: %s, Email: %s.", envName, envArgs.Namespace, envArgs.Email)
return message, nil
}
//Create env. If env already exists, return error
func CreateEnv(ctx context.Context, c client.Client, envName string, namespace string) (string, error) {
var message = ""
var envArgs = types.EnvMeta{
Name: envName,
Namespace: namespace,
}
data, err := json.Marshal(envArgs)
if err != nil {
return message, err
}
_, err = GetEnvByName(envName)
func GetStringPointer(v string) *string {
return &v
}
// CreateEnv will only create. If env already exists, return error
func CreateEnv(ctx context.Context, c client.Client, envName string, envArgs *types.EnvMeta) (string, error) {
_, err := GetEnvByName(envName)
if err == nil {
message = fmt.Sprintf("Env %s already exist", envName)
message := fmt.Sprintf("Env %s already exist", envName)
return message, errors.New(message)
}
if err := c.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: envArgs.Namespace}}); err != nil && !apierrors.IsAlreadyExists(err) {
return message, err
}
envdir, err := system.GetEnvDir()
if err != nil {
return message, err
}
subEnvDir := filepath.Join(envdir, envName)
if _, err := system.CreateIfNotExist(subEnvDir); err != nil {
return "", nil
}
if err = ioutil.WriteFile(filepath.Join(subEnvDir, system.EnvConfigName), data, 0644); err != nil {
return message, err
}
message = "Create env succeed"
return message, err
return CreateOrUpdateEnv(ctx, c, envName, envArgs)
}
//Update Env, if env does not exist, return error

View File

@@ -148,12 +148,47 @@ func SimplifyCapabilityStruct(capabilityList []types.Capability) []apis.TraitMet
return traitList
}
func ValidateAndMutateForCore(traitType, workloadName string, flags *pflag.FlagSet, env *types.EnvMeta) error {
switch traitType {
case "route":
domain, _ := flags.GetString("domain")
if domain == "" {
if env.Domain == "" {
return fmt.Errorf("--domain is required if not set in env")
}
if strings.HasPrefix(env.Domain, "https://") {
env.Domain = strings.TrimPrefix(env.Domain, "https://")
}
if strings.HasPrefix(env.Domain, "http://") {
env.Domain = strings.TrimPrefix(env.Domain, "http://")
}
if err := flags.Set("domain", workloadName+"."+env.Domain); err != nil {
return fmt.Errorf("set flag for vela-core trait('route') err %v, please make sure your template is right", err)
}
}
issuer, _ := flags.GetString("issuer")
if issuer == "" {
if env.Issuer == "" {
return fmt.Errorf("--issuer is required, you can also set email in env and let it generate automatically")
}
if err := flags.Set("issuer", env.Issuer); err != nil {
return fmt.Errorf("set flag for vela-core trait('route') err %v, please make sure your template is right", err)
}
}
}
return nil
}
//AddOrUpdateTrait attach trait to workload
func AddOrUpdateTrait(envName string, appName string, workloadName string, flagSet *pflag.FlagSet, template types.Capability) (*application.Application, error) {
func AddOrUpdateTrait(env *types.EnvMeta, appName string, workloadName string, flagSet *pflag.FlagSet, template types.Capability) (*application.Application, error) {
err := ValidateAndMutateForCore(template.Name, workloadName, flagSet, env)
if err != nil {
return nil, err
}
if appName == "" {
appName = workloadName
}
app, err := application.Load(envName, appName)
app, err := application.Load(env.Name, appName)
if err != nil {
return app, err
}
@@ -181,7 +216,7 @@ func AddOrUpdateTrait(envName string, appName string, workloadName string, flagS
if err = app.SetTrait(workloadName, traitAlias, traitData); err != nil {
return app, err
}
return app, app.Save(envName)
return app, app.Save(env.Name)
}
func AttachTrait(c *gin.Context, body apis.TraitBody) (string, error) {
@@ -204,12 +239,13 @@ func AttachTrait(c *gin.Context, body apis.TraitBody) (string, error) {
if err != nil {
return "", err
}
appObj, err = AddOrUpdateTrait(body.EnvName, body.AppGroup, body.WorkloadName, fs, template)
// Run step
env, err := GetEnvByName(body.EnvName)
if err != nil {
return "", err
}
// Run step
env, err := GetEnvByName(body.EnvName)
appObj, err = AddOrUpdateTrait(env, body.AppGroup, body.WorkloadName, fs, template)
if err != nil {
return "", err
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"github.com/oam-dev/kubevela/api/types"
@@ -100,12 +101,28 @@ func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Capab
if err != nil {
return types.Capability{}, err
}
if tmp.CueTemplate == "" {
return types.Capability{}, errors.New("template not exist in definition")
var cueTemplate string
if tmp.CueTemplateURI != "" {
res, err := http.Get(tmp.CueTemplateURI)
if err != nil {
return types.Capability{}, err
}
defer res.Body.Close()
b, err := ioutil.ReadAll(res.Body)
if err != nil {
return types.Capability{}, err
}
cueTemplate = string(b)
} else {
if tmp.CueTemplate == "" {
return types.Capability{}, errors.New("template not exist in definition")
}
cueTemplate = tmp.CueTemplate
}
_, _ = system.CreateIfNotExist(syncDir)
filePath := filepath.Join(syncDir, name+".cue")
err = ioutil.WriteFile(filePath, []byte(tmp.CueTemplate), 0644)
err = ioutil.WriteFile(filePath, []byte(cueTemplate), 0644)
if err != nil {
return types.Capability{}, err
}

View File

@@ -64,6 +64,37 @@ var _ = Describe("DefinitionFiles", func() {
},
},
}
websvc := types.Capability{
Name: "webservice",
Type: types.TypeWorkload,
CueTemplateURI: "https://raw.githubusercontent.com/oam-dev/kubevela/master/vela-templates/web-service.cue",
Parameters: []types.Parameter{
{
Name: "name",
Required: true,
Default: "",
Type: cue.StringKind,
},
{
Name: "image",
Type: cue.StringKind,
Default: "",
Short: "i",
Required: true,
Usage: "specify app image",
},
{
Name: "port",
Type: cue.IntKind,
Short: "p",
Default: int64(6379),
Usage: "specify port for container",
},
},
CrdName: "webservice.testapps",
}
req, _ := labels.NewRequirement("usecase", selection.Equals, []string{"forplugintest"})
selector := labels.NewSelector().Add(*req)
@@ -89,7 +120,7 @@ var _ = Describe("DefinitionFiles", func() {
workloadDefs[i].CueTemplate = ""
workloadDefs[i].DefinitionPath = ""
}
Expect(workloadDefs).Should(Equal([]types.Capability{deployment}))
Expect(workloadDefs).Should(Equal([]types.Capability{deployment, websvc}))
})
It("getall", func() {
alldef, err := GetCapabilitiesFromCluster(context.Background(), DefinitionNamespace, k8sClient, definitionDir, selector)
@@ -99,6 +130,6 @@ var _ = Describe("DefinitionFiles", func() {
alldef[i].CueTemplate = ""
alldef[i].DefinitionPath = ""
}
Expect(alldef).Should(Equal([]types.Capability{deployment, route}))
Expect(alldef).Should(Equal([]types.Capability{deployment, websvc, route}))
})
})

View File

@@ -37,7 +37,7 @@ var k8sClient client.Client
var testEnv *envtest.Environment
var definitionDir string
var td v1alpha2.TraitDefinition
var wd v1alpha2.WorkloadDefinition
var wd, websvcWD v1alpha2.WorkloadDefinition
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
@@ -138,6 +138,14 @@ var _ = BeforeSuite(func(done Done) {
logf.Log.Info("Creating workload definition", "data", wd)
Expect(k8sClient.Create(ctx, &wd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
websvcWorkloadData, err := ioutil.ReadFile("testdata/websvcWorkloadDef.yaml")
Expect(err).Should(BeNil())
Expect(yaml.Unmarshal(websvcWorkloadData, &websvcWD)).Should(BeNil())
websvcWD.Namespace = DefinitionNamespace
logf.Log.Info("Creating workload definition whose CUE template from remote", "data", &websvcWD)
Expect(k8sClient.Create(ctx, &websvcWD)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
close(done)
}, 60)

View File

@@ -0,0 +1,11 @@
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: webservice.testapps
labels:
usecase: forplugintest
spec:
definitionRef:
name: webservice.testapps
extension:
templateURI: "https://raw.githubusercontent.com/oam-dev/kubevela/master/vela-templates/web-service.cue"

View File

@@ -10,6 +10,8 @@ import (
type Environment struct {
EnvName string `json:"envName" binding:"required,min=1,max=32"`
Namespace string `json:"namespace" binding:"required,min=1,max=32"`
Email string `json:"email"`
Domain string `json:"domain"`
Current string `json:"current,omitempty"`
}

View File

@@ -2,6 +2,7 @@ package handler
import (
"github.com/gin-gonic/gin"
"github.com/oam-dev/kubevela/api/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -23,9 +24,16 @@ func CreateEnv(c *gin.Context) {
if namespace == "" {
namespace = "default"
}
ctx := util.GetContext(c)
kubeClient := c.MustGet("KubeClient")
message, err := oam.CreateEnv(ctx, kubeClient.(client.Client), name, namespace)
message, err := oam.CreateEnv(ctx, kubeClient.(client.Client), name, &types.EnvMeta{
Name: name,
Current: environment.Current,
Namespace: namespace,
Email: environment.Email,
Domain: environment.Domain,
})
util.AssembleResponse(c, message, err)
}

20
vela-templates/route.cue Normal file
View File

@@ -0,0 +1,20 @@
#Template: {
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Route"
spec: {
host: route.domain
path: route.path
tls: {
issuerName: route.issuer
}
backend: {
port: route.port
}
}
}
route: {
domain: *"" | string
path: *"" | string
port: *443 | int
issuer: *"" | string
}

View File

@@ -1,21 +1,23 @@
#Template: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ContainerizedWorkload"
apiVersion: "standard.oam.dev/v1alpha1"
kind: "Containerized"
metadata:
name: webservice.name
spec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
replicas: 1
podSpec: {
containers: [{
image: webservice.image
name: webservice.name
ports: [{
containerPort: webservice.port
protocol: "TCP"
name: "default"
}]
}]
}]
}
}
}
webservice: {
name: string
// +usage=specify app image