diff --git a/apis/types/types.go b/apis/types/types.go index e5e2962b5..faf8c339b 100644 --- a/apis/types/types.go +++ b/apis/types/types.go @@ -159,8 +159,15 @@ const ( ) const ( - // TerrfaormComponentPrefix is the prefix of component type of terraform-xxx - TerrfaormComponentPrefix = "terraform-" + // TerraformComponentPrefix is the prefix of component type of terraform-xxx + TerraformComponentPrefix = "terraform-" + + // ProviderAppPrefix is the prefix of the application to create a Terraform Provider + ProviderAppPrefix = "config-terraform-provider" + // ProviderNamespace is the namespace of Terraform Cloud Provider + ProviderNamespace = "default" + // VelaCoreConfig is to mark application, config and its secret or Terraform provider lelong to a KubeVela config + VelaCoreConfig = "velacore-config" ) const ( diff --git a/go.mod b/go.mod index c246b193c..a232ca299 100644 --- a/go.mod +++ b/go.mod @@ -64,10 +64,10 @@ require ( github.com/wonderflow/cert-manager-api v1.0.3 go.mongodb.org/mongo-driver v1.5.1 go.uber.org/zap v1.18.1 - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 - golang.org/x/tools v0.1.7 // indirect + golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/src-d/go-git.v4 v4.13.1 @@ -118,7 +118,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect + github.com/BurntSushi/toml v0.4.1 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect @@ -259,7 +259,7 @@ require ( go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/mod v0.4.2 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 1a8d59f79..1ba7a4f32 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,9 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= @@ -1668,7 +1669,7 @@ github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= @@ -1810,8 +1811,9 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1855,8 +1857,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1921,7 +1924,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc= @@ -2064,7 +2067,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -2221,8 +2224,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II= +golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apiserver/rest/usecase/config.go b/pkg/apiserver/rest/usecase/config.go index ea60f9881..41c63cb97 100644 --- a/pkg/apiserver/rest/usecase/config.go +++ b/pkg/apiserver/rest/usecase/config.go @@ -23,6 +23,7 @@ import ( "strings" set "github.com/deckarep/golang-set" + terraformtypes "github.com/oam-dev/terraform-controller/api/types" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -38,13 +39,13 @@ import ( "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" + "github.com/oam-dev/kubevela/pkg/utils/config" ) const ( definitionAlias = definition.UserPrefix + "alias.config.oam.dev" definitionType = definition.UserPrefix + "type.config.oam.dev" - velaCoreConfig = "velacore-config" configIsReady = "Ready" configIsNotReady = "Not ready" terraformProviderAlias = "Terraform Cloud Provider" @@ -82,7 +83,7 @@ type configUseCaseImpl struct { 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 { + client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig}); err != nil { return nil, err } @@ -137,7 +138,7 @@ func (u *configUseCaseImpl) GetConfigType(ctx context.Context, configType string func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateConfigRequest) error { p := req.Properties // If the component is Terraform type, set the provider name same as the application name and the component name - if strings.HasPrefix(req.ComponentType, types.TerrfaormComponentPrefix) { + if strings.HasPrefix(req.ComponentType, types.TerraformComponentPrefix) { var properties map[string]interface{} if err := json.Unmarshal([]byte(p), &properties); err != nil { return errors.Wrapf(err, "unable to process the properties of %s", req.ComponentType) @@ -149,52 +150,45 @@ func (u *configUseCaseImpl) CreateConfig(ctx context.Context, req apis.CreateCon } p = string(tmp) } - app := v1beta1.Application{ - ObjectMeta: metav1.ObjectMeta{ - Name: req.Name, - Namespace: types.DefaultKubeVelaNS, - Annotations: map[string]string{ - types.AnnotationConfigAlias: req.Alias, - types.AnnotationConfigDescription: req.Description, - }, - 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(p)}, - }, - }, - }, + ui := config.UIParam{ + Alias: req.Alias, + Description: req.Description, + Project: req.Project, } - return u.kubeClient.Create(ctx, &app) + return config.CreateApplication(ctx, u.kubeClient, req.Name, req.ComponentType, p, ui) } 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 { + providers, err := config.ListTerraformProviders(ctx, u.kubeClient) + if err != nil { return nil, err } - var configs []*apis.Config - for _, d := range defs.Items { - subConfigs, err := u.getConfigsByConfigType(ctx, d.Name) - if err != nil { + configs := make([]*apis.Config, len(providers)) + for i, p := range providers { + var a v1beta1.Application + if err := u.kubeClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: p.Name}, &a); err != nil { + if kerrors.IsNotFound(err) { + t := p.CreationTimestamp.Time + configs[i] = &apis.Config{ + Name: p.Name, + CreatedTime: &t, + } + if p.Status.State == terraformtypes.ProviderIsReady { + configs[i].Status = configIsReady + } else { + configs[i].Status = configIsNotReady + } + continue + } return nil, err } - configs = append(configs, subConfigs...) + // If the application doesn't have any components, skip it as something wrong happened. + if !strings.HasPrefix(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) { + continue + } + configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject]) } return configs, nil @@ -209,7 +203,7 @@ func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configTy if err := u.kubeClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.MatchingLabels{ model.LabelSourceOfTruth: model.FromInner, - types.LabelConfigCatalog: velaCoreConfig, + types.LabelConfigCatalog: types.VelaCoreConfig, types.LabelConfigType: configType, }); err != nil { return nil, err @@ -218,12 +212,6 @@ func (u *configUseCaseImpl) getConfigsByConfigType(ctx context.Context, configTy configs := make([]*apis.Config, len(apps.Items)) for i, a := range apps.Items { configs[i] = retrieveConfigFromApplication(a, a.Labels[types.LabelConfigProject]) - switch a.Status.Phase { - case common.ApplicationRunning: - configs[i].Status = configIsReady - default: - configs[i].Status = configIsNotReady - } } return configs, nil } @@ -245,11 +233,11 @@ func (u *configUseCaseImpl) GetConfig(ctx context.Context, configType, name stri } 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 + var isTerraformProvider bool + if strings.HasPrefix(configType, types.TerraformComponentPrefix) { + isTerraformProvider = true } - return u.kubeClient.Delete(ctx, a) + return config.DeleteApplication(ctx, u.kubeClient, name, isTerraformProvider) } // ApplicationDeployTarget is the struct of application deploy target @@ -265,7 +253,7 @@ func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, t var secrets v1.SecretList if err := k8sClient.List(ctx, &secrets, client.InNamespace(types.DefaultKubeVelaNS), client.MatchingLabels{ - types.LabelConfigCatalog: velaCoreConfig, + types.LabelConfigCatalog: types.VelaCoreConfig, types.LabelConfigSyncToMultiCluster: "true", }); err != nil { return err @@ -324,7 +312,7 @@ func SyncConfigs(ctx context.Context, k8sClient client.Client, project string, t Namespace: types.DefaultKubeVelaNS, Labels: map[string]string{ model.LabelSourceOfTruth: model.FromInner, - types.LabelConfigCatalog: velaCoreConfig, + types.LabelConfigCatalog: types.VelaCoreConfig, types.LabelConfigProject: project, }, }, diff --git a/pkg/apiserver/rest/usecase/config_test.go b/pkg/apiserver/rest/usecase/config_test.go index 188adea9c..f087d8d67 100644 --- a/pkg/apiserver/rest/usecase/config_test.go +++ b/pkg/apiserver/rest/usecase/config_test.go @@ -22,8 +22,11 @@ import ( "sort" "strings" "testing" + "time" . "github.com/agiledragon/gomonkey/v2" + terraformtypes "github.com/oam-dev/terraform-controller/api/types" + terraformapi "github.com/oam-dev/terraform-controller/api/v1beta1" "gotest.tools/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "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/model" @@ -53,7 +57,7 @@ func TestListConfigTypes(t *testing.T) { Name: "def1", Namespace: types.DefaultKubeVelaNS, Labels: map[string]string{ - definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig, + definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig, definitionType: types.TerraformProvider, }, }, @@ -70,7 +74,7 @@ func TestListConfigTypes(t *testing.T) { definitionAlias: "Def2", }, Labels: map[string]string{ - definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig, + definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig, }, }, } @@ -150,7 +154,7 @@ func TestGetConfigType(t *testing.T) { definitionAlias: "Def2", }, Labels: map[string]string{ - definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig, + definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig, }, }, } @@ -285,37 +289,53 @@ 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", - }, + terraformapi.AddToScheme(s) + createdTime, _ := time.Parse(time.UnixDate, "Wed Apr 7 11:06:39 PST 2022") + + provider1 := &terraformapi.Provider{ ObjectMeta: metav1.ObjectMeta{ - Name: "def1", - Namespace: types.DefaultKubeVelaNS, - Labels: map[string]string{ - definition.UserPrefix + "catalog.config.oam.dev": velaCoreConfig, - definitionType: types.TerraformProvider, - }, + Name: "provider1", + Namespace: "default", + CreationTimestamp: metav1.NewTime(createdTime), + }, + Status: terraformapi.ProviderStatus{ + State: terraformtypes.ProviderIsReady, }, } - def2 := &v1beta1.ComponentDefinition{ - TypeMeta: metav1.TypeMeta{ - Kind: "ComponentDefinition", - APIVersion: "core.oam.dev/v1beta1", - }, + + provider2 := &terraformapi.Provider{ 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, - }, + Name: "provider2", + Namespace: "default", + CreationTimestamp: metav1.NewTime(createdTime), + }, + Status: terraformapi.ProviderStatus{ + State: terraformtypes.ProviderIsNotReady, }, } - k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(def1, def2).Build() + + provider3 := &terraformapi.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "provider3", + Namespace: "default", + }, + } + + app1 := &v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "provider3", + Namespace: types.DefaultKubeVelaNS, + Labels: map[string]string{ + types.LabelConfigType: "terraform-alibaba", + }, + CreationTimestamp: metav1.NewTime(createdTime), + }, + Status: common.AppStatus{ + Phase: common.ApplicationRendering, + }, + } + + k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1).Build() h := &configUseCaseImpl{kubeClient: k8sClient} @@ -343,7 +363,25 @@ func TestGetConfigs(t *testing.T) { h: h, }, want: want{ - configs: nil, + configs: []*apis.Config{ + { + Name: "provider1", + CreatedTime: &createdTime, + Status: "Ready", + }, + { + Name: "provider2", + CreatedTime: &createdTime, + Status: "Not ready", + }, + { + Name: "provider3", + CreatedTime: &createdTime, + Status: "Not ready", + ConfigType: "terraform-alibaba", + ApplicationStatus: common.ApplicationRendering, + }, + }, }, }, } @@ -519,7 +557,7 @@ func TestSyncConfigs(t *testing.T) { Name: "s1", Namespace: types.DefaultKubeVelaNS, Labels: map[string]string{ - types.LabelConfigCatalog: velaCoreConfig, + types.LabelConfigCatalog: types.VelaCoreConfig, types.LabelConfigProject: "p1", types.LabelConfigSyncToMultiCluster: "true", }, @@ -604,3 +642,129 @@ func TestSyncConfigs(t *testing.T) { }) } } + +func TestDeleteConfig(t *testing.T) { + s := runtime.NewScheme() + v1beta1.AddToScheme(s) + corev1.AddToScheme(s) + terraformapi.AddToScheme(s) + provider1 := &terraformapi.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p1", + Namespace: "default", + }, + } + + provider2 := &terraformapi.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p2", + Namespace: "default", + }, + } + + provider3 := &terraformapi.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p3", + Namespace: "default", + }, + } + + app1 := &v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "config-terraform-provider-p1", + Namespace: types.DefaultKubeVelaNS, + Labels: map[string]string{ + types.LabelConfigType: "terraform-alibaba", + }, + }, + } + + app2 := &v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "p2", + Namespace: types.DefaultKubeVelaNS, + Labels: map[string]string{ + types.LabelConfigType: "terraform-alibaba", + }, + }, + } + + normalApp := &v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "a9", + Namespace: types.DefaultKubeVelaNS, + }, + } + + k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(provider1, provider2, provider3, app1, app2, normalApp).Build() + + h := &configUseCaseImpl{kubeClient: k8sClient} + + type args struct { + configType string + name string + h ConfigHandler + } + + type want struct { + errMsg string + } + + ctx := context.Background() + + testcases := []struct { + name string + args args + want want + }{ + { + name: "delete a legacy terraform provider", + args: args{ + configType: "terraform-alibaba", + name: "p1", + h: h, + }, + want: want{}, + }, + { + name: "delete a terraform provider", + args: args{ + configType: "terraform-alibaba", + name: "p2", + h: h, + }, + want: want{}, + }, + { + name: "delete a terraform provider, but its application not found", + args: args{ + configType: "terraform-alibaba", + name: "p3", + h: h, + }, + want: want{ + errMsg: "could not be disabled because it was created by enabling a Terraform provider or was manually created", + }, + }, + { + name: "delete a normal config, but failed", + args: args{ + configType: "config-image-registry", + name: "a10", + h: h, + }, + want: want{ + errMsg: "not found", + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.args.h.DeleteConfig(ctx, tc.args.configType, tc.args.name) + if tc.want.errMsg != "" || err != nil { + assert.ErrorContains(t, err, tc.want.errMsg) + } + }) + } +} diff --git a/pkg/apiserver/rest/usecase/project.go b/pkg/apiserver/rest/usecase/project.go index 5350e8a03..e383ff18b 100644 --- a/pkg/apiserver/rest/usecase/project.go +++ b/pkg/apiserver/rest/usecase/project.go @@ -487,7 +487,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config if err := p.k8sClient.List(ctx, apps, client.InNamespace(types.DefaultKubeVelaNS), client.MatchingLabels{ model.LabelSourceOfTruth: model.FromInner, - types.LabelConfigCatalog: velaCoreConfig, + types.LabelConfigCatalog: types.VelaCoreConfig, }); err != nil { return nil, err } @@ -499,7 +499,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config return nil, err } for _, p := range providers.Items { - if p.Labels[types.LabelConfigCatalog] == velaCoreConfig { + if p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig { continue } t := p.CreationTimestamp.Time @@ -520,7 +520,7 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config for _, a := range apps.Items { appProject := a.Labels[types.LabelConfigProject] if a.Status.Phase != common.ApplicationRunning || (appProject != "" && appProject != projectName) || - !strings.Contains(a.Labels[types.LabelConfigType], types.TerrfaormComponentPrefix) { + !strings.Contains(a.Labels[types.LabelConfigType], types.TerraformComponentPrefix) { continue } configs = append(configs, retrieveConfigFromApplication(a, appProject)) @@ -561,13 +561,6 @@ func (p *projectUsecaseImpl) GetConfigs(ctx context.Context, projectName, config configs[i].ConfigTypeAlias = d.Annotations[definitionAlias] } } - if c.ApplicationStatus != "" { - if c.ApplicationStatus == common.ApplicationRunning { - configs[i].Status = configIsReady - } else { - configs[i].Status = configIsNotReady - } - } } return configs, nil } @@ -600,12 +593,22 @@ func ConvertProjectUserModel2Base(user *model.ProjectUser) *apisv1.ProjectUserBa } func retrieveConfigFromApplication(a v1beta1.Application, project string) *apisv1.Config { + var ( + applicationStatus = a.Status.Phase + status string + ) + if applicationStatus == common.ApplicationRunning { + status = configIsReady + } else { + status = configIsNotReady + } return &apisv1.Config{ ConfigType: a.Labels[types.LabelConfigType], Name: a.Name, Project: project, CreatedTime: &(a.CreationTimestamp.Time), - ApplicationStatus: a.Status.Phase, + ApplicationStatus: applicationStatus, + Status: status, Alias: a.Annotations[types.AnnotationConfigAlias], Description: a.Annotations[types.AnnotationConfigDescription], } diff --git a/pkg/apiserver/rest/usecase/project_test.go b/pkg/apiserver/rest/usecase/project_test.go index e714b76d5..c11a83931 100644 --- a/pkg/apiserver/rest/usecase/project_test.go +++ b/pkg/apiserver/rest/usecase/project_test.go @@ -293,7 +293,7 @@ func TestProjectGetConfigs(t *testing.T) { Namespace: velatypes.DefaultKubeVelaNS, Labels: map[string]string{ model.LabelSourceOfTruth: model.FromInner, - velatypes.LabelConfigCatalog: velaCoreConfig, + velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig, velatypes.LabelConfigType: "terraform-provider", "config.oam.dev/project": "p1", }, @@ -308,7 +308,7 @@ func TestProjectGetConfigs(t *testing.T) { Namespace: velatypes.DefaultKubeVelaNS, Labels: map[string]string{ model.LabelSourceOfTruth: model.FromInner, - velatypes.LabelConfigCatalog: velaCoreConfig, + velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig, velatypes.LabelConfigType: "terraform-provider", }, CreationTimestamp: metav1.NewTime(createdTime), @@ -322,7 +322,7 @@ func TestProjectGetConfigs(t *testing.T) { Namespace: velatypes.DefaultKubeVelaNS, Labels: map[string]string{ model.LabelSourceOfTruth: model.FromInner, - velatypes.LabelConfigCatalog: velaCoreConfig, + velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig, velatypes.LabelConfigType: "dex-connector", "config.oam.dev/project": "p3", }, @@ -347,7 +347,7 @@ func TestProjectGetConfigs(t *testing.T) { Name: "provider2", Namespace: "default", Labels: map[string]string{ - velatypes.LabelConfigCatalog: velaCoreConfig, + velatypes.LabelConfigCatalog: velatypes.VelaCoreConfig, }, }, Status: terraformapi.ProviderStatus{ diff --git a/pkg/utils/config/application.go b/pkg/utils/config/application.go new file mode 100644 index 000000000..76df9ca81 --- /dev/null +++ b/pkg/utils/config/application.go @@ -0,0 +1,160 @@ +/* +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 config + +import ( + "context" + "fmt" + "strings" + + tcv1beta1 "github.com/oam-dev/terraform-controller/api/v1beta1" + "github.com/pkg/errors" + kerrors "k8s.io/apimachinery/pkg/api/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/model" +) + +const ( + errAuthenticateProvider = "failed to authenticate Terraform cloud provider %s for %s" + errProviderExists = "terraform provider %s for %s already exists" + errDeleteProvider = "failed to delete Terraform Provider %s" + errCouldNotDeleteProvider = "the Terraform Provider %s could not be disabled because it was created by enabling a Terraform provider or was manually created" + errCheckProviderExistence = "failed to check if Terraform Provider %s exists" +) + +// UIParam is the UI parameters from VelaUX for the application +type UIParam struct { + Alias string `json:"alias"` + Description string `json:"description"` + Project string `json:"project"` +} + +// CreateApplication creates a new application for the config +func CreateApplication(ctx context.Context, k8sClient client.Client, name, componentType, properties string, ui UIParam) error { + if strings.HasPrefix(componentType, types.TerraformComponentPrefix) { + existed, err := IsTerraformProviderExisted(ctx, k8sClient, name) + if err != nil { + return errors.Wrapf(err, errAuthenticateProvider, name, componentType) + } + if existed { + return fmt.Errorf(errProviderExists, name, componentType) + } + } + app := v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: types.DefaultKubeVelaNS, + Annotations: map[string]string{ + types.AnnotationConfigAlias: ui.Alias, + types.AnnotationConfigDescription: ui.Description, + }, + Labels: map[string]string{ + model.LabelSourceOfTruth: model.FromInner, + types.LabelConfigCatalog: types.VelaCoreConfig, + types.LabelConfigType: componentType, + types.LabelConfigProject: ui.Project, + }, + }, + Spec: v1beta1.ApplicationSpec{ + Components: []common.ApplicationComponent{ + { + Name: name, + Type: componentType, + Properties: &runtime.RawExtension{Raw: []byte(properties)}, + }, + }, + }, + } + return k8sClient.Create(ctx, &app) +} + +// DeleteApplication deletes a config application, including a Terraform provider. +// For a Terraform Provider, it can come from +// 1). manually created a Terraform Provider object, like https://github.com/oam-dev/terraform-controller/blob/master/getting-started.md#aws +// 2). by enabling a Terraform provider addon in version older than v1.3.0 +// 3). by create a Terraform provider via `vela provider add` +// 4). by VelaUX +// We will only target on deleting a provider which comes from 3) or 4) as for 1), it can be easily delete by hand, and +// for 2), it will be recreated by the addon. +func DeleteApplication(ctx context.Context, k8sClient client.Client, name string, isTerraformProvider bool) error { + if isTerraformProvider { + existed, err := IsTerraformProviderExisted(ctx, k8sClient, name) + if err != nil { + return errors.Wrapf(err, errCheckProviderExistence, name) + } + if existed { + // In version 1.3.0, we used `providerAppName` as the name of the application to create a provider, but + // in version 1.3.1, a config name is the config name, ie, the provider name. To keep backward compatibility, + // we need to check the legacy name and the current name of the application. + legacyName := fmt.Sprintf("%s-%s", types.ProviderAppPrefix, name) + err1 := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: legacyName}, &v1beta1.Application{}) + if err1 == nil { + name = legacyName + } + if err1 != nil { + if kerrors.IsNotFound(err1) { + err2 := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, &v1beta1.Application{}) + if err2 != nil { + if kerrors.IsNotFound(err2) { + return fmt.Errorf(errCouldNotDeleteProvider, name) + } + return fmt.Errorf(errDeleteProvider, name) + } + } + return fmt.Errorf(errDeleteProvider, name) + } + } + } + + a := &v1beta1.Application{} + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: types.DefaultKubeVelaNS, Name: name}, a); err != nil { + return err + } + if err := k8sClient.Delete(ctx, a); err != nil { + return err + } + return nil +} + +// ListTerraformProviders returns a list of Terraform providers. +func ListTerraformProviders(ctx context.Context, k8sClient client.Client) ([]tcv1beta1.Provider, error) { + l := &tcv1beta1.ProviderList{} + if err := k8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil { + return nil, err + } + return l.Items, nil +} + +// IsTerraformProviderExisted returns whether a Terraform provider exists. +func IsTerraformProviderExisted(ctx context.Context, k8sClient client.Client, name string) (bool, error) { + l, err := ListTerraformProviders(ctx, k8sClient) + if err != nil { + return false, err + } + for _, p := range l { + if p.Name == name { + return true, nil + } + } + return false, nil +} diff --git a/references/cli/provider.go b/references/cli/provider.go index a28285511..2bdc09c8c 100644 --- a/references/cli/provider.go +++ b/references/cli/provider.go @@ -27,27 +27,22 @@ import ( "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" + "github.com/oam-dev/kubevela/pkg/utils/config" 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" - - providerNameParam = "name" + providerNameParam = "name" + errAuthenticateProvider = "failed to authenticate Terraform cloud provider %s" ) // NewProviderCommand create `addon` command @@ -193,38 +188,14 @@ func prepareProviderAddSubCommand(c common.Args, ioStreams cmdutil.IOStreams) ([ } data, err := json.Marshal(properties) if err != nil { - return fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType) + return fmt.Errorf(errAuthenticateProvider, 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 fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType) - } - ioStreams.Infof("Successfully authenticate provider %s for %s\n", name, providerType) - return nil - } - return fmt.Errorf("failed to authenticate Terraform cloud provider %s", providerType) + + if err := config.CreateApplication(ctx, k8sClient, name, providerType, string(data), config.UIParam{}); err != nil { + return fmt.Errorf(errAuthenticateProvider, providerType) } - return fmt.Errorf("terraform provider %s for %s already exists", name, providerType) + ioStreams.Infof("Successfully authenticate provider %s for %s\n", name, providerType) + return nil } cmds[i] = cmd } @@ -259,14 +230,14 @@ func listProviders(ctx context.Context, k8sClient client.Client, ioStreams cmdut currentProviders []tcv1beta1.Provider legacyProviders []tcv1beta1.Provider ) - tcProviders := &tcv1beta1.ProviderList{} - // client.MatchingLabels{: } - if err := k8sClient.List(ctx, tcProviders, client.InNamespace(ProviderNamespace)); err != nil { + l, err := config.ListTerraformProviders(ctx, k8sClient) + if err != nil { return errors.Wrap(err, "failed to retrieve providers") } - for _, p := range tcProviders.Items { - if p.Labels["config.oam.dev/type"] == types.TerraformProvider { + for _, p := range l { + // The first condition matches the providers created by `vela provider` in 1.3.2 and earlier. + if p.Labels[types.LabelConfigType] == types.TerraformProvider || p.Labels[types.LabelConfigCatalog] == types.VelaCoreConfig { currentProviders = append(currentProviders, p) } else { // if not labeled, the provider is manually created or created by `vela addon enable`. @@ -429,15 +400,8 @@ func prepareProviderDeleteSubCommand(c common.Args, ioStreams cmdutil.IOStreams) 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 + if err := config.DeleteApplication(ctx, k8sClient, name, true); err != nil { + return errors.Wrapf(err, "failed to delete Terraform Cloud Provider %s", name) } ioStreams.Infof("Successfully delete provider %s for %s\n", name, providerType) return nil diff --git a/references/cli/provider_test.go b/references/cli/provider_test.go index a215c2731..49a4a18d4 100644 --- a/references/cli/provider_test.go +++ b/references/cli/provider_test.go @@ -34,7 +34,7 @@ import ( "github.com/oam-dev/kubevela/pkg/utils/util" ) -func TestLlistProviders(t *testing.T) { +func TestListProviders(t *testing.T) { ctx := context.Background() type args struct { k8sClient client.Client