diff --git a/cloudaccounts/handler/handler.go b/cloudaccounts/handler/handler.go index c61ab0a..a3e8225 100644 --- a/cloudaccounts/handler/handler.go +++ b/cloudaccounts/handler/handler.go @@ -1,6 +1,7 @@ package handler import ( + "strconv" "strings" "gofr.dev/pkg/gofr" @@ -56,6 +57,64 @@ func (h *Handler) ListCloudAccounts(ctx *gofr.Context) (interface{}, error) { return resp, nil } +func (h *Handler) ListDeploymentSpace(ctx *gofr.Context) (interface{}, error) { + id := ctx.PathParam("id") + id = strings.TrimSpace(id) + + cloudAccountID, err := strconv.Atoi(id) + if err != nil { + return nil, http.ErrorInvalidParam{Params: []string{"id"}} + } + + res, err := h.service.FetchDeploymentSpace(ctx, cloudAccountID) + if err != nil { + return nil, err + } + + return res, nil +} + +func (h *Handler) ListNamespaces(ctx *gofr.Context) (interface{}, error) { + id := ctx.PathParam("id") + id = strings.TrimSpace(id) + + cloudAccountID, err := strconv.Atoi(id) + if err != nil { + return nil, http.ErrorInvalidParam{Params: []string{"id"}} + } + + clusterName := strings.TrimSpace(ctx.Param("name")) + clusterRegion := strings.TrimSpace(ctx.Param("region")) + + if clusterName == "" || clusterRegion == "" { + return nil, http.ErrorInvalidParam{Params: []string{"cluster"}} + } + + res, err := h.service.ListNamespaces(ctx, cloudAccountID, clusterName, clusterRegion) + if err != nil { + return nil, err + } + + return res, nil +} + +func (h *Handler) ListDeploymentSpaceOptions(ctx *gofr.Context) (interface{}, error) { + id := ctx.PathParam("id") + id = strings.TrimSpace(id) + + cloudAccountID, err := strconv.Atoi(id) + if err != nil { + return nil, http.ErrorInvalidParam{Params: []string{"id"}} + } + + res, err := h.service.FetchDeploymentSpaceOptions(ctx, cloudAccountID) + if err != nil { + return nil, err + } + + return res, nil +} + // validateCloudAccount checks the required fields and values in a CloudAccount. func validateCloudAccount(cloudAccount *store.CloudAccount) error { params := []string{} diff --git a/cloudaccounts/handler/handler_test.go b/cloudaccounts/handler/handler_test.go index 7c53cba..ed8f6d0 100644 --- a/cloudaccounts/handler/handler_test.go +++ b/cloudaccounts/handler/handler_test.go @@ -29,7 +29,6 @@ func TestHandler_AddCloudAccount(t *testing.T) { defer ctrl.Finish() mockService := service.NewMockCloudAccountService(ctrl) - handler := New(mockService) testCases := []struct { @@ -82,7 +81,7 @@ func TestHandler_AddCloudAccount(t *testing.T) { tc.mockBehavior() // Prepare HTTP request - req := httptest.NewRequest(netHTTP.MethodPost, "/add", strings.NewReader(tc.requestBody)) + req := httptest.NewRequest(netHTTP.MethodPost, "/add/{id}", strings.NewReader(tc.requestBody)) req.Header.Set("Content-Type", "application/json") ctx := &gofr.Context{Context: context.Background(), Request: http.NewRequest(req)} diff --git a/cloudaccounts/service/interface.go b/cloudaccounts/service/interface.go index c9466aa..577ca6d 100644 --- a/cloudaccounts/service/interface.go +++ b/cloudaccounts/service/interface.go @@ -9,4 +9,7 @@ import ( type CloudAccountService interface { AddCloudAccount(ctx *gofr.Context, accounts *store.CloudAccount) (*store.CloudAccount, error) FetchAllCloudAccounts(ctx *gofr.Context) ([]store.CloudAccount, error) + FetchDeploymentSpace(ctx *gofr.Context, cloudAccountID int) (interface{}, error) + ListNamespaces(ctx *gofr.Context, id int, clusterName, clusterRegion string) (interface{}, error) + FetchDeploymentSpaceOptions(ctx *gofr.Context, id int) ([]DeploymentSpaceOptions, error) } diff --git a/cloudaccounts/service/mock_interface.go b/cloudaccounts/service/mock_interface.go index ad4cfd4..c757af8 100644 --- a/cloudaccounts/service/mock_interface.go +++ b/cloudaccounts/service/mock_interface.go @@ -6,6 +6,7 @@ package service import ( reflect "reflect" + store "github.com/zopdev/zop-api/cloudaccounts/store" gomock "go.uber.org/mock/gomock" @@ -64,3 +65,48 @@ func (mr *MockCloudAccountServiceMockRecorder) FetchAllCloudAccounts(ctx interfa mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAllCloudAccounts", reflect.TypeOf((*MockCloudAccountService)(nil).FetchAllCloudAccounts), ctx) } + +// FetchDeploymentSpace mocks base method. +func (m *MockCloudAccountService) FetchDeploymentSpace(ctx *gofr.Context, cloudAccountID int) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fetch", ctx, cloudAccountID) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchDeploymentSpace indicates an expected call of FetchDeploymentSpace. +func (mr *MockCloudAccountServiceMockRecorder) FetchDeploymentSpace(ctx, cloudAccountID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockCloudAccountService)(nil).FetchDeploymentSpace), ctx, cloudAccountID) +} + +// FetchDeploymentSpaceOptions mocks base method. +func (m *MockCloudAccountService) FetchDeploymentSpaceOptions(ctx *gofr.Context, id int) ([]DeploymentSpaceOptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchDeploymentSpaceOptions", ctx, id) + ret0, _ := ret[0].([]DeploymentSpaceOptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchDeploymentSpaceOptions indicates an expected call of FetchDeploymentSpaceOptions. +func (mr *MockCloudAccountServiceMockRecorder) FetchDeploymentSpaceOptions(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchDeploymentSpaceOptions", reflect.TypeOf((*MockCloudAccountService)(nil).FetchDeploymentSpaceOptions), ctx, id) +} + +// ListNamespaces mocks base method. +func (m *MockCloudAccountService) ListNamespaces(ctx *gofr.Context, id int, clusterName, clusterRegion string) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNamespaces", ctx, id, clusterName, clusterRegion) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNamespaces indicates an expected call of ListNamespaces. +func (mr *MockCloudAccountServiceMockRecorder) ListNamespaces(ctx, id, clusterName, clusterRegion interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNamespaces", reflect.TypeOf((*MockCloudAccountService)(nil).ListNamespaces), ctx, id, clusterName, clusterRegion) +} diff --git a/cloudaccounts/service/models.go b/cloudaccounts/service/models.go index 16d8e6e..ae6de43 100644 --- a/cloudaccounts/service/models.go +++ b/cloudaccounts/service/models.go @@ -13,3 +13,10 @@ type gcpCredentials struct { ClientX509CertURL string `json:"client_x509_cert_url"` UniverseDomain string `json:"universe_domain"` } + +// DeploymentSpaceOptions contains options to setup deployment space. +type DeploymentSpaceOptions struct { + Name string `json:"name"` + Path string `json:"path"` + Type string `json:"type"` +} diff --git a/cloudaccounts/service/service.go b/cloudaccounts/service/service.go index 9adb189..ea1fe29 100644 --- a/cloudaccounts/service/service.go +++ b/cloudaccounts/service/service.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "errors" + "fmt" "strings" "time" @@ -11,15 +12,17 @@ import ( "gofr.dev/pkg/gofr/http" "github.com/zopdev/zop-api/cloudaccounts/store" + "github.com/zopdev/zop-api/provider" ) type Service struct { - store store.CloudAccountStore + store store.CloudAccountStore + deploymentSpace provider.Provider } // New creates a new CloudAccountService with the provided CloudAccountStore. -func New(clStore store.CloudAccountStore) CloudAccountService { - return &Service{store: clStore} +func New(clStore store.CloudAccountStore, deploySpace provider.Provider) CloudAccountService { + return &Service{store: clStore, deploymentSpace: deploySpace} } // AddCloudAccount adds a new cloud account to the store if it doesn't already exist. @@ -77,3 +80,72 @@ func fetchGCPProviderDetails(ctx *gofr.Context, cloudAccount *store.CloudAccount return nil } + +func (s *Service) FetchDeploymentSpace(ctx *gofr.Context, cloudAccountID int) (interface{}, error) { + cloudAccount, err := s.store.GetCloudAccountByID(ctx, cloudAccountID) + if err != nil { + return nil, err + } + + credentials, err := s.store.GetCredentials(ctx, cloudAccount.ID) + if err != nil { + return nil, err + } + + deploymentSpaceAccount := provider.CloudAccount{ + ID: cloudAccount.ID, + Name: cloudAccount.Name, + Provider: cloudAccount.Provider, + ProviderID: cloudAccount.ProviderID, + ProviderDetails: cloudAccount.ProviderDetails, + } + + clusters, err := s.deploymentSpace.ListAllClusters(ctx, &deploymentSpaceAccount, credentials) + if err != nil { + return nil, err + } + + return clusters, nil +} + +func (s *Service) ListNamespaces(ctx *gofr.Context, id int, clusterName, clusterRegion string) (interface{}, error) { + cloudAccount, err := s.store.GetCloudAccountByID(ctx, id) + if err != nil { + return nil, err + } + + credentials, err := s.store.GetCredentials(ctx, cloudAccount.ID) + if err != nil { + return nil, err + } + + deploymentSpaceAccount := provider.CloudAccount{ + ID: cloudAccount.ID, + Name: cloudAccount.Name, + Provider: cloudAccount.Provider, + ProviderID: cloudAccount.ProviderID, + ProviderDetails: cloudAccount.ProviderDetails, + } + + cluster := provider.Cluster{ + Name: clusterName, + Region: clusterRegion, + } + + res, err := s.deploymentSpace.ListNamespace(ctx, &cluster, &deploymentSpaceAccount, credentials) + if err != nil { + return nil, err + } + + return res, nil +} + +func (*Service) FetchDeploymentSpaceOptions(_ *gofr.Context, id int) ([]DeploymentSpaceOptions, error) { + options := []DeploymentSpaceOptions{ + {Name: "gke", + Path: fmt.Sprintf("/cloud-accounts/%v/deployment-space/clusters", id), + Type: "type"}, + } + + return options, nil +} diff --git a/cloudaccounts/service/service_test.go b/cloudaccounts/service/service_test.go index b6483c1..1c6e3ae 100644 --- a/cloudaccounts/service/service_test.go +++ b/cloudaccounts/service/service_test.go @@ -7,10 +7,12 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/http" "github.com/zopdev/zop-api/cloudaccounts/store" + "github.com/zopdev/zop-api/provider" ) var ( @@ -22,6 +24,7 @@ func TestService_AddCloudAccount(t *testing.T) { defer ctrl.Finish() mockStore := store.NewMockCloudAccountStore(ctrl) + mockProvider := provider.NewMockProvider(ctrl) ctx := &gofr.Context{} @@ -100,7 +103,7 @@ func TestService_AddCloudAccount(t *testing.T) { t.Run(tc.name, func(t *testing.T) { tc.mockBehavior() - service := New(mockStore) + service := New(mockStore, mockProvider) _, err := service.AddCloudAccount(ctx, tc.input) if tc.expectedError != nil { @@ -118,6 +121,7 @@ func TestService_FetchAllCloudAccounts(t *testing.T) { defer ctrl.Finish() mockStore := store.NewMockCloudAccountStore(ctrl) + mockProvider := provider.NewMockProvider(ctrl) ctx := &gofr.Context{} @@ -160,7 +164,7 @@ func TestService_FetchAllCloudAccounts(t *testing.T) { t.Run(tc.name, func(t *testing.T) { tc.mockBehavior() - service := New(mockStore) + service := New(mockStore, mockProvider) _, err := service.FetchAllCloudAccounts(ctx) if tc.expectedError != nil { diff --git a/cloudaccounts/store/interface.go b/cloudaccounts/store/interface.go index 73939b2..fad6d12 100644 --- a/cloudaccounts/store/interface.go +++ b/cloudaccounts/store/interface.go @@ -6,4 +6,6 @@ type CloudAccountStore interface { InsertCloudAccount(ctx *gofr.Context, config *CloudAccount) (*CloudAccount, error) GetALLCloudAccounts(ctx *gofr.Context) ([]CloudAccount, error) GetCloudAccountByProvider(ctx *gofr.Context, providerType, providerID string) (*CloudAccount, error) + GetCloudAccountByID(ctx *gofr.Context, cloudAccountID int) (*CloudAccount, error) + GetCredentials(ctx *gofr.Context, cloudAccountID int64) (interface{}, error) } diff --git a/cloudaccounts/store/mock_interface.go b/cloudaccounts/store/mock_interface.go index f69e6d4..af74de4 100644 --- a/cloudaccounts/store/mock_interface.go +++ b/cloudaccounts/store/mock_interface.go @@ -49,6 +49,21 @@ func (mr *MockCloudAccountStoreMockRecorder) GetALLCloudAccounts(ctx interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetALLCloudAccounts", reflect.TypeOf((*MockCloudAccountStore)(nil).GetALLCloudAccounts), ctx) } +// GetCloudAccountByID mocks base method. +func (m *MockCloudAccountStore) GetCloudAccountByID(ctx *gofr.Context, cloudAccountID int) (*CloudAccount, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCloudAccountByID", ctx, cloudAccountID) + ret0, _ := ret[0].(*CloudAccount) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCloudAccountByID indicates an expected call of GetCloudAccountByID. +func (mr *MockCloudAccountStoreMockRecorder) GetCloudAccountByID(ctx, cloudAccountID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCloudAccountByID", reflect.TypeOf((*MockCloudAccountStore)(nil).GetCloudAccountByID), ctx, cloudAccountID) +} + // GetCloudAccountByProvider mocks base method. func (m *MockCloudAccountStore) GetCloudAccountByProvider(ctx *gofr.Context, providerType, providerID string) (*CloudAccount, error) { m.ctrl.T.Helper() @@ -64,6 +79,21 @@ func (mr *MockCloudAccountStoreMockRecorder) GetCloudAccountByProvider(ctx, prov return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCloudAccountByProvider", reflect.TypeOf((*MockCloudAccountStore)(nil).GetCloudAccountByProvider), ctx, providerType, providerID) } +// GetCredentials mocks base method. +func (m *MockCloudAccountStore) GetCredentials(ctx *gofr.Context, cloudAccountID int64) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCredentials", ctx, cloudAccountID) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCredentials indicates an expected call of GetCredentials. +func (mr *MockCloudAccountStoreMockRecorder) GetCredentials(ctx, cloudAccountID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredentials", reflect.TypeOf((*MockCloudAccountStore)(nil).GetCredentials), ctx, cloudAccountID) +} + // InsertCloudAccount mocks base method. func (m *MockCloudAccountStore) InsertCloudAccount(ctx *gofr.Context, config *CloudAccount) (*CloudAccount, error) { m.ctrl.T.Helper() diff --git a/cloudaccounts/store/models.go b/cloudaccounts/store/models.go index b27a434..3438807 100644 --- a/cloudaccounts/store/models.go +++ b/cloudaccounts/store/models.go @@ -2,25 +2,18 @@ package store // CloudAccount represents a cloud account with necessary attributes. type CloudAccount struct { + // ID is a unique identifier for the cloud account. + ID int64 `json:"id,omitempty"` // Name is the name of the cloud account. Name string `json:"name"` - // ID is a unique identifier for the cloud account. - ID int64 `json:"id,omitempty"` - // Provider is the name of the cloud service provider. Provider string `json:"provider"` // ProviderID is the identifier for the provider account. ProviderID string `json:"providerId"` - // ProviderDetails contains additional details specific to the provider. - ProviderDetails interface{} `json:"providerDetails"` - - // Credentials hold authentication information for access to the provider. - Credentials interface{} `json:"credentials,omitempty"` - // CreatedAt is the timestamp of when the cloud account was created. CreatedAt string `json:"createdAt"` @@ -29,4 +22,10 @@ type CloudAccount struct { // DeletedAt is the timestamp of when the cloud account was deleted, if applicable. DeletedAt string `json:"deletedAt,omitempty"` + + // ProviderDetails contains additional details specific to the provider. + ProviderDetails interface{} `json:"providerDetails"` + + // Credentials hold authentication information for access to the provider. + Credentials interface{} `json:"credentials,omitempty"` } diff --git a/cloudaccounts/store/query.go b/cloudaccounts/store/query.go index 0a4b341..924cb4c 100644 --- a/cloudaccounts/store/query.go +++ b/cloudaccounts/store/query.go @@ -7,4 +7,9 @@ const ( GETBYPROVIDERQUERY = "SELECT id, name, provider, provider_id, provider_details, created_at," + " updated_at FROM cloud_account WHERE provider = ? " + "AND provider_id = ? AND deleted_at IS NULL;" + GETBYPROVIDERIDQUERY = "SELECT id, name, provider, provider_id, provider_details, created_at," + + " updated_at FROM cloud_account WHERE " + + "id = ? AND deleted_at IS NULL;" + //nolint:gosec //query + GETCREDENTIALSQUERY = "SELECT credentials from cloud_account WHERE id = ? AND deleted_at IS NULL;" ) diff --git a/cloudaccounts/store/store.go b/cloudaccounts/store/store.go index 545996c..9a5f946 100644 --- a/cloudaccounts/store/store.go +++ b/cloudaccounts/store/store.go @@ -69,7 +69,7 @@ func (*Store) GetALLCloudAccounts(ctx *gofr.Context) ([]CloudAccount, error) { return cloudAccounts, nil } -// GetCloudAccountByProvider retrieves a cloud account by provider type and provider ID. +// GetCloudAccountByProvider retrieves a cloud account by provider type and provider Identifier. func (*Store) GetCloudAccountByProvider(ctx *gofr.Context, providerType, providerID string) (*CloudAccount, error) { row := ctx.SQL.QueryRowContext(ctx, GETBYPROVIDERQUERY, providerType, providerID) @@ -93,3 +93,52 @@ func (*Store) GetCloudAccountByProvider(ctx *gofr.Context, providerType, provide return &cloudAccount, nil } + +// GetCloudAccountByID retrieves a cloud account by id. +func (*Store) GetCloudAccountByID(ctx *gofr.Context, cloudAccountID int) (*CloudAccount, error) { + row := ctx.SQL.QueryRowContext(ctx, GETBYPROVIDERIDQUERY, cloudAccountID) + + if row.Err() != nil { + return nil, row.Err() + } + + cloudAccount := CloudAccount{} + + var providerDetails sql.NullString + + err := row.Scan(&cloudAccount.ID, &cloudAccount.Name, &cloudAccount.Provider, &cloudAccount.ProviderID, + &providerDetails, &cloudAccount.CreatedAt, &cloudAccount.UpdatedAt) + if err != nil { + return nil, err + } + + if providerDetails.Valid { + cloudAccount.ProviderDetails = providerDetails.String + } + + return &cloudAccount, nil +} + +func (*Store) GetCredentials(ctx *gofr.Context, cloudAccountID int64) (interface{}, error) { + row := ctx.SQL.QueryRowContext(ctx, GETCREDENTIALSQUERY, cloudAccountID) + + if row.Err() != nil { + return nil, row.Err() + } + + var credentials string + + err := row.Scan(&credentials) + if err != nil { + return nil, err + } + + var jsonCred map[string]string + + err = json.Unmarshal([]byte(credentials), &jsonCred) + if err != nil { + return nil, err + } + + return jsonCred, nil +} diff --git a/main.go b/main.go index c68598b..87e691a 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "github.com/zopdev/zop-api/cloudaccounts/handler" "github.com/zopdev/zop-api/cloudaccounts/service" "github.com/zopdev/zop-api/cloudaccounts/store" + "github.com/zopdev/zop-api/provider/gcp" appHandler "github.com/zopdev/zop-api/applications/handler" appService "github.com/zopdev/zop-api/applications/service" @@ -17,8 +18,10 @@ func main() { app.Migrate(migrations.All()) + gkeSvc := gcp.New() + cloudAccountStore := store.New() - cloudAccountService := service.New(cloudAccountStore) + cloudAccountService := service.New(cloudAccountStore, gkeSvc) cloudAccountHandler := handler.New(cloudAccountService) applicationStore := appStore.New() @@ -27,6 +30,9 @@ func main() { app.POST("/cloud-accounts", cloudAccountHandler.AddCloudAccount) app.GET("/cloud-accounts", cloudAccountHandler.ListCloudAccounts) + app.GET("/cloud-accounts/{id}/deployment-space/clusters", cloudAccountHandler.ListDeploymentSpace) + app.GET("/cloud-accounts/{id}/deployment-space/namespaces", cloudAccountHandler.ListNamespaces) + app.GET("/cloud-accounts/{id}/deployment-space/options", cloudAccountHandler.ListDeploymentSpaceOptions) app.POST("/applications", applicationHandler.AddApplication) app.GET("/applications", applicationHandler.ListApplications) diff --git a/provider/gcp/gke.go b/provider/gcp/gke.go index c059e7f..e5f2091 100644 --- a/provider/gcp/gke.go +++ b/provider/gcp/gke.go @@ -94,6 +94,9 @@ func (g *GCP) ListAllClusters(ctx *gofr.Context, cloudAccount *provider.CloudAcc "name": "name", }, }, + Metadata: provider.Metadata{ + Name: "GKE Cluster", + }, } return response, nil @@ -259,6 +262,9 @@ func (*GCP) fetchNamespaces(ctx *gofr.Context, client *http.Client, credBody []b return &provider.NamespaceResponse{ Options: namespaces, + Metadata: provider.Metadata{ + Name: "namespace", + }, }, nil } diff --git a/provider/models.go b/provider/models.go index 4356689..3e8b2ae 100644 --- a/provider/models.go +++ b/provider/models.go @@ -13,6 +13,14 @@ type ClusterResponse struct { // Clusters is a list of clusters available for the provider. Clusters []Cluster `json:"options"` + // NextPage contains pagination information for retrieving the next set of resources. + Next Next `json:"next"` + + Metadata Metadata `json:"metadata"` +} + +type Metadata struct { + Name string `json:"name"` // Next contains pagination information for retrieving the next set of resources. Next Next `json:"next"` } @@ -99,6 +107,8 @@ type CloudAccount struct { type NamespaceResponse struct { // Options is a list of available namespaces. Options []Namespace `json:"options"` + + Metadata Metadata `json:"metadata"` } // Namespace represents a namespace within a cloud provider. It contains the name and type of the namespace.