Compare commits

..

16 Commits

Author SHA1 Message Date
Zheng Xi Zhou
3aa4412a0f Fix: remove config image registry (#3572)
Temporarily removed image registry config

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
2022-04-01 01:07:43 +08:00
wyike
ef4b9816e1 fix bug (#3569)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix bugs

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix

fix
2022-04-01 01:04:29 +08:00
Tianxin Dong
1c5aab1852 Fix: fix dex config field (#3568)
Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-04-01 00:51:17 +08:00
Zheng Xi Zhou
966dbc1c74 Feat: add config management apis (#3562)
* Feat: add config management apis

Added some APIs for config management

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix check-diff

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix ci issue

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix config sync

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix static check

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* fix sync

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>

* Fix: sync config bug

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-04-01 00:15:03 +08:00
qiaozp
4eafb46c87 Chore: bump test k8s to 1.20 (#3567)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-31 22:12:55 +08:00
Tianxin Dong
a97a4d0ed7 Feat: add update dex config in apiserver (#3548)
* Feat: add update dex config in apiserver

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* use get connectors to get dex connectors

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* lint the code

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: seperate dex config from a component to an application

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: use dex config from secret

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix not found

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix restart dex

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix system info

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix restart

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
2022-03-31 18:42:18 +08:00
Jianbo Sun
77c02f9eec Chore: add video records in readme for chinese community call (#3565)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-31 18:05:22 +08:00
Jianbo Sun
3157efd421 Chore: refine chart readme and notes (#3563)
Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
2022-03-31 17:33:06 +08:00
wyike
8ff93b33e2 Feat: add helm repo list endpoint (#3564)
* add helm repo list

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* fix commit

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* fix comments

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

* build swagger

Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>
2022-03-31 17:18:54 +08:00
qiaozp
c6b9abe4c4 Chore: bump k8s version contraint (#3560)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-31 16:42:14 +08:00
Somefive
150ef6e99e Fix: livediff minor bug (#3558)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-31 13:13:55 +08:00
wyike
0ada407fbe optimize the ux of addon (#3557)
Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com>

fix

fix
2022-03-31 10:24:20 +08:00
Somefive
c4af1ba643 Fix: topology use original resource namespace when not specify (#3554)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
2022-03-30 19:52:10 +08:00
qiaozp
de84421487 Feat: add name param in vela logs (#3556)
Signed-off-by: qiaozp <chivalry.pp@gmail.com>
2022-03-30 17:56:59 +08:00
yangs
38a8a7f88a Fix: fix the componentDefinition webservice parameter error (#3553)
Signed-off-by: yangsoon <songyang.song@alibaba-inc.com>

Co-authored-by: yangsoon <songyang.song@alibaba-inc.com>
2022-03-30 15:55:49 +08:00
Xiangbo Ma
b4ddf0e4c3 Feat: a new ComponentDefinition cron-task Signed-off-by: Xiangbo Ma <maxiangboo@cmbchina.com> (#3541)
Signed-off-by: fourierr <maxiangboo@qq.com>
2022-03-30 13:27:21 +08:00
56 changed files with 3089 additions and 614 deletions

View File

@@ -65,7 +65,7 @@ jobs:
- name: Setup Kind Cluster (Worker)
run: |
kind delete cluster --name worker
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kubectl version
kubectl cluster-info
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
@@ -74,7 +74,7 @@ jobs:
- name: Setup Kind Cluster (Hub)
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster (Worker)
run: |
kind delete cluster --name worker
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kubectl version
kubectl cluster-info
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
@@ -69,7 +69,7 @@ jobs:
- name: Setup Kind Cluster (Hub)
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -60,7 +60,7 @@ jobs:
- name: Setup Kind Cluster
run: |
kind delete cluster
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca
kubectl version
kubectl cluster-info

View File

@@ -51,7 +51,8 @@ Full documentation is available on the [KubeVela website](https://kubevela.io/).
- Wechat Group (*Chinese*): Broker wechat to add you into the user group.
<img src="https://static.kubevela.net/images/barnett-wechat.jpg" width="200" />
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs)
- Bi-weekly Community Call: [Meeting Notes](https://docs.google.com/document/d/1nqdFEyULekyksFHtFvgvFAYE-0AMHKoS3RMnaKsarjs).
- Bi-weekly Chinese Community Call: [Video Records](https://space.bilibili.com/180074935/channel/seriesdetail?sid=1842207).
## Talks and Conferences

View File

@@ -47,7 +47,7 @@ var (
// Workflow meta
var (
WorkflowKind = "Workflow"
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(PolicyKind)
WorkflowGroupVersionKind = SchemeGroupVersion.WithKind(WorkflowKind)
)
func init() {

View File

@@ -61,6 +61,18 @@ const (
AnnoIngressControllerHTTPSPort = "ingress.controller/https-port"
// AnnoIngressControllerHTTPPort define ingress controller listen port for http
AnnoIngressControllerHTTPPort = "ingress.controller/http-port"
// LabelConfigType is the label for config type
LabelConfigType = "config.oam.dev/type"
// LabelConfigCatalog is the label for config catalog
LabelConfigCatalog = "config.oam.dev/catalog"
// LabelConfigSubType is the sub-type for a config type
LabelConfigSubType = "config.oam.dev/sub-type"
// LabelConfigProject is the label for config project
LabelConfigProject = "config.oam.dev/project"
// LabelConfigSyncToMultiCluster is the label to decide whether a config will be synchronized to multi-cluster
LabelConfigSyncToMultiCluster = "config.oam.dev/multi-cluster"
// AnnotationConfigAlias is the annotation for config alias
AnnotationConfigAlias = "config.oam.dev/alias"
)
const (
@@ -118,3 +130,13 @@ var DefaultFilterAnnots = []string{
oam.AnnotationFilterAnnotationKeys,
oam.AnnotationLastAppliedConfiguration,
}
// ConfigType is the type of config
type ConfigType string
const (
// TerraformProvider is the config type for terraform provider
TerraformProvider = "terraform-provider"
// DexConnector is the config type for dex connector
DexConnector = "config-dex-connector"
)

View File

@@ -125,18 +125,20 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
## Uninstalling the Chart
## Uninstallation
To uninstall/delete the KubeVela helm release
### Vela CLI
To uninstall KubeVela, you can just run the following command by vela CLI:
```shell
$ helm uninstall -n vela-system kubevela
vela uninstall --force
```
The command removes all the Kubernetes components associated with kubevela and deletes the release.
### Helm CLI
**Notice**: You must disable all the addons before uninstallation, this is a script for convenience.
**Notice**: If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.Please guarantee there is no application in cluster use this addon and disable it firstly before uninstall the helm chart.
You can use this script to disable all addons.
```shell
#! /bin/sh
addon=$(vela addon list|grep enabled|awk {'print $1'})
@@ -156,6 +158,15 @@ if [ $fluxcd ]; then
fi
```
To uninstall the KubeVela helm release:
```shell
$ helm uninstall -n vela-system kubevela
```
Finally, this command will remove all the Kubernetes resources associated with KubeVela and remove this chart release.

View File

@@ -27,9 +27,5 @@ Welcome to use the KubeVela! Enjoy your shipping application journey!
| . \| |_| || |_) || __/ \ V /| __/| || (_| |
|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|
** Please note before uninstalling **
If you enable fluxcd addon when install the chart by set `enableFluxcdAddon=true` .
Uninstall wouldn't disable the fluxcd addon ,and it will be kept in the cluster.
Please guarantee there is no application in cluster using this addon and disable it firstly before uninstall the helm chart.
And you can find the script of one-short disable all addons from the uninstalling section of https://github.com/oam-dev/kubevela/blob/master/charts/vela-core/README.md.
You can refer to https://kubevela.io for more details.

View File

@@ -1,70 +0,0 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-dex-connector.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Dex Connector
definition.oam.dev/description: Config information to authenticate Dex connectors
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "false"
custom.definition.oam.dev/type.config.oam.dev: dex-connector
name: config-dex-connector
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "dex-connector"
"config.oam.dev/multi-cluster": "false"
"config.oam.dev/identifier": parameter.name
"config.oam.dev/sub-type": parameter.type
}
}
type: "Opaque"
if parameter.type == "github" {
stringData: parameter.github
}
if parameter.type == "ldap" {
stringData: parameter.ldap
}
}
parameter: {
// +usage=Config type
type: "github" | "ldap"
github?: {
// +usage=GitHub client ID
clientID: string
// +usage=GitHub client secret
clientSecret: string
// +usage=GitHub call back URL
callbackURL: string
}
ldap?: {
host: string
insecureNoSSL: *true | bool
insecureSkipVerify: bool
startTLS: bool
usernamePrompt: string
userSearch: {
baseDN: string
username: string
idAttr: string
emailAttr: string
nameAttr: string
}
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -1,69 +0,0 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-image-registry.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"encoding/base64"
"encoding/json"
)
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: context.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "image-registry"
"config.oam.dev/multi-cluster": "true"
"config.oam.dev/identifier": parameter.registry
"config.oam.dev/sub-type": "auth"
}
}
type: "kubernetes.io/dockerconfigjson"
stringData: {
if parameter.auth != _|_ {
".dockerconfigjson": json.Marshal({
auths: "\(parameter.registry)": {
username: parameter.auth.username
password: parameter.auth.password
if parameter.auth.email != _|_ {
email: parameter.auth.email
}
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
}
})
}
}
}
parameter: {
// +usage=Image registry FQDN
registry: string
// +usage=Authenticate the image registry
auth?: {
// +usage=Private Image registry username
username: string
// +usage=Private Image registry password
password: string
// +usage=Private Image registry email
email?: string
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -0,0 +1,308 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
name: cron-task
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
spec: {
schedule: parameter.schedule
concurrencyPolicy: parameter.concurrencyPolicy
suspend: parameter.suspend
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
if parameter.startingDeadlineSeconds != _|_ {
startingDeadlineSeconds: parameter.startingDeadlineSeconds
}
jobTemplate: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
parallelism: parameter.count
completions: parameter.count
if parameter.ttlSecondsAfterFinished != _|_ {
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
}
if parameter.activeDeadlineSeconds != _|_ {
activeDeadlineSeconds: parameter.activeDeadlineSeconds
}
backoffLimit: parameter.backoffLimit
template: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
restartPolicy: parameter.restart
containers: [{
name: context.name
image: parameter.image
if parameter["imagePullPolicy"] != _|_ {
imagePullPolicy: parameter.imagePullPolicy
}
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if parameter["cpu"] != _|_ {
resources: {
limits: cpu: parameter.cpu
requests: cpu: parameter.cpu
}
}
if parameter["memory"] != _|_ {
resources: {
limits: memory: parameter.memory
requests: memory: parameter.memory
}
}
if parameter["volumes"] != _|_ {
volumeMounts: [ for v in parameter.volumes {
{
mountPath: v.mountPath
name: v.name
}}]
}
}]
if parameter["volumes"] != _|_ {
volumes: [ for v in parameter.volumes {
{
name: v.name
if v.type == "pvc" {
persistentVolumeClaim: claimName: v.claimName
}
if v.type == "configMap" {
configMap: {
defaultMode: v.defaultMode
name: v.cmName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "secret" {
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "emptyDir" {
emptyDir: medium: v.medium
}
}}]
}
if parameter["imagePullSecrets"] != _|_ {
imagePullSecrets: [ for v in parameter.imagePullSecrets {
name: v
},
]
}
if parameter.hostAliases != _|_ {
hostAliases: [ for v in parameter.hostAliases {
ip: v.ip
hostnames: v.hostnames
},
]
}
}
}
}
}
}
}
parameter: {
// +usage=Specify the labels in the workload
labels?: [string]: string
// +usage=Specify the annotations in the workload
annotations?: [string]: string
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
schedule: string
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
startingDeadlineSeconds?: int
// +usage=suspend subsequent executions
suspend: *false | bool
// +usage=Specifies how to treat concurrent executions of a Job
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
// +usage=The number of successful finished jobs to retain
successfulJobsHistoryLimit: *3 | int
// +usage=The number of failed finished jobs to retain
failedJobsHistoryLimit: *1 | int
// +usage=Specify number of tasks to run in parallel
// +short=c
count: *1 | int
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Specify image pull policy for your service
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// +usage=Specify image pull secrets for your service
imagePullSecrets?: [...string]
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
restart: *"Never" | string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
// +usage=Selects a key of a config map in the pod's namespace
configMapKeyRef: {
// +usage=The name of the config map in the pod's namespace to select from
name: string
// +usage=The key of the config map to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
// +usage=Specifies the attributes of the memory resource required for the container.
memory?: string
// +usage=Declare volumes and volumeMounts
volumes?: [...{
name: string
mountPath: string
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
type: "pvc" | "configMap" | "secret" | "emptyDir"
if type == "pvc" {
claimName: string
}
if type == "configMap" {
defaultMode: *420 | int
cmName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "secret" {
defaultMode: *420 | int
secretName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "emptyDir" {
medium: *"" | "Memory"
}
}]
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
hostAliases?: [...{
ip: string
hostnames: [...string]
}]
// +usage=Limits the lifetime of a Job that has finished
ttlSecondsAfterFinished?: int
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
activeDeadlineSeconds?: int
// +usage=The number of retries before marking this job failed
backoffLimit: *6 | int
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
workload:
definition:
apiVersion: batch/v1beta1
kind: CronJob
type: cronjobs.batch

View File

@@ -453,6 +453,12 @@ spec:
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
#HealthProbe: {
@@ -494,12 +500,6 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
status:
customStatus: |-

View File

@@ -1,70 +0,0 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-dex-connector.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Dex Connector
definition.oam.dev/description: Config information to authenticate Dex connectors
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "false"
custom.definition.oam.dev/type.config.oam.dev: dex-connector
name: config-dex-connector
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "dex-connector"
"config.oam.dev/multi-cluster": "false"
"config.oam.dev/identifier": parameter.name
"config.oam.dev/sub-type": parameter.type
}
}
type: "Opaque"
if parameter.type == "github" {
stringData: parameter.github
}
if parameter.type == "ldap" {
stringData: parameter.ldap
}
}
parameter: {
// +usage=Config type
type: "github" | "ldap"
github?: {
// +usage=GitHub client ID
clientID: string
// +usage=GitHub client secret
clientSecret: string
// +usage=GitHub call back URL
callbackURL: string
}
ldap?: {
host: string
insecureNoSSL: *true | bool
insecureSkipVerify: bool
startTLS: bool
usernamePrompt: string
userSearch: {
baseDN: string
username: string
idAttr: string
emailAttr: string
nameAttr: string
}
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -1,69 +0,0 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/config-image-registry.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
import (
"encoding/base64"
"encoding/json"
)
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: context.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "image-registry"
"config.oam.dev/multi-cluster": "true"
"config.oam.dev/identifier": parameter.registry
"config.oam.dev/sub-type": "auth"
}
}
type: "kubernetes.io/dockerconfigjson"
stringData: {
if parameter.auth != _|_ {
".dockerconfigjson": json.Marshal({
auths: "\(parameter.registry)": {
username: parameter.auth.username
password: parameter.auth.password
if parameter.auth.email != _|_ {
email: parameter.auth.email
}
auth: base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
}
})
}
}
}
parameter: {
// +usage=Image registry FQDN
registry: string
// +usage=Authenticate the image registry
auth?: {
// +usage=Private Image registry username
username: string
// +usage=Private Image registry password
password: string
// +usage=Private Image registry email
email?: string
}
}
workload:
type: autodetects.core.oam.dev

View File

@@ -0,0 +1,308 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/cron-task.cue
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Describes cron jobs that run code or a script to completion.
name: cron-task
namespace: {{ include "systemDefinitionNamespace" . }}
spec:
schematic:
cue:
template: |
output: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
spec: {
schedule: parameter.schedule
concurrencyPolicy: parameter.concurrencyPolicy
suspend: parameter.suspend
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
if parameter.startingDeadlineSeconds != _|_ {
startingDeadlineSeconds: parameter.startingDeadlineSeconds
}
jobTemplate: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
parallelism: parameter.count
completions: parameter.count
if parameter.ttlSecondsAfterFinished != _|_ {
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
}
if parameter.activeDeadlineSeconds != _|_ {
activeDeadlineSeconds: parameter.activeDeadlineSeconds
}
backoffLimit: parameter.backoffLimit
template: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
restartPolicy: parameter.restart
containers: [{
name: context.name
image: parameter.image
if parameter["imagePullPolicy"] != _|_ {
imagePullPolicy: parameter.imagePullPolicy
}
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if parameter["cpu"] != _|_ {
resources: {
limits: cpu: parameter.cpu
requests: cpu: parameter.cpu
}
}
if parameter["memory"] != _|_ {
resources: {
limits: memory: parameter.memory
requests: memory: parameter.memory
}
}
if parameter["volumes"] != _|_ {
volumeMounts: [ for v in parameter.volumes {
{
mountPath: v.mountPath
name: v.name
}}]
}
}]
if parameter["volumes"] != _|_ {
volumes: [ for v in parameter.volumes {
{
name: v.name
if v.type == "pvc" {
persistentVolumeClaim: claimName: v.claimName
}
if v.type == "configMap" {
configMap: {
defaultMode: v.defaultMode
name: v.cmName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "secret" {
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "emptyDir" {
emptyDir: medium: v.medium
}
}}]
}
if parameter["imagePullSecrets"] != _|_ {
imagePullSecrets: [ for v in parameter.imagePullSecrets {
name: v
},
]
}
if parameter.hostAliases != _|_ {
hostAliases: [ for v in parameter.hostAliases {
ip: v.ip
hostnames: v.hostnames
},
]
}
}
}
}
}
}
}
parameter: {
// +usage=Specify the labels in the workload
labels?: [string]: string
// +usage=Specify the annotations in the workload
annotations?: [string]: string
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
schedule: string
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
startingDeadlineSeconds?: int
// +usage=suspend subsequent executions
suspend: *false | bool
// +usage=Specifies how to treat concurrent executions of a Job
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
// +usage=The number of successful finished jobs to retain
successfulJobsHistoryLimit: *3 | int
// +usage=The number of failed finished jobs to retain
failedJobsHistoryLimit: *1 | int
// +usage=Specify number of tasks to run in parallel
// +short=c
count: *1 | int
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Specify image pull policy for your service
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// +usage=Specify image pull secrets for your service
imagePullSecrets?: [...string]
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
restart: *"Never" | string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
// +usage=Selects a key of a config map in the pod's namespace
configMapKeyRef: {
// +usage=The name of the config map in the pod's namespace to select from
name: string
// +usage=The key of the config map to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
// +usage=Specifies the attributes of the memory resource required for the container.
memory?: string
// +usage=Declare volumes and volumeMounts
volumes?: [...{
name: string
mountPath: string
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
type: "pvc" | "configMap" | "secret" | "emptyDir"
if type == "pvc" {
claimName: string
}
if type == "configMap" {
defaultMode: *420 | int
cmName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "secret" {
defaultMode: *420 | int
secretName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "emptyDir" {
medium: *"" | "Memory"
}
}]
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
hostAliases?: [...{
ip: string
hostnames: [...string]
}]
// +usage=Limits the lifetime of a Job that has finished
ttlSecondsAfterFinished?: int
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
activeDeadlineSeconds?: int
// +usage=The number of retries before marking this job failed
backoffLimit: *6 | int
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
workload:
definition:
apiVersion: batch/v1beta1
kind: CronJob
type: cronjobs.batch

View File

@@ -453,6 +453,12 @@ spec:
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
#HealthProbe: {
@@ -494,12 +500,6 @@ spec:
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
status:
customStatus: |-

View File

@@ -5,7 +5,7 @@ This guide helps you get started developing KubeVela.
## Prerequisites
1. Golang version 1.17+
2. Kubernetes version v1.18+ with `~/.kube/config` configured.
2. Kubernetes version v1.20+ with `~/.kube/config` configured.
3. ginkgo 1.14.0+ (just for [E2E test](./developer-guide.md#e2e-test))
4. golangci-lint 1.38.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.
5. kubebuilder v3.1.0+ and you need to manually install the dependency tools for unit test.
@@ -177,7 +177,7 @@ To execute the e2e test of the API module, the mongodb service needs to exist lo
# save your config
mv ~/.kube/config ~/.kube/config.save
kind create cluster --image kindest/node:v1.18.15@sha256:5c1b980c4d0e0e8e7eb9f36f7df525d079a96169c8a8f20d8bd108c0d0889cc4 --name worker
kind create cluster --image kindest/node:v1.20.7@sha256:688fba5ce6b825be62a7c7fe1415b35da2bdfbb5a69227c499ea4cc0008661ca --name worker
kind get kubeconfig --name worker --internal > /tmp/worker.kubeconfig
kind get kubeconfig --name worker > /tmp/worker.client.kubeconfig

View File

@@ -4304,6 +4304,49 @@
}
}
},
"/api/v1/repository/chart_repos": {
"get": {
"consumes": [
"application/xml",
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"tags": [
"repository",
"helm"
],
"summary": "list chart repo",
"operationId": "listRepo",
"parameters": [
{
"type": "string",
"description": "the config project",
"name": "project",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/bcode.Bcode"
}
}
}
}
},
"/api/v1/repository/charts": {
"get": {
"consumes": [
@@ -4326,6 +4369,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -4369,6 +4418,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -4409,6 +4464,12 @@
"description": "helm repository url",
"name": "repoUrl",
"in": "query"
},
{
"type": "string",
"description": "secret of the repo",
"name": "secretName",
"in": "query"
}
],
"responses": {
@@ -7148,10 +7209,10 @@
},
"v1.ApplicationDeployResponse": {
"required": [
"createTime",
"version",
"note",
"status",
"note",
"createTime",
"envName",
"triggerType"
],
@@ -8244,10 +8305,10 @@
"v1.DetailAddonResponse": {
"required": [
"name",
"icon",
"invisible",
"version",
"description",
"icon",
"version",
"invisible",
"schema",
"uiSchema",
"definitions",
@@ -8328,12 +8389,12 @@
"v1.DetailApplicationResponse": {
"required": [
"alias",
"description",
"createTime",
"name",
"project",
"updateTime",
"description",
"icon",
"name",
"createTime",
"updateTime",
"policies",
"envBindings",
"applicationType",
@@ -8394,20 +8455,20 @@
},
"v1.DetailClusterResponse": {
"required": [
"updateTime",
"kubeConfig",
"name",
"icon",
"reason",
"provider",
"status",
"apiServerURL",
"dashboardURL",
"createTime",
"kubeConfigSecret",
"icon",
"labels",
"kubeConfig",
"updateTime",
"name",
"alias",
"description",
"labels",
"status",
"kubeConfigSecret",
"reason",
"createTime",
"provider",
"resourceInfo"
],
"properties": {
@@ -8465,14 +8526,14 @@
},
"v1.DetailComponentResponse": {
"required": [
"createTime",
"updateTime",
"appPrimaryKey",
"type",
"main",
"name",
"creator",
"alias",
"name",
"main",
"createTime",
"type",
"definition"
],
"properties": {
@@ -8574,13 +8635,13 @@
},
"v1.DetailPolicyResponse": {
"required": [
"type",
"description",
"creator",
"properties",
"createTime",
"updateTime",
"name",
"type"
"name"
],
"properties": {
"createTime": {
@@ -8610,17 +8671,17 @@
},
"v1.DetailRevisionResponse": {
"required": [
"envName",
"reason",
"deployUser",
"updateTime",
"status",
"note",
"triggerType",
"workflowName",
"version",
"deployUser",
"createTime",
"status",
"workflowName",
"envName",
"appPrimaryKey",
"version"
"reason",
"note"
],
"properties": {
"appPrimaryKey": {
@@ -8674,9 +8735,9 @@
},
"v1.DetailTargetResponse": {
"required": [
"name",
"createTime",
"project",
"createTime",
"name",
"updateTime"
],
"properties": {
@@ -8717,11 +8778,11 @@
},
"v1.DetailUserResponse": {
"required": [
"name",
"email",
"disabled",
"createTime",
"lastLoginTime",
"name",
"email",
"projects",
"roles"
],
@@ -8762,12 +8823,12 @@
},
"v1.DetailWorkflowRecordResponse": {
"required": [
"workflowName",
"workflowAlias",
"applicationRevision",
"status",
"name",
"namespace",
"workflowName",
"workflowAlias",
"applicationRevision",
"deployTime",
"deployUser",
"note",
@@ -8819,14 +8880,14 @@
},
"v1.DetailWorkflowResponse": {
"required": [
"description",
"enable",
"envName",
"createTime",
"updateTime",
"name",
"alias",
"enable",
"default",
"envName"
"description",
"name",
"alias"
],
"properties": {
"alias": {
@@ -9021,8 +9082,8 @@
},
"v1.EnvBindingTarget": {
"required": [
"name",
"alias"
"alias",
"name"
],
"properties": {
"alias": {
@@ -9405,11 +9466,11 @@
},
"v1.LoginUserInfoResponse": {
"required": [
"name",
"email",
"disabled",
"createTime",
"lastLoginTime",
"name",
"email",
"projects",
"platformPermissions",
"projectPermissions"
@@ -9733,11 +9794,11 @@
},
"v1.SystemInfoResponse": {
"required": [
"createTime",
"updateTime",
"installID",
"enableCollection",
"loginType",
"createTime",
"updateTime",
"systemVersion"
],
"properties": {

View File

@@ -3,6 +3,12 @@ kind: Application
metadata:
name: config-dex-connector-dev
namespace: vela-system
labels:
"app.oam.dev/source-of-truth": "from-inner-system"
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "config-dex-connector"
"config.oam.dev/sub-type": "github"
project: abc
spec:
components:
- name: dev
@@ -12,4 +18,4 @@ spec:
github:
clientID: "aa"
clientSecret: "bb"
callbackURL: "http://localhost:8080/callback"
redirectURI: "http://localhost:8080/callback"

View File

@@ -1,11 +1,16 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: config-image-registry-account-auth-dev
name: image-dev
namespace: vela-system
labels:
"app.oam.dev/source-of-truth": "from-inner-system"
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "config-image-registry"
project: abc
spec:
components:
- name: account-auth
- name: image-dev
type: config-image-registry
properties:
registry: "registry.cn-beijing.aliyuncs.com"

View File

@@ -12,12 +12,11 @@ spec:
type: ref-objects
properties:
objects:
- apiVersion: v1
kind: Secret
name: image-registry-dev
- name: reg-demo
resource: secret
policies:
- type: topology
name: dev
properties:
clusters: ["bj"]
# namespaces: ["ns1"]
namespace: default

1
go.mod
View File

@@ -145,6 +145,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/emicklei/proto v1.6.15 // indirect
github.com/emirpasic/gods v1.12.0 // indirect

3
go.sum
View File

@@ -420,8 +420,9 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=

View File

@@ -30,9 +30,38 @@ const (
// SystemInfo systemInfo model
type SystemInfo struct {
BaseModel
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
DexConfig DexConfig `json:"dexConfig,omitempty"`
}
// DexConfig dex config
type DexConfig struct {
Issuer string `json:"issuer"`
Web DexWeb `json:"web"`
Storage DexStorage `json:"storage"`
StaticClients []DexStaticClient `json:"staticClients"`
Connectors []interface{} `json:"connectors,omitempty"`
EnablePasswordDB bool `json:"enablePasswordDB"`
}
// DexStorage dex storage
type DexStorage struct {
Type string `json:"type"`
}
// DexWeb dex web
type DexWeb struct {
HTTP string `json:"http"`
}
// DexStaticClient dex static client
type DexStaticClient struct {
ID string `json:"id"`
Name string `json:"name"`
Secret string `json:"secret"`
RedirectURIs []string `json:"redirectURIs"`
}
// TableName return custom table name

View File

@@ -184,6 +184,25 @@ type AddonArgsResponse struct {
Args map[string]string `json:"args"`
}
// ConfigType define the format for listing configuration types
type ConfigType struct {
Definitions []string `json:"definitions"`
Alias string `json:"alias"`
Name string `json:"name"`
Description string `json:"description"`
}
// Config define the metadata of a config
type Config struct {
ConfigType string `json:"configType"`
Name string `json:"name"`
Project string `json:"project"`
Identifier string `json:"identifier"`
Description string `json:"description"`
CreatedTime *time.Time `json:"createdTime"`
UpdatedTime *time.Time `json:"updatedTime"`
}
// AccessKeyRequest request parameters to access cloud provider
type AccessKeyRequest struct {
AccessKeyID string `json:"accessKeyID"`
@@ -382,6 +401,15 @@ type CreateApplicationRequest struct {
Component *CreateComponentRequest `json:"component"`
}
// CreateConfigRequest is the request body to creates a config
type CreateConfigRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias"`
Project string `json:"project"`
ComponentType string `json:"componentType" validate:"checkname"`
Properties string `json:"properties,omitempty"`
}
// UpdateApplicationRequest update application base config
type UpdateApplicationRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
@@ -1081,14 +1109,22 @@ type DetailRevisionResponse struct {
// SystemInfoResponse get SystemInfo
type SystemInfoResponse struct {
model.SystemInfo
SystemInfo
SystemVersion SystemVersion `json:"systemVersion"`
}
// SystemInfo system info
type SystemInfo struct {
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
}
// SystemInfoRequest request by update SystemInfo
type SystemInfoRequest struct {
EnableCollection bool `json:"enableCollection"`
LoginType string `json:"loginType"`
VelaAddress string `json:"velaAddress,omitempty"`
}
// SystemVersion contains KubeVela version
@@ -1277,3 +1313,14 @@ type LoginUserInfoResponse struct {
PlatformPermissions []PermissionBase `json:"platformPermissions"`
ProjectPermissions map[string][]PermissionBase `json:"projectPermissions"`
}
// ChartRepoResponse the response body of chart repo
type ChartRepoResponse struct {
URL string `json:"url"`
SecretName string `json:"secretName"`
}
// ChartRepoResponseList the response body of list chart repo
type ChartRepoResponseList struct {
ChartRepoResponse []*ChartRepoResponse `json:"repos"`
}

View File

@@ -686,7 +686,7 @@ func (c *applicationUsecaseImpl) DetailPolicy(ctx context.Context, app *model.Ap
}, nil
}
// Deploy deploy app to cluster
// Deploy deploys app to cluster
// means to render oam application config and apply to cluster.
// An event record is generated for each deploy.
func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Application, req apisv1.ApplicationDeployRequest) (*apisv1.ApplicationDeployResponse, error) {
@@ -704,6 +704,23 @@ func (c *applicationUsecaseImpl) Deploy(ctx context.Context, app *model.Applicat
return nil, err
}
// sync configs to clusters
// TODO(zzxwill) need to check the type of the componentDefinition, if it is `Cloud`, skip the sync
targets, err := listTarget(ctx, c.ds, app.Project, nil)
if err != nil {
return nil, err
}
var clusterTargets []*model.ClusterTarget
for i, t := range targets {
if t.Cluster != nil {
clusterTargets = append(clusterTargets, targets[i].Cluster)
}
}
if err := SyncConfigs(ctx, c.kubeClient, app.Project, clusterTargets); err != nil {
return nil, fmt.Errorf("sync config failure %w", err)
}
// step2: check and create deploy event
if !req.Force {
var lastVersion = model.ApplicationRevision{

View File

@@ -18,6 +18,7 @@ package usecase
import (
"context"
"encoding/json"
"errors"
"net/http"
"time"
@@ -25,26 +26,30 @@ import (
"github.com/coreos/go-oidc"
"github.com/form3tech-oss/jwt-go"
"golang.org/x/oauth2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
const (
secretDexConfig = "dex-config"
keyDex = "dex"
dexConfigName = "dex-config"
secretDexConfigKey = "config.yaml"
dexAddonName = "addon-dex"
jwtIssuer = "vela-issuer"
signedKey = "vela-singned"
// GrantTypeAccess is the grant type for access token
GrantTypeAccess = "access"
@@ -52,12 +57,15 @@ const (
GrantTypeRefresh = "refresh"
)
var signedKey = ""
// AuthenticationUsecase is the usecase of authentication
type AuthenticationUsecase interface {
Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error)
RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error)
GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error)
GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error)
UpdateDexConfig(ctx context.Context) error
}
type authenticationUsecaseImpl struct {
@@ -148,7 +156,7 @@ func (a *authenticationUsecaseImpl) newLocalHandler(req apisv1.LoginRequest) (*l
func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.LoginRequest) (*apisv1.LoginResponse, error) {
var handler authHandler
var err error
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := a.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}
@@ -175,11 +183,11 @@ func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.L
if userBase.Disabled {
return nil, bcode.ErrUserAlreadyDisabled
}
accessToken, err := a.generateJWTToken(userBase.Name, GrantTypeAccess, time.Hour)
accessToken, err := a.generateJWTToken(ctx, userBase.Name, GrantTypeAccess, time.Hour)
if err != nil {
return nil, err
}
refreshToken, err := a.generateJWTToken(userBase.Name, GrantTypeRefresh, time.Hour*24)
refreshToken, err := a.generateJWTToken(ctx, userBase.Name, GrantTypeRefresh, time.Hour*24)
if err != nil {
return nil, err
}
@@ -190,7 +198,7 @@ func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.L
}, nil
}
func (a *authenticationUsecaseImpl) generateJWTToken(username, grantType string, expireDuration time.Duration) (string, error) {
func (a *authenticationUsecaseImpl) generateJWTToken(ctx context.Context, username, grantType string, expireDuration time.Duration) (string, error) {
expire := time.Now().Add(expireDuration)
claims := model.CustomClaims{
StandardClaims: jwt.StandardClaims{
@@ -202,8 +210,24 @@ func (a *authenticationUsecaseImpl) generateJWTToken(username, grantType string,
GrantType: grantType,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := a.getSignedKey(ctx)
if err != nil {
return "", err
}
return token.SignedString([]byte(signed))
}
return token.SignedString([]byte(signedKey))
func (a *authenticationUsecaseImpl) getSignedKey(ctx context.Context) (string, error) {
if signedKey != "" {
return signedKey, nil
}
info, err := a.sysUsecase.Get(ctx)
if err != nil {
return "", err
}
signedKey = info.InstallID
return signedKey, nil
}
func (a *authenticationUsecaseImpl) RefreshToken(ctx context.Context, refreshToken string) (*apisv1.RefreshTokenResponse, error) {
@@ -212,7 +236,7 @@ func (a *authenticationUsecaseImpl) RefreshToken(ctx context.Context, refreshTok
return nil, err
}
if claim.GrantType == GrantTypeRefresh {
accessToken, err := a.generateJWTToken(claim.Username, GrantTypeAccess, time.Hour)
accessToken, err := a.generateJWTToken(ctx, claim.Username, GrantTypeAccess, time.Hour)
if err != nil {
return nil, err
}
@@ -253,33 +277,10 @@ func ParseToken(tokenString string) (*model.CustomClaims, error) {
}
func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error) {
secret := &v1.Secret{}
if err := a.kubeClient.Get(ctx, types.NamespacedName{
Name: secretDexConfig,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
if apierrors.IsNotFound(err) {
return nil, bcode.ErrDexConfigNotFound
}
log.Logger.Errorf("failed to get dex config: %s", err.Error())
config, err := getDexConfig(ctx, a.kubeClient)
if err != nil {
return nil, err
}
var config struct {
Issuer string `json:"issuer"`
StaticClients []struct {
ID string `json:"id"`
Secret string `json:"secret"`
RedirectURIs []string `json:"redirectURIs"`
} `json:"staticClients"`
}
if err := yaml.Unmarshal(secret.Data[secretDexConfigKey], &config); err != nil {
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
return nil, bcode.ErrInvalidDexConfig
}
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
return nil, bcode.ErrInvalidDexConfig
}
return &apisv1.DexConfigResponse{
Issuer: config.Issuer,
ClientID: config.StaticClients[0].ID,
@@ -288,8 +289,121 @@ func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.D
}, nil
}
func (a *authenticationUsecaseImpl) UpdateDexConfig(ctx context.Context) error {
connectors, err := utils.GetDexConnectors(ctx, a.kubeClient)
if err != nil {
return err
}
dexConfig := &corev1.Secret{}
if err := a.kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexConfig); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
}
config := &model.JSONStruct{}
if dexConfig == nil || dexConfig.Data == nil {
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
if err := a.kubeClient.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
secretDexConfigKey: c,
},
}); err != nil {
return err
}
} else {
err = yaml.Unmarshal(dexConfig.Data[secretDexConfigKey], config)
if err != nil {
return err
}
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
dexConfig.Data[secretDexConfigKey] = c
if err := a.kubeClient.Update(ctx, dexConfig); err != nil {
return err
}
}
return restartDex(ctx, a.kubeClient)
}
func restartDex(ctx context.Context, kubeClient client.Client) error {
dexApp := &v1beta1.Application{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexAddonName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexApp); err != nil {
if kerrors.IsNotFound(err) {
return bcode.ErrDexNotFound
}
return err
}
for i, comp := range dexApp.Spec.Components {
if comp.Name == keyDex {
var v model.JSONStruct
err := json.Unmarshal(comp.Properties.Raw, &v)
if err != nil {
return err
}
// restart the dex server
if _, ok := v["values"]; ok {
v["values"].(map[string]interface{})["env"] = map[string]string{
"TIME_STAMP": time.Now().Format(time.RFC3339),
}
}
dexApp.Spec.Components[i].Properties = v.RawExtension()
if err := kubeClient.Update(ctx, dexApp); err != nil {
return err
}
break
}
}
return nil
}
func getDexConfig(ctx context.Context, kubeClient client.Client) (*model.DexConfig, error) {
dexConfigSecret := &corev1.Secret{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, dexConfigSecret); err != nil {
if kerrors.IsNotFound(err) {
return nil, bcode.ErrDexConfigNotFound
}
return nil, err
}
if dexConfigSecret.Data == nil {
return nil, bcode.ErrInvalidDexConfig
}
config := &model.DexConfig{}
if err := yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config); err != nil {
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
return nil, bcode.ErrInvalidDexConfig
}
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
return nil, bcode.ErrInvalidDexConfig
}
return config, nil
}
func (a *authenticationUsecaseImpl) GetLoginType(ctx context.Context) (*apisv1.GetLoginTypeResponse, error) {
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := a.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}

View File

@@ -19,6 +19,7 @@ package usecase
import (
"context"
"encoding/json"
"io/ioutil"
"reflect"
"strconv"
"time"
@@ -30,10 +31,16 @@ import (
"golang.org/x/oauth2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
@@ -51,7 +58,7 @@ var _ = Describe("Test authentication usecase functions", func() {
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
authUsecase = &authenticationUsecaseImpl{kubeClient: k8sClient, ds: ds}
sysUsecase = &systemInfoUsecaseImpl{ds: ds}
sysUsecase = &systemInfoUsecaseImpl{ds: ds, kubeClient: k8sClient}
userUsecase = &userUsecaseImpl{ds: ds, sysUsecase: sysUsecase}
})
It("Test Dex login", func() {
@@ -99,37 +106,85 @@ var _ = Describe("Test authentication usecase functions", func() {
Expect(resp.Name).Should(Equal("test-login"))
})
It("Test get dex config", func() {
It("Test update dex config", func() {
err := k8sClient.Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-system",
},
})
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
err = k8sClient.Create(context.Background(), &corev1.Secret{
webserver, err := ioutil.ReadFile("./testdata/dex-config-def.yaml")
Expect(err).Should(Succeed())
var cd v1beta1.ComponentDefinition
err = yaml.Unmarshal(webserver, &cd)
Expect(err).Should(Succeed())
err = k8sClient.Create(context.Background(), &cd)
Expect(err).Should(Succeed())
err = k8sClient.Create(context.Background(), &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: secretDexConfig,
Name: "addon-dex",
Namespace: "vela-system",
},
StringData: map[string]string{
secretDexConfigKey: `
issuer: https://dex.oam.dev
staticClients:
- id: client-id
secret: client-secret
redirectURIs:
- http://localhost:8080/auth/callback
`,
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "dex",
// only for test here
Type: "dex-config",
Properties: &runtime.RawExtension{Raw: []byte(`{"values":{"test":"test"}}`)},
Traits: []common.ApplicationTrait{},
Scopes: map[string]string{},
},
},
},
})
Expect(err).Should(BeNil())
config, err := authUsecase.GetDexConfig(context.Background())
err = k8sClient.Create(context.Background(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
Namespace: "vela-system",
Labels: map[string]string{
"app.oam.dev/source-of-truth": "from-inner-system",
"config.oam.dev/catalog": "velacore-config",
"config.oam.dev/type": "config-dex-connector",
"config.oam.dev/sub-type": "ldap",
"project": "abc",
},
},
StringData: map[string]string{
"ldap": `{"clientID":"clientID","clientSecret":"clientSecret"}`,
},
Type: corev1.SecretTypeOpaque,
})
Expect(err).Should(BeNil())
By("try to update dex config without config secret")
err = authUsecase.UpdateDexConfig(context.Background())
Expect(err).Should(BeNil())
dexConfigSecret := &corev1.Secret{}
err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "dex-config", Namespace: "vela-system"}, dexConfigSecret)
Expect(err).Should(BeNil())
config := &model.DexConfig{}
err = yaml.Unmarshal(dexConfigSecret.Data[secretDexConfigKey], config)
Expect(err).Should(BeNil())
Expect(len(config.Connectors)).Should(Equal(1))
By("try to update dex config with config secret")
err = authUsecase.UpdateDexConfig(context.Background())
Expect(err).Should(BeNil())
Expect(config.Issuer).Should(Equal("https://dex.oam.dev"))
Expect(config.ClientID).Should(Equal("client-id"))
Expect(config.ClientSecret).Should(Equal("client-secret"))
Expect(config.RedirectURL).Should(Equal("http://localhost:8080/auth/callback"))
})
It("Test get dex config", func() {
_, err := authUsecase.GetDexConfig(context.Background())
Expect(err).Should(Equal(bcode.ErrInvalidDexConfig))
_, err = sysUsecase.UpdateSystemInfo(context.Background(), apisv1.SystemInfoRequest{
LoginType: model.LoginTypeDex,
VelaAddress: "http://velaux.com",
})
Expect(err).Should(BeNil())
config, err := authUsecase.GetDexConfig(context.Background())
Expect(err).Should(BeNil())
Expect(config.Issuer).Should(Equal("http://velaux.com/dex"))
Expect(config.ClientID).Should(Equal("velaux"))
Expect(config.ClientSecret).Should(Equal("velaux-secret"))
Expect(config.RedirectURL).Should(Equal("http://velaux.com/callback"))
})
})

View File

@@ -0,0 +1,393 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package usecase
import (
"context"
"encoding/json"
"fmt"
"time"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/yaml"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/definition"
)
const (
definitionAlias = definition.UserPrefix + "alias.config.oam.dev"
definitionType = definition.UserPrefix + "type.config.oam.dev"
velaCoreConfig = "velacore-config"
)
// ConfigHandler handle CRUD of configs
type ConfigHandler interface {
ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error)
GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error)
CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error
GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error)
GetConfig(ctx context.Context, configType, name string) (*apis.Config, error)
DeleteConfig(ctx context.Context, configType, name string) error
}
// NewConfigUseCase returns a config use case
func NewConfigUseCase(authenticationUseCase AuthenticationUsecase) ConfigHandler {
k8sClient, err := clients.GetKubeClient()
if err != nil {
panic(err)
}
return &configUseCaseImpl{
authenticationUseCase: authenticationUseCase,
kubeClient: k8sClient,
}
}
type configUseCaseImpl struct {
kubeClient client.Client
authenticationUseCase AuthenticationUsecase
}
// ListConfigTypes returns all config types
func (u *configUseCaseImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) {
defs := &v1beta1.ComponentDefinitionList{}
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig}); err != nil {
return nil, err
}
var tfDefs []v1beta1.ComponentDefinition
var configTypes []*apis.ConfigType
for _, d := range defs.Items {
if d.Labels[definitionType] == types.TerraformProvider {
tfDefs = append(tfDefs, d)
continue
}
configTypes = append(configTypes, &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Name: d.Name,
Definitions: []string{d.Name},
Description: d.Annotations[types.AnnoDefinitionDescription],
})
}
tfType := &apis.ConfigType{
Alias: "Terraform Cloud Provider",
Name: types.TerraformProvider,
}
definitions := make([]string, len(tfDefs))
for i, tf := range tfDefs {
definitions[i] = tf.Name
}
tfType.Definitions = definitions
return append(configTypes, tfType), nil
}
// GetConfigType returns a config type
func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string) (*apis.ConfigType, error) {
d := &v1beta1.ComponentDefinition{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: configType}, d); err != nil {
return nil, errors.Wrap(err, "failed to get config type")
}
t := &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Name: configType,
Description: d.Annotations[types.AnnoDefinitionDescription],
}
return t, nil
}
func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error {
app := v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
types.AnnotationConfigAlias: req.Alias,
},
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigType: req.ComponentType,
types.LabelConfigProject: req.Project,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: req.Name,
Type: req.ComponentType,
Properties: &runtime.RawExtension{Raw: []byte(req.Properties)},
},
},
},
}
if err := u.kubeClient.Create(ctx, &app); err != nil {
return err
}
// try to check whether the underlying config secrets is successfully created
var succeeded bool
var configApp v1beta1.Application
for i := 0; i < 100; i++ {
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: req.Name}, &configApp); err == nil {
if configApp.Status.Phase == common.ApplicationRunning {
succeeded = true
break
}
}
time.Sleep(time.Second)
}
// clean up failed application
if !succeeded {
if err := u.kubeClient.Delete(ctx, &app); err != nil {
return err
}
return errors.New("failed to create config")
}
if succeeded && req.ComponentType == types.DexConnector {
return u.authenticationUseCase.UpdateDexConfig(ctx)
}
return nil
}
func (u *configUseCaseImpl) GetConfigs(ctx context.Context, configType string) ([]*apis.Config, error) {
switch configType {
case types.TerraformProvider:
defs := &v1beta1.ComponentDefinitionList{}
if err := u.kubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definition.UserPrefix + "type.config.oam.dev": types.TerraformProvider,
}); err != nil {
return nil, err
}
var configs []*apis.Config
for _, d := range defs.Items {
subConfigs, err := u.getConfigsByConfigType(ctx, d.Name)
if err != nil {
return nil, err
}
configs = append(configs, subConfigs...)
}
return configs, nil
default:
return u.getConfigsByConfigType(ctx, configType)
}
}
func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configType string) ([]*apis.Config, error) {
var apps = &v1beta1.ApplicationList{}
if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigType: configType,
}); err != nil {
return nil, err
}
configs := make([]*apis.Config, len(apps.Items))
for i, a := range apps.Items {
configs[i] = &apis.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: a.Labels[types.LabelConfigProject],
CreatedTime: &(a.CreationTimestamp.Time),
}
}
return configs, nil
}
func (u *configUseCaseImpl) GetConfig(ctx context.Context, configType, name string) (*apis.Config, error) {
var a = &v1beta1.Application{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
return nil, err
}
config := &apis.Config{
ConfigType: a.Labels[types.LabelConfigType],
Name: a.Name,
Project: a.Labels[types.LabelConfigProject],
CreatedTime: &a.CreationTimestamp.Time,
}
return config, nil
}
func (u *configUseCaseImpl) DeleteConfig(ctx context.Context, configType, name string) error {
var a = &v1beta1.Application{}
if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil {
return err
}
return u.kubeClient.Delete(ctx, a)
}
// ApplicationDeployTarget is the struct of application deploy target
type ApplicationDeployTarget struct {
Namespace string `json:"namespace"`
Clusters []string `json:"clusters"`
}
// SyncConfigs will sync configs to working clusters
func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, targets []*model.ClusterTarget) error {
name := fmt.Sprintf("config-sync-%s", project)
// get all configs which can be synced to working clusters in the project
var secrets v1.SecretList
if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigProject: project,
types.LabelConfigSyncToMultiCluster: "true",
}); err != nil {
return err
}
if len(secrets.Items) == 0 {
return nil
}
objects := make([]map[string]string, len(secrets.Items))
for i, s := range secrets.Items {
objects[i] = map[string]string{
"name": s.Name,
"resource": "secret",
}
}
objectsBytes, err := json.Marshal(map[string][]map[string]string{"objects": objects})
if err != nil {
return err
}
var app = &v1beta1.Application{}
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, app); err != nil {
if !kerrors.IsNotFound(err) {
return err
}
// config sync application doesn't exist, create one
scratch := &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
model.LabelSourceOfTruth: model.FromInner,
types.LabelConfigCatalog: velaCoreConfig,
types.LabelConfigProject: project,
},
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: name,
Type: "ref-objects",
Properties: &runtime.RawExtension{Raw: objectsBytes},
},
},
},
}
if err := k8sClient.Create(ctx, scratch); err != nil {
return err
}
}
// config sync application exists, update it
app.Spec.Components = []common.ApplicationComponent{
{
Name: name,
Type: "ref-objects",
Properties: &runtime.RawExtension{Raw: objectsBytes},
},
}
currentTargets := make([]ApplicationDeployTarget, len(app.Spec.Policies))
for i, p := range app.Spec.Policies {
var t ApplicationDeployTarget
if err := json.Unmarshal(p.Properties.Raw, &t); err != nil {
return err
}
currentTargets[i] = t
}
mergedTarget := mergeTargets(currentTargets, targets)
mergedPolicies := make([]v1beta1.AppPolicy, len(mergedTarget))
for i, t := range mergedTarget {
properties, err := json.Marshal(t)
if err != nil {
return err
}
mergedPolicies[i] = v1beta1.AppPolicy{
Type: "topology",
Name: t.Namespace,
Properties: &runtime.RawExtension{
Raw: properties,
},
}
}
app.Spec.Policies = mergedPolicies
out, _ := yaml.Marshal(app)
fmt.Println(string(out))
return k8sClient.Update(ctx, app)
}
func mergeTargets(currentTargets []ApplicationDeployTarget, targets []*model.ClusterTarget) []ApplicationDeployTarget {
var mergedTargets []ApplicationDeployTarget
for _, c := range currentTargets {
var hasSameNamespace bool
for _, t := range targets {
if c.Namespace == t.Namespace {
hasSameNamespace = true
clusters := append(c.Clusters, t.ClusterName)
mergedTargets = append(mergedTargets, ApplicationDeployTarget{Namespace: c.Namespace, Clusters: clusters})
}
}
if !hasSameNamespace {
mergedTargets = append(mergedTargets, c)
}
}
for _, t := range targets {
var hasSameNamespace bool
for _, c := range currentTargets {
if c.Namespace == t.Namespace {
hasSameNamespace = true
}
}
if !hasSameNamespace {
mergedTargets = append(mergedTargets, ApplicationDeployTarget{Namespace: t.Namespace, Clusters: []string{t.ClusterName}})
}
}
return mergedTargets
}

View File

@@ -0,0 +1,340 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package usecase
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
. "github.com/agiledragon/gomonkey/v2"
"gotest.tools/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/multicluster"
)
func TestListConfigTypes(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def1 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definitionType: types.TerraformProvider,
},
},
}
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
return k8sClient, nil, nil
})
defer patches.Reset()
h := NewConfigUseCase(nil)
type args struct {
h ConfigHandler
}
type want struct {
configTypes []*apis.ConfigType
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
h: h,
},
want: want{
configTypes: []*apis.ConfigType{
{
Name: "def2",
Alias: "Def2",
Definitions: []string{"def2"},
},
{
Alias: "Terraform Cloud Provider",
Name: types.TerraformProvider,
Definitions: []string{
"def1",
},
},
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.ListConfigTypes(ctx, "")
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configTypes)
})
}
}
func TestGetConfigType(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def2).Build()
patches := ApplyFunc(multicluster.GetMulticlusterKubernetesClient, func() (client.Client, *rest.Config, error) {
return k8sClient, nil, nil
})
defer patches.Reset()
h := NewConfigUseCase(nil)
type args struct {
h ConfigHandler
name string
}
type want struct {
configType *apis.ConfigType
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
h: h,
name: "def2",
},
want: want{
configType: &apis.ConfigType{
Alias: "Def2",
Name: "def2",
},
},
},
{
name: "error",
args: args{
h: h,
name: "def99",
},
want: want{
errMsg: "failed to get config type",
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.GetConfigType(ctx, tc.args.name)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configType)
})
}
}
func TestCreateConfig(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
h := &configUseCaseImpl{kubeClient: k8sClient}
type args struct {
h ConfigHandler
req apis.CreateConfigRequest
}
type want struct {
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "delete config when it's not ready",
args: args{
h: h,
req: apis.CreateConfigRequest{
Name: "a",
ComponentType: "b",
Project: "c",
},
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
err := tc.args.h.CreateConfig(ctx, tc.args.req)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
})
}
}
func TestGetConfigs(t *testing.T) {
s := runtime.NewScheme()
v1beta1.AddToScheme(s)
corev1.AddToScheme(s)
def1 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
definitionType: types.TerraformProvider,
},
},
}
def2 := &v1beta1.ComponentDefinition{
TypeMeta: metav1.TypeMeta{
Kind: "ComponentDefinition",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "def2",
Namespace: types.DefaultKubeVelaNS,
Annotations: map[string]string{
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig,
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build()
h := &configUseCaseImpl{kubeClient: k8sClient}
type args struct {
configType string
h ConfigHandler
}
type want struct {
configs []*apis.Config
errMsg string
}
ctx := context.Background()
testcases := []struct {
name string
args args
want want
}{
{
name: "success",
args: args{
configType: types.TerraformProvider,
h: h,
},
want: want{
configs: nil,
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.args.h.GetConfigs(ctx, tc.args.configType)
if tc.want.errMsg != "" || err != nil {
assert.ErrorContains(t, err, tc.want.errMsg)
}
assert.DeepEqual(t, got, tc.want.configs)
})
}
}

View File

@@ -20,32 +20,48 @@ import (
"context"
"strconv"
"helm.sh/helm/v3/pkg/repo"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/helm"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"helm.sh/helm/v3/pkg/repo"
)
// NewHelmUsecase return a helmHandler
func NewHelmUsecase() HelmHandler {
c, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kube client failure %s", err.Error())
}
return defaultHelmHandler{
helper: helm.NewHelperWithCache(),
helper: helm.NewHelperWithCache(),
k8sClient: c,
}
}
// HelmHandler responsible handle helm related interface
type HelmHandler interface {
ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error)
ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error)
GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error)
ListChartNames(ctx context.Context, url string, secretName string, skipCache bool) ([]string, error)
ListChartVersions(ctx context.Context, url string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error)
GetChartValues(ctx context.Context, url string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error)
ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error)
}
type defaultHelmHandler struct {
helper *helm.Helper
helper *helm.Helper
k8sClient client.Client
}
func (d defaultHelmHandler) ListChartNames(ctx context.Context, url string, skipCache bool) ([]string, error) {
func (d defaultHelmHandler) ListChartNames(ctx context.Context, url string, secretName string, skipCache bool) ([]string, error) {
// TODO(wangyikewxgm): support authority helm repo
charts, err := d.helper.ListChartsFromRepo(url, skipCache)
if err != nil {
log.Logger.Errorf("cannot fetch charts repo: %s, error: %s", url, err.Error())
@@ -54,7 +70,7 @@ func (d defaultHelmHandler) ListChartNames(ctx context.Context, url string, skip
return charts, nil
}
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, url string, chartName string, skipCache bool) (repo.ChartVersions, error) {
func (d defaultHelmHandler) ListChartVersions(ctx context.Context, url string, chartName string, secretName string, skipCache bool) (repo.ChartVersions, error) {
chartVersions, err := d.helper.ListVersions(url, chartName, skipCache)
if err != nil {
log.Logger.Errorf("cannot fetch chart versions repo: %s, chart: %s error: %s", url, chartName, err.Error())
@@ -67,7 +83,7 @@ func (d defaultHelmHandler) ListChartVersions(ctx context.Context, url string, c
return chartVersions, nil
}
func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, chartName string, version string, skipCache bool) (map[string]interface{}, error) {
func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, chartName string, version string, secretName string, skipCache bool) (map[string]interface{}, error) {
v, err := d.helper.GetValuesFromChart(url, chartName, version, skipCache)
if err != nil {
log.Logger.Errorf("cannot fetch chart values repo: %s, chart: %s, version: %s, error: %s", url, chartName, version, err.Error())
@@ -78,6 +94,50 @@ func (d defaultHelmHandler) GetChartValues(ctx context.Context, url string, char
return res, nil
}
func (d defaultHelmHandler) ListChartRepo(ctx context.Context, projectName string) (*v1.ChartRepoResponseList, error) {
var res []*v1.ChartRepoResponse
var err error
if len(projectName) != 0 {
projectSecrets := corev1.SecretList{}
opts := []client.ListOption{
client.MatchingLabels{oam.LabelConfigType: "helm-repository", oam.LabelProject: projectName},
client.InNamespace(types.DefaultKubeVelaNS),
}
err = d.k8sClient.List(ctx, &projectSecrets, opts...)
if err != nil {
return nil, err
}
for _, item := range projectSecrets.Items {
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
}
}
globalSecrets := corev1.SecretList{}
selector := metav1.LabelSelector{
MatchLabels: map[string]string{oam.LabelConfigType: "helm-repository"},
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: oam.LabelProject, Operator: metav1.LabelSelectorOpDoesNotExist},
},
}
ls, _ := metav1.LabelSelectorAsSelector(&selector)
err = d.k8sClient.List(ctx, &globalSecrets, &client.ListOptions{
LabelSelector: ls,
Namespace: types.DefaultKubeVelaNS,
})
if err != nil {
return nil, err
}
for _, item := range globalSecrets.Items {
res = append(res, &v1.ChartRepoResponse{URL: string(item.Data["url"]), SecretName: item.Name})
}
return &v1.ChartRepoResponseList{ChartRepoResponse: res}, nil
}
// this func will flatten a nested map, the key will flatten with separator "." and the value's type will be keep
// src is the map you want to flatten the output will be set in dest map
// eg : src is {a:{b:{c:true}}} , the dest is {a.b.c:true}

View File

@@ -17,9 +17,19 @@ limitations under the License.
package usecase
import (
"context"
"encoding/json"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/oam/util"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
"github.com/stretchr/testify/assert"
)
@@ -37,6 +47,63 @@ func TestFlattenKeyFunc(t *testing.T) {
assert.Equal(t, dstMap, res)
}
var _ = Describe("Test helm repo list", func() {
ctx := context.Background()
var pSec, gSec v1.Secret
BeforeEach(func() {
pSec = v1.Secret{}
gSec = v1.Secret{}
Expect(k8sClient.Create(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "vela-system"}})).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
Expect(yaml.Unmarshal([]byte(projectSecret), &pSec)).Should(BeNil())
Expect(yaml.Unmarshal([]byte(globalSecret), &gSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &pSec)).Should(BeNil())
Expect(k8sClient.Create(ctx, &gSec)).Should(BeNil())
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, &gSec)).Should(BeNil())
Expect(k8sClient.Delete(ctx, &pSec)).Should(BeNil())
})
It("Test list with project ", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "my-project")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(2))
found := 0
for _, response := range list.ChartRepoResponse {
if response.SecretName == "project-helm-repo" {
Expect(response.URL).Should(BeEquivalentTo("https://kedacore.github.io/charts"))
found++
}
if response.SecretName == "global-helm-repo" {
Expect(response.URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
found++
}
}
Expect(found).Should(BeEquivalentTo(2))
})
It("Test list func with not exist project", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "not-exist-project")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
})
It("Test list func without project", func() {
u := NewHelmUsecase()
list, err := u.ListChartRepo(ctx, "")
Expect(err).Should(BeNil())
Expect(len(list.ChartRepoResponse)).Should(BeEquivalentTo(1))
Expect(list.ChartRepoResponse[0].URL).Should(BeEquivalentTo("https://charts.bitnami.com/bitnami"))
Expect(list.ChartRepoResponse[0].SecretName).Should(BeEquivalentTo("global-helm-repo"))
})
})
var (
src = `{
"OAMSpecVer":"v0.2",
@@ -175,4 +242,29 @@ var (
"webhookService.port": 11443,
"webhookService.type": "ClusterIP"
}`
globalSecret = `
apiVersion: v1
stringData:
url: https://charts.bitnami.com/bitnami
kind: Secret
metadata:
labels:
config.oam.dev/type: helm-repository
name: global-helm-repo
namespace: vela-system
type: Opaque
`
projectSecret = `
apiVersion: v1
kind: Secret
metadata:
name: project-helm-repo
namespace: vela-system
labels:
config.oam.dev/type: helm-repository
core.oam.dev/project: my-project
stringData:
url: https://kedacore.github.io/charts
type: Opaque
`
)

View File

@@ -207,6 +207,14 @@ var ResourceMaps = map[string]resourceMetadata{
"permission": {},
"systemSetting": {},
"definition": {},
"configType": {
pathName: "configType",
subResources: map[string]resourceMetadata{
"config": {
pathName: "name",
},
},
},
}
var existResourcePaths = convert(ResourceMaps)

View File

@@ -18,31 +18,52 @@ package usecase
import (
"context"
"errors"
"fmt"
"reflect"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/rand"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/version"
)
// SystemInfoUsecase is usecase for systemInfoCollection
type SystemInfoUsecase interface {
Get(ctx context.Context) (*model.SystemInfo, error)
GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error)
UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error)
Init(ctx context.Context) error
}
type systemInfoUsecaseImpl struct {
ds datastore.DataStore
ds datastore.DataStore
kubeClient client.Client
}
// NewSystemInfoUsecase return a systemInfoCollectionUsecase
func NewSystemInfoUsecase(ds datastore.DataStore) SystemInfoUsecase {
return &systemInfoUsecaseImpl{ds: ds}
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("failed to get kube client: %s", err.Error())
}
return &systemInfoUsecaseImpl{ds: ds, kubeClient: kubecli}
}
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
func (u systemInfoUsecaseImpl) Get(ctx context.Context) (*model.SystemInfo, error) {
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
info := &model.SystemInfo{}
entities, err := u.ds.List(ctx, info, &datastore.ListOptions{})
@@ -54,7 +75,7 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
if info.LoginType == "" {
info.LoginType = model.LoginTypeLocal
}
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return info, nil
}
installID := rand.String(16)
info.InstallID = installID
@@ -64,11 +85,26 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return info, nil
}
func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error) {
// first get request will init systemInfoCollection{installId: {random}, enableCollection: true}
info, err := u.Get(ctx)
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{
SystemInfo: convertInfoToBase(info),
SystemVersion: v1.SystemVersion{
VelaVersion: version.VelaVersion,
GitVersion: version.GitRevision,
},
}, nil
}
func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error) {
info, err := u.GetSystemInfo(ctx)
info, err := u.Get(ctx)
if err != nil {
return nil, err
}
@@ -78,10 +114,116 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
LoginType: sysInfo.LoginType,
BaseModel: model.BaseModel{
CreateTime: info.CreateTime,
}}
},
}
if sysInfo.LoginType == model.LoginTypeDex {
if err := generateDexConfig(ctx, u.kubeClient, sysInfo.VelaAddress, &modifiedInfo); err != nil {
return nil, err
}
}
err = u.ds.Put(ctx, &modifiedInfo)
if err != nil {
return nil, err
}
return &v1.SystemInfoResponse{SystemInfo: modifiedInfo, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
return &v1.SystemInfoResponse{
SystemInfo: v1.SystemInfo{
InstallID: modifiedInfo.InstallID,
EnableCollection: modifiedInfo.EnableCollection,
LoginType: modifiedInfo.LoginType,
},
SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision},
}, nil
}
func (u systemInfoUsecaseImpl) Init(ctx context.Context) error {
_, err := initDexConfig(ctx, u.kubeClient, "http://velaux.com", &model.SystemInfo{})
return err
}
func convertInfoToBase(info *model.SystemInfo) v1.SystemInfo {
return v1.SystemInfo{
InstallID: info.InstallID,
EnableCollection: info.EnableCollection,
LoginType: info.LoginType,
}
}
func generateDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) error {
secret, err := initDexConfig(ctx, kubeClient, velaAddress, info)
if err != nil {
return err
}
connectors, err := utils.GetDexConnectors(ctx, kubeClient)
if err != nil {
return err
}
config, err := model.NewJSONStructByStruct(info.DexConfig)
if err != nil {
return err
}
(*config)["connectors"] = connectors
c, err := yaml.Marshal(config)
if err != nil {
return err
}
if !reflect.DeepEqual(secret.Data[secretDexConfigKey], c) {
secret.Data[secretDexConfigKey] = c
if err := kubeClient.Update(ctx, secret); err != nil {
return err
}
if err := restartDex(ctx, kubeClient); err != nil && !errors.Is(err, bcode.ErrDexNotFound) {
return err
}
}
return nil
}
func initDexConfig(ctx context.Context, kubeClient client.Client, velaAddress string, info *model.SystemInfo) (*corev1.Secret, error) {
dexConfig := model.DexConfig{
Issuer: fmt.Sprintf("%s/dex", velaAddress),
Web: model.DexWeb{
HTTP: "0.0.0.0:5556",
},
Storage: model.DexStorage{
Type: "memory",
},
StaticClients: []model.DexStaticClient{
{
ID: "velaux",
Name: "Vela UX",
Secret: "velaux-secret",
RedirectURIs: []string{fmt.Sprintf("%s/callback", velaAddress)},
},
},
EnablePasswordDB: true,
}
info.DexConfig = dexConfig
secret := &corev1.Secret{}
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
if !apierrors.IsNotFound(err) {
return nil, err
}
config, err := yaml.Marshal(info.DexConfig)
if err != nil {
return nil, err
}
if err := kubeClient.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dexConfigName,
Namespace: velatypes.DefaultKubeVelaNS,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
secretDexConfigKey: config,
},
}); err != nil {
return nil, err
}
}
return secret, nil
}

View File

@@ -0,0 +1,26 @@
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
definition.oam.dev/description: Dex config allow users to specify dex config in properties
labels:
custom.definition.oam.dev/ui-hidden: "true"
name: dex-config
namespace: vela-system
spec:
schematic:
cue:
template: |
import "encoding/yaml"
output: {
apiVersion: "v1"
kind: "Secret"
metadata: name: context.name
namespace: context.namespace
type: "Opaque"
stringData: "config.yaml": yaml.Marshal(parameter)
}
parameter: {}
workload:
type: autodetects.core.oam.dev

View File

@@ -96,7 +96,7 @@ func (u *userUsecaseImpl) Init(ctx context.Context) error {
return err
}
// print default password of admin user in log
log.Logger.Infof("init admin user, password is %s", pwd)
log.Logger.Infof("initialized admin username and password: admin / %s\n", pwd)
secret := &corev1.Secret{}
if err := u.k8sClient.Get(ctx, k8stypes.NamespacedName{
Name: admin,
@@ -192,7 +192,7 @@ func (u *userUsecaseImpl) DeleteUser(ctx context.Context, username string) error
// CreateUser create user
func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserRequest) (*apisv1.UserBase, error) {
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := u.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}
@@ -220,7 +220,7 @@ func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserR
// UpdateUser update user
func (u *userUsecaseImpl) UpdateUser(ctx context.Context, user *model.User, req apisv1.UpdateUserRequest) (*apisv1.UserBase, error) {
sysInfo, err := u.sysUsecase.GetSystemInfo(ctx)
sysInfo, err := u.sysUsecase.Get(ctx)
if err != nil {
return nil, err
}

View File

@@ -25,11 +25,14 @@ import (
"github.com/emicklei/go-restful/v3"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
@@ -74,7 +77,12 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test HandleApplicationWebhook function", func() {
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
var ns = corev1.Namespace{}
ns.Name = types.DefaultKubeVelaNS
err := k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
_, err = projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
Expect(err).Should(BeNil())
_, err = targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook", Project: "project-webhook"})

View File

@@ -33,4 +33,6 @@ var (
ErrUserInconsistentPassword = NewBcode(401, 14007, "the password is inconsistent with the user")
// ErrUsernameNotExist is the error of username not exist
ErrUsernameNotExist = NewBcode(401, 14008, "the username is not exist")
// ErrDexNotFound is the error of dex not found
ErrDexNotFound = NewBcode(200, 14009, "the dex is not found")
)

View File

@@ -0,0 +1,52 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
"encoding/json"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
)
// GetDexConnectors returns the dex connectors for Dex connector controller
func GetDexConnectors(ctx context.Context, k8sClient client.Client) ([]map[string]interface{}, error) {
secrets := &v1.SecretList{}
if err := k8sClient.List(ctx, secrets, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{types.LabelConfigType: types.DexConnector}); err != nil {
return nil, err
}
connectors := make([]map[string]interface{}, len(secrets.Items))
for i, s := range secrets.Items {
var data map[string]interface{}
key := s.Labels[types.LabelConfigSubType]
err := json.Unmarshal(s.Data[key], &data)
if err != nil {
return nil, err
}
connectors[i] = map[string]interface{}{
"type": s.Labels[types.LabelConfigSubType],
"id": s.Name,
"name": s.Name,
"config": data,
}
}
return connectors, nil
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
"encoding/json"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestGetDexConnectors(t *testing.T) {
ctx := context.Background()
type args struct {
k8sClient client.Client
}
type want struct {
connectors []map[string]interface{}
err error
}
ldap := map[string]interface{}{
"clientID": "clientID",
"clientSecret": "clientSecret",
"callbackURL": "redirectURL",
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
}
data, err := json.Marshal(ldap)
assert.NoError(t, err)
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "a",
Namespace: "vela-system",
Labels: map[string]string{
"app.oam.dev/source-of-truth": "from-inner-system",
"config.oam.dev/catalog": "velacore-config",
"config.oam.dev/type": "config-dex-connector",
"config.oam.dev/sub-type": "ldap",
"project": "abc",
},
},
Data: map[string][]byte{
"ldap": data,
},
Type: v1.SecretTypeOpaque,
}
k8sClient := fake.NewClientBuilder().WithObjects(secret).Build()
testcaes := map[string]struct {
args args
want want
}{
"test": {args: args{
k8sClient: k8sClient,
},
want: want{
connectors: []map[string]interface{}{{
"id": "a",
"name": "a",
"type": "ldap",
"config": map[string]interface{}{
"clientID": "clientID",
"clientSecret": "clientSecret",
"callbackURL": "redirectURL",
"xxx": map[string]interface{}{"aaa": "bbb", "ccc": "ddd"},
},
}},
},
},
}
for name, tc := range testcaes {
t.Run(name, func(t *testing.T) {
got, err := GetDexConnectors(ctx, tc.args.k8sClient)
if err != tc.want.err {
t.Errorf("%s: GetDexConnectors() error = %v, wantErr %v", name, err, tc.want.err)
return
}
if !reflect.DeepEqual(got, tc.want.connectors) {
t.Errorf("%s: GetDexConnectors() = %v, want %v", name, got, tc.want.connectors)
}
})
}
}

View File

@@ -0,0 +1,192 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package webservice
import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
// ConfigWebService returns config web service
func ConfigWebService(u usecase.ConfigHandler, rbacUseCase usecase.RBACUsecase) WebService {
return &configWebService{
handler: u,
rbacUseCase: rbacUseCase,
}
}
type configWebService struct {
handler usecase.ConfigHandler
rbacUseCase usecase.RBACUsecase
}
func (s *configWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix+"/config_types").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for configuration management")
tags := []string{"config"}
ws.Route(ws.GET("/").To(s.listConfigTypes).
Doc("list all config types").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "list")).
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
Returns(200, "OK", []apis.ConfigType{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]apis.ConfigType{}))
ws.Route(ws.GET("/{configType}").To(s.getConfigType).
Doc("get a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "get")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Returns(200, "OK", apis.ConfigType{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.POST("/{configType}").To(s.createConfig).
Doc("create or update a config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("configType", "create")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Reads(apis.CreateConfigRequest{}).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{configType}/configs").To(s.getConfigs).
Doc("get configs from a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "list")).
Param(ws.PathParameter("configType", "identifier of the config").DataType("string")).
Returns(200, "OK", []*apis.Config{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.GET("/{configType}/configs/{name}").To(s.getConfig).
Doc("get a config from a config type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "get")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
Returns(200, "OK", []*apis.Config{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ConfigType{}))
ws.Route(ws.DELETE("/{configType}/configs/{name}").To(s.deleteConfig).
Doc("delete a config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUseCase.CheckPerm("config", "delete")).
Param(ws.PathParameter("configType", "identifier of the config type").DataType("string")).
Param(ws.PathParameter("name", "identifier of the config").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Filter(authCheckFilter)
return ws
}
func (s *configWebService) listConfigTypes(req *restful.Request, res *restful.Response) {
types, err := s.handler.ListConfigTypes(req.Request.Context(), req.QueryParameter("query"))
if len(types) == 0 && err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(types)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfigType(req *restful.Request, res *restful.Response) {
t, err := s.handler.GetConfigType(req.Request.Context(), req.PathParameter("configType"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(t)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) createConfig(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateConfigRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
err := s.handler.CreateConfig(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("failed to create config: %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfigs(req *restful.Request, res *restful.Response) {
configs, err := s.handler.GetConfigs(req.Request.Context(), req.PathParameter("configType"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(configs)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) getConfig(req *restful.Request, res *restful.Response) {
t, err := s.handler.GetConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(t)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (s *configWebService) deleteConfig(req *restful.Request, res *restful.Response) {
err := s.handler.DeleteConfig(req.Request.Context(), req.PathParameter("configType"), req.PathParameter("name"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}

View File

@@ -46,11 +46,21 @@ func (h helmWebService) GetWebService() *restful.WebService {
tags := []string{"repository", "helm"}
// List charts
ws.Route(ws.GET("/chart_repos").To(h.listRepo).
Doc("list chart repo").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("project", "the config project").DataType("string")).
Returns(200, "OK", []string{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
// List charts
ws.Route(ws.GET("/charts").To(h.listCharts).
Doc("list charts").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", []string{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -60,6 +70,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
Doc("list versions").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", v1.ChartVersionListResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -69,6 +80,7 @@ func (h helmWebService) GetWebService() *restful.WebService {
Doc("get chart value").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("repoUrl", "helm repository url").DataType("string")).
Param(ws.QueryParameter("secretName", "secret of the repo").DataType("string")).
Returns(200, "OK", map[string]interface{}{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]string{}))
@@ -79,12 +91,13 @@ func (h helmWebService) GetWebService() *restful.WebService {
func (h helmWebService) listCharts(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
secName := req.QueryParameter("secretName")
skipCache, err := isSkipCache(req)
if err != nil {
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
return
}
charts, err := h.usecase.ListChartNames(context.Background(), url, skipCache)
charts, err := h.usecase.ListChartNames(context.Background(), url, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -99,13 +112,14 @@ func (h helmWebService) listCharts(req *restful.Request, res *restful.Response)
func (h helmWebService) listVersions(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
chartName := req.PathParameter("chart")
secName := req.QueryParameter("secretName")
skipCache, err := isSkipCache(req)
if err != nil {
bcode.ReturnError(req, res, bcode.ErrSkipCacheParameter)
return
}
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, skipCache)
versions, err := h.usecase.ListChartVersions(context.Background(), url, chartName, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -119,6 +133,7 @@ func (h helmWebService) listVersions(req *restful.Request, res *restful.Response
func (h helmWebService) chartValues(req *restful.Request, res *restful.Response) {
url := req.QueryParameter("repoUrl")
secName := req.QueryParameter("secretName")
chartName := req.PathParameter("chart")
version := req.PathParameter("version")
skipCache, err := isSkipCache(req)
@@ -127,7 +142,7 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
return
}
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, skipCache)
versions, err := h.usecase.GetChartValues(context.Background(), url, chartName, version, secName, skipCache)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -139,6 +154,20 @@ func (h helmWebService) chartValues(req *restful.Request, res *restful.Response)
}
}
func (h helmWebService) listRepo(req *restful.Request, res *restful.Response) {
project := req.QueryParameter("project")
repos, err := h.usecase.ListChartRepo(context.Background(), project)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
err = res.WriteEntity(repos)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func isSkipCache(req *restful.Request) (bool, error) {
skipStr := req.QueryParameter("skipCache")
skipCache := false

View File

@@ -73,15 +73,16 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
definitionUsecase := usecase.NewDefinitionUsecase()
addonUsecase := usecase.NewAddonUsecase(addonCacheTime)
envBindingUsecase := usecase.NewEnvBindingUsecase(ds, workflowUsecase, definitionUsecase, envUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
helmUsecase := usecase.NewHelmUsecase()
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase, rbacUsecase)
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase, userUsecase)
configUseCase := usecase.NewConfigUseCase(authenticationUsecase)
applicationUsecase := usecase.NewApplicationUsecase(ds, workflowUsecase, envBindingUsecase, envUsecase, targetUsecase, definitionUsecase, projectUsecase)
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
// Modules that require default data initialization, Call it here in order
if initDatabase {
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase)
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase, systemInfoUsecase)
}
// Application
@@ -95,6 +96,9 @@ func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Durat
RegisterWebService(NewEnabledAddonWebService(addonUsecase, rbacUsecase))
RegisterWebService(NewAddonRegistryWebService(addonUsecase, rbacUsecase))
// Config management
RegisterWebService(ConfigWebService(configUseCase, rbacUsecase))
// Resources
RegisterWebService(NewClusterWebService(clusterUsecase, rbacUsecase))
RegisterWebService(NewOAMApplication(oamApplicationUsecase, rbacUsecase))

View File

@@ -90,6 +90,12 @@ const (
// LabelRuntimeNamespaceUsage mark the usage of the namespace in runtime cluster.
// A control plane cluster can also be used as runtime cluster
LabelRuntimeNamespaceUsage = "usage.oam.dev/runtime"
// LabelConfigType means the config type
LabelConfigType = "config.oam.dev/type"
// LabelProject recorde the project the resource belong to
LabelProject = "core.oam.dev/project"
)
const (

View File

@@ -279,13 +279,13 @@ func RealtimePrintCommandOutput(cmd *exec.Cmd, logFile string) error {
// ClusterObject2Map convert ClusterObjectReference to a readable map
func ClusterObject2Map(refs []common.ClusterObjectReference) map[string]string {
clusterResourceRefTmpl := "Cluster: %s | Namespace: %s | Component: %s | Kind: %s"
clusterResourceRefTmpl := "Cluster: %s | Namespace: %s | Kind: %s | Name: %s"
objs := make(map[string]string, len(refs))
for _, r := range refs {
if r.Cluster == "" {
r.Cluster = "local"
}
objs[r.Cluster+"/"+r.Namespace+"/"+r.Name+"/"+r.Kind] = fmt.Sprintf(clusterResourceRefTmpl, r.Cluster, r.Namespace, r.Name, r.Kind)
objs[r.Cluster+"/"+r.Namespace+"/"+r.Name+"/"+r.Kind] = fmt.Sprintf(clusterResourceRefTmpl, r.Cluster, r.Namespace, r.Kind, r.Name)
}
return objs
}
@@ -312,6 +312,11 @@ func clusterObjectReferenceTypeFilterGenerator(allowedKinds ...string) clusterOb
var isWorkloadClusterObjectReferenceFilter = clusterObjectReferenceTypeFilterGenerator("Deployment", "StatefulSet", "CloneSet", "Job", "Configuration")
var isPortForwardEndpointClusterObjectReferenceFilter = clusterObjectReferenceTypeFilterGenerator("Deployment",
"StatefulSet", "CloneSet", "Job", "Service", "HelmRelease")
var resourceNameClusterObjectReferenceFilter = func(resourceName string) clusterObjectReferenceFilter {
return func(reference common.ClusterObjectReference) bool {
return resourceName == reference.Name
}
}
func filterResource(inputs []common.ClusterObjectReference, filters ...clusterObjectReferenceFilter) (outputs []common.ClusterObjectReference) {
for _, item := range inputs {
@@ -419,13 +424,22 @@ func filterClusterObjectRefFromAddonObservability(resources []common.ClusterObje
// AskToChooseOneEnvResource will ask users to select one applied resource of the application if more than one
// resource is a map for component to applied resources
// return the selected ClusterObjectReference
func AskToChooseOneEnvResource(app *v1beta1.Application) (*common.ClusterObjectReference, error) {
return askToChooseOneResource(app, isWorkloadClusterObjectReferenceFilter)
func AskToChooseOneEnvResource(app *v1beta1.Application, resourceName ...string) (*common.ClusterObjectReference, error) {
filters := []clusterObjectReferenceFilter{isWorkloadClusterObjectReferenceFilter}
for _, n := range resourceName {
filters = append(filters, resourceNameClusterObjectReferenceFilter(n))
}
return askToChooseOneResource(app, filters...)
}
// AskToChooseOnePortForwardEndpoint will ask user to select one applied resource as port forward endpoint
func AskToChooseOnePortForwardEndpoint(app *v1beta1.Application) (*common.ClusterObjectReference, error) {
return askToChooseOneResource(app, isPortForwardEndpointClusterObjectReferenceFilter)
func AskToChooseOnePortForwardEndpoint(app *v1beta1.Application, resourceName ...string) (*common.ClusterObjectReference, error) {
filters := []clusterObjectReferenceFilter{isPortForwardEndpointClusterObjectReferenceFilter}
for _, n := range resourceName {
filters = append(filters, resourceNameClusterObjectReferenceFilter(n))
}
return askToChooseOneResource(app, filters...)
}
func askToChooseOneInApplication(category string, options []string) (decision string, err error) {

View File

@@ -180,10 +180,7 @@ func (p *provider) ExpandTopology(ctx wfContext.Context, v *value.Value, act wfT
return errors.Wrapf(e, "failed to get cluster %s", cluster)
}
}
if ns == "" {
ns = p.app.GetNamespace()
}
if !resourcekeeper.AllowCrossNamespaceResource && ns != p.app.GetNamespace() {
if !resourcekeeper.AllowCrossNamespaceResource && (ns != p.app.GetNamespace() && ns != "") {
return errors.Errorf("cannot cross namespace")
}
placement := v1alpha1.PlacementDecision{Cluster: cluster, Namespace: ns}

View File

@@ -576,7 +576,7 @@ func TestExpandTopology(t *testing.T) {
},
"topology-by-clusters": {
Input: `{inputs:{policies:[{name:"topology-policy",type:"topology",properties:{clusters:["cluster-a"]}}]}}`,
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: "test"}},
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: ""}},
},
"topology-by-cluster-selector-404": {
Input: `{inputs:{policies:[{name:"topology-policy",type:"topology",properties:{clusterSelector:{"key":"bad-value"}}}]}}`,
@@ -584,11 +584,11 @@ func TestExpandTopology(t *testing.T) {
},
"topology-by-cluster-selector": {
Input: `{inputs:{policies:[{name:"topology-policy",type:"topology",properties:{clusterSelector:{"key":"value"}}}]}}`,
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: "test"}, {Cluster: "cluster-b", Namespace: "test"}},
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: ""}, {Cluster: "cluster-b", Namespace: ""}},
},
"topology-by-cluster-label-selector": {
Input: `{inputs:{policies:[{name:"topology-policy",type:"topology",properties:{clusterLabelSelector:{"key":"value"}}}]}}`,
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: "test"}, {Cluster: "cluster-b", Namespace: "test"}},
Outputs: []v1alpha1.PlacementDecision{{Cluster: "cluster-a", Namespace: ""}, {Cluster: "cluster-b", Namespace: ""}},
},
"topology-by-cluster-selector-and-namespace-invalid": {
Input: `{inputs:{policies:[{name:"topology-policy",type:"topology",properties:{clusterSelector:{"key":"value"},namespace:"override"}}]}}`,

View File

@@ -68,6 +68,7 @@ const (
const (
statusEnabled = "enabled"
statusDisabled = "disabled"
statusSuspend = "suspend"
)
var forceDisable bool
@@ -204,7 +205,12 @@ func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient cli
return
}
if name == "velaux" {
fmt.Println(`Please use command: "vela port-forward -n vela-system addon-velaux 9082:80" and Select "Cluster: local | Namespace: vela-system | Component: velaux | Kind: Service" to check the dashboard.`)
fmt.Println(`To check the initialized admin user name and password by:`)
fmt.Println(` vela logs -n vela-system --name apiserver addon-velaux | grep "initialized admin username"`)
fmt.Println(`To open the dashboard directly by port-forward:`)
fmt.Println(` vela port-forward -n vela-system addon-velaux 9082:80`)
fmt.Println(`Select "Cluster: local | Namespace: vela-system | Component: velaux | Kind: Service" from the prompt.`)
fmt.Println(`Please refer to https://kubevela.io/docs/reference/addons/velaux for more VelaUX addon installation and visiting method.`)
}
}
@@ -252,7 +258,7 @@ Upgrade addon for specific clusters, (local means control plane):
}
ioStream.Infof("enable addon by local dir: %s \n", addonOrDir)
// args[0] is a local path install with local dir
name := filepath.Base(addonOrDir)
name = filepath.Base(addonOrDir)
_, err = pkgaddon.FetchAddonRelatedApp(context.Background(), k8sClient, name)
if err != nil {
return errors.Wrapf(err, "cannot fetch addon related addon %s", name)
@@ -265,7 +271,7 @@ Upgrade addon for specific clusters, (local means control plane):
if filepath.IsAbs(addonOrDir) || strings.HasPrefix(addonOrDir, ".") || strings.HasSuffix(addonOrDir, "/") {
return fmt.Errorf("addon directory %s not found in local", addonOrDir)
}
name = addonOrDir
_, err = pkgaddon.FetchAddonRelatedApp(context.Background(), k8sClient, addonOrDir)
if err != nil {
return errors.Wrapf(err, "cannot fetch addon related addon %s", addonOrDir)
@@ -399,7 +405,9 @@ func statusAddon(name string, ioStreams cmdutil.IOStreams, cmd *cobra.Command, c
if err != nil {
return err
}
fmt.Printf("addon %s status is %s \n", name, status.AddonPhase)
fmt.Print(generateAddonInfo(name, status))
if status.AddonPhase != statusEnabled && status.AddonPhase != statusDisabled {
fmt.Printf("diagnose addon info from application %s", pkgaddon.Convert2AppName(name))
err := printAppStatus(context.Background(), k8sClient, ioStreams, pkgaddon.Convert2AppName(name), types.DefaultKubeVelaNS, cmd, c)
@@ -410,6 +418,35 @@ func statusAddon(name string, ioStreams cmdutil.IOStreams, cmd *cobra.Command, c
return nil
}
func generateAddonInfo(name string, status pkgaddon.Status) string {
var res string
var phase string
switch status.AddonPhase {
case statusEnabled:
c := color.New(color.FgGreen)
phase = c.Sprintf("%s", status.AddonPhase)
case statusSuspend:
c := color.New(color.FgRed)
phase = c.Sprintf("%s", status.AddonPhase)
default:
phase = status.AddonPhase
}
res += fmt.Sprintf("addon %s status is %s \n", name, phase)
if len(status.InstalledVersion) != 0 {
res += fmt.Sprintf("installedVersion: %s \n", status.InstalledVersion)
}
if len(status.Clusters) != 0 {
var ic []string
for c := range status.Clusters {
ic = append(ic, c)
}
res += fmt.Sprintf("installedClusters: %s \n", ic)
}
return res
}
func listAddons(ctx context.Context, clt client.Client, registry string) error {
var addons []*pkgaddon.UIData
var err error
@@ -504,9 +541,31 @@ func waitApplicationRunning(k8sClient client.Client, addonName string) error {
}
// generate the available version
// this func put the installed version as the first version and keep the origin order
// print ... if available version too much
func genAvailableVersionInfo(versions []string, status pkgaddon.Status) string {
var v []string
// put installed-version as the first version and keep the origin order
if len(status.InstalledVersion) != 0 {
for i, version := range versions {
if version == status.InstalledVersion {
v = append(v, version)
versions = append(versions[:i], versions[i+1:]...)
}
}
}
v = append(v, versions...)
res := "["
for _, version := range versions {
var count int
for _, version := range v {
if count == 3 {
// just show newest 3 versions
res += "..."
break
}
if version == status.InstalledVersion {
col := color.New(color.Bold, color.FgGreen)
res += col.Sprintf("%s", version)
@@ -514,6 +573,7 @@ func genAvailableVersionInfo(versions []string, status pkgaddon.Status) string {
res += version
}
res += ", "
count++
}
res = strings.TrimSuffix(res, ", ")
res += "]"

View File

@@ -18,8 +18,13 @@ package cli
import (
"fmt"
"strings"
"testing"
"github.com/fatih/color"
pkgaddon "github.com/oam-dev/kubevela/pkg/addon"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
@@ -151,3 +156,62 @@ func TestTransCluster(t *testing.T) {
assert.DeepEqual(t, transClusters(s.str), s.res)
}
}
func TestGenerateStatusIn(t *testing.T) {
testcases := []struct {
c pkgaddon.Status
res []string
}{
{
c: pkgaddon.Status{InstalledVersion: "1.2.1", Clusters: map[string]map[string]interface{}{"cluster1": nil, "cluster2": nil}, AddonPhase: statusEnabled},
res: []string{"installedVersion: 1.2.1", "installedClusters: [cluster1 cluster2]", fmt.Sprintf("status is %s", color.New(color.FgGreen).Sprintf(statusEnabled))},
},
{
c: pkgaddon.Status{InstalledVersion: "1.2.3", AddonPhase: statusSuspend},
res: []string{"installedVersion: 1.2.3", fmt.Sprintf("status is %s", color.New(color.FgRed).Sprintf(statusSuspend))},
},
}
for _, testcase := range testcases {
res := generateAddonInfo("test", testcase.c)
for _, re := range testcase.res {
assert.Equal(t, strings.Contains(res, re), true)
}
}
}
func TestGenerateAvailableVersions(t *testing.T) {
type testcase struct {
inVersion string
versions []string
}
testcases := []struct {
c testcase
res string
}{
{
c: testcase{
inVersion: "1.2.1",
versions: []string{"1.2.1"},
},
res: fmt.Sprintf("[%s]", color.New(color.Bold, color.FgGreen).Sprintf("1.2.1")),
},
{
c: testcase{
inVersion: "1.2.1",
versions: []string{"1.2.3", "1.2.2", "1.2.1"},
},
res: fmt.Sprintf("[%s, 1.2.3, 1.2.2]", color.New(color.Bold, color.FgGreen).Sprintf("1.2.1")),
},
{
c: testcase{
inVersion: "1.2.1",
versions: []string{"1.2.3", "1.2.2", "1.2.1", "1.2.0"},
},
res: fmt.Sprintf("[%s, 1.2.3, 1.2.2, ...]", color.New(color.Bold, color.FgGreen).Sprintf("1.2.1")),
},
}
for _, s := range testcases {
re := genAvailableVersionInfo(s.c.versions, pkgaddon.Status{InstalledVersion: s.c.inVersion})
assert.Equal(t, re, s.res)
}
}

View File

@@ -43,7 +43,7 @@ import (
)
// defaultConstraint
const defaultConstraint = ">= 1.19, <= 1.21"
const defaultConstraint = ">= 1.19, <= 1.22"
const kubevelaInstallerHelmRepoURL = "https://charts.kubevela.net/core/"

View File

@@ -86,6 +86,7 @@ func NewLogsCommand(c common.Args, order string, ioStreams util.IOStreams) *cobr
cmd.Flags().StringVarP(&largs.Output, "output", "o", "default", "output format for logs, support: [default, raw, json]")
cmd.Flags().StringVarP(&largs.Container, "container", "c", "", "specify container name for output")
cmd.Flags().StringVar(&largs.Name, "name", "", "specify resource name for output")
addNamespaceAndEnvArg(cmd)
return cmd
}
@@ -96,6 +97,7 @@ type Args struct {
Args common.Args
Namespace string
Container string
Name string
App *v1beta1.Application
}
@@ -111,7 +113,7 @@ func (l *Args) Run(ctx context.Context, ioStreams util.IOStreams) error {
if err != nil {
return err
}
selectedRes, err := common.AskToChooseOneEnvResource(l.App)
selectedRes, err := common.AskToChooseOneEnvResource(l.App, l.Name)
if err != nil {
return err
}

View File

@@ -1,66 +0,0 @@
"config-dex-connector": {
type: "component"
annotations: {
"alias.config.oam.dev": "Dex Connector"
}
labels: {
"catalog.config.oam.dev": "velacore-config"
"type.config.oam.dev": "dex-connector"
"multi-cluster.config.oam.dev": "false"
}
description: "Config information to authenticate Dex connectors"
attributes: workload: type: "autodetects.core.oam.dev"
}
template: {
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "dex-connector"
"config.oam.dev/multi-cluster": "false"
"config.oam.dev/identifier": parameter.name
"config.oam.dev/sub-type": parameter.type
}
}
type: "Opaque"
if parameter.type == "github" {
stringData: parameter.github
}
if parameter.type == "ldap" {
stringData: parameter.ldap
}
}
parameter: {
// +usage=Config type
type: "github" | "ldap"
github?: {
// +usage=GitHub client ID
clientID: string
// +usage=GitHub client secret
clientSecret: string
// +usage=GitHub call back URL
callbackURL: string
}
ldap?: {
host: string
insecureNoSSL: *true | bool
insecureSkipVerify: bool
startTLS: bool
usernamePrompt: string
userSearch: {
baseDN: string
username: string
idAttr: string
emailAttr: string
nameAttr: string
}
}
}
}

View File

@@ -1,65 +0,0 @@
import (
"encoding/base64"
"encoding/json"
)
"config-image-registry": {
type: "component"
annotations: {
"alias.config.oam.dev": "Image Registry"
}
labels: {
"catalog.config.oam.dev": "velacore-config"
"type.config.oam.dev": "image-registry"
"multi-cluster.config.oam.dev": "true"
}
description: "Config information to authenticate image registry"
attributes: workload: type: "autodetects.core.oam.dev"
}
template: {
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: context.name
namespace: context.namespace
labels: {
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "image-registry"
"config.oam.dev/multi-cluster": "true"
"config.oam.dev/identifier": parameter.registry
"config.oam.dev/sub-type": "auth"
}
}
type: "kubernetes.io/dockerconfigjson"
stringData: {
if parameter.auth != _|_ {
".dockerconfigjson": json.Marshal({
"auths": "\(parameter.registry)": {
"username": parameter.auth.username
"password": parameter.auth.password
if parameter.auth.email != _|_ {
"email": parameter.auth.email
}
"auth": base64.Encode(null, (parameter.auth.username + ":" + parameter.auth.password))
}
})
}
}
}
parameter: {
// +usage=Image registry FQDN
registry: string
// +usage=Authenticate the image registry
auth?: {
// +usage=Private Image registry username
username: string
// +usage=Private Image registry password
password: string
// +usage=Private Image registry email
email?: string
}
}
}

View File

@@ -0,0 +1,306 @@
"cron-task": {
type: "component"
annotations: {}
labels: {}
description: "Describes cron jobs that run code or a script to completion."
attributes: workload: {
definition: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
}
type: "cronjobs.batch"
}
}
template: {
output: {
apiVersion: "batch/v1beta1"
kind: "CronJob"
spec: {
schedule: parameter.schedule
concurrencyPolicy: parameter.concurrencyPolicy
suspend: parameter.suspend
successfulJobsHistoryLimit: parameter.successfulJobsHistoryLimit
failedJobsHistoryLimit: parameter.failedJobsHistoryLimit
if parameter.startingDeadlineSeconds != _|_ {
startingDeadlineSeconds: parameter.startingDeadlineSeconds
}
jobTemplate: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
parallelism: parameter.count
completions: parameter.count
if parameter.ttlSecondsAfterFinished != _|_ {
ttlSecondsAfterFinished: parameter.ttlSecondsAfterFinished
}
if parameter.activeDeadlineSeconds != _|_ {
activeDeadlineSeconds: parameter.activeDeadlineSeconds
}
backoffLimit: parameter.backoffLimit
template: {
if parameter.labels != _|_ {
metadata: labels: parameter.labels
}
if parameter.annotations != _|_ {
metadata: annotations: parameter.annotations
}
spec: {
restartPolicy: parameter.restart
containers: [{
name: context.name
image: parameter.image
if parameter["imagePullPolicy"] != _|_ {
imagePullPolicy: parameter.imagePullPolicy
}
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if parameter["cpu"] != _|_ {
resources: {
limits: cpu: parameter.cpu
requests: cpu: parameter.cpu
}
}
if parameter["memory"] != _|_ {
resources: {
limits: memory: parameter.memory
requests: memory: parameter.memory
}
}
if parameter["volumes"] != _|_ {
volumeMounts: [ for v in parameter.volumes {
{
mountPath: v.mountPath
name: v.name
}}]
}
}]
if parameter["volumes"] != _|_ {
volumes: [ for v in parameter.volumes {
{
name: v.name
if v.type == "pvc" {
persistentVolumeClaim: claimName: v.claimName
}
if v.type == "configMap" {
configMap: {
defaultMode: v.defaultMode
name: v.cmName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "secret" {
secret: {
defaultMode: v.defaultMode
secretName: v.secretName
if v.items != _|_ {
items: v.items
}
}
}
if v.type == "emptyDir" {
emptyDir: medium: v.medium
}
}}]
}
if parameter["imagePullSecrets"] != _|_ {
imagePullSecrets: [ for v in parameter.imagePullSecrets {
name: v
},
]
}
if parameter.hostAliases != _|_ {
hostAliases: [ for v in parameter.hostAliases {
ip: v.ip
hostnames: v.hostnames
},
]
}
}
}
}
}
}
}
parameter: {
// +usage=Specify the labels in the workload
labels?: [string]: string
// +usage=Specify the annotations in the workload
annotations?: [string]: string
// +usage=Specify the schedule in Cron format, see https://en.wikipedia.org/wiki/Cron
schedule: string
// +usage=Specify deadline in seconds for starting the job if it misses scheduled
startingDeadlineSeconds?: int
// +usage=suspend subsequent executions
suspend: *false | bool
// +usage=Specifies how to treat concurrent executions of a Job
concurrencyPolicy: *"Allow" | "Allow" | "Forbid" | "Replace"
// +usage=The number of successful finished jobs to retain
successfulJobsHistoryLimit: *3 | int
// +usage=The number of failed finished jobs to retain
failedJobsHistoryLimit: *1 | int
// +usage=Specify number of tasks to run in parallel
// +short=c
count: *1 | int
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Specify image pull policy for your service
imagePullPolicy?: "Always" | "Never" | "IfNotPresent"
// +usage=Specify image pull secrets for your service
imagePullSecrets?: [...string]
// +usage=Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never.
restart: *"Never" | string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
// +usage=Selects a key of a config map in the pod's namespace
configMapKeyRef: {
// +usage=The name of the config map in the pod's namespace to select from
name: string
// +usage=The key of the config map to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
// +usage=Specifies the attributes of the memory resource required for the container.
memory?: string
// +usage=Declare volumes and volumeMounts
volumes?: [...{
name: string
mountPath: string
// +usage=Specify volume type, options: "pvc","configMap","secret","emptyDir"
type: "pvc" | "configMap" | "secret" | "emptyDir"
if type == "pvc" {
claimName: string
}
if type == "configMap" {
defaultMode: *420 | int
cmName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "secret" {
defaultMode: *420 | int
secretName: string
items?: [...{
key: string
path: string
mode: *511 | int
}]
}
if type == "emptyDir" {
medium: *"" | "Memory"
}
}]
// +usage=An optional list of hosts and IPs that will be injected into the pod's hosts file
hostAliases?: [...{
ip: string
hostnames: [...string]
}]
// +usage=Limits the lifetime of a Job that has finished
ttlSecondsAfterFinished?: int
// +usage=The duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it
activeDeadlineSeconds?: int
// +usage=The number of retries before marking this job failed
backoffLimit: *6 | int
// +usage=Instructions for assessing whether the container is alive.
livenessProbe?: #HealthProbe
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
}
#HealthProbe: {
// +usage=Instructions for assessing container health by executing a command. Either this attribute or the httpGet attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the httpGet attribute and the tcpSocket attribute.
exec?: {
// +usage=A command to be executed inside the container to assess its health. Each space delimited token of the command is a separate array element. Commands exiting 0 are considered to be successful probes, whilst all other exit codes are considered failures.
command: [...string]
}
// +usage=Instructions for assessing container health by executing an HTTP GET request. Either this attribute or the exec attribute or the tcpSocket attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the tcpSocket attribute.
httpGet?: {
// +usage=The endpoint, relative to the port, to which the HTTP GET request should be directed.
path: string
// +usage=The TCP socket within the container to which the HTTP GET request should be directed.
port: int
httpHeaders?: [...{
name: string
value: string
}]
}
// +usage=Instructions for assessing container health by probing a TCP socket. Either this attribute or the exec attribute or the httpGet attribute MUST be specified. This attribute is mutually exclusive with both the exec attribute and the httpGet attribute.
tcpSocket?: {
// +usage=The TCP socket within the container that should be probed to assess container health.
port: int
}
// +usage=Number of seconds after the container is started before the first probe is initiated.
initialDelaySeconds: *0 | int
// +usage=How often, in seconds, to execute the probe.
periodSeconds: *10 | int
// +usage=Number of seconds after which the probe times out.
timeoutSeconds: *1 | int
// +usage=Minimum consecutive successes for the probe to be considered successful after having failed.
successThreshold: *1 | int
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
}
}

View File

@@ -499,6 +499,12 @@ template: {
// +usage=Instructions for assessing whether the container is in a suitable state to serve traffic.
readinessProbe?: #HealthProbe
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
#HealthProbe: {
@@ -541,11 +547,5 @@ template: {
// +usage=Number of consecutive failures required to determine the container is not alive (liveness probe) or not ready (readiness probe).
failureThreshold: *3 | int
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
}