diff --git a/charts/vela-core/templates/defwithtemplate/deploy2runtime.yaml b/charts/vela-core/templates/defwithtemplate/deploy2runtime.yaml new file mode 100644 index 000000000..5b007b43d --- /dev/null +++ b/charts/vela-core/templates/defwithtemplate/deploy2runtime.yaml @@ -0,0 +1,44 @@ +# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file. +# Definition source cue file: vela-templates/definitions/internal/deploy2runtime.cue +apiVersion: core.oam.dev/v1beta1 +kind: WorkflowStepDefinition +metadata: + annotations: + definition.oam.dev/description: Deploy application to runtime clusters + name: deploy2runtime + namespace: {{.Values.systemDefinitionNamespace}} +spec: + schematic: + cue: + template: | + import ( + "vela/op" + ) + + app: op.#Steps & { + load: op.#Load @step(1) + clusters: [...string] + if parameter.clusters == _|_ { + listClusters: op.#ListClusters @step(2) + clusters: listClusters.outputs.clusters + } + if parameter.clusters != _|_ { + clusters: parameter.clusters + } + + apply: op.#Steps & { + for _, cluster_ in clusters { + for name, c in load.value { + "\(cluster_)-\(name)": op.#ApplyComponent & { + value: c + cluster: cluster_ + } @step(3) + } + } + } + } + parameter: { + // +usage=Declare the runtime clusters to apply, if empty, all runtime clusters will be used + clusters?: [...string] + } + diff --git a/charts/vela-minimal/templates/defwithtemplate/deploy2runtime.yaml b/charts/vela-minimal/templates/defwithtemplate/deploy2runtime.yaml new file mode 100644 index 000000000..5b007b43d --- /dev/null +++ b/charts/vela-minimal/templates/defwithtemplate/deploy2runtime.yaml @@ -0,0 +1,44 @@ +# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file. +# Definition source cue file: vela-templates/definitions/internal/deploy2runtime.cue +apiVersion: core.oam.dev/v1beta1 +kind: WorkflowStepDefinition +metadata: + annotations: + definition.oam.dev/description: Deploy application to runtime clusters + name: deploy2runtime + namespace: {{.Values.systemDefinitionNamespace}} +spec: + schematic: + cue: + template: | + import ( + "vela/op" + ) + + app: op.#Steps & { + load: op.#Load @step(1) + clusters: [...string] + if parameter.clusters == _|_ { + listClusters: op.#ListClusters @step(2) + clusters: listClusters.outputs.clusters + } + if parameter.clusters != _|_ { + clusters: parameter.clusters + } + + apply: op.#Steps & { + for _, cluster_ in clusters { + for name, c in load.value { + "\(cluster_)-\(name)": op.#ApplyComponent & { + value: c + cluster: cluster_ + } @step(3) + } + } + } + } + parameter: { + // +usage=Declare the runtime clusters to apply, if empty, all runtime clusters will be used + clusters?: [...string] + } + diff --git a/cmd/apiserver/main.go b/cmd/apiserver/main.go index 6b5f253cf..d211b6186 100644 --- a/cmd/apiserver/main.go +++ b/cmd/apiserver/main.go @@ -28,8 +28,8 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" "github.com/go-openapi/spec" - "github.com/google/uuid" + "github.com/oam-dev/kubevela/pkg/apiserver/log" "github.com/oam-dev/kubevela/pkg/apiserver/rest" "github.com/oam-dev/kubevela/version" diff --git a/docs/examples/multicluster/deploy2runtime.yaml b/docs/examples/multicluster/deploy2runtime.yaml new file mode 100644 index 000000000..3461f21a8 --- /dev/null +++ b/docs/examples/multicluster/deploy2runtime.yaml @@ -0,0 +1,21 @@ +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: runtime-app + namespace: default +spec: + components: + - name: default-webservice + type: webservice + properties: + image: crccheck/hello-world + port: 8000 + + workflow: + steps: + # by default, this step will deploy this application to all runtime clusters + - name: deploy-all + type: deploy2runtime + # uncomment the following part to deploy this application only to selected clusters + # properties: + # clusters: ["cluster-a", "cluster-b"] diff --git a/pkg/apiserver/model/application.go b/pkg/apiserver/model/application.go index 00dbb2779..32323277f 100644 --- a/pkg/apiserver/model/application.go +++ b/pkg/apiserver/model/application.go @@ -191,7 +191,7 @@ var RevisionStatusComplete = "complete" // RevisionStatusFail event status failure var RevisionStatusFail = "failure" -// RevisionStatusTerminated event status terminated +// RevisionStatusTerminated event status terminated var RevisionStatusTerminated = "terminated" // ApplicationRevision be created when an application initiates deployment and describes the phased version of the application. diff --git a/pkg/multicluster/utils.go b/pkg/multicluster/utils.go index 4cff7f6ff..06b19193d 100644 --- a/pkg/multicluster/utils.go +++ b/pkg/multicluster/utils.go @@ -170,3 +170,12 @@ func GetMulticlusterKubernetesClient() (client.Client, *rest.Config, error) { k8sClient, err := Initialize(k8sConfig, false) return k8sClient, k8sConfig, err } + +// ListExistingClusterSecrets list existing cluster secrets +func ListExistingClusterSecrets(ctx context.Context, c client.Client) ([]v1.Secret, error) { + secrets := &v1.SecretList{} + if err := c.List(ctx, secrets, client.InNamespace(ClusterGatewaySecretNamespace), client.HasLabels{v1alpha1.LabelKeyClusterCredentialType}); err != nil { + return nil, errors2.Wrapf(err, "failed to list cluster secrets") + } + return secrets.Items, nil +} diff --git a/pkg/stdlib/op.cue b/pkg/stdlib/op.cue index 923c25ed9..b3b4f39a4 100644 --- a/pkg/stdlib/op.cue +++ b/pkg/stdlib/op.cue @@ -107,6 +107,14 @@ import ( #ApplyEnvBindApp: multicluster.#ApplyEnvBindApp +#LoadPolicies: oam.#LoadPolicies + +#ListClusters: multicluster.#ListClusters + +#MakePlacementDecisions: multicluster.#MakePlacementDecisions + +#PatchApplication: multicluster.#PatchApplication + #HTTPGet: http.#Do & {method: "GET"} #HTTPPost: http.#Do & {method: "POST"} diff --git a/pkg/stdlib/pkgs/multicluster.cue b/pkg/stdlib/pkgs/multicluster.cue index 7b65c6cef..119c54231 100644 --- a/pkg/stdlib/pkgs/multicluster.cue +++ b/pkg/stdlib/pkgs/multicluster.cue @@ -30,8 +30,8 @@ #do: "read-placement-decisions" inputs: { - policy: string - envName: string + policyName: string + envName: string } outputs: { @@ -123,3 +123,12 @@ } } } + +#ListClusters: { + #provider: "multicluster" + #do: "list-clusters" + + outputs: { + clusters: [...string] + } +} diff --git a/pkg/workflow/providers/multicluster/multicluster.go b/pkg/workflow/providers/multicluster/multicluster.go index 50952327e..39dd45995 100644 --- a/pkg/workflow/providers/multicluster/multicluster.go +++ b/pkg/workflow/providers/multicluster/multicluster.go @@ -17,6 +17,8 @@ limitations under the License. package multicluster import ( + "context" + "github.com/pkg/errors" "sigs.k8s.io/controller-runtime/pkg/client" @@ -146,6 +148,18 @@ func (p *provider) PatchApplication(ctx wfContext.Context, v *value.Value, act w return v.FillObject(newApp, "outputs") } +func (p *provider) ListClusters(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error { + secrets, err := multicluster.ListExistingClusterSecrets(context.Background(), p.Client) + if err != nil { + return err + } + var clusters []string + for _, secret := range secrets { + clusters = append(clusters, secret.Name) + } + return v.FillObject(clusters, "outputs", "clusters") +} + // Install register handlers to provider discover. func Install(p providers.Providers, c client.Client, app *v1beta1.Application) { prd := &provider{Client: c, app: app} @@ -153,5 +167,6 @@ func Install(p providers.Providers, c client.Client, app *v1beta1.Application) { "read-placement-decisions": prd.ReadPlacementDecisions, "make-placement-decisions": prd.MakePlacementDecisions, "patch-application": prd.PatchApplication, + "list-clusters": prd.ListClusters, }) } diff --git a/pkg/workflow/providers/multicluster/multicluster_test.go b/pkg/workflow/providers/multicluster/multicluster_test.go index 4c5e236f4..53c97f743 100644 --- a/pkg/workflow/providers/multicluster/multicluster_test.go +++ b/pkg/workflow/providers/multicluster/multicluster_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "testing" + v1alpha12 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" v12 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -477,3 +478,33 @@ func TestPatchApplication(t *testing.T) { } } } + +func TestListClusters(t *testing.T) { + multicluster.ClusterGatewaySecretNamespace = types.DefaultKubeVelaNS + r := require.New(t) + cli := fake.NewClientBuilder().WithScheme(common.Scheme).Build() + clusterNames := []string{"cluster-a", "cluster-b"} + for _, secretName := range clusterNames { + secret := &v1.Secret{} + secret.Name = secretName + secret.Namespace = multicluster.ClusterGatewaySecretNamespace + secret.Labels = map[string]string{v1alpha12.LabelKeyClusterCredentialType: "X509"} + r.NoError(cli.Create(context.Background(), secret)) + } + app := &v1beta1.Application{} + p := &provider{ + Client: cli, + app: app, + } + act := &mock.Action{} + v, err := value.NewValue("", nil, "") + r.NoError(err) + r.NoError(p.ListClusters(nil, v, act)) + outputs, err := v.LookupValue("outputs") + r.NoError(err) + obj := struct { + Clusters []string `json:"clusters"` + }{} + r.NoError(outputs.UnmarshalTo(&obj)) + r.Equal(clusterNames, obj.Clusters) +} diff --git a/pkg/workflow/providers/oam/apply.go b/pkg/workflow/providers/oam/apply.go index 51b942cdc..be767abb2 100644 --- a/pkg/workflow/providers/oam/apply.go +++ b/pkg/workflow/providers/oam/apply.go @@ -50,7 +50,7 @@ type provider struct { app *v1beta1.Application } -// ApplyComponent apply component. +// RenderComponent render component func (p *provider) RenderComponent(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error { comp, patcher, clusterName, overrideNamespace, err := lookUpValues(v) if err != nil { diff --git a/vela-templates/definitions/internal/deploy2runtime.cue b/vela-templates/definitions/internal/deploy2runtime.cue new file mode 100644 index 000000000..b7862e878 --- /dev/null +++ b/vela-templates/definitions/internal/deploy2runtime.cue @@ -0,0 +1,39 @@ +import ( + "vela/op" +) + +"deploy2runtime": { + type: "workflow-step" + annotations: {} + labels: {} + description: "Deploy application to runtime clusters" +} +template: { + app: op.#Steps & { + load: op.#Load @step(1) + clusters: [...string] + if parameter.clusters == _|_ { + listClusters: op.#ListClusters @step(2) + clusters: listClusters.outputs.clusters + } + if parameter.clusters != _|_ { + clusters: parameter.clusters + } + + apply: op.#Steps & { + for _, cluster_ in clusters { + for name, c in load.value { + "\(cluster_)-\(name)": op.#ApplyComponent & { + value: c + cluster: cluster_ + } @step(3) + } + } + } + } + + parameter: { + // +usage=Declare the runtime clusters to apply, if empty, all runtime clusters will be used + clusters?: [...string] + } +}