Fix: support multiple-tenant cloud resources (#3305)

* Fix: support multiple-tenant cloud resources

Split terraform-xxx to two parts, the original addon will only install
definitions, and the provider object will be created by `vela provider`

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

* continue

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

* continue 0302

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

* support `vela provider list`

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

* generate a configmap in an addon terraform-xxx

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

* fix listing providers

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

* support add a provider

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

* support delete a provider

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

* support legacy providers

Signed-off-by: Zheng Xi Zhou <zzxwill@gmail.com>
This commit is contained in:
Zheng Xi Zhou
2022-03-28 21:55:33 +08:00
committed by GitHub
parent 2af198bda7
commit 5d61cee3f6
2 changed files with 450 additions and 0 deletions

View File

@@ -101,6 +101,7 @@ func NewCommand() *cobra.Command {
NewRegistryCommand(ioStream, "6"),
NewTraitCommand(commandArgs, ioStream),
NewComponentsCommand(commandArgs, ioStream),
NewProviderCommand(commandArgs, "10", ioStream),
// System
NewInstallCommand(commandArgs, "1", ioStream),

449
references/cli/provider.go Normal file
View File

@@ -0,0 +1,449 @@
/*
Copyright 2021 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 (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/gosuri/uitable"
tcv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
coreapi "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/definition"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// ProviderNamespace is the namespace of Terraform Cloud Provider
ProviderNamespace = "default"
labelVal = "terraform-provider"
providerNameParam = "name"
)
// NewProviderCommand create `addon` command
func NewProviderCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "provider",
Short: "Authenticate Terraform Cloud Providers",
Long: "Authenticate Terraform Cloud Providers by managing Terraform Controller Providers with its credential secret",
Annotations: map[string]string{
types.TagCommandOrder: order,
types.TagCommandType: types.TypeExtension,
},
}
add, err := prepareProviderAddCommand(c)
if err == nil {
cmd.AddCommand(add)
}
delete, err := prepareProviderDeleteCommand(c)
if err == nil {
cmd.AddCommand(delete)
}
cmd.AddCommand(
NewProviderListCommand(c),
)
return cmd
}
// NewProviderListCommand create addon list command
func NewProviderListCommand(c common.Args) *cobra.Command {
return &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List Terraform Cloud Providers",
Long: "List Terraform Cloud Providers",
RunE: func(cmd *cobra.Command, args []string) error {
k8sClient, err := c.GetClient()
if err != nil {
return err
}
err = listProviders(context.Background(), k8sClient)
if err != nil {
return err
}
return nil
},
}
}
func prepareProviderAddCommand(c common.Args) (*cobra.Command, error) {
ctx := context.Background()
k8sClient, err := c.GetClient()
if err != nil {
return nil, err
}
cmd := &cobra.Command{
Use: "add",
Short: "Authenticate Terraform Cloud Provider",
Long: "Authenticate Terraform Cloud Provider by creating a credential secret and a Terraform Controller Provider",
Example: "vela provider add <provider-type>",
}
addSubCommands, err := prepareProviderAddSubCommand(c)
if err != nil {
return nil, err
}
cmd.AddCommand(addSubCommands...)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
defs, err := getTerraformProviderTypes(ctx, k8sClient)
if len(args) < 1 {
errMsg := "must specify a Terraform Cloud Provider type"
if err == nil {
if len(defs) > 0 {
providerDefNames := make([]string, len(defs))
for i, def := range defs {
providerDefNames[i] = def.Name
}
names := strings.Join(providerDefNames, ", ")
errMsg += fmt.Sprintf(": select one from %s", names)
} else {
errMsg += "\nNo Terraform Cloud Provider types exist. Please run `vela addon enable` first"
}
}
return errors.New(errMsg)
} else if err == nil {
var found bool
for _, def := range defs {
if def.Name == args[0] {
found = true
}
}
if !found {
return fmt.Errorf("%s is not valid", args[0])
}
}
return nil
}
return cmd, nil
}
func prepareProviderAddSubCommand(c common.Args) ([]*cobra.Command, error) {
ctx := context.Background()
k8sClient, err := c.GetClient()
if err != nil {
return nil, err
}
defs, err := getTerraformProviderTypes(ctx, k8sClient)
if err == nil {
cmds := make([]*cobra.Command, len(defs))
for i, d := range defs {
providerType := d.Name
cmd := &cobra.Command{
Use: providerType,
Short: fmt.Sprintf("Authenticate Terraform Cloud Provider %s", providerType),
Long: fmt.Sprintf("Authenticate Terraform Cloud Provider %s by creating a credential secret and a Terraform Controller Provider", providerType),
Example: fmt.Sprintf("vela provider add %s", providerType),
}
parameters, err := getParameters(ctx, k8sClient, providerType)
if err != nil {
return nil, err
}
for _, p := range parameters {
cmd.Flags().String(p.Name, fmt.Sprint(p.Default), p.Usage)
}
cmd.RunE = func(cmd *cobra.Command, args []string) error {
name, err := cmd.Flags().GetString(providerNameParam)
if err != nil || name == "" {
return fmt.Errorf("must specify a name for the Terraform Cloud Provider %s", providerType)
}
var properties = make(map[string]string, len(parameters))
for _, p := range parameters {
value, err := cmd.Flags().GetString(p.Name)
if err != nil {
return err
}
if value == "" {
return fmt.Errorf("must specify a value for %s", p.Name)
}
properties[p.Name] = value
}
data, err := json.Marshal(properties)
if err != nil {
return fmt.Errorf("failed to authentiate Terraform cloud provier %s", providerType)
}
providerAppName := fmt.Sprintf("config-terraform-provider-%s", name)
a := &v1beta1.Application{}
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: providerAppName}, a); err != nil {
if kerrors.IsNotFound(err) {
a = &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: providerAppName,
Namespace: types.DefaultKubeVelaNS,
},
Spec: v1beta1.ApplicationSpec{
Components: []coreapi.ApplicationComponent{
{
Name: providerAppName,
Type: providerType,
Properties: &runtime.RawExtension{
Raw: data,
},
},
},
},
}
if err := k8sClient.Create(ctx, a); err != nil {
return err
}
}
}
return fmt.Errorf("terraform provider %s for %s already exists", name, providerType)
}
cmds[i] = cmd
}
return cmds, nil
}
return nil, nil
}
// getParameters gets parameter from a Terraform Cloud Provider, ie the ComponentDefinition
func getParameters(ctx context.Context, k8sClient client.Client, providerType string) ([]types.Parameter, error) {
def, err := getTerraformProviderType(ctx, k8sClient, providerType)
if err != nil {
return nil, err
}
cap, err := plugins.GetCapabilityByComponentDefinitionObject(*def, "")
if err != nil {
return nil, err
}
return cap.Parameters, nil
}
// ProviderMeta is the metadata for a Terraform Cloud Provider
type ProviderMeta struct {
Type string
Name string
Age string
}
func listProviders(ctx context.Context, k8sClient client.Client) error {
var (
providers []ProviderMeta
currentProviders []tcv1beta1.Provider
legacyProviders []tcv1beta1.Provider
)
tcProviders := &tcv1beta1.ProviderList{}
// client.MatchingLabels{: }
if err := k8sClient.List(ctx, tcProviders, client.InNamespace(ProviderNamespace)); err != nil {
return errors.Wrap(err, "failed to retrieve providers")
}
for _, p := range tcProviders.Items {
if p.Labels["config.oam.dev/type"] == labelVal {
currentProviders = append(currentProviders, p)
} else {
// if not labeled, the provider is manually created or created by `vela addon enable`.
legacyProviders = append(legacyProviders, p)
}
}
defs, err := getTerraformProviderTypes(ctx, k8sClient)
if err != nil {
if kerrors.IsNotFound(err) {
return errors.New("no Terraform Cloud Provider found, please run `vela addon enable` first")
}
return errors.Wrap(err, "failed to retrieve providers")
}
for _, d := range defs {
var found bool
for _, p := range currentProviders {
if p.Labels[oam.WorkloadTypeLabel] == d.Name {
found = true
providers = append(providers, ProviderMeta{
Type: p.Labels[oam.WorkloadTypeLabel],
Name: p.Name,
Age: p.CreationTimestamp.String(),
})
}
}
if !found {
providers = append(providers, ProviderMeta{
Type: d.Name,
})
}
}
for _, p := range legacyProviders {
providers = append(providers, ProviderMeta{
Type: "-",
Name: p.Name + "(legacy)",
Age: p.CreationTimestamp.String(),
})
}
if len(providers) == 0 {
return errors.New("no Terraform Cloud Provider found, please run `vela addon enable` first")
}
table := uitable.New()
table.AddRow("TYPE", "NAME", "CREATED-TIME")
for _, p := range providers {
table.AddRow(p.Type, p.Name, p.Age)
}
fmt.Println(table.String())
return nil
}
// getTerraformProviderTypes retrieves all ComponentDefinition for Terraform Cloud Providers which are delivered by
// Terraform Cloud provider addons
func getTerraformProviderTypes(ctx context.Context, k8sClient client.Client) ([]v1beta1.ComponentDefinition, error) {
defs := &v1beta1.ComponentDefinitionList{}
if err := k8sClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{definition.UserPrefix + "type.config.oam.dev": labelVal}); err != nil {
return nil, err
}
return defs.Items, nil
}
// getTerraformProviderType retrieves the ComponentDefinition for a Terraform Cloud Provider which is delivered by
// the Terraform Cloud provider addon
func getTerraformProviderType(ctx context.Context, k8sClient client.Client, name string) (*v1beta1.ComponentDefinition, error) {
def := &v1beta1.ComponentDefinition{}
if err := k8sClient.Get(ctx, k8stypes.NamespacedName{Namespace: types.DefaultKubeVelaNS, Name: name}, def); err != nil {
return nil, err
}
return def, nil
}
func prepareProviderDeleteCommand(c common.Args) (*cobra.Command, error) {
ctx := context.Background()
k8sClient, err := c.GetClient()
if err != nil {
return nil, err
}
cmd := &cobra.Command{
Use: "delete",
Aliases: []string{"rm", "del"},
Short: "Delete Terraform Cloud Provider",
Long: "Delete Terraform Cloud Provider",
Example: "vela provider delete <provider-type> -name <provider-name>",
}
deleteSubCommands, err := prepareProviderDeleteSubCommand(c)
if err != nil {
return nil, err
}
cmd.AddCommand(deleteSubCommands...)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
defs, err := getTerraformProviderTypes(ctx, k8sClient)
if len(args) < 1 {
errMsg := "must specify a Terraform Cloud Provider type"
if err == nil {
if len(defs) > 0 {
providerDefNames := make([]string, len(defs))
for i, def := range defs {
providerDefNames[i] = def.Name
}
names := strings.Join(providerDefNames, ", ")
errMsg += fmt.Sprintf(": select one from %s", names)
} else {
errMsg += "\nNo Terraform Cloud Provider types exist. Please run `vela addon enable` first"
}
}
return errors.New(errMsg)
} else if err == nil {
var found bool
for _, def := range defs {
if def.Name == args[0] {
found = true
}
}
if !found {
return fmt.Errorf("%s is not valid", args[0])
}
}
return nil
}
return cmd, nil
}
func prepareProviderDeleteSubCommand(c common.Args) ([]*cobra.Command, error) {
ctx := context.Background()
k8sClient, err := c.GetClient()
if err != nil {
return nil, err
}
defs, err := getTerraformProviderTypes(ctx, k8sClient)
if err == nil {
cmds := make([]*cobra.Command, len(defs))
for i, d := range defs {
providerType := d.Name
cmd := &cobra.Command{
Use: providerType,
Short: fmt.Sprintf("Delete Terraform Cloud Provider %s", providerType),
Long: fmt.Sprintf("Delete Terraform Cloud Provider %s", providerType),
Example: fmt.Sprintf("vela provider delete %s", providerType),
}
parameters, err := getParameters(ctx, k8sClient, providerType)
if err != nil {
return nil, err
}
for _, p := range parameters {
if p.Name == providerNameParam {
cmd.Flags().String(p.Name, fmt.Sprint(p.Default), p.Usage)
}
}
cmd.RunE = func(cmd *cobra.Command, args []string) error {
name, err := cmd.Flags().GetString(providerNameParam)
if err != nil || name == "" {
return fmt.Errorf("must specify a name for the Terraform Cloud Provider %s", providerType)
}
providerAppName := fmt.Sprintf("config-terraform-provider-%s", name)
a := &v1beta1.Application{}
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: providerAppName}, a); err != nil {
if kerrors.IsNotFound(err) {
return fmt.Errorf("provider %s for %s does not exist", name, providerType)
}
}
if err := k8sClient.Delete(ctx, a); err != nil {
return err
}
fmt.Printf("Successfully delete provider %s for %s\n", name, providerType)
return nil
}
cmds[i] = cmd
}
return cmds, nil
}
return nil, nil
}