mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Feat: vela revision support read the apprevision details in yaml format (#4659)
Signed-off-by: cezhang <c1zhang.dev@gmail.com> make reviewable fix errors Signed-off-by: cezhang <c1zhang.dev@gmail.com> fix error Signed-off-by: cezhang <c1zhang.dev@gmail.com> fix error Signed-off-by: cezhang <c1zhang.dev@gmail.com> fix errors Signed-off-by: cezhang <c1zhang.dev@gmail.com> fix errors Signed-off-by: cezhang <c1zhang.dev@gmail.com> Signed-off-by: cezhang <c1zhang.dev@gmail.com>
This commit is contained in:
33
charts/vela-core/templates/velaql/application-revision.yaml
Normal file
33
charts/vela-core/templates/velaql/application-revision.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata:
|
||||
name: "application-revision-view"
|
||||
namespace: {{ include "systemDefinitionNamespace" . }}
|
||||
data:
|
||||
template: |
|
||||
import (
|
||||
"vela/op"
|
||||
)
|
||||
|
||||
output: {
|
||||
op.#Read & {
|
||||
value: {
|
||||
apiVersion: "core.oam.dev/v1beta1"
|
||||
kind: "ApplicationRevision"
|
||||
metadata: {
|
||||
name: parameter.name
|
||||
namespace: parameter.namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameter: {
|
||||
// +usage=Specify the name of the object
|
||||
name: string
|
||||
// +usage=Specify the namespace of the object
|
||||
namespace: *"default" | string
|
||||
}
|
||||
|
||||
status: output.value
|
||||
|
||||
@@ -15,12 +15,17 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
|
||||
"github.com/oam-dev/kubevela/pkg/velaql"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
@@ -29,6 +34,10 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
revisionView = "application-revision-view"
|
||||
)
|
||||
|
||||
// RevisionCommandGroup the commands for managing application revisions
|
||||
func RevisionCommandGroup(c common.Args) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
@@ -41,6 +50,7 @@ func RevisionCommandGroup(c common.Args) *cobra.Command {
|
||||
}
|
||||
cmd.AddCommand(
|
||||
NewRevisionListCommand(c),
|
||||
NewRevisionGetCommand(c),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -109,3 +119,132 @@ func NewRevisionListCommand(c common.Args) *cobra.Command {
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewRevisionGetCommand gets specific revision of application
|
||||
func NewRevisionGetCommand(c common.Args) *cobra.Command {
|
||||
var outputFormat string
|
||||
ctx := context.Background()
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Aliases: []string{"get"},
|
||||
Short: "get specific revision of application",
|
||||
Long: "get specific revision of application",
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := args[0]
|
||||
def, err := cmd.Flags().GetString("definition")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return getRevision(ctx, c, outputFormat, cmd.OutOrStdout(), name, namespace, def)
|
||||
},
|
||||
}
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
cmd.Flags().StringP("definition", "d", "", "component definition")
|
||||
cmd.Flags().StringVarP(&outputFormat, "output", "o", "", "raw Application output format. One of: (json, yaml, jsonpath)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getRevision(ctx context.Context, c common.Args, format string, out io.Writer, name string, namespace string, def string) error {
|
||||
|
||||
kubeConfig, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dm, err := c.GetDiscoveryMapper()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pd, err := c.GetPackageDiscover()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := map[string]string{
|
||||
"name": name,
|
||||
"namespace": namespace,
|
||||
}
|
||||
query, err := velaql.ParseVelaQL(MakeVelaQL(revisionView, params, "status"))
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to parse ql string %s", err.Error())
|
||||
return fmt.Errorf(fmt.Sprintf("Unable to get application revision %s in namespace %s", name, namespace))
|
||||
}
|
||||
|
||||
queryValue, err := velaql.NewViewHandler(cli, kubeConfig, dm, pd).QueryView(ctx, query)
|
||||
if err != nil {
|
||||
log.Logger.Errorf("fail to query the view %s", err.Error())
|
||||
return fmt.Errorf(fmt.Sprintf("Unable to get application revision %s in namespace %s", name, namespace))
|
||||
}
|
||||
|
||||
apprev := v1beta1.ApplicationRevision{}
|
||||
err = queryValue.UnmarshalTo(&apprev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if apprev.CreationTimestamp.IsZero() {
|
||||
fmt.Fprintf(out, "No such application revision %s in namespace %s", name, namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
if def != "" {
|
||||
if cd, exist := apprev.Spec.ComponentDefinitions[def]; exist {
|
||||
ba, err := yaml.Marshal(&cd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, string(ba))
|
||||
} else {
|
||||
fmt.Fprintf(out, "No such definition %s", def)
|
||||
}
|
||||
} else {
|
||||
if format == "" {
|
||||
printApprev(out, apprev)
|
||||
} else {
|
||||
output, err := convertApplicationRevisionTo(format, &apprev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, output)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printApprev(out io.Writer, apprev v1beta1.ApplicationRevision) {
|
||||
table := newUITable().AddRow("NAME", "PUBLISH_VERSION", "SUCCEEDED", "HASH", "BEGIN_TIME", "STATUS", "SIZE")
|
||||
var begin, status, hash, size string
|
||||
status = "NotStart"
|
||||
if apprev.Status.Workflow != nil {
|
||||
begin = apprev.Status.Workflow.StartTime.Format("2006-01-02 15:04:05")
|
||||
// aggregate workflow result
|
||||
switch {
|
||||
case apprev.Status.Succeeded:
|
||||
status = "Succeeded"
|
||||
case apprev.Status.Workflow.Terminated || apprev.Status.Workflow.Suspend || apprev.Status.Workflow.Finished:
|
||||
status = "Failed"
|
||||
default:
|
||||
status = "Executing or Failed"
|
||||
}
|
||||
}
|
||||
if labels := apprev.GetLabels(); labels != nil {
|
||||
hash = apprev.GetLabels()[oam.LabelAppRevisionHash]
|
||||
}
|
||||
if bs, err := yaml.Marshal(apprev.Spec); err == nil {
|
||||
size = utils.ByteCountIEC(int64(len(bs)))
|
||||
}
|
||||
table.AddRow(apprev.Name, oam.GetPublishVersion(apprev.DeepCopy()), apprev.Status.Succeeded, hash, begin, status, size)
|
||||
fmt.Fprint(out, table.String())
|
||||
}
|
||||
|
||||
745
references/cli/revision_test.go
Normal file
745
references/cli/revision_test.go
Normal file
@@ -0,0 +1,745 @@
|
||||
/*
|
||||
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 cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
common2 "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/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var compDef string = `apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network traffic
|
||||
from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif
|
||||
v.subPath != _|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname:
|
||||
v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap
|
||||
{\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif v.subPath != _|_ {\n\t\t\t\t\tsubPath:
|
||||
v.subPath\n\t\t\t\t}\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret:
|
||||
*[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tif v.subPath != _|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname:
|
||||
v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif v.subPath != _|_ {\n\t\t\t\t\tsubPath:
|
||||
v.subPath\n\t\t\t\t}\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\tif v.subPath != _|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname:
|
||||
v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap:
|
||||
{\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath:
|
||||
*[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesList:
|
||||
volumesArray.pvc + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir
|
||||
+ volumesArray.hostPath\ndeDupVolumesArray: [\n\tfor val in [\n\t\tfor i,
|
||||
vi in volumesList {\n\t\t\tfor j, vj in volumesList if j < i && vi.name ==
|
||||
vj.name {\n\t\t\t\t_ignore: true\n\t\t\t}\n\t\t\tvi\n\t\t},\n\t] if val._ignore
|
||||
== _|_ {\n\t\tval\n\t},\n]\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind:
|
||||
\ \"Deployment\"\n\tspec: {\n\t\tselector: matchLabels: \"app.oam.dev/component\":
|
||||
context.name\n\n\t\ttemplate: {\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif
|
||||
parameter.labels != _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter.addRevisionLabel {\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/name\":
|
||||
\ context.appName\n\t\t\t\t\t\"app.oam.dev/component\": context.name\n\t\t\t\t}\n\t\t\t\tif
|
||||
parameter.annotations != _|_ {\n\t\t\t\t\tannotations: parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec:
|
||||
{\n\t\t\t\tcontainers: [{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage:
|
||||
parameter.image\n\t\t\t\t\tif parameter[\"port\"] != _|_ && parameter[\"ports\"]
|
||||
== _|_ {\n\t\t\t\t\t\tports: [{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy: parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cmd\"] != _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_ {\n\t\t\t\t\t\tvolumeMounts:
|
||||
[ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir + mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"livenessProbe\"] != _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe: parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif
|
||||
parameter[\"hostAliases\"] != _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases:
|
||||
parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif parameter[\"imagePullSecrets\"]
|
||||
!= _|_ {\n\t\t\t\t\timagePullSecrets: [ for v in parameter.imagePullSecrets
|
||||
{\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_ {\n\t\t\t\t\tvolumes:
|
||||
[ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\tif
|
||||
v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: deDupVolumesArray\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name ==
|
||||
_|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]: string\n\n\t//
|
||||
+usage=Specify the annotations in the workload\n\tannotations?: [string]:
|
||||
string\n\n\t// +usage=Which image would you like to use for your service\n\t//
|
||||
+short=i\n\timage: string\n\n\t// +usage=Specify image pull policy for your
|
||||
service\n\timagePullPolicy?: \"Always\" | \"Never\" | \"IfNotPresent\"\n\n\t//
|
||||
+usage=Specify image pull secrets for your service\n\timagePullSecrets?: [...string]\n\n\t//
|
||||
+ignore\n\t// +usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to, defaults
|
||||
to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose on the pod's
|
||||
IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?: string\n\t\t//
|
||||
+usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol: *\"TCP\"
|
||||
| \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should be exposed\n\t\texpose:
|
||||
*false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify what kind of Service
|
||||
you want. options: \"ClusterIP\", \"NodePort\", \"LoadBalancer\"\n\texposeType:
|
||||
*\"ClusterIP\" | \"NodePort\" | \"LoadBalancer\"\n\n\t// +ignore\n\t// +usage=If
|
||||
addRevisionLabel is true, the revision label will be added to the underlying
|
||||
pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run in
|
||||
the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments by using
|
||||
environment variables\n\tenv?: [...{\n\t\t// +usage=Environment variable name\n\t\tname:
|
||||
string\n\t\t// +usage=The value of the environment variable\n\t\tvalue?: string\n\t\t//
|
||||
+usage=Specifies a source the value of this var should come from\n\t\tvalueFrom?:
|
||||
{\n\t\t\t// +usage=Selects a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the secret in the pod's namespace to select
|
||||
from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the secret to select
|
||||
from. Must be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects
|
||||
a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?: {\n\t\t\t\t//
|
||||
+usage=The name of the config map in the pod's namespace to select from\n\t\t\t\tname:
|
||||
string\n\t\t\t\t// +usage=The key of the config map to select from. Must be
|
||||
a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number
|
||||
of CPU units for the service\n\tcpu?: string\n\n\t// +usage=Specifies the
|
||||
attributes of the memory resource required for the container.\n\tmemory?:
|
||||
string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount PVC type volume\n\t\tpvc?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tsubPath?: string\n\t\t\t//
|
||||
+usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t// +usage=Mount
|
||||
ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
\ string\n\t\t\tsubPath?: string\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName:
|
||||
\ string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode:
|
||||
*511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount Secret type volume\n\t\tsecret?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tsubPath?:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t//
|
||||
+usage=Mount EmptyDir type volume\n\t\temptyDir?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\tsubPath?: string\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}]\n\t\t//
|
||||
+usage=Mount HostPath type volume\n\t\thostPath?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\tsubPath?: string\n\t\t\tpath: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Deprecated field, use volumeMounts instead.\n\tvolumes?: [...{\n\t\tname:
|
||||
\ string\n\t\tmountPath: string\n\t\t// +usage=Specify volume type, options:
|
||||
\"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype: \"pvc\" | \"configMap\"
|
||||
| \"secret\" | \"emptyDir\"\n\t\tif type == \"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif
|
||||
type == \"configMap\" {\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"secret\" {\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?:
|
||||
#HealthProbe\n\n\t// +usage=Instructions for assessing whether the container
|
||||
is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip: string\n\t\thostnames:
|
||||
[...string]\n\t}]\n}\n#HealthProbe: {\n\n\t// +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.\n\texec?:
|
||||
{\n\t\t// +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.\n\t\tcommand: [...string]\n\t}\n\n\t//
|
||||
+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.\n\thttpGet?: {\n\t\t// +usage=The endpoint,
|
||||
relative to the port, to which the HTTP GET request should be directed.\n\t\tpath:
|
||||
string\n\t\t// +usage=The TCP socket within the container to which the HTTP
|
||||
GET request should be directed.\n\t\tport: int\n\t\thost?: string\n\t\tscheme?:
|
||||
*\"HTTP\" | string\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue:
|
||||
string\n\t\t}]\n\t}\n\n\t// +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.\n\ttcpSocket?: {\n\t\t//
|
||||
+usage=The TCP socket within the container that should be probed to assess
|
||||
container health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after
|
||||
the container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times out.\n\ttimeoutSeconds:
|
||||
*1 | int\n\n\t// +usage=Minimum consecutive successes for the probe to be
|
||||
considered successful after having failed.\n\tsuccessThreshold: *1 | int\n\n\t//
|
||||
+usage=Number of consecutive failures required to determine the container
|
||||
is not alive (liveness probe) or not ready (readiness probe).\n\tfailureThreshold:
|
||||
*3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas: *0
|
||||
| int\n\treplicas: *0 | int\n\tobservedGeneration: *0 | int\n} & {\n\tif
|
||||
context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas: context.output.status.updatedReplicas\n\t}\n\tif
|
||||
context.output.status.readyReplicas != _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration: context.output.status.observedGeneration\n\t}\n}\nisHealth:
|
||||
(context.output.spec.replicas == ready.readyReplicas) && (context.output.spec.replicas
|
||||
== ready.updatedReplicas) && (context.output.spec.replicas == ready.replicas)
|
||||
&& (ready.observedGeneration == context.output.metadata.generation || ready.observedGeneration
|
||||
> context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
`
|
||||
|
||||
var firstVelaAppRev string = `
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
annotations:
|
||||
oam.dev/kubevela-version: v1.5.2
|
||||
generation: 1
|
||||
labels:
|
||||
app.oam.dev/app-revision-hash: 1c3d847600ac0514
|
||||
app.oam.dev/name: first-vela-app
|
||||
name: first-vela-app-v1
|
||||
namespace: vela-system
|
||||
spec:
|
||||
application:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
finalizers:
|
||||
- app.oam.dev/resource-tracker-finalizer
|
||||
name: first-vela-app
|
||||
namespace: vela-system
|
||||
spec:
|
||||
components:
|
||||
- name: express-server
|
||||
properties:
|
||||
image: oamdev/hello-world
|
||||
ports:
|
||||
- expose: true
|
||||
port: 8000
|
||||
traits:
|
||||
- properties:
|
||||
replicas: 1
|
||||
type: scaler
|
||||
type: webservice
|
||||
status: {}
|
||||
componentDefinitions:
|
||||
webservice:
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: ComponentDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
definition.oam.dev/description: Describes long-running, scalable, containerized
|
||||
services that have a stable network endpoint to receive external network
|
||||
traffic from customers.
|
||||
meta.helm.sh/release-name: kubevela
|
||||
meta.helm.sh/release-namespace: vela-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
name: webservice
|
||||
namespace: vela-system
|
||||
spec:
|
||||
schematic:
|
||||
cue:
|
||||
template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor
|
||||
v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif
|
||||
v.subPath != _|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname:
|
||||
v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor v in
|
||||
parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif
|
||||
v.subPath != _|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname:
|
||||
v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret
|
||||
{\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif v.subPath !=
|
||||
_|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif v.subPath !=
|
||||
_|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tif v.subPath !=
|
||||
_|_ {\n\t\t\t\t\tsubPath: v.subPath\n\t\t\t\t}\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\nvolumesArray: {\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tpersistentVolumeClaim: claimName:
|
||||
v.claimName\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor
|
||||
v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap:
|
||||
{\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname:
|
||||
v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName:
|
||||
\ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath
|
||||
{\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t]
|
||||
| []\n}\nvolumesList: volumesArray.pvc + volumesArray.configMap + volumesArray.secret
|
||||
+ volumesArray.emptyDir + volumesArray.hostPath\ndeDupVolumesArray:
|
||||
[\n\tfor val in [\n\t\tfor i, vi in volumesList {\n\t\t\tfor j, vj in
|
||||
volumesList if j < i && vi.name == vj.name {\n\t\t\t\t_ignore: true\n\t\t\t}\n\t\t\tvi\n\t\t},\n\t]
|
||||
if val._ignore == _|_ {\n\t\tval\n\t},\n]\noutput: {\n\tapiVersion:
|
||||
\"apps/v1\"\n\tkind: \"Deployment\"\n\tspec: {\n\t\tselector:
|
||||
matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate:
|
||||
{\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels
|
||||
!= _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel
|
||||
{\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/name\":
|
||||
\ context.appName\n\t\t\t\t\t\"app.oam.dev/component\": context.name\n\t\t\t\t}\n\t\t\t\tif
|
||||
parameter.annotations != _|_ {\n\t\t\t\t\tannotations: parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec:
|
||||
{\n\t\t\t\tcontainers: [{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage:
|
||||
parameter.image\n\t\t\t\t\tif parameter[\"port\"] != _|_ && parameter[\"ports\"]
|
||||
== _|_ {\n\t\t\t\t\t\tports: [{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif
|
||||
parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports
|
||||
{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol:
|
||||
\ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname:
|
||||
\"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy:
|
||||
parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits:
|
||||
memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath:
|
||||
v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc
|
||||
+ mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir
|
||||
+ mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"]
|
||||
!= _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif
|
||||
parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe:
|
||||
parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"]
|
||||
!= _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets:
|
||||
[ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_
|
||||
{\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname:
|
||||
v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim:
|
||||
claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type ==
|
||||
\"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode:
|
||||
v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif
|
||||
v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif
|
||||
v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: deDupVolumesArray\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts:
|
||||
[\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort:
|
||||
v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name
|
||||
== _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs:
|
||||
{\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion:
|
||||
\"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec:
|
||||
{\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports:
|
||||
exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter:
|
||||
{\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]:
|
||||
string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?:
|
||||
[string]: string\n\n\t// +usage=Which image would you like to use for
|
||||
your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify
|
||||
image pull policy for your service\n\timagePullPolicy?: \"Always\" |
|
||||
\"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets
|
||||
for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t//
|
||||
+usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?:
|
||||
int\n\n\t// +usage=Which ports do you want customer traffic sent to,
|
||||
defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose
|
||||
on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?:
|
||||
string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol:
|
||||
*\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should
|
||||
be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify
|
||||
what kind of Service you want. options: \"ClusterIP\", \"NodePort\",
|
||||
\"LoadBalancer\"\n\texposeType: *\"ClusterIP\" | \"NodePort\" | \"LoadBalancer\"\n\n\t//
|
||||
+ignore\n\t// +usage=If addRevisionLabel is true, the revision label
|
||||
will be added to the underlying pods\n\taddRevisionLabel: *false | bool\n\n\t//
|
||||
+usage=Commands to run in the container\n\tcmd?: [...string]\n\n\t//
|
||||
+usage=Define arguments by using environment variables\n\tenv?: [...{\n\t\t//
|
||||
+usage=Environment variable name\n\t\tname: string\n\t\t// +usage=The
|
||||
value of the environment variable\n\t\tvalue?: string\n\t\t// +usage=Specifies
|
||||
a source the value of this var should come from\n\t\tvalueFrom?: {\n\t\t\t//
|
||||
+usage=Selects a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the secret in the pod's namespace to
|
||||
select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
secret to select from. Must be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t//
|
||||
+usage=Selects a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?:
|
||||
{\n\t\t\t\t// +usage=The name of the config map in the pod's namespace
|
||||
to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the
|
||||
config map to select from. Must be a valid secret key\n\t\t\t\tkey:
|
||||
string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for
|
||||
the service\n\tcpu?: string\n\n\t//
|
||||
+usage=Specifies the attributes of the memory resource required for
|
||||
the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount
|
||||
PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\tsubPath?: string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName:
|
||||
string\n\t\t}]\n\t\t// +usage=Mount ConfigMap type volume\n\t\tconfigMap?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tsubPath?:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount Secret type volume\n\t\tsecret?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tsubPath?:
|
||||
\ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?:
|
||||
[...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511
|
||||
| int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tsubPath?:
|
||||
\ string\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount
|
||||
HostPath type volume\n\t\thostPath?: [...{\n\t\t\tname: string\n\t\t\tmountPath:
|
||||
string\n\t\t\tsubPath?: string\n\t\t\tpath: string\n\t\t}]\n\t}\n\n\t//
|
||||
+usage=Deprecated field, use volumeMounts instead.\n\tvolumes?: [...{\n\t\tname:
|
||||
\ string\n\t\tmountPath: string\n\t\t// +usage=Specify volume type,
|
||||
options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype: \"pvc\"
|
||||
| \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type == \"pvc\"
|
||||
{\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\" {\n\t\t\tdefaultMode:
|
||||
*420 | int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey:
|
||||
\ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif
|
||||
type == \"secret\" {\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName:
|
||||
\ string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode:
|
||||
*511 | int\n\t\t\t}]\n\t\t}\n\t\tif type == \"emptyDir\" {\n\t\t\tmedium:
|
||||
*\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t// +usage=Instructions for assessing
|
||||
whether the container is alive.\n\tlivenessProbe?: #HealthProbe\n\n\t//
|
||||
+usage=Instructions for assessing whether the container is in a suitable
|
||||
state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t// +usage=Specify
|
||||
the hostAliases to add\n\thostAliases?: [...{\n\t\tip: string\n\t\thostnames:
|
||||
[...string]\n\t}]\n}\n#HealthProbe: {\n\n\t// +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.\n\texec?: {\n\t\t// +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.\n\t\tcommand: [...string]\n\t}\n\n\t// +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.\n\thttpGet?: {\n\t\t// +usage=The
|
||||
endpoint, relative to the port, to which the HTTP GET request should
|
||||
be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket within
|
||||
the container to which the HTTP GET request should be directed.\n\t\tport:
|
||||
\ int\n\t\thost?: string\n\t\tscheme?: *\"HTTP\" | string\n\t\thttpHeaders?:
|
||||
[...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t//
|
||||
+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.\n\ttcpSocket?: {\n\t\t// +usage=The
|
||||
TCP socket within the container that should be probed to assess container
|
||||
health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the
|
||||
container is started before the first probe is initiated.\n\tinitialDelaySeconds:
|
||||
*0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds:
|
||||
*10 | int\n\n\t// +usage=Number of seconds after which the probe times
|
||||
out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive
|
||||
successes for the probe to be considered successful after having failed.\n\tsuccessThreshold:
|
||||
*1 | int\n\n\t// +usage=Number of consecutive failures required to determine
|
||||
the container is not alive (liveness probe) or not ready (readiness
|
||||
probe).\n\tfailureThreshold: *3 | int\n}\n"
|
||||
status:
|
||||
customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage:
|
||||
\"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\""
|
||||
healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas:
|
||||
\ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration:
|
||||
*0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas:
|
||||
context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas
|
||||
!= _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif
|
||||
context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif
|
||||
context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration:
|
||||
context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas
|
||||
== ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas)
|
||||
&& (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration
|
||||
== context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)"
|
||||
workload:
|
||||
definition:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
type: deployments.apps
|
||||
status: {}
|
||||
|
||||
status: {}
|
||||
`
|
||||
|
||||
var _ = Describe("Test getRevision", func() {
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
arg common.Args
|
||||
name string
|
||||
namespace string
|
||||
format string
|
||||
out *bytes.Buffer
|
||||
def string
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
// delete application and view if exist
|
||||
app := v1beta1.ApplicationRevision{}
|
||||
Expect(yaml.Unmarshal([]byte(firstVelaAppRev), &app)).Should(BeNil())
|
||||
_ = k8sClient.Delete(context.TODO(), &app)
|
||||
_ = k8sClient.Delete(context.TODO(), &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: revisionView,
|
||||
Namespace: types.DefaultKubeVelaNS,
|
||||
}})
|
||||
|
||||
// prepare args
|
||||
ctx = context.Background()
|
||||
format = ""
|
||||
out = &bytes.Buffer{}
|
||||
arg = common.Args{}
|
||||
arg.SetConfig(cfg)
|
||||
arg.SetClient(k8sClient)
|
||||
name = "first-vela-app-v1"
|
||||
namespace = types.DefaultKubeVelaNS
|
||||
def = ""
|
||||
})
|
||||
|
||||
It("Test no pre-defined view", func() {
|
||||
err := getRevision(ctx, arg, format, out, name, namespace, def)
|
||||
Expect(err).ToNot(Succeed())
|
||||
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("Unable to get application revision %s in namespace %s", name, namespace)))
|
||||
})
|
||||
|
||||
It("Test with no application revision existing", func() {
|
||||
|
||||
// setup view
|
||||
setupView()
|
||||
|
||||
err := getRevision(ctx, arg, format, out, name, namespace, def)
|
||||
Expect(err).To(Succeed())
|
||||
Expect(out.String()).To(Equal(fmt.Sprintf("No such application revision %s in namespace %s", name, namespace)))
|
||||
})
|
||||
|
||||
It("Test normal case with default output", func() {
|
||||
|
||||
// setup view
|
||||
setupView()
|
||||
|
||||
// setup application
|
||||
app := v1beta1.ApplicationRevision{}
|
||||
Expect(yaml.Unmarshal([]byte(firstVelaAppRev), &app)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.TODO(), &app)).Should(BeNil())
|
||||
|
||||
Expect(getRevision(ctx, arg, format, out, name, namespace, def)).To(Succeed())
|
||||
table := newUITable().AddRow("NAME", "PUBLISH_VERSION", "SUCCEEDED", "HASH", "BEGIN_TIME", "STATUS", "SIZE")
|
||||
table.AddRow("first-vela-app-v1", "", "false", "1c3d847600ac0514", "", "NotStart", "19.1 KiB")
|
||||
Expect(out.String()).To(Equal(table.String()))
|
||||
})
|
||||
|
||||
It("Test normal case with yaml format", func() {
|
||||
|
||||
// setup view
|
||||
setupView()
|
||||
|
||||
// setup application
|
||||
app := v1beta1.ApplicationRevision{}
|
||||
Expect(yaml.Unmarshal([]byte(firstVelaAppRev), &app)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.TODO(), &app)).Should(BeNil())
|
||||
|
||||
// override args
|
||||
format = "yaml"
|
||||
|
||||
Expect(getRevision(ctx, arg, format, out, name, namespace, def)).To(Succeed())
|
||||
Expect(out.String()).Should(SatisfyAll(
|
||||
ContainSubstring("app.oam.dev/name: first-vela-app"),
|
||||
ContainSubstring("name: first-vela-app-v1"),
|
||||
ContainSubstring("- name: express-server"),
|
||||
ContainSubstring("succeeded: false"),
|
||||
))
|
||||
})
|
||||
|
||||
It("Test normal case with returning definition", func() {
|
||||
|
||||
// setup view
|
||||
setupView()
|
||||
|
||||
// setup application
|
||||
app := v1beta1.ApplicationRevision{}
|
||||
Expect(yaml.Unmarshal([]byte(firstVelaAppRev), &app)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.TODO(), &app)).Should(BeNil())
|
||||
|
||||
// override args
|
||||
def = "webservice"
|
||||
|
||||
Expect(getRevision(ctx, arg, format, out, name, namespace, def)).To(Succeed())
|
||||
Expect(out.String()).Should(Equal(compDef))
|
||||
})
|
||||
|
||||
It("Test normal case with returning unknown definition", func() {
|
||||
|
||||
// setup view
|
||||
setupView()
|
||||
|
||||
// setup application
|
||||
app := v1beta1.ApplicationRevision{}
|
||||
Expect(yaml.Unmarshal([]byte(firstVelaAppRev), &app)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.TODO(), &app)).Should(BeNil())
|
||||
|
||||
// prepare args
|
||||
def = "webservice1"
|
||||
|
||||
Expect(getRevision(ctx, arg, format, out, name, namespace, def)).To(Succeed())
|
||||
Expect(out.String()).Should(Equal(fmt.Sprintf("No such definition %s", def)))
|
||||
})
|
||||
})
|
||||
|
||||
func TestPrintApprev(t *testing.T) {
|
||||
|
||||
tiFormat := "2006-01-02T15:04:05.000Z"
|
||||
tiStr := "2022-08-12T11:45:26.371Z"
|
||||
ti, err := time.Parse(tiFormat, tiStr)
|
||||
assert.Nil(t, err)
|
||||
|
||||
cases := map[string]struct {
|
||||
out *bytes.Buffer
|
||||
apprev v1beta1.ApplicationRevision
|
||||
exp string
|
||||
}{
|
||||
"NotStart": {out: &bytes.Buffer{}, apprev: v1beta1.ApplicationRevision{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-apprev0",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{},
|
||||
Status: v1beta1.ApplicationRevisionStatus{},
|
||||
}, exp: tableOut("test-apprev0", "", "false", "", "", "NotStart", "95 B"),
|
||||
},
|
||||
"Succeeded": {out: &bytes.Buffer{}, apprev: v1beta1.ApplicationRevision{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-apprev1",
|
||||
Namespace: "dev1",
|
||||
Labels: map[string]string{
|
||||
oam.LabelAppRevisionHash: "1111231adfdf",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app1",
|
||||
Namespace: "dev2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1beta1.ApplicationRevisionStatus{
|
||||
Workflow: &common2.WorkflowStatus{
|
||||
StartTime: metav1.Time{
|
||||
Time: ti,
|
||||
},
|
||||
},
|
||||
Succeeded: true,
|
||||
},
|
||||
}, exp: tableOut("test-apprev1", "", "true", "1111231adfdf", "2022-08-12 11:45:26", "Succeeded", "135 B")},
|
||||
"Failed": {out: &bytes.Buffer{}, apprev: v1beta1.ApplicationRevision{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-apprev2",
|
||||
Namespace: "dev2",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{},
|
||||
Status: v1beta1.ApplicationRevisionStatus{
|
||||
Workflow: &common2.WorkflowStatus{
|
||||
StartTime: metav1.Time{
|
||||
Time: ti,
|
||||
},
|
||||
Terminated: true,
|
||||
},
|
||||
},
|
||||
}, exp: tableOut("test-apprev2", "", "false", "", "2022-08-12 11:45:26", "Failed", "95 B"),
|
||||
},
|
||||
"Executing or Failed": {out: &bytes.Buffer{}, apprev: v1beta1.ApplicationRevision{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-apprev3",
|
||||
Namespace: "dev3",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{},
|
||||
Status: v1beta1.ApplicationRevisionStatus{
|
||||
Workflow: &common2.WorkflowStatus{
|
||||
StartTime: metav1.Time{
|
||||
Time: ti,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: tableOut("test-apprev3", "", "false", "", "2022-08-12 11:45:26", "Executing or Failed", "95 B"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
printApprev(tc.out, tc.apprev)
|
||||
//fmt.Println(tc.out.String())
|
||||
diff := cmp.Diff(tc.exp, tc.out.String())
|
||||
if diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func tableOut(name, pv, s, hash, bt, status, size string) string {
|
||||
table := newUITable().AddRow("NAME", "PUBLISH_VERSION", "SUCCEEDED", "HASH", "BEGIN_TIME", "STATUS", "SIZE")
|
||||
table.AddRow(name, pv, s, hash, bt, status, size)
|
||||
return table.String()
|
||||
}
|
||||
|
||||
func setupView() {
|
||||
viewContent, err := ioutil.ReadFile("../../charts/vela-core/templates/velaql/application-revision.yaml")
|
||||
Expect(err).Should(BeNil())
|
||||
viewContent = bytes.ReplaceAll(viewContent, []byte("{{ include \"systemDefinitionNamespace\" . }}"), []byte(types.DefaultKubeVelaNS))
|
||||
cm := &v1.ConfigMap{}
|
||||
Expect(yaml.Unmarshal(viewContent, cm)).Should(BeNil())
|
||||
Expect(k8sClient.Create(context.TODO(), cm)).Should(BeNil())
|
||||
}
|
||||
@@ -229,3 +229,59 @@ func AskToChooseOneService(services []types.ResourceItem, selectPort bool) (*typ
|
||||
// it should never happen.
|
||||
return nil, 0, errors.New("no service match for your choice")
|
||||
}
|
||||
|
||||
func convertApplicationRevisionTo(format string, apprev *v1beta1.ApplicationRevision) (string, error) {
|
||||
var ret string
|
||||
|
||||
if format == "" {
|
||||
return "", fmt.Errorf("no format provided")
|
||||
}
|
||||
|
||||
// No, we don't want managedFields, get rid of it.
|
||||
apprev.ManagedFields = nil
|
||||
|
||||
switch format {
|
||||
case "yaml":
|
||||
b, err := yaml.Marshal(apprev)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
case "json":
|
||||
b, err := json.MarshalIndent(apprev, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = string(b)
|
||||
default:
|
||||
// format is not any of json/yaml/jsonpath, not supported
|
||||
if !strings.HasPrefix(format, "jsonpath") {
|
||||
return "", fmt.Errorf("%s is not supported", format)
|
||||
}
|
||||
|
||||
// format = jsonpath
|
||||
s := strings.Split(format, "=")
|
||||
if len(s) < 2 {
|
||||
return "", fmt.Errorf("jsonpath template format specified but no template given")
|
||||
}
|
||||
path, err := get.RelaxedJSONPathExpression(s[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jp := jsonpath.New("").AllowMissingKeys(true)
|
||||
err = jp.Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = jp.Execute(buf, apprev)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ret = buf.String()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"gotest.tools/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@@ -88,3 +90,146 @@ status: {}
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, str, "core.oam.dev/v1beta1")
|
||||
}
|
||||
|
||||
func TestConvertApplicationRevisionTo(t *testing.T) {
|
||||
|
||||
type Exp struct {
|
||||
out string
|
||||
err string
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
format string
|
||||
apprev *v1beta1.ApplicationRevision
|
||||
exp Exp
|
||||
}{
|
||||
"no format": {format: "", apprev: &v1beta1.ApplicationRevision{}, exp: Exp{out: "", err: "no format provided"}},
|
||||
"no support format": {format: "jsonnet", apprev: &v1beta1.ApplicationRevision{}, exp: Exp{out: "", err: "jsonnet is not supported"}},
|
||||
"yaml": {format: "yaml", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-apprev",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: `apiVersion: core.oam.dev/v1beta1
|
||||
kind: ApplicationRevision
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test-apprev
|
||||
namespace: dev
|
||||
spec:
|
||||
application:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: test-app
|
||||
namespace: dev
|
||||
spec:
|
||||
components: null
|
||||
status: {}
|
||||
status:
|
||||
succeeded: false
|
||||
`, err: ""}},
|
||||
"json": {format: "json", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-apprev",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: `{
|
||||
"kind": "ApplicationRevision",
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"metadata": {
|
||||
"name": "test-apprev",
|
||||
"namespace": "dev",
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {
|
||||
"application": {
|
||||
"metadata": {
|
||||
"name": "test-app",
|
||||
"namespace": "dev",
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"spec": {
|
||||
"components": null
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"succeeded": false
|
||||
}
|
||||
}`, err: ""}},
|
||||
"jsonpath": {format: "jsonpath={.apiVersion}", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-apprev",
|
||||
Namespace: "dev",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: "core.oam.dev/v1beta1", err: ""}},
|
||||
"jsonpath with error": {format: "jsonpath", apprev: &v1beta1.ApplicationRevision{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "ApplicationRevision",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-apprev",
|
||||
Namespace: "dev1",
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "dev1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, exp: Exp{out: "", err: "jsonpath template format specified but no template given"}},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out, err := convertApplicationRevisionTo(tc.format, tc.apprev)
|
||||
if err != nil {
|
||||
assert.Equal(t, tc.exp.err, err.Error())
|
||||
}
|
||||
diff := cmp.Diff(tc.exp.out, out)
|
||||
if diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user