diff --git a/controllers/account/controllers/account_controller.go b/controllers/account/controllers/account_controller.go index b696741be5d..707c2d9268c 100644 --- a/controllers/account/controllers/account_controller.go +++ b/controllers/account/controllers/account_controller.go @@ -27,6 +27,8 @@ import ( "strings" "time" + "sigs.k8s.io/controller-runtime/pkg/event" + "go.mongodb.org/mongo-driver/bson/primitive" "github.com/google/uuid" @@ -112,6 +114,9 @@ func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // determine the resource quota created by the owner user and the resource quota initialized by the account user, // and only the resource quota created by the team user _, err = r.syncAccount(ctx, owner, "ns-"+user.Name) + if errors.Is(err, gorm.ErrRecordNotFound) && user.CreationTimestamp.Add(20*24*time.Hour).Before(time.Now()) { + return ctrl.Result{}, nil + } return ctrl.Result{}, err } else if client.IgnoreNotFound(err) != nil { return ctrl.Result{}, err @@ -203,10 +208,7 @@ func (r *AccountReconciler) syncAccount(ctx context.Context, owner string, userN } account, err := r.AccountV2.NewAccount(&pkgtypes.UserQueryOpts{Owner: owner}) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil - } - return nil, fmt.Errorf("failed to create %s account: %v", owner, err) + return nil, err } return account, nil } @@ -290,11 +292,36 @@ func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager, rateOpts controll r.AccountSystemNamespace = env.GetEnvWithDefault(ACCOUNTNAMESPACEENV, DEFAULTACCOUNTNAMESPACE) return ctrl.NewControllerManagedBy(mgr). For(&userv1.User{}, builder.WithPredicates(OnlyCreatePredicate{})). - Watches(&accountv1.Payment{}, &handler.EnqueueRequestForObject{}). + Watches(&accountv1.Payment{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(PaymentPredicate{})). WithOptions(rateOpts). Complete(r) } +type PaymentPredicate struct{} + +func (PaymentPredicate) Create(e event.CreateEvent) bool { + if payment, ok := e.Object.(*accountv1.Payment); ok { + fmt.Println("payment create", payment.Status.TradeNO, payment.Status.Status) + return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess + } + return false +} + +func (PaymentPredicate) Update(e event.UpdateEvent) bool { + if payment, ok := e.ObjectNew.(*accountv1.Payment); ok { + return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess + } + return false +} + +func (PaymentPredicate) Delete(_ event.DeleteEvent) bool { + return false +} + +func (PaymentPredicate) Generic(_ event.GenericEvent) bool { + return false +} + func RawParseRechargeConfig() (activities pkgtypes.Activities, discountsteps []int64, discountratios []float64, returnErr error) { // local test //config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) diff --git a/controllers/account/controllers/debt_controller.go b/controllers/account/controllers/debt_controller.go index 986fc6f47bf..2ad797408d7 100644 --- a/controllers/account/controllers/debt_controller.go +++ b/controllers/account/controllers/debt_controller.go @@ -155,6 +155,10 @@ func (r *DebtReconciler) reconcile(ctx context.Context, owner string) error { account, err := r.AccountV2.GetAccount(&pkgtypes.UserQueryOpts{Owner: owner}) if account == nil { if errors.Is(err, gorm.ErrRecordNotFound) { + _, err = r.AccountV2.NewAccount(&pkgtypes.UserQueryOpts{Owner: owner}) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("failed to create account %s: %v", owner, err) + } userOwner := &userv1.User{} if err := r.Get(ctx, types.NamespacedName{Name: owner, Namespace: r.accountSystemNamespace}, userOwner); err != nil { // if user not exist, skip diff --git a/controllers/pkg/crypto/crypto.go b/controllers/pkg/crypto/crypto.go index e55763cfd85..f43f6568421 100644 --- a/controllers/pkg/crypto/crypto.go +++ b/controllers/pkg/crypto/crypto.go @@ -30,7 +30,7 @@ import ( "strconv" ) -const defaultEncryptionKey = "0123456789ABCDEF0123456789ABCDEF" +const defaultEncryptionKey = "Bg1c3Dd5e9e0F84bdF0A5887cF43aB63" var encryptionKey = defaultEncryptionKey @@ -79,56 +79,6 @@ func DecryptInt64(in string) (int64, error) { return strconv.ParseInt(string(out), 10, 64) } -func DecryptFloat64(in string) (float64, error) { - out, err := Decrypt(in) - if err != nil { - return 0, fmt.Errorf("failed to decrpt balance: %w", err) - } - return strconv.ParseFloat(string(out), 64) -} - -func EncryptFloat64(in float64) (*string, error) { - out, err := Encrypt([]byte(strconv.FormatFloat(in, 'f', -1, 64))) - return &out, err -} - -// DecryptInt64WithKey decrypts the given ciphertext using AES-GCM. -func DecryptInt64WithKey(in string, encryptionKey []byte) (int64, error) { - out, err := DecryptWithKey(in, encryptionKey) - if err != nil { - return 0, fmt.Errorf("failed to decrpt balance: %w", err) - } - return strconv.ParseInt(string(out), 10, 64) -} - -func RechargeBalance(rawBalance *string, amount int64) error { - balanceInt, err := DecryptInt64(*rawBalance) - if err != nil { - return fmt.Errorf("failed to recharge balance: %w", err) - } - balanceInt += amount - encryptBalance, err := EncryptInt64(balanceInt) - if err != nil { - return fmt.Errorf("failed to recharge balance: %w", err) - } - *rawBalance = *encryptBalance - return nil -} - -func DeductBalance(balance *string, amount int64) error { - balanceInt, err := DecryptInt64(*balance) - if err != nil { - return fmt.Errorf("failed to deduct balance: %w", err) - } - balanceInt -= amount - encryptBalance, err := EncryptInt64(balanceInt) - if err != nil { - return fmt.Errorf("failed to deduct balance: %w", err) - } - *balance = *encryptBalance - return nil -} - // Decrypt decrypts the given ciphertext using AES-GCM. func Decrypt(ciphertextBase64 string) ([]byte, error) { return DecryptWithKey(ciphertextBase64, []byte(encryptionKey)) diff --git a/controllers/pkg/database/cockroach/accountv2.go b/controllers/pkg/database/cockroach/accountv2.go index e0c53c50f99..ebc0e22defd 100644 --- a/controllers/pkg/database/cockroach/accountv2.go +++ b/controllers/pkg/database/cockroach/accountv2.go @@ -107,7 +107,7 @@ func (c *Cockroach) GetUser(ops *types.UserQueryOpts) (*types.User, error) { } else if ops.Owner != "" { userCr, err := c.GetUserCr(ops) if err != nil { - return nil, fmt.Errorf("failed to get user: %v", err) + return nil, fmt.Errorf("failed to get user cr: %v", err) } queryUser.UID = userCr.UserUID } @@ -160,6 +160,17 @@ func (c *Cockroach) GetUserUID(ops *types.UserQueryOpts) (uuid.UUID, error) { return userCr.UserUID, nil } +func (c *Cockroach) GetWorkspace(namespaces ...string) ([]types.Workspace, error) { + if len(namespaces) == 0 { + return nil, fmt.Errorf("empty namespaces") + } + var workspaces []types.Workspace + if err := c.Localdb.Where("id IN ?", namespaces).Find(&workspaces).Error; err != nil { + return nil, fmt.Errorf("failed to get workspaces: %v", err) + } + return workspaces, nil +} + func checkOps(ops *types.UserQueryOpts) error { if ops.Owner == "" && ops.UID == uuid.Nil && ops.ID == "" { return fmt.Errorf("empty query opts") @@ -276,18 +287,8 @@ func (c *Cockroach) getAccount(ops *types.UserQueryOpts) (*types.Account, error) if ops.IgnoreEmpty && errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil } - return nil, fmt.Errorf("failed to search account from db: %w", err) - } - balance, err := crypto.DecryptInt64(account.EncryptBalance) - if err != nil { - return nil, fmt.Errorf("failed to descrypt balance: %v", err) - } - deductionBalance, err := crypto.DecryptInt64(account.EncryptDeductionBalance) - if err != nil { - return nil, fmt.Errorf("failed to descrypt deduction balance: %v", err) + return nil, err } - account.Balance = balance - account.DeductionBalance = deductionBalance return &account, nil } @@ -317,52 +318,35 @@ func (c *Cockroach) updateBalance(tx *gorm.DB, ops *types.UserQueryOpts, amount } ops.UID = user.UserUID } - var account types.Account - //TODO update UserUid = ? - if err := tx.Where(&types.Account{UserUID: ops.UID}).First(&account).Error; err != nil { - return fmt.Errorf("failed to get account: %w", err) + var account = &types.Account{} + if err := tx.Where(&types.Account{UserUID: ops.UID}).First(account).Error; err != nil { + // if not found, create a new account and retry + if !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("failed to get account: %w", err) + } + if account, err = c.NewAccount(ops); err != nil { + return fmt.Errorf("failed to create account: %v", err) + } } - - if err := c.updateWithAccount(isDeduction, add, &account, amount); err != nil { + if err := c.updateWithAccount(isDeduction, add, account, amount); err != nil { return err } - if err := tx.Save(&account).Error; err != nil { + if err := tx.Save(account).Error; err != nil { return fmt.Errorf("failed to update account balance: %w", err) } return nil } func (c *Cockroach) updateWithAccount(isDeduction bool, add bool, account *types.Account, amount int64) error { - var fieldToUpdate string + balancePtr := &account.Balance if isDeduction { - fieldToUpdate = account.EncryptDeductionBalance - } else { - fieldToUpdate = account.EncryptBalance - } - - currentBalance, err := crypto.DecryptInt64(fieldToUpdate) - if err != nil { - return fmt.Errorf("failed to decrypt balance: %w", err) + balancePtr = &account.DeductionBalance } - if add { - currentBalance += amount + *balancePtr += amount } else { - currentBalance -= amount + *balancePtr -= amount } - - newEncryptBalance, err := crypto.EncryptInt64(currentBalance) - if err != nil { - return fmt.Errorf("failed to encrypt balance: %v", err) - } - if isDeduction { - account.EncryptDeductionBalance = *newEncryptBalance - account.DeductionBalance = currentBalance - } else { - account.EncryptBalance = *newEncryptBalance - account.Balance = currentBalance - } - return nil } @@ -618,6 +602,29 @@ func (c *Cockroach) payment(payment *types.Payment, updateBalance bool) error { }) } +func (c *Cockroach) GetRegions() ([]types.Region, error) { + var regions []types.Region + if err := c.DB.Find(®ions).Error; err != nil { + return nil, fmt.Errorf("failed to get regions: %v", err) + } + return regions, nil +} + +func (c *Cockroach) GetLocalRegion() types.Region { + if c.LocalRegion.Domain == "" { + regions, err := c.GetRegions() + if err == nil { + for i := range regions { + if regions[i].UID == c.LocalRegion.UID { + c.LocalRegion = ®ions[i] + return *c.LocalRegion + } + } + } + } + return *c.LocalRegion +} + func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) { userUID, err := c.GetUserUID(ops) if err != nil { diff --git a/controllers/pkg/database/interface.go b/controllers/pkg/database/interface.go index 4cd6afa1b04..6f364ac4ba1 100644 --- a/controllers/pkg/database/interface.go +++ b/controllers/pkg/database/interface.go @@ -53,7 +53,6 @@ type Account interface { GetUnsettingBillingHandler(owner string) ([]resources.BillingHandler, error) UpdateBillingStatus(orderID string, status resources.BillingStatus) error GetUpdateTimeForCategoryAndPropertyFromMetering(category string, property string) (time.Time, error) - GetAllPricesMap() (map[string]resources.Price, error) GetAllPayment() ([]resources.Billing, error) InitDefaultPropertyTypeLS() error SavePropertyTypes(types []resources.PropertyType) error @@ -90,10 +89,14 @@ type AccountV2 interface { Close() error GetUserCr(user *types.UserQueryOpts) (*types.RegionUserCr, error) GetUser(ops *types.UserQueryOpts) (*types.User, error) - CreateUser(oAuth *types.OauthProvider, regionUserCr *types.RegionUserCr, user *types.User, workspace *types.Workspace, userWorkspace *types.UserWorkspace) error GetAccount(user *types.UserQueryOpts) (*types.Account, error) - SetAccountCreateLocalRegion(account *types.Account, region string) error + GetRegions() ([]types.Region, error) + GetLocalRegion() types.Region GetUserOauthProvider(ops *types.UserQueryOpts) ([]types.OauthProvider, error) + GetWorkspace(namespaces ...string) ([]types.Workspace, error) + GetUserAccountRechargeDiscount(user *types.UserQueryOpts) (*types.RechargeDiscount, error) + SetAccountCreateLocalRegion(account *types.Account, region string) error + CreateUser(oAuth *types.OauthProvider, regionUserCr *types.RegionUserCr, user *types.User, workspace *types.Workspace, userWorkspace *types.UserWorkspace) error AddBalance(user *types.UserQueryOpts, balance int64) error ReduceBalance(ops *types.UserQueryOpts, amount int64) error ReduceDeductionBalance(ops *types.UserQueryOpts, amount int64) error @@ -106,7 +109,6 @@ type AccountV2 interface { TransferAccount(from, to *types.UserQueryOpts, amount int64) error TransferAccountAll(from, to *types.UserQueryOpts) error TransferAccountV1(owner string, account *types.Account) (*types.Account, error) - GetUserAccountRechargeDiscount(user *types.UserQueryOpts) (*types.RechargeDiscount, error) AddDeductionBalance(user *types.UserQueryOpts, balance int64) error AddDeductionBalanceWithFunc(ops *types.UserQueryOpts, amount int64, preDo, postDo func() error) error } diff --git a/controllers/pkg/database/mongo/account.go b/controllers/pkg/database/mongo/account.go index 0264d6c826e..2ca0d2a8dbf 100644 --- a/controllers/pkg/database/mongo/account.go +++ b/controllers/pkg/database/mongo/account.go @@ -30,7 +30,6 @@ import ( gonanoid "github.com/matoous/go-nanoid/v2" accountv1 "github.com/labring/sealos/controllers/account/api/v1" - "github.com/labring/sealos/controllers/pkg/crypto" "github.com/labring/sealos/controllers/pkg/resources" "github.com/labring/sealos/controllers/pkg/utils/logger" @@ -65,13 +64,6 @@ const ( DefaultTrafficConn = "traffic" ) -const DefaultRetentionDay = 30 - -// override this value at build time -const defaultCryptoKey = "Af0b2Bc5e9d0C84adF0A5887cF43aB63" - -var cryptoKey = defaultCryptoKey - type mongoDB struct { Client *mongo.Client AccountDB string @@ -83,7 +75,6 @@ type mongoDB struct { MonitorConnPrefix string MeteringConn string BillingConn string - PricesConn string PropertiesConn string TrafficConn string } @@ -306,36 +297,6 @@ func (m *mongoDB) GetDistinctMonitorCombinations(startTime, endTime time.Time) ( return monitors, nil } -func (m *mongoDB) GetAllPricesMap() (map[string]resources.Price, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - cursor, err := m.getPricesCollection().Find(ctx, bson.M{}) - if err != nil { - return nil, fmt.Errorf("get all prices error: %v", err) - } - var prices []struct { - Property string `json:"property" bson:"property"` - Price string `json:"price" bson:"price"` - Detail string `json:"detail" bson:"detail"` - } - if err = cursor.All(ctx, &prices); err != nil { - return nil, fmt.Errorf("get all prices error: %v", err) - } - var pricesMap = make(map[string]resources.Price, len(prices)) - for i := range prices { - price, err := crypto.DecryptInt64WithKey(prices[i].Price, []byte(cryptoKey)) - if err != nil { - return nil, fmt.Errorf("decrypt price error: %v", err) - } - pricesMap[prices[i].Property] = resources.Price{ - Price: price, - Detail: prices[i].Detail, - Property: prices[i].Property, - } - } - return pricesMap, nil -} - func (m *mongoDB) GetAllPayment() ([]resources.Billing, error) { filter := bson.M{ "type": 1, @@ -393,7 +354,7 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou minutes := endTime.Sub(startTime).Minutes() groupStage := bson.D{ - primitive.E{Key: "_id", Value: bson.D{{Key: "type", Value: "$type"}, {Key: "name", Value: "$name"}, {Key: "category", Value: "$category"}}}, + primitive.E{Key: "_id", Value: bson.D{{Key: "type", Value: "$type"}, {Key: "name", Value: "$name"}, {Key: "category", Value: "$category"}, {Key: "parent_type", Value: "$parent_type"}, {Key: "parent_name", Value: "$parent_name"}}}, primitive.E{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}, } @@ -401,6 +362,8 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou primitive.E{Key: "_id", Value: 0}, primitive.E{Key: "type", Value: "$_id.type"}, primitive.E{Key: "name", Value: "$_id.name"}, + primitive.E{Key: "parent_type", Value: "$_id.parent_type"}, + primitive.E{Key: "parent_name", Value: "$_id.parent_name"}, primitive.E{Key: "category", Value: "$_id.category"}, } @@ -464,16 +427,18 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou } defer cursor.Close(context.Background()) - var appCostsMap = make(map[string]map[uint8][]resources.AppCost) + var appCostsMap = make(map[string]map[string][]resources.AppCost) // map[ns/type]int64 var nsTypeAmount = make(map[string]int64) for cursor.Next(context.Background()) { var result struct { - Type uint8 `bson:"type"` - Namespace string `bson:"category"` - Name string `bson:"name"` - Used resources.EnumUsedMap `bson:"used"` + Type uint8 `bson:"type"` + Namespace string `bson:"category"` + Name string `bson:"name"` + ParentType uint8 `bson:"parent_type"` + ParentName string `bson:"parent_name"` + Used resources.EnumUsedMap `bson:"used"` } err := cursor.Decode(&result) @@ -485,12 +450,14 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou //logger.Info("generate billing data", "result", result) if _, ok := appCostsMap[result.Namespace]; !ok { - appCostsMap[result.Namespace] = make(map[uint8][]resources.AppCost) + appCostsMap[result.Namespace] = make(map[string][]resources.AppCost) } - if _, ok := appCostsMap[result.Namespace][result.Type]; !ok { - appCostsMap[result.Namespace][result.Type] = make([]resources.AppCost, 0) + strType := strconv.Itoa(int(result.Type)) + if _, ok := appCostsMap[result.Namespace][strType]; !ok { + appCostsMap[result.Namespace][strType] = make([]resources.AppCost, 0) } appCost := resources.AppCost{ + Type: result.Type, Used: result.Used, Name: result.Name, UsedAmount: make(map[uint8]int64), @@ -507,13 +474,17 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou if appCost.Amount == 0 { continue } - nsTypeAmount[result.Namespace+strconv.Itoa(int(result.Type))] += appCost.Amount - appCostsMap[result.Namespace][result.Type] = append(appCostsMap[result.Namespace][result.Type], appCost) + key := result.Namespace + "/" + strType + if result.ParentType != 0 && result.ParentName != "" { + key = result.Namespace + "/" + strconv.Itoa(int(result.ParentType)) + "/" + result.ParentName + } + nsTypeAmount[key] += appCost.Amount + appCostsMap[result.Namespace][key] = append(appCostsMap[result.Namespace][key], appCost) } for ns, appCostMap := range appCostsMap { for tp, appCost := range appCostMap { - amountt := nsTypeAmount[ns+strconv.Itoa(int(tp))] + amountt := nsTypeAmount[tp] if amountt == 0 { continue } @@ -521,11 +492,21 @@ func (m *mongoDB) GenerateBillingData(startTime, endTime time.Time, prols *resou if err != nil { return nil, 0, fmt.Errorf("generate billing id error: %v", err) } + // tp = ns/type/parentName && parentName not contain "/" + appType, appName := 0, "" + switch strings.Count(tp, "/") { + case 1: + appType, _ = strconv.Atoi(strings.Split(tp, "/")[1]) + case 2: + appType, _ = strconv.Atoi(strings.Split(tp, "/")[1]) + appName = strings.Split(tp, "/")[2] + } billing := resources.Billing{ OrderID: id, Type: accountv1.Consumption, Namespace: ns, - AppType: tp, + AppType: uint8(appType), + AppName: appName, AppCosts: appCost, Amount: amountt, Owner: owner, @@ -901,10 +882,6 @@ func (m *mongoDB) getMonitorCollectionName(collTime time.Time) string { return fmt.Sprintf("%s_%s", m.MonitorConnPrefix, collTime.Format("20060102")) } -func (m *mongoDB) getPricesCollection() *mongo.Collection { - return m.Client.Database(m.AccountDB).Collection(m.PricesConn) -} - func (m *mongoDB) getBillingCollection() *mongo.Collection { return m.Client.Database(m.AccountDB).Collection(m.BillingConn) } @@ -1008,7 +985,6 @@ func NewMongoInterface(ctx context.Context, URL string) (database.Interface, err MeteringConn: DefaultMeteringConn, MonitorConnPrefix: DefaultMonitorConn, BillingConn: DefaultBillingConn, - PricesConn: DefaultPricesConn, PropertiesConn: DefaultPropertiesConn, TrafficConn: env.GetEnvWithDefault(EnvTrafficConn, DefaultTrafficConn), CvmConn: env.GetEnvWithDefault(EnvCVMConn, DefaultCVMConn), diff --git a/controllers/pkg/database/mongo/account_test.go b/controllers/pkg/database/mongo/account_test.go index 14d31d37204..d8cbfa0fee9 100644 --- a/controllers/pkg/database/mongo/account_test.go +++ b/controllers/pkg/database/mongo/account_test.go @@ -479,25 +479,6 @@ func TestMongoDB_GetBillingLastUpdateTime(t *testing.T) { t.Logf("lastUpdateTime: %v", lastUpdateTime) } -func TestMongoDB_GetAllPricesMap(t *testing.T) { - dbCTX := context.Background() - - m, err := NewMongoInterface(dbCTX, os.Getenv("MONGODB_URI")) - if err != nil { - t.Errorf("failed to connect mongo: error = %v", err) - } - defer func() { - if err = m.Disconnect(dbCTX); err != nil { - t.Errorf("failed to disconnect mongo: error = %v", err) - } - }() - pricesMap, err := m.GetAllPricesMap() - if err != nil { - t.Fatalf("failed to get all prices map: %v", err) - } - t.Logf("pricesMap: %v", pricesMap) -} - func TestMongoDB_DropMonitorCollectionsOlderThan(t *testing.T) { dbCTX := context.Background() m, err := NewMongoInterface(dbCTX, os.Getenv("MONGODB_URI")) diff --git a/controllers/pkg/resources/named.go b/controllers/pkg/resources/named.go index 44cd8cb5d9c..b194a0d0c16 100644 --- a/controllers/pkg/resources/named.go +++ b/controllers/pkg/resources/named.go @@ -17,6 +17,8 @@ package resources import ( "strings" + "github.com/labring/sealos/controllers/pkg/utils/label" + corev1 "k8s.io/api/core/v1" sealos_networkmanager "github.com/dinoallo/sealos-networkmanager-protoapi" @@ -46,6 +48,7 @@ const ( TerminalIDLabelKey = "TerminalID" AppLabelKey = "app" AppDeployLabelKey = "cloud.sealos.io/app-deploy-manager" + AppStoreDeployLabelKey = "cloud.sealos.io/deploy-on-sealos" JobNameLabelKey = "job-name" ACMEChallengeKey = "acme.cert-manager.io/http01-solver" KubeBlocksBackUpName = "kubeblocks-backup-data" @@ -55,8 +58,10 @@ const ( type ResourceNamed struct { _name string // db or app or terminal or job or other - _type string - labels map[string]string + _type string + parentType string + parentName string + labels map[string]string } func NewResourceNamed(cr client.Object) *ResourceNamed { @@ -66,7 +71,7 @@ func NewResourceNamed(cr client.Object) *ResourceNamed { case labels[DBPodLabelComponentNameKey] != "": p._type = DB p._name = labels[DBPodLabelInstanceKey] - case labels[TerminalIDLabelKey] != "": + case labels[TerminalIDLabelKey] != "" || (labels[label.AppManagedBy] == label.DefaultManagedBy && labels[label.AppPartOf] == "terminal"): p._type = TERMINAL p._name = "" case labels[AppLabelKey] != "": @@ -91,6 +96,15 @@ func NewResourceNamed(cr client.Object) *ResourceNamed { return p } +func (r *ResourceNamed) SetInstanceParent(instances map[string]struct{}) { + for ins := range instances { + if strings.HasPrefix(r._name, ins) { + r.parentType = AppStore + r.parentName = ins + } + } +} + func NewObjStorageResourceNamed(bucket string) *ResourceNamed { return &ResourceNamed{ _type: ObjectStorage, @@ -121,22 +135,30 @@ func getACMEResolverName(obj client.Object) string { return pod.Name } -func (p *ResourceNamed) Type() uint8 { - return AppType[p._type] +func (r *ResourceNamed) Type() uint8 { + return AppType[r._type] +} + +func (r *ResourceNamed) ParentType() uint8 { + return AppType[r.parentType] +} + +func (r *ResourceNamed) ParentName() string { + return r.parentName } -func (p *ResourceNamed) Labels() map[string]string { +func (r *ResourceNamed) Labels() map[string]string { label := make(map[string]string) - switch p.Type() { + switch r.Type() { case db: - label[DBPodLabelComponentNameKey] = p.labels[DBPodLabelComponentNameKey] - label[DBPodLabelInstanceKey] = p.labels[DBPodLabelInstanceKey] + label[DBPodLabelComponentNameKey] = r.labels[DBPodLabelComponentNameKey] + label[DBPodLabelInstanceKey] = r.labels[DBPodLabelInstanceKey] case terminal: - label[TerminalIDLabelKey] = p.labels[TerminalIDLabelKey] + label[TerminalIDLabelKey] = r.labels[TerminalIDLabelKey] case app: - label[AppLabelKey] = p.labels[AppLabelKey] + label[AppLabelKey] = r.labels[AppLabelKey] case job: - label[JobNameLabelKey] = p.labels[JobNameLabelKey] + label[JobNameLabelKey] = r.labels[JobNameLabelKey] //case other: //default: } @@ -151,8 +173,8 @@ var notExistLabels = func() map[uint8][]*sealos_networkmanager.Label { return labels }() -func (p *ResourceNamed) GetNotExistLabels() []*sealos_networkmanager.Label { - return notExistLabels[p.Type()] +func (r *ResourceNamed) GetNotExistLabels() []*sealos_networkmanager.Label { + return notExistLabels[r.Type()] } func getNotExistLabels(tp uint8) []*sealos_networkmanager.Label { @@ -185,41 +207,41 @@ func getNotExistLabels(tp uint8) []*sealos_networkmanager.Label { return labels } -func (p *ResourceNamed) GetInLabels() []*sealos_networkmanager.Label { +func (r *ResourceNamed) GetInLabels() []*sealos_networkmanager.Label { var labelsEqual []*sealos_networkmanager.Label - switch p.Type() { + switch r.Type() { case db: labelsEqual = append(labelsEqual, &sealos_networkmanager.Label{ Key: DBPodLabelComponentNameKey, - Value: []string{p.labels[DBPodLabelComponentNameKey]}, + Value: []string{r.labels[DBPodLabelComponentNameKey]}, }) case terminal: labelsEqual = append(labelsEqual, &sealos_networkmanager.Label{ Key: TerminalIDLabelKey, - Value: []string{p.labels[TerminalIDLabelKey]}, + Value: []string{r.labels[TerminalIDLabelKey]}, }) case app: labelsEqual = append(labelsEqual, &sealos_networkmanager.Label{ Key: AppLabelKey, - Value: []string{p.labels[AppLabelKey]}, + Value: []string{r.labels[AppLabelKey]}, }) case job: labelsEqual = append(labelsEqual, &sealos_networkmanager.Label{ Key: JobNameLabelKey, - Value: []string{p.labels[JobNameLabelKey]}, + Value: []string{r.labels[JobNameLabelKey]}, }) } return labelsEqual } -func (p *ResourceNamed) TypeString() string { - return p._type +func (r *ResourceNamed) TypeString() string { + return r._type } -func (p *ResourceNamed) Name() string { - return p._name +func (r *ResourceNamed) Name() string { + return r._name } -func (p *ResourceNamed) String() string { - return p._type + "/" + p._name +func (r *ResourceNamed) String() string { + return r._type + "/" + r._name } diff --git a/controllers/pkg/resources/resources.go b/controllers/pkg/resources/resources.go index 04ea7b711ab..d93e660c3e4 100644 --- a/controllers/pkg/resources/resources.go +++ b/controllers/pkg/resources/resources.go @@ -15,15 +15,11 @@ package resources import ( - "fmt" "strings" "time" "github.com/labring/sealos/controllers/pkg/common" - "github.com/labring/sealos/controllers/pkg/crypto" - "github.com/labring/sealos/controllers/pkg/utils/logger" - "github.com/labring/sealos/controllers/pkg/gpu" "github.com/labring/sealos/controllers/pkg/utils/env" @@ -79,11 +75,13 @@ type Price struct { type Monitor struct { Time time.Time `json:"time" bson:"time"` // equal namespace - Category string `json:"category" bson:"category"` - Type uint8 `json:"type" bson:"type"` - Name string `json:"name" bson:"name"` - Used EnumUsedMap `json:"used" bson:"used"` - Property string `json:"property,omitempty" bson:"property,omitempty"` + Category string `json:"category" bson:"category"` + Type uint8 `json:"type" bson:"type"` + ParentType uint8 `json:"parent_type" bson:"parent_type"` + ParentName string `json:"parent_name" bson:"parent_name"` + Name string `json:"name" bson:"name"` + Used EnumUsedMap `json:"used" bson:"used"` + Property string `json:"property,omitempty" bson:"property,omitempty"` } type BillingType int @@ -98,6 +96,7 @@ type Billing struct { //UsedAmount Used `json:"used_amount" bson:"used_amount"` AppCosts []AppCost `json:"app_costs,omitempty" bson:"app_costs,omitempty"` + AppName string `json:"app_name,omitempty" bson:"app_name,omitempty"` AppType uint8 `json:"app_type,omitempty" bson:"app_type,omitempty"` Amount int64 `json:"amount" bson:"amount,omitempty"` @@ -127,6 +126,7 @@ type Transfer struct { } type AppCost struct { + Type uint8 `json:"type" bson:"type"` Used EnumUsedMap `json:"used" bson:"used"` UsedAmount EnumUsedMap `json:"used_amount" bson:"used_amount"` Amount int64 `json:"amount" bson:"amount,omitempty"` @@ -162,6 +162,7 @@ const ( other objectStorage cvm + appStore ) const ( @@ -172,14 +173,15 @@ const ( OTHER = "OTHER" ObjectStorage = "OBJECT-STORAGE" CVM = "CLOUD-VM" + AppStore = "APP-STORE" ) var AppType = map[string]uint8{ - DB: db, APP: app, TERMINAL: terminal, JOB: job, OTHER: other, ObjectStorage: objectStorage, CVM: cvm, + DB: db, APP: app, TERMINAL: terminal, JOB: job, OTHER: other, ObjectStorage: objectStorage, CVM: cvm, AppStore: appStore, } var AppTypeReverse = map[uint8]string{ - db: DB, app: APP, terminal: TERMINAL, job: JOB, other: OTHER, objectStorage: ObjectStorage, cvm: CVM, + db: DB, app: APP, terminal: TERMINAL, job: JOB, other: OTHER, objectStorage: ObjectStorage, cvm: CVM, appStore: AppStore, } // resource consumption @@ -287,11 +289,6 @@ func ConvertEnumUsedToString(costs map[uint8]int64) (costsMap map[string]int64) } func NewPropertyTypeLS(types []PropertyType) (ls *PropertyTypeLS) { - types, err := decryptPrice(types) - if err != nil { - logger.Warn("failed to decrypt price : %v", err) - types = DefaultPropertyTypeList - } return newPropertyTypeLS(types) } @@ -311,21 +308,6 @@ func newPropertyTypeLS(types []PropertyType) (ls *PropertyTypeLS) { return } -func decryptPrice(types []PropertyType) ([]PropertyType, error) { - for i := range types { - if types[i].EncryptUnitPrice == "" { - return types, fmt.Errorf("encrypt %s unit price is empty", types[i].Name) - } - price, err := crypto.DecryptFloat64(types[i].EncryptUnitPrice) - if err != nil { - return types, fmt.Errorf("failed to decrypt %s unit price : %v", types[i].Name, err) - } - types[i].UnitPrice = price - logger.Info("parse properties", types[i].Enum, types[i].UnitPrice) - } - return types, nil -} - type PropertyTypeEnumMap map[uint8]PropertyType type PropertyTypeStringMap map[string]PropertyType diff --git a/controllers/pkg/types/global.go b/controllers/pkg/types/global.go index 2a20bdcb95e..4d22bc23036 100644 --- a/controllers/pkg/types/global.go +++ b/controllers/pkg/types/global.go @@ -21,8 +21,9 @@ import ( ) type Account struct { - UserUID uuid.UUID `gorm:"column:userUid;type:uuid;default:gen_random_uuid();primary_key"` - ActivityBonus int64 `gorm:"column:activityBonus;type:bigint;not null"` + UserUID uuid.UUID `gorm:"column:userUid;type:uuid;default:gen_random_uuid();primary_key"` + ActivityBonus int64 `gorm:"column:activityBonus;type:bigint;not null"` + // Discard EncryptBalance and EncryptDeductionBalance EncryptBalance string `gorm:"column:encryptBalance;type:text;not null"` EncryptDeductionBalance string `gorm:"column:encryptDeductionBalance;type:text;not null"` CreatedAt time.Time `gorm:"type:timestamp(3) with time zone;default:current_timestamp()"` diff --git a/controllers/resources/config/rbac/role.yaml b/controllers/resources/config/rbac/role.yaml index 9ae57392009..64d2d82e730 100644 --- a/controllers/resources/config/rbac/role.yaml +++ b/controllers/resources/config/rbac/role.yaml @@ -84,25 +84,17 @@ rules: - list - watch - apiGroups: - - infra.sealos.io + - app.sealos.io resources: - - infras + - instances verbs: - get - list - watch - apiGroups: - - infra.sealos.io + - app.sealos.io resources: - - infras/finalizers - verbs: - - get - - list - - watch -- apiGroups: - - infra.sealos.io - resources: - - infras/status + - instances/status verbs: - get - list diff --git a/controllers/resources/controllers/monitor_controller.go b/controllers/resources/controllers/monitor_controller.go index 456bb007e9c..15fa69e4ee2 100644 --- a/controllers/resources/controllers/monitor_controller.go +++ b/controllers/resources/controllers/monitor_controller.go @@ -25,6 +25,10 @@ import ( "sync" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + appv1 "github.com/labring/sealos/controllers/app/api/v1" + "golang.org/x/sync/errgroup" "github.com/labring/sealos/controllers/pkg/utils/env" @@ -104,11 +108,10 @@ const ( //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=resourcequotas,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=resourcequotas/status,verbs=get;list;watch -//+kubebuilder:rbac:groups=infra.sealos.io,resources=infras,verbs=get;list;watch -//+kubebuilder:rbac:groups=infra.sealos.io,resources=infras/status,verbs=get;list;watch -//+kubebuilder:rbac:groups=infra.sealos.io,resources=infras/finalizers,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=services/status,verbs=get;list;watch +//+kubebuilder:rbac:groups=app.sealos.io,resources=instances,verbs=get;list;watch +//+kubebuilder:rbac:groups=app.sealos.io,resources=instances/status,verbs=get;list;watch func NewMonitorReconciler(mgr ctrl.Manager) (*MonitorReconciler, error) { r := &MonitorReconciler{ @@ -299,12 +302,15 @@ func (r *MonitorReconciler) monitorResourceUsage(namespace *corev1.Namespace) er timeStamp := time.Now().UTC() resUsed := map[string]map[corev1.ResourceName]*quantity{} resNamed := make(map[string]*resources.ResourceNamed) - - if err := r.monitorPodResourceUsage(namespace.Name, resUsed, resNamed); err != nil { + instances, err := r.getInstances(namespace.Name) + if err != nil { + return fmt.Errorf("failed to get instances: %v", err) + } + if err := r.monitorPodResourceUsage(namespace.Name, resUsed, resNamed, instances); err != nil { return fmt.Errorf("failed to monitor pod resource usage: %v", err) } - if err := r.monitorPVCResourceUsage(namespace.Name, resUsed, resNamed); err != nil { + if err := r.monitorPVCResourceUsage(namespace.Name, resUsed, resNamed, instances); err != nil { return fmt.Errorf("failed to monitor PVC resource usage: %v", err) } @@ -312,7 +318,7 @@ func (r *MonitorReconciler) monitorResourceUsage(namespace *corev1.Namespace) er return fmt.Errorf("failed to monitor backup resource usage: %v", err) } - if err := r.monitorServiceResourceUsage(namespace.Name, resUsed, resNamed); err != nil { + if err := r.monitorServiceResourceUsage(namespace.Name, resUsed, resNamed, instances); err != nil { return fmt.Errorf("failed to monitor service resource usage: %v", err) } @@ -328,17 +334,36 @@ func (r *MonitorReconciler) monitorResourceUsage(namespace *corev1.Namespace) er continue } monitors = append(monitors, &resources.Monitor{ - Category: namespace.Name, - Used: used, - Time: timeStamp, - Type: resNamed[name].Type(), - Name: resNamed[name].Name(), + Category: namespace.Name, + Used: used, + Time: timeStamp, + Type: resNamed[name].Type(), + Name: resNamed[name].Name(), + ParentType: resNamed[name].ParentType(), + ParentName: resNamed[name].ParentName(), }) } return r.DBClient.InsertMonitor(context.Background(), monitors...) } -func (r *MonitorReconciler) monitorPodResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed) error { +func (r *MonitorReconciler) getInstances(namespace string) (map[string]struct{}, error) { + instances := make(map[string]struct{}) + insList := metav1.PartialObjectMetadataList{} + insList.SetGroupVersionKind(appv1.GroupVersion.WithKind("InstanceList")) + if err := r.List(context.Background(), &insList, client.InNamespace(namespace)); err != nil { + return nil, fmt.Errorf("failed to list instances: %v", err) + } + for i := range insList.Items { + name := insList.Items[i].Labels[resources.AppStoreDeployLabelKey] + if name == "" { + name = insList.Items[i].Name + } + instances[name] = struct{}{} + } + return instances, nil +} + +func (r *MonitorReconciler) monitorPodResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed, instances map[string]struct{}) error { podList := &corev1.PodList{} if err := r.List(context.Background(), podList, &client.ListOptions{ Namespace: namespace, @@ -352,6 +377,7 @@ func (r *MonitorReconciler) monitorPodResourceUsage(namespace string, resUsed ma continue } podResNamed := resources.NewResourceNamed(pod) + podResNamed.SetInstanceParent(instances) resNamed[podResNamed.String()] = podResNamed if resUsed[podResNamed.String()] == nil { resUsed[podResNamed.String()] = initResources() @@ -383,7 +409,7 @@ func (r *MonitorReconciler) monitorPodResourceUsage(namespace string, resUsed ma return nil } -func (r *MonitorReconciler) monitorPVCResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed) error { +func (r *MonitorReconciler) monitorPVCResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed, instances map[string]struct{}) error { pvcList := &corev1.PersistentVolumeClaimList{} if err := r.List(context.Background(), pvcList, &client.ListOptions{ Namespace: namespace, @@ -397,6 +423,7 @@ func (r *MonitorReconciler) monitorPVCResourceUsage(namespace string, resUsed ma continue } pvcRes := resources.NewResourceNamed(pvc) + pvcRes.SetInstanceParent(instances) if resUsed[pvcRes.String()] == nil { resNamed[pvcRes.String()] = pvcRes resUsed[pvcRes.String()] = initResources() @@ -424,7 +451,8 @@ func (r *MonitorReconciler) monitorDatabaseBackupUsage(namespace string, resUsed return nil } -func (r *MonitorReconciler) monitorServiceResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed) error { +// instance is the app instance name +func (r *MonitorReconciler) monitorServiceResourceUsage(namespace string, resUsed map[string]map[corev1.ResourceName]*quantity, resNamed map[string]*resources.ResourceNamed, instances map[string]struct{}) error { svcList := &corev1.ServiceList{} if err := r.List(context.Background(), svcList, &client.ListOptions{ Namespace: namespace, @@ -442,6 +470,7 @@ func (r *MonitorReconciler) monitorServiceResourceUsage(namespace string, resUse port[svcPort.NodePort] = struct{}{} } svcRes := resources.NewResourceNamed(svc) + svcRes.SetInstanceParent(instances) if resUsed[svcRes.String()] == nil { resNamed[svcRes.String()] = svcRes resUsed[svcRes.String()] = initResources() diff --git a/controllers/resources/deploy/manifests/deploy.yaml b/controllers/resources/deploy/manifests/deploy.yaml index b3f4f34c103..3ab1fdcfebc 100644 --- a/controllers/resources/deploy/manifests/deploy.yaml +++ b/controllers/resources/deploy/manifests/deploy.yaml @@ -134,25 +134,17 @@ rules: - list - watch - apiGroups: - - infra.sealos.io + - app.sealos.io resources: - - infras + - instances verbs: - get - list - watch - apiGroups: - - infra.sealos.io + - app.sealos.io resources: - - infras/finalizers - verbs: - - get - - list - - watch -- apiGroups: - - infra.sealos.io - resources: - - infras/status + - instances/status verbs: - get - list diff --git a/controllers/resources/main.go b/controllers/resources/main.go index 7b6e1e034fc..ff26ecbe530 100644 --- a/controllers/resources/main.go +++ b/controllers/resources/main.go @@ -30,6 +30,8 @@ import ( "github.com/labring/sealos/controllers/pkg/objectstorage" "github.com/labring/sealos/controllers/pkg/resources" "github.com/labring/sealos/controllers/pkg/utils/env" + + appv1 "github.com/labring/sealos/controllers/app/api/v1" "github.com/labring/sealos/controllers/resources/controllers" _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -51,6 +53,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(appv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/service/account/api/api.go b/service/account/api/api.go index af1873b9d25..2613e36ac34 100644 --- a/service/account/api/api.go +++ b/service/account/api/api.go @@ -1,8 +1,17 @@ package api import ( + "bytes" + "crypto/tls" + "encoding/json" "fmt" "net/http" + "os" + "strings" + + auth2 "github.com/labring/sealos/service/pkg/auth" + + "github.com/labring/sealos/controllers/pkg/resources" "github.com/labring/sealos/controllers/pkg/database/cockroach" @@ -36,7 +45,7 @@ func GetBillingHistoryNamespaceList(c *gin.Context) { c.JSON(http.StatusBadRequest, helper.ErrorMessage{Error: fmt.Sprintf("failed to parse namespace billing history request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, helper.ErrorMessage{Error: fmt.Sprintf("authenticate error : %v", err)}) return } @@ -47,11 +56,8 @@ func GetBillingHistoryNamespaceList(c *gin.Context) { c.JSON(http.StatusInternalServerError, helper.ErrorMessage{Error: fmt.Sprintf("failed to get namespace billing history list: %v", err)}) return } - c.JSON(http.StatusOK, helper.NamespaceBillingHistoryResp{ - Data: helper.NamespaceBillingHistoryRespData{ - List: nsList, - }, - Message: "successfully retrieved namespace billing history list", + c.JSON(http.StatusOK, gin.H{ + "data": nsList, }) } @@ -66,10 +72,6 @@ func GetBillingHistoryNamespaceList(c *gin.Context) { // @Failure 500 {object} helper.ErrorMessage "failed to get properties" // @Router /account/v1alpha1/properties [post] func GetProperties(c *gin.Context) { - if err := helper.AuthenticateWithBind(c); err != nil { - c.JSON(http.StatusUnauthorized, helper.ErrorMessage{Error: fmt.Sprintf("authenticate error : %v", err)}) - return - } // Get the properties from the database properties, err := dao.DBClient.GetProperties() if err != nil { @@ -102,7 +104,7 @@ func GetConsumptionAmount(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user consumption amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -134,7 +136,7 @@ func GetPayment(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user payment request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -166,7 +168,7 @@ func GetRechargeAmount(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user recharge amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -198,7 +200,7 @@ func GetPropertiesUsedAmount(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user properties used amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -238,7 +240,7 @@ func GetCosts(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user hour costs amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -270,7 +272,7 @@ func GetAccount(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse user hour costs amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -302,7 +304,7 @@ func SetPaymentInvoice(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse set payment invoice request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -333,7 +335,7 @@ func TransferAmount(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse transfer amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -369,7 +371,7 @@ func GetTransfer(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse get transfer amount request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -414,7 +416,7 @@ func GetAPPCosts(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse get app cost request: %v", err)}) return } - if err := helper.Authenticate(req.Auth); err != nil { + if err := CheckAuthAndCalibrate(req.Auth); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) return } @@ -427,3 +429,283 @@ func GetAPPCosts(c *gin.Context) { "app_costs": cost, }) } + +// CheckPermission +// @Summary Check permission +// @Description Check permission +// @Tags Permission +// @Accept json +// @Produce json +// @Param request body helper.UserBaseReq true "Check permission request" +// @Success 200 {object} map[string]interface{} "successfully check permission" +// @Failure 400 {object} map[string]interface{} "failed to parse check permission request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to check permission" +// @Router /account/v1alpha1/check-permission [post] +func CheckPermission(c *gin.Context) { + req, err := helper.ParseUserBaseReq(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("failed to parse check permission request: %v", err)}) + return + } + if err = CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "userID": req.Auth.UserID, + "message": "successfully check permission", + }) +} + +// GetRegions +// @Summary Get regions +// @Description Get regions +// @Tags Regions +// @Accept json +// @Produce json +// @Success 200 {object} map[string]interface{} "successfully get regions" +// @Failure 500 {object} map[string]interface{} "failed to get regions" +// @Router /account/v1alpha1/regions [post] +func GetRegions(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "regions": dao.Cfg.Regions, + }) +} + +// GetCostOverview +// @Summary Get cost overview +// @Description Get cost overview +// @Tags CostOverview +// @Accept json +// @Produce json +// @Param request body helper.GetCostAppListReq true "Cost overview request" +// @Success 200 {object} helper.CostOverviewResp "successfully get cost overview" +// @Failure 400 {object} map[string]interface{} "failed to parse cost overview request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get cost overview" +// @Router /account/v1alpha1/cost-overview [post] +func GetCostOverview(c *gin.Context) { + req, err := helper.ParseGetCostAppListReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse cost overview request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + overview, err := dao.DBClient.GetCostOverview(*req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get cost overview : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": overview, + }) +} + +// GetCostAppList +// @Summary Get cost app list +// @Description Get cost app list +// @Tags CostAppList +// @Accept json +// @Produce json +// @Param request body helper.GetCostAppListReq true "Cost app list request" +// @Success 200 {object} helper.CostAppListResp "successfully get cost app list" +// @Failure 400 {object} map[string]interface{} "failed to parse cost app list request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get cost app list" +// @Router /account/v1alpha1/cost-app-list [post] +func GetCostAppList(c *gin.Context) { + req, err := helper.ParseGetCostAppListReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse cost app list request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + apps, err := dao.DBClient.GetCostAppList(*req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get cost app list : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": apps, + }) +} + +// GetAppTypeList +// @Summary Get app type list +// @Description Get app type list +// @Tags AppTypeList +// @Accept json +// @Produce json +// @Success 200 {object} map[string]interface{} "successfully get app type list" +// @Failure 500 {object} map[string]interface{} "failed to get app type list" +// @Router /account/v1alpha1/cost-app-type-list [post] +func GetAppTypeList(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "data": resources.AppTypeReverse, + }) +} + +// GetBasicCostDistribution +// @Summary Get basic cost distribution +// @Description Get basic cost distribution +// @Tags BasicCostDistribution +// @Accept json +// @Produce json +// @Param request body helper.GetCostAppListReq true "Basic cost distribution request" +// @Success 200 {object} map[string]interface{} "successfully get basic cost distribution" +// @Failure 400 {object} map[string]interface{} "failed to parse basic cost distribution request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get basic cost distribution" +// @Router /account/v1alpha1/basic-cost-distribution [post] +func GetBasicCostDistribution(c *gin.Context) { + req, err := helper.ParseGetCostAppListReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse basic cost distribution request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + costs, err := dao.DBClient.GetBasicCostDistribution(*req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get basic cost distribution : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": costs, + }) +} + +// GetAppCostTimeRange +// @Summary Get app cost time range +// @Description Get app cost time range +// @Tags AppCostTimeRange +// @Accept json +// @Produce json +// @Param request body helper.GetCostAppListReq true "App cost time range request" +// @Success 200 {object} map[string]interface{} "successfully get app cost time range" +// @Failure 400 {object} map[string]interface{} "failed to parse app cost time range request" +// @Failure 401 {object} map[string]interface{} "authenticate error" +// @Failure 500 {object} map[string]interface{} "failed to get app cost time range" +// @Router /account/v1alpha1/cost-app-time-range [post] +func GetAppCostTimeRange(c *gin.Context) { + req, err := helper.ParseGetCostAppListReq(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse app cost time range request: %v", err)}) + return + } + if err := CheckAuthAndCalibrate(req.Auth); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("authenticate error : %v", err)}) + return + } + timeRange, err := dao.DBClient.GetAppCostTimeRange(*req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get app cost time range : %v", err)}) + return + } + c.JSON(http.StatusOK, gin.H{ + "data": timeRange, + }) +} + +func CheckAuthAndCalibrate(auth *helper.Auth) (err error) { + if !dao.Debug || auth.KubeConfig != "" { + if err = checkAuth(auth); err != nil { + return fmt.Errorf("check auth error: %v", err) + } + } + auth.Owner, err = dao.DBClient.GetUserCrName(types.UserQueryOpts{ID: auth.UserID}) + if err != nil { + return fmt.Errorf("get user cr name error: %v", err) + } + fmt.Printf("auth: %v\n", auth) + return nil +} + +func checkAuth(auth *helper.Auth) error { + if err := helper.AuthenticateKC(*auth); err != nil { + return fmt.Errorf("authenticate error : %v", err) + } + host, err := auth2.GetKcHost(auth.KubeConfig) + if err != nil { + return fmt.Errorf("failed to get kc host: %v", err) + } + host = strings.TrimPrefix(strings.TrimPrefix(host, "https://"), "http://") + if !strings.Contains(host, dao.Cfg.LocalRegionDomain) { + if err := CalibrateRegionAuth(auth, host); err != nil { + return fmt.Errorf("calibrate region auth error: %v", err) + } + } else { + user, err := auth2.GetKcUser(auth.KubeConfig) + if err != nil { + return fmt.Errorf("failed to get kc user: %v", err) + } + userID, err := dao.DBClient.GetUserID(types.UserQueryOpts{Owner: user}) + if err != nil { + return fmt.Errorf("get user id error: %v", err) + } + auth.UserID = userID + } + auth.Owner, err = dao.DBClient.GetUserCrName(types.UserQueryOpts{ID: auth.UserID}) + if err != nil { + return fmt.Errorf("get user cr name error: %v", err) + } + return nil +} + +func CalibrateRegionAuth(auth *helper.Auth, kcHost string) error { + for i := range dao.Cfg.Regions { + reg := dao.Cfg.Regions[i] + if !strings.Contains(kcHost, reg.Domain) { + continue + } + svcURL := fmt.Sprintf("https://%s%s%s", reg.AccountSvc, helper.GROUP, helper.CheckPermission) + + authBody, err := json.Marshal(auth) + if err != nil { + return fmt.Errorf("failed to marshal auth: %v", err) + } + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: os.Getenv("INSECURE_VERIFY") != "true", MinVersion: tls.VersionTLS13}, + } + client := &http.Client{Transport: tr} + resp, err := client.Post(svcURL, "application/json", bytes.NewBuffer(authBody)) + if err != nil { + return fmt.Errorf("failed to post request: %v", err) + } + defer resp.Body.Close() + + responseBody := new(bytes.Buffer) + _, err = responseBody.ReadFrom(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + var respMap map[string]interface{} + if err = json.Unmarshal(responseBody.Bytes(), &respMap); err != nil { + return fmt.Errorf("failed to unmarshal response body: %v", err) + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to check permission: %v, error: %s", resp, respMap["error"]) + } + _userID, ok := respMap["userID"] + if !ok { + return fmt.Errorf("failed to get userID from response: %v", respMap) + } + userID, ok := _userID.(string) + if !ok { + return fmt.Errorf("failed to convert userID to string: %v", _userID) + } + auth.UserID = userID + return nil + } + return fmt.Errorf("failed to calibrate region auth") +} diff --git a/service/account/common/account.go b/service/account/common/account.go index 5964db16ec8..83633cff74d 100644 --- a/service/account/common/account.go +++ b/service/account/common/account.go @@ -19,13 +19,14 @@ type AppCosts struct { } type AppCost struct { - AppName string `json:"app_name,omitempty" bson:"app_name,omitempty" example:"app"` - AppType int32 `json:"app_type,omitempty" bson:"app_type,omitempty" example:"app"` - Time time.Time `json:"time,omitempty" bson:"time,omitempty" example:"2021-01-01T00:00:00Z"` - OrderID string `json:"order_id,omitempty" bson:"order_id,omitempty" example:"order_id"` - Namespace string `json:"namespace,omitempty" bson:"namespace,omitempty" example:"ns-admin"` - Used Used `json:"used,omitempty" bson:"used,omitempty"` - Amount int64 `json:"amount,omitempty" bson:"amount,omitempty" example:"100000000"` + AppName string `json:"app_name,omitempty" bson:"app_name,omitempty" example:"app"` + AppType int32 `json:"app_type,omitempty" bson:"app_type,omitempty" example:"app"` + Time time.Time `json:"time,omitempty" bson:"time,omitempty" example:"2021-01-01T00:00:00Z"` + OrderID string `json:"order_id,omitempty" bson:"order_id,omitempty" example:"order_id"` + Namespace string `json:"namespace,omitempty" bson:"namespace,omitempty" example:"ns-admin"` + Used Used `json:"used,omitempty" bson:"used,omitempty"` + UsedAmount Used `json:"used_amount,omitempty" bson:"used_amount,omitempty"` + Amount int64 `json:"amount,omitempty" bson:"amount,omitempty" example:"100000000"` } type Used map[uint8]int64 diff --git a/service/account/dao/init.go b/service/account/dao/init.go index c4587ee7ab7..52d155016cd 100644 --- a/service/account/dao/init.go +++ b/service/account/dao/init.go @@ -4,10 +4,29 @@ import ( "fmt" "os" + "github.com/goccy/go-json" + "github.com/labring/sealos/service/account/helper" ) -var DBClient Interface +type Config struct { + LocalRegionDomain string `json:"localRegionDomain"` + Regions []Region `json:"regions"` +} + +type Region struct { + Domain string `json:"domain"` + AccountSvc string `json:"accountSvc"` + UID string `json:"uid"` + // zh: region name, en: region name + Name map[string]string `json:"name"` +} + +var ( + DBClient Interface + Cfg *Config + Debug bool +) func InitDB() error { var err error @@ -23,13 +42,50 @@ func InitDB() error { if mongoURI == "" { return fmt.Errorf("empty mongo uri, please check env: %s", helper.EnvMongoURI) } - fmt.Println("cockroachStr: ", globalCockroach) - fmt.Println("localRegionStr: ", localCockroach) - fmt.Println("mongoURI: ", mongoURI) + Debug = os.Getenv("DEBUG") == "true" DBClient, err = NewAccountInterface(mongoURI, globalCockroach, localCockroach) if err != nil { return err } - _, err = DBClient.GetProperties() - return err + if _, err = DBClient.GetProperties(); err != nil { + return fmt.Errorf("get properties error: %v", err) + } + + file := helper.ConfigPath + if _, err := os.Stat(file); err == nil { + data, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("read config file error: %v", err) + } + fmt.Printf("config file found, use config file: \n%s\n", file) + + Cfg = &Config{} + // json marshal + if err = json.Unmarshal(data, Cfg); err != nil { + return fmt.Errorf("unmarshal config file error: %v", err) + } + } + if len(Cfg.Regions) == 0 { + regions, err := DBClient.GetRegions() + if err != nil { + return fmt.Errorf("get regions error: %v", err) + } + Cfg = &Config{ + Regions: make([]Region, 0), + } + for i := range regions { + Cfg.Regions = append(Cfg.Regions, Region{ + Domain: regions[i].Domain, + AccountSvc: "account-api." + regions[i].Domain, + UID: regions[i].UID.String(), + Name: map[string]string{ + "zh": regions[i].DisplayName, + "en": regions[i].DisplayName, + }, + }) + } + } + Cfg.LocalRegionDomain = DBClient.GetLocalRegion().Domain + fmt.Println("region-info: ", Cfg) + return nil } diff --git a/service/account/dao/interface.go b/service/account/dao/interface.go index 3bd42025f27..335f196fe96 100644 --- a/service/account/dao/interface.go +++ b/service/account/dao/interface.go @@ -25,18 +25,28 @@ import ( ) type Interface interface { - GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHistoryReq) ([]string, error) + GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHistoryReq) ([][]string, error) GetProperties() ([]common.PropertyQuery, error) GetCosts(user string, startTime, endTime time.Time) (common.TimeCostsMap, error) GetAppCosts(req *helper.AppCostsReq) (*common.AppCosts, error) + GetAppCostTimeRange(req helper.GetCostAppListReq) (helper.TimeRange, error) + GetCostOverview(req helper.GetCostAppListReq) (helper.CostOverviewResp, error) + GetBasicCostDistribution(req helper.GetCostAppListReq) (map[string]int64, error) + GetCostAppList(req helper.GetCostAppListReq) (helper.CostAppListResp, error) + Disconnect(ctx context.Context) error GetConsumptionAmount(user, namespace, appType string, startTime, endTime time.Time) (int64, error) GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) GetPropertiesUsedAmount(user string, startTime, endTime time.Time) (map[string]int64, error) GetAccount(ops types.UserQueryOpts) (*types.Account, error) GetPayment(ops types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) + GetWorkspaceName(namespaces []string) ([][]string, error) SetPaymentInvoice(req *helper.SetPaymentInvoiceReq) error Transfer(req *helper.TransferAmountReq) error GetTransfer(ops *types.GetTransfersReq) (*types.GetTransfersResp, error) + GetUserID(ops types.UserQueryOpts) (string, error) + GetUserCrName(ops types.UserQueryOpts) (string, error) + GetRegions() ([]types.Region, error) + GetLocalRegion() types.Region } type Account struct { @@ -64,6 +74,34 @@ func (g *Cockroach) GetAccount(ops types.UserQueryOpts) (*types.Account, error) return account, nil } +func (g *Cockroach) GetWorkspaceName(namespaces []string) ([][]string, error) { + workspaceList := make([][]string, 0) + workspaces, err := g.ck.GetWorkspace(namespaces...) + if err != nil { + return nil, fmt.Errorf("failed to get workspace: %v", err) + } + for _, workspace := range workspaces { + workspaceList = append(workspaceList, []string{workspace.ID, workspace.DisplayName}) + } + return workspaceList, nil +} + +func (g *Cockroach) GetUserID(ops types.UserQueryOpts) (string, error) { + user, err := g.ck.GetUser(&ops) + if err != nil { + return "", fmt.Errorf("failed to get user: %v", err) + } + return user.ID, nil +} + +func (g *Cockroach) GetUserCrName(ops types.UserQueryOpts) (string, error) { + user, err := g.ck.GetUserCr(&ops) + if err != nil { + return "", fmt.Errorf("failed to get user: %v", err) + } + return user.CrName, nil +} + func (g *Cockroach) GetPayment(ops types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) { return g.ck.GetPayment(&ops, startTime, endTime) } @@ -83,6 +121,14 @@ func (g *Cockroach) GetTransfer(ops *types.GetTransfersReq) (*types.GetTransfers return g.ck.GetTransfer(ops) } +func (g *Cockroach) GetRegions() ([]types.Region, error) { + return g.ck.GetRegions() +} + +func (g *Cockroach) GetLocalRegion() types.Region { + return g.ck.GetLocalRegion() +} + func (g *Cockroach) GetRechargeAmount(ops types.UserQueryOpts, startTime, endTime time.Time) (int64, error) { payment, err := g.GetPayment(ops, startTime, endTime) if err != nil { @@ -152,54 +198,254 @@ func (m *MongoDB) GetCosts(user string, startTime, endTime time.Time) (common.Ti return costsMap, nil } -func (m *MongoDB) GetAppCosts(req *helper.AppCostsReq) (*common.AppCosts, error) { +func (m *MongoDB) GetAppCosts(req *helper.AppCostsReq) (results *common.AppCosts, rErr error) { + if req.Page <= 0 { + req.Page = 1 + } + if req.PageSize <= 0 { + req.PageSize = 10 + } pageSize := req.PageSize - currentPage := req.Page + results = &common.AppCosts{ + CurrentPage: req.Page, + } + if req.OrderID != "" { + costs, err := m.GetAppCostsByOrderIDAndAppName(req) + if err != nil { + rErr = fmt.Errorf("failed to get app costs by order id and app name: %w", err) + return + } + results.Costs = costs + results.TotalRecords = len(costs) + results.TotalPages = 1 + return + } + + timeMatch := bson.E{Key: "time", Value: bson.D{{Key: "$gte", Value: req.StartTime}, {Key: "$lte", Value: req.EndTime}}} + + matchConditions := bson.D{timeMatch} + matchConditions = append(matchConditions, bson.E{Key: "owner", Value: req.Owner}) + if req.AppName != "" && req.AppType != "" { + if strings.ToUpper(req.AppType) != resources.AppStore { + matchConditions = append(matchConditions, bson.E{Key: "app_costs.name", Value: req.AppName}) + } else { + matchConditions = append(matchConditions, bson.E{Key: "app_name", Value: req.AppName}) + } + } + if req.Namespace != "" { + matchConditions = append(matchConditions, bson.E{Key: "namespace", Value: req.Namespace}) + } + + if strings.ToUpper(req.AppType) != resources.AppStore { + var match bson.D + if req.AppType != "" { + match = append(matchConditions, bson.E{Key: "app_type", Value: resources.AppType[strings.ToUpper(req.AppType)]}) + } else { + match = append(matchConditions, bson.E{Key: "app_type", Value: bson.M{"$ne": resources.AppType[resources.AppStore]}}) + } + if req.OrderID != "" { + match = bson.D{ + {Key: "order_id", Value: req.OrderID}, + {Key: "owner", Value: req.Owner}, + } + } + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$facet", Value: bson.D{ + {Key: "totalRecords", Value: bson.A{ + bson.D{{Key: "$unwind", Value: "$app_costs"}}, + bson.D{{Key: "$match", Value: matchConditions}}, + bson.D{{Key: "$count", Value: "count"}}, + }}, + {Key: "costs", Value: bson.A{ + bson.D{{Key: "$unwind", Value: "$app_costs"}}, + bson.D{{Key: "$match", Value: matchConditions}}, + bson.D{{Key: "$sort", Value: bson.D{ + {Key: "time", Value: -1}, + {Key: "app_costs.name", Value: 1}, + {Key: "_id", Value: 1}, + }}}, + bson.D{{Key: "$skip", Value: (req.Page - 1) * pageSize}}, + bson.D{{Key: "$limit", Value: pageSize}}, + bson.D{{Key: "$project", Value: bson.D{ + {Key: "_id", Value: 0}, + {Key: "time", Value: 1}, + {Key: "order_id", Value: 1}, + {Key: "namespace", Value: 1}, + {Key: "used", Value: "$app_costs.used"}, + {Key: "used_amount", Value: "$app_costs.used_amount"}, + {Key: "amount", Value: "$app_costs.amount"}, + {Key: "app_name", Value: "$app_costs.name"}, + {Key: "app_type", Value: "$app_type"}, + }}}, + }}, + }}}, + {{Key: "$project", Value: bson.D{ + {Key: "total_records", Value: bson.D{{Key: "$arrayElemAt", Value: bson.A{"$totalRecords.count", 0}}}}, + {Key: "total_pages", Value: bson.D{{Key: "$ceil", Value: bson.D{{Key: "$divide", Value: bson.A{bson.D{{Key: "$arrayElemAt", Value: bson.A{"$totalRecords.count", 0}}}, pageSize}}}}}}, + {Key: "costs", Value: 1}, + }}}, + } + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return nil, fmt.Errorf("failed to aggregate billing collection: %v", err) + } + if cursor.Next(context.Background()) { + if err := cursor.Decode(results); err != nil { + return nil, fmt.Errorf("failed to decode result: %v", err) + } + } + } + + if req.AppType == "" || strings.ToUpper(req.AppType) == resources.AppStore { + matchConditions = append(matchConditions, bson.E{Key: "app_type", Value: resources.AppType[resources.AppStore]}) + appStoreTotal, err := m.getAppStoreCostsTotal(req) + if err != nil { + rErr = fmt.Errorf("failed to get app store costs total: %w", err) + return + } + currentAppPageIsFull := len(results.Costs) == pageSize + maxAppPageSize := (results.TotalRecords + pageSize - 1) / pageSize + completedNum := calculateComplement(results.TotalRecords, pageSize) + + if req.Page == maxAppPageSize { + if !currentAppPageIsFull { + appStoreCost, err := m.getAppStoreCosts(matchConditions, 0, completedNum) + if err != nil { + rErr = fmt.Errorf("failed to get app store costs: %w", err) + return + } + results.Costs = append(results.Costs, appStoreCost.Costs...) + } + } else if req.Page > maxAppPageSize { + skipPageSize := (req.Page - maxAppPageSize - 1) * pageSize + if skipPageSize < 0 { + skipPageSize = 0 + } + appStoreCost, err := m.getAppStoreCosts(matchConditions, completedNum+skipPageSize, req.PageSize) + if err != nil { + rErr = fmt.Errorf("failed to get app store costs: %w", err) + return + } + results.Costs = append(results.Costs, appStoreCost.Costs...) + } + results.TotalRecords += int(appStoreTotal) + } + results.TotalPages = (results.TotalRecords + pageSize - 1) / pageSize + return results, nil +} - timeMatch := bson.E{Key: "time", Value: bson.D{{Key: "$gte", Value: req.StartTime}, {Key: "$lt", Value: req.EndTime}}} +func (m *MongoDB) getAppStoreCostsTotal(req *helper.AppCostsReq) (int64, error) { + matchConditions := bson.D{ + {Key: "owner", Value: req.Owner}, + {Key: "app_type", Value: resources.AppType[resources.AppStore]}, + } + if req.AppName != "" { + matchConditions = append(matchConditions, bson.E{Key: "app_costs.name", Value: req.AppName}) + } + if req.Namespace != "" { + matchConditions = append(matchConditions, bson.E{Key: "namespace", Value: req.Namespace}) + } + matchConditions = append(matchConditions, bson.E{Key: "time", Value: bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + }}) pipeline := mongo.Pipeline{ - // Initially matches a document with app_costs.name equal to the specified value | 初步匹配 app_costs.name 等于指定值的文档 - {{Key: "$match", Value: bson.D{ - {Key: "app_costs.name", Value: req.AppName}, - {Key: "app_type", Value: resources.AppType[strings.ToUpper(req.AppType)]}, - {Key: "namespace", Value: req.Namespace}, - {Key: "owner", Value: req.Owner}, - timeMatch, - }}}, - // Process total records and paging data in parallel | 并行处理总记录数和分页数据 - {{Key: "$facet", Value: bson.D{ - {Key: "totalRecords", Value: bson.A{ - bson.D{{Key: "$unwind", Value: "$app_costs"}}, - bson.D{{Key: "$match", Value: bson.D{ - {Key: "app_costs.name", Value: req.AppName}, - timeMatch, - }}}, - bson.D{{Key: "$count", Value: "count"}}, - }}, - {Key: "costs", Value: bson.A{ - bson.D{{Key: "$unwind", Value: "$app_costs"}}, - bson.D{{Key: "$match", Value: bson.D{ - {Key: "app_costs.name", Value: req.AppName}, - timeMatch, - }}}, - bson.D{{Key: "$skip", Value: (currentPage - 1) * pageSize}}, - bson.D{{Key: "$limit", Value: pageSize}}, - bson.D{{Key: "$project", Value: bson.D{ - {Key: "_id", Value: 0}, - {Key: "time", Value: 1}, - {Key: "order_id", Value: 1}, - {Key: "namespace", Value: 1}, - {Key: "used", Value: "$app_costs.used"}, - {Key: "amount", Value: "$app_costs.amount"}, - {Key: "app_name", Value: "$app_costs.name"}, - {Key: "app_type", Value: "$app_type"}, - }}}, - }}, + {{Key: "$match", Value: matchConditions}}, + {{Key: "$count", Value: "total_records"}}, + } + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return 0, fmt.Errorf("failed to aggregate billing collection: %v", err) + } + defer cursor.Close(context.Background()) + var result struct { + TotalRecords int64 `bson:"total_records"` + } + if cursor.Next(context.Background()) { + if err := cursor.Decode(&result); err != nil { + return 0, fmt.Errorf("failed to decode result: %v", err) + } + } + return result.TotalRecords, nil +} + +func (m *MongoDB) GetAppCostsByOrderIDAndAppName(req *helper.AppCostsReq) ([]common.AppCost, error) { + var pipeline mongo.Pipeline + if req.AppType == resources.AppStore { + pipeline = mongo.Pipeline{ + {{Key: "$match", Value: bson.D{{Key: "order_id", Value: req.OrderID}}}}, + {{Key: "$unwind", Value: "$app_costs"}}, + {{Key: "$project", Value: bson.D{ + {Key: "app_name", Value: "$app_costs.name"}, + {Key: "app_type", Value: "$app_costs.type"}, + {Key: "time", Value: "$time"}, + {Key: "order_id", Value: "$order_id"}, + {Key: "namespace", Value: "$namespace"}, + {Key: "used", Value: "$app_costs.used"}, + {Key: "used_amount", Value: "$app_costs.used_amount"}, + {Key: "amount", Value: "$app_costs.amount"}, + }}}, + } + } else { + pipeline = mongo.Pipeline{ + {{Key: "$match", Value: bson.D{{Key: "order_id", Value: req.OrderID}}}}, + {{Key: "$unwind", Value: "$app_costs"}}, + {{Key: "$match", Value: bson.D{{Key: "app_costs.name", Value: req.AppName}}}}, + {{Key: "$project", Value: bson.D{ + {Key: "app_name", Value: "$app_costs.name"}, + {Key: "app_type", Value: "$app_type"}, + {Key: "time", Value: "$time"}, + {Key: "order_id", Value: "$order_id"}, + {Key: "namespace", Value: "$namespace"}, + {Key: "used", Value: "$app_costs.used"}, + {Key: "used_amount", Value: "$app_costs.used_amount"}, + {Key: "amount", Value: "$app_costs.amount"}, + }}}, + } + } + fmt.Printf("pipeline: %v\n", pipeline) + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return nil, fmt.Errorf("failed to aggregate billing collection: %v", err) + } + defer cursor.Close(context.Background()) + + var results []common.AppCost + for cursor.Next(context.Background()) { + var appCost common.AppCost + if err := cursor.Decode(&appCost); err != nil { + return nil, fmt.Errorf("failed to decode result: %v", err) + } + results = append(results, appCost) + } + + if err := cursor.Err(); err != nil { + return nil, fmt.Errorf("cursor error: %v", err) + } + + return results, nil +} + +func (m *MongoDB) getAppStoreCosts(matchConditions bson.D, skip, limit int) (*common.AppCosts, error) { + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: matchConditions}}, + {{Key: "$sort", Value: bson.D{ + {Key: "time", Value: -1}, + {Key: "app_name", Value: 1}, + {Key: "_id", Value: 1}, }}}, + {{Key: "$skip", Value: skip}}, + {{Key: "$limit", Value: limit}}, {{Key: "$project", Value: bson.D{ - {Key: "total_records", Value: bson.D{{Key: "$arrayElemAt", Value: bson.A{"$totalRecords.count", 0}}}}, - {Key: "total_pages", Value: bson.D{{Key: "$ceil", Value: bson.D{{Key: "$divide", Value: bson.A{bson.D{{Key: "$arrayElemAt", Value: bson.A{"$totalRecords.count", 0}}}, pageSize}}}}}}, - {Key: "costs", Value: 1}, + {Key: "_id", Value: 0}, + {Key: "time", Value: 1}, + {Key: "order_id", Value: 1}, + {Key: "namespace", Value: 1}, + {Key: "amount", Value: 1}, + {Key: "app_name", Value: 1}, + {Key: "app_type", Value: 1}, }}}, } @@ -207,13 +453,12 @@ func (m *MongoDB) GetAppCosts(req *helper.AppCostsReq) (*common.AppCosts, error) if err != nil { return nil, fmt.Errorf("failed to aggregate billing collection: %v", err) } + defer cursor.Close(context.Background()) + var results common.AppCosts - if cursor.Next(context.Background()) { - if err := cursor.Decode(&results); err != nil { - return nil, fmt.Errorf("failed to decode result: %v", err) - } + if err := cursor.All(context.Background(), &results.Costs); err != nil { + return nil, fmt.Errorf("failed to decode results: %v", err) } - results.CurrentPage = currentPage return &results, nil } @@ -221,6 +466,527 @@ func (m *MongoDB) GetConsumptionAmount(user, namespace, appType string, startTim return m.getAmountWithType(0, user, namespace, appType, startTime, endTime) } +func (m *MongoDB) GetCostOverview(req helper.GetCostAppListReq) (resp helper.CostOverviewResp, rErr error) { + appResp, err := m.GetCostAppList(req) + if err != nil { + rErr = fmt.Errorf("failed to get app store list: %w", err) + return + } + resp.LimitResp = appResp.LimitResp + for _, app := range appResp.Apps { + totalAmount, err := m.GetTotalAppCost(req.Owner, app.Namespace, app.AppName, app.AppType) + if err != nil { + rErr = fmt.Errorf("failed to get total app cost: %w", err) + return + } + resp.Overviews = append(resp.Overviews, helper.CostOverview{ + Amount: totalAmount, + Namespace: app.Namespace, + AppType: app.AppType, + AppName: app.AppName, + }) + } + return +} + +func (m *MongoDB) GetTotalAppCost(owner string, namespace string, appName string, appType uint8) (int64, error) { + var pipeline mongo.Pipeline + + if appType == resources.AppType[resources.AppStore] { + // If appType is 8, match app_name and app_type directly + pipeline = mongo.Pipeline{ + {{Key: "$match", Value: bson.D{ + {Key: "owner", Value: owner}, + {Key: "namespace", Value: namespace}, + {Key: "app_name", Value: appName}, + {Key: "app_type", Value: appType}, + }}}, + {{Key: "$group", Value: bson.D{ + {Key: "_id", Value: nil}, + {Key: "totalAmount", Value: bson.D{{Key: "$sum", Value: "$amount"}}}, + }}}, + } + } else { + // Otherwise, match inside app_costs + pipeline = mongo.Pipeline{ + {{Key: "$match", Value: bson.D{ + {Key: "owner", Value: owner}, + {Key: "namespace", Value: namespace}, + {Key: "app_costs.name", Value: appName}, + {Key: "app_type", Value: appType}, + }}}, + {{Key: "$unwind", Value: "$app_costs"}}, + {{Key: "$match", Value: bson.D{ + {Key: "app_costs.name", Value: appName}, + }}}, + {{Key: "$group", Value: bson.D{ + {Key: "_id", Value: nil}, + {Key: "totalAmount", Value: bson.D{{Key: "$sum", Value: "$app_costs.amount"}}}, + }}}, + } + } + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return 0, fmt.Errorf("failed to execute aggregate query: %w", err) + } + defer cursor.Close(context.Background()) + + var result struct { + TotalAmount int64 `bson:"totalAmount"` + } + + if cursor.Next(context.Background()) { + if err := cursor.Decode(&result); err != nil { + return 0, fmt.Errorf("failed to decode aggregate result: %w", err) + } + } else { + return 0, fmt.Errorf("no records found") + } + + return result.TotalAmount, nil +} + +func (m *MongoDB) GetCostAppList(req helper.GetCostAppListReq) (resp helper.CostAppListResp, rErr error) { + var ( + result []helper.CostApp + ) + if req.PageSize <= 0 { + req.PageSize = 10 + } + if req.Page <= 0 { + req.Page = 1 + } + pageSize := req.PageSize + if strings.ToUpper(req.AppType) != resources.AppStore { + match := bson.M{ + "owner": req.Owner, + // Exclude app store + "app_type": bson.M{"$ne": resources.AppType[resources.AppStore]}, + } + if req.Namespace != "" { + match["namespace"] = req.Namespace + } + if req.AppType != "" { + match["app_type"] = resources.AppType[strings.ToUpper(req.AppType)] + } + if req.AppName != "" { + match["app_costs.name"] = req.AppName + } + if req.StartTime.IsZero() { + req.StartTime = time.Now().UTC().Add(-time.Hour * 24 * 30) + req.EndTime = time.Now().UTC() + } + match["time"] = bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + } + + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$unwind", Value: "$app_costs"}}, + {{Key: "$match", Value: bson.D{ + {Key: "app_costs.name", Value: req.AppName}, + }}}, + } + if req.AppName == "" { + pipeline = mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$unwind", Value: "$app_costs"}}, + } + } + + pipeline = append(pipeline, mongo.Pipeline{ + {{Key: "$group", Value: bson.D{ + {Key: "_id", Value: bson.D{ + {Key: "app_type", Value: "$app_type"}, + {Key: "app_name", Value: "$app_costs.name"}, + }}, + {Key: "namespace", Value: bson.D{{Key: "$first", Value: "$namespace"}}}, + {Key: "owner", Value: bson.D{{Key: "$first", Value: "$owner"}}}, + }}}, + {{Key: "$project", Value: bson.D{ + {Key: "_id", Value: 0}, + {Key: "namespace", Value: 1}, + {Key: "appType", Value: "$_id.app_type"}, + {Key: "owner", Value: 1}, + {Key: "appName", Value: "$_id.app_name"}, + }}}, + }...) + + limitPipeline := append(pipeline, bson.D{{Key: "$skip", Value: (req.Page - 1) * req.PageSize}}, bson.D{{Key: "$limit", Value: req.PageSize}}) + + countPipeline := append(pipeline, bson.D{{Key: "$count", Value: "total"}}) + + countCursor, err := m.getBillingCollection().Aggregate(context.Background(), countPipeline) + if err != nil { + rErr = fmt.Errorf("failed to execute count aggregate query: %w", err) + return + } + defer countCursor.Close(context.Background()) + + if countCursor.Next(context.Background()) { + var countResult struct { + Total int64 `bson:"total"` + } + if err := countCursor.Decode(&countResult); err != nil { + rErr = fmt.Errorf("failed to decode count result: %w", err) + return + } + resp.Total = countResult.Total + } + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), limitPipeline) + if err != nil { + rErr = fmt.Errorf("failed to execute aggregate query: %w", err) + return + } + defer cursor.Close(context.Background()) + + if err := cursor.All(context.Background(), &result); err != nil { + rErr = fmt.Errorf("failed to decode all billing record: %w", err) + return + } + } + appStoreTotal, err := m.getAppStoreTotal(req) + if err != nil { + rErr = fmt.Errorf("failed to get app store total: %w", err) + return + } + + if req.AppType == "" || strings.ToUpper(req.AppType) == resources.AppStore { + currentAppPageIsFull := len(result) == req.PageSize + maxAppPageSize := (resp.Total + int64(req.PageSize) - 1) / int64(req.PageSize) + completedNum := calculateComplement(int(resp.Total), req.PageSize) + appPageSize := (resp.Total + int64(req.PageSize) - 1) / int64(req.PageSize) + if req.Page == int(maxAppPageSize) { + if !currentAppPageIsFull { + appStoreResp, err := m.getAppStoreList(req, 0, completedNum) + if err != nil { + rErr = fmt.Errorf("failed to get app store list: %w", err) + return + } + result = append(result, appStoreResp.Apps...) + } + } else if req.Page > int(maxAppPageSize) { + skipPageSize := (req.Page - int(appPageSize) - 1) * req.PageSize + if skipPageSize < 0 { + skipPageSize = 0 + } + appStoreResp, err := m.getAppStoreList(req, completedNum+skipPageSize, req.PageSize) + if err != nil { + rErr = fmt.Errorf("failed to get app store list: %w", err) + return + } + result = append(result, appStoreResp.Apps...) + } + resp.Total += appStoreTotal + } + + resp.TotalPage = (resp.Total + int64(pageSize) - 1) / int64(pageSize) + resp.Apps = result + return resp, nil +} + +func calculateComplement(a, b int) int { + remainder := a % b + if remainder == 0 { + return 0 + } + return b - remainder +} + +func (m *MongoDB) executeCountQuery(ctx context.Context, pipeline []bson.M) (int64, error) { + countCursor, err := m.getBillingCollection().Aggregate(ctx, pipeline) + if err != nil { + return 0, fmt.Errorf("failed to execute count aggregate query: %w", err) + } + defer countCursor.Close(ctx) + + var countResult struct { + Total int64 `bson:"total"` + } + if countCursor.Next(ctx) { + if err := countCursor.Decode(&countResult); err != nil { + return 0, fmt.Errorf("failed to decode count result: %w", err) + } + } + return countResult.Total, nil +} + +// GetBasicCostDistribution cost: map[string]int64: key: property type (cpu,memory,storage,network,nodeport: 0,1,2,3,4), value: used amount +func (m *MongoDB) GetBasicCostDistribution(req helper.GetCostAppListReq) (map[string]int64, error) { + cost := make(map[string]int64, len(resources.DefaultPropertyTypeLS.EnumMap)) + for i := range resources.DefaultPropertyTypeLS.EnumMap { + cost[strconv.Itoa(int(i))] = 0 + } + + match := buildMatchCriteria(req) + groupStage := buildGroupStage() + projectStage := buildProjectStage() + + if req.AppType == "" || strings.ToUpper(req.AppType) != resources.AppStore { + if err := aggregateAndUpdateCost(m, match, groupStage, projectStage, req.AppName, cost); err != nil { + return nil, err + } + } + + if req.AppType == "" || strings.ToUpper(req.AppType) == resources.AppStore { + match["app_type"] = resources.AppType[resources.AppStore] + delete(match, "app_costs.name") + if req.AppName != "" { + match["app_name"] = req.AppName + } + if err := aggregateAndUpdateCost(m, match, groupStage, projectStage, "", cost); err != nil { + return nil, err + } + } + return cost, nil +} + +func (m *MongoDB) GetAppCostTimeRange(req helper.GetCostAppListReq) (helper.TimeRange, error) { + match := buildMatchCriteria(req) + delete(match, "time") // Remove time constraint from match criteria + + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$unwind", Value: "$app_costs"}}, + } + + if req.AppName != "" { + pipeline = append(pipeline, bson.D{{Key: "$match", Value: bson.M{"app_costs.name": req.AppName}}}) + } + + pipeline = append(pipeline, + bson.D{{Key: "$group", Value: bson.D{ + {Key: "_id", Value: nil}, + {Key: "startTime", Value: bson.D{{Key: "$min", Value: "$time"}}}, + {Key: "endTime", Value: bson.D{{Key: "$max", Value: "$time"}}}, + }}}, + ) + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return helper.TimeRange{}, fmt.Errorf("failed to execute aggregate query: %w", err) + } + defer cursor.Close(context.Background()) + + var result helper.TimeRange + if cursor.Next(context.Background()) { + if err := cursor.Decode(&result); err != nil { + return helper.TimeRange{}, fmt.Errorf("failed to decode aggregate result: %w", err) + } + } else { + return helper.TimeRange{}, fmt.Errorf("no records found") + } + + // If the app type is empty or app store, also check the app store records + if req.AppType == "" || strings.ToUpper(req.AppType) == resources.AppStore { + match["app_type"] = resources.AppType[resources.AppStore] + delete(match, "app_costs.name") + if req.AppName != "" { + match["app_name"] = req.AppName + } + + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$group", Value: bson.D{ + {Key: "_id", Value: nil}, + {Key: "startTime", Value: bson.D{{Key: "$min", Value: "$time"}}}, + {Key: "endTime", Value: bson.D{{Key: "$max", Value: "$time"}}}, + }}}, + } + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return helper.TimeRange{}, fmt.Errorf("failed to execute aggregate query for app store: %w", err) + } + defer cursor.Close(context.Background()) + + var appStoreResult helper.TimeRange + if cursor.Next(context.Background()) { + if err := cursor.Decode(&appStoreResult); err != nil { + return helper.TimeRange{}, fmt.Errorf("failed to decode aggregate result for app store: %w", err) + } + + // Update the overall time range if necessary + if appStoreResult.StartTime.Before(result.StartTime) { + result.StartTime = appStoreResult.StartTime + } + if appStoreResult.EndTime.After(result.EndTime) { + result.EndTime = appStoreResult.EndTime + } + } + } + + return result, nil +} + +func buildMatchCriteria(req helper.GetCostAppListReq) bson.M { + match := bson.M{ + "owner": req.Owner, + "app_type": bson.M{"$ne": resources.AppType[resources.AppStore]}, + } + if req.Namespace != "" { + match["namespace"] = req.Namespace + } + if req.AppType != "" { + match["app_type"] = resources.AppType[strings.ToUpper(req.AppType)] + } + if req.AppName != "" { + match["app_costs.name"] = req.AppName + } + if req.StartTime.IsZero() { + req.StartTime = time.Now().UTC().Add(-time.Hour * 24 * 30) + req.EndTime = time.Now().UTC() + } + match["time"] = bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + } + return match +} + +func buildGroupStage() bson.D { + groupFields := bson.D{} + for i := range resources.DefaultPropertyTypeLS.EnumMap { + key := fmt.Sprintf("used_amount_%d", i) + field := fmt.Sprintf("$app_costs.used_amount.%d", i) + groupFields = append(groupFields, bson.E{ + Key: key, Value: bson.D{ + {Key: "$sum", Value: bson.D{ + {Key: "$ifNull", Value: bson.A{bson.D{{Key: "$toLong", Value: field}}, 0}}, + }}, + }, + }) + } + return bson.D{{Key: "$group", Value: append(bson.D{{Key: "_id", Value: nil}}, groupFields...)}} +} + +func buildProjectStage() bson.D { + projectFields := bson.D{} + for i := range resources.DefaultPropertyTypeLS.EnumMap { + key := fmt.Sprintf("used_amount.%d", i) + field := fmt.Sprintf("$used_amount_%d", i) + projectFields = append(projectFields, bson.E{Key: key, Value: field}) + } + return bson.D{{Key: "$project", Value: projectFields}} +} + +func aggregateAndUpdateCost(m *MongoDB, match bson.M, groupStage, projectStage bson.D, appName string, cost map[string]int64) error { + pipeline := mongo.Pipeline{ + {{Key: "$match", Value: match}}, + {{Key: "$unwind", Value: "$app_costs"}}, + } + if appName != "" { + pipeline = append(pipeline, bson.D{{Key: "$match", Value: bson.M{"app_costs.name": appName}}}) + } + pipeline = append(pipeline, groupStage, projectStage) + + cursor, err := m.getBillingCollection().Aggregate(context.Background(), pipeline) + if err != nil { + return fmt.Errorf("failed to execute aggregate query: %w", err) + } + defer cursor.Close(context.Background()) + + var result struct { + UsedAmount map[string]int64 `bson:"used_amount"` + } + if cursor.Next(context.Background()) { + if err := cursor.Decode(&result); err != nil { + return fmt.Errorf("failed to decode aggregate result: %w", err) + } + for i, v := range result.UsedAmount { + cost[i] += v + } + } + return nil +} + +func (m *MongoDB) getAppPipeLine(req helper.GetCostAppListReq) []bson.M { + match := bson.M{ + "owner": req.Owner, + "app_type": resources.AppType[resources.AppStore], + } + if req.Namespace != "" { + match["namespace"] = req.Namespace + } + if req.AppName != "" { + match["app_name"] = req.AppName + } + if !req.StartTime.IsZero() { + match["time"] = bson.M{ + "$gte": req.StartTime, + "$lte": req.EndTime, + } + } + + pipeline := []bson.M{ + {"$match": match}, + {"$unwind": "$app_costs"}, + {"$group": bson.M{ + "_id": bson.M{ + "namespace": "$namespace", + "app_type": "$app_type", + "owner": "$owner", + "app_name": "$app_name", + }, + }}, + {"$project": bson.M{ + "_id": 0, + "namespace": "$_id.namespace", + "appType": "$_id.app_type", + "owner": "$_id.owner", + "appName": "$_id.app_name", + }}, + } + return pipeline +} + +func (m *MongoDB) getAppStoreTotal(req helper.GetCostAppListReq) (int64, error) { + return m.executeCountQuery(context.Background(), append(m.getAppPipeLine(req), bson.M{"$count": "total"})) +} + +func (m *MongoDB) getAppStoreList(req helper.GetCostAppListReq, skip, pageSize int) (resp helper.CostAppListResp, rErr error) { + pipeline := m.getAppPipeLine(req) + skipStage := bson.M{"$skip": skip} + limitStage := bson.M{"$limit": pageSize} + limitPipeline := append(pipeline, skipStage, limitStage) + + resp.Total, rErr = m.executeCountQuery(context.Background(), append(m.getAppPipeLine(req), bson.M{"$count": "total"})) + if rErr != nil { + rErr = fmt.Errorf("failed to execute count aggregate query: %w", rErr) + return + } + if req.PageSize > 0 { + resp.TotalPage = (resp.Total + int64(req.PageSize) - 1) / int64(req.PageSize) + } + + if req.PageSize > 0 { + cursor, err := m.getBillingCollection().Aggregate(context.Background(), limitPipeline) + if err != nil { + rErr = fmt.Errorf("failed to execute aggregate query: %w", err) + return + } + defer cursor.Close(context.Background()) + + var result []helper.CostApp + if err = cursor.All(context.Background(), &result); err != nil { + rErr = fmt.Errorf("failed to decode all billing record: %w", err) + return + } + resp.Apps = result + } + return +} + +func (m *MongoDB) Disconnect(ctx context.Context) error { + if m == nil { + return nil + } + return m.Client.Disconnect(ctx) +} + func (m *MongoDB) getAmountWithType(_type int64, user, namespace, _appType string, startTime, endTime time.Time) (int64, error) { timeMatchValue := bson.D{primitive.E{Key: "$gte", Value: startTime}, primitive.E{Key: "$lte", Value: endTime}} matchValue := bson.D{ @@ -332,6 +1098,40 @@ func NewAccountInterface(mongoURI, globalCockRoachURI, localCockRoachURI string) return account, nil } +func newAccountForTest(mongoURI, globalCockRoachURI, localCockRoachURI string) (Interface, error) { + account := &Account{} + if mongoURI != "" { + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(mongoURI)) + if err != nil { + return nil, fmt.Errorf("failed to connect mongodb: %v", err) + } + if err = client.Ping(context.Background(), nil); err != nil { + return nil, fmt.Errorf("failed to ping mongodb: %v", err) + } + account.MongoDB = &MongoDB{ + Client: client, + AccountDBName: "sealos-resources", + BillingConn: "billing", + PropertiesConn: "properties", + } + } else { + fmt.Printf("mongoURI is empty, skip connecting to mongodb\n") + } + if globalCockRoachURI != "" && localCockRoachURI != "" { + ck, err := cockroach.NewCockRoach(globalCockRoachURI, localCockRoachURI) + if err != nil { + return nil, fmt.Errorf("failed to connect cockroach: %v", err) + } + if err = ck.InitTables(); err != nil { + return nil, fmt.Errorf("failed to init tables: %v", err) + } + account.Cockroach = &Cockroach{ck: ck} + } else { + fmt.Printf("globalCockRoachURI or localCockRoachURI is empty, skip connecting to cockroach\n") + } + return account, nil +} + func (m *MongoDB) getProperties() (*resources.PropertyTypeLS, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -353,7 +1153,7 @@ func (m *MongoDB) getPropertiesCollection() *mongo.Collection { return m.Client.Database(m.AccountDBName).Collection(m.PropertiesConn) } -func (m *MongoDB) GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHistoryReq) ([]string, error) { +func (m *Account) GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHistoryReq) ([][]string, error) { filter := bson.M{ "owner": req.Owner, } @@ -379,7 +1179,7 @@ func (m *MongoDB) GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHis defer cur.Close(context.Background()) if !cur.Next(context.Background()) { - return []string{}, nil + return [][]string{}, nil } var result struct { @@ -388,7 +1188,7 @@ func (m *MongoDB) GetBillingHistoryNamespaceList(req *helper.NamespaceBillingHis if err := cur.Decode(&result); err != nil { return nil, err } - return result.Namespaces, nil + return m.GetWorkspaceName(result.Namespaces) } func (m *MongoDB) getBillingCollection() *mongo.Collection { diff --git a/service/account/dao/interface_test.go b/service/account/dao/interface_test.go index 887deb05636..2c193d75432 100644 --- a/service/account/dao/interface_test.go +++ b/service/account/dao/interface_test.go @@ -1,6 +1,9 @@ package dao import ( + "context" + "encoding/json" + "fmt" "os" "testing" "time" @@ -11,7 +14,7 @@ import ( ) func TestCockroach_GetPayment(t *testing.T) { - db, err := NewAccountInterface("", "", "") + db, err := newAccountForTest("", os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI")) if err != nil { t.Fatalf("NewAccountInterface() error = %v", err) return @@ -25,7 +28,7 @@ func TestCockroach_GetPayment(t *testing.T) { } func TestMongoDB_GetAppCosts(t *testing.T) { - db, err := NewAccountInterface("", "", "") + db, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") if err != nil { t.Fatalf("NewAccountInterface() error = %v", err) return @@ -36,7 +39,7 @@ func TestMongoDB_GetAppCosts(t *testing.T) { StartTime: time.Now().Add(-24 * time.Hour * 30), EndTime: time.Now(), }, - Auth: helper.Auth{ + Auth: &helper.Auth{ Owner: "xxx", }, }, @@ -56,7 +59,7 @@ func TestMongoDB_GetAppCosts(t *testing.T) { func TestCockroach_GetTransfer(t *testing.T) { os.Setenv("LOCAL_REGION", "97925cb0-c8e2-4d52-8b39-d8bf0cbb414a") - db, err := NewAccountInterface("", "", "") + db, err := newAccountForTest("", os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI")) if err != nil { t.Fatalf("NewAccountInterface() error = %v", err) return @@ -85,3 +88,373 @@ func TestCockroach_GetTransfer(t *testing.T) { }) t.Logf("transfer = %+v", transfer.LimitResp) } + +func TestMongoDB_GetCostAppList(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := helper.GetCostAppListReq{ + Auth: &helper.Auth{ + Owner: "5uxfy8jl", + }, + //Namespace: "ns-hwhbg4vf", + //AppType: "APP-STORE", + //AppName: "cronicle-ldokpaus", + LimitReq: helper.LimitReq{ + Page: 1, + PageSize: 5, + }, + } + appList, err := m.GetCostAppList(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("len costAppList: %v", len(appList.Apps)) + t.Logf("costAppList: %#+v", appList) + b, err := json.MarshalIndent(appList, "", " ") + if err != nil { + t.Fatalf("failed to marshal cost app list: %v", err) + } + t.Logf("costAppList json: %s", string(b)) +} + +func TestMongoDB_GetCostOverview(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := helper.GetCostAppListReq{ + Auth: &helper.Auth{ + Owner: "5uxfy8jl", + }, + //Namespace: "ns-hwhbg4vf", + //AppType: "APP", + //AppName: "hello-world", + } + + /* + "overviews": [ + { + "amount": 605475, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 2, + "appName": "hello-world" + }, + { + "amount": 4030, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 1, + "appName": "wordpress-nwdzwqkv-mysql" + }, + { + "amount": 544983, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 1, + "appName": "test" + }, + { + "amount": 8057, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 2, + "appName": "wordpress-nwdzwqkv" + }, + { + "amount": 805435, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 8, + "appName": "wordpress-nwdzwqkv" + }, + { + "amount": 1083138, + "namespace": "ns-5uxfy8jl", + "regionDomain": "", + "appType": 8, + "appName": "rustdesk-ijhdszru" + } + ], + "total": 6, + "totalPage": 1 + } + + */ + + for _, appType := range []string{"", "DB", "APP", "APP-STORE", "TERMINAL", "JOB"} { + req.AppType = appType + for i := 1; i <= 10; i++ { + for j := 1; j <= 10; j++ { + req.LimitReq = helper.LimitReq{ + Page: i, + PageSize: j, + } + appList, err := m.GetCostOverview(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + if len(appList.Overviews) != GetCurrentPageItemCount(int(appList.Total), j, i) { + fmt.Printf("limit: %#+v\n", req.LimitReq) + fmt.Printf("total: %v\n", appList.Total) + t.Fatalf("len costAppList: %v, not equal getPageCount: %v", len(appList.Overviews), GetCurrentPageItemCount(int(appList.Total), j, i)) + } + + t.Logf("len costAppList: %v", len(appList.Overviews)) + //t.Logf("costAppList: %#+v", appList) + + // 转json + if len(appList.Overviews) != 0 { + b, err := json.MarshalIndent(appList, "", " ") + if err != nil { + t.Fatalf("failed to marshal cost app list: %v", err) + } + t.Logf("costoverview json: %s", string(b)) + } + t.Logf("success: %#+v", req.LimitReq) + } + } + } + + //req.LimitReq = helper.LimitReq{ + // Page: 2, + // PageSize: 2, + //} + ////req.AppType = "APP-STORE" + //req.AppName = "rustdesk-ijhdszru" + //appList, err := m.GetCostOverview(req) + //if err != nil { + // t.Fatalf("failed to get cost app list: %v", err) + //} + //if len(appList.Overviews) != GetCurrentPageItemCount(int(appList.Total), req.PageSize, req.Page) { + // fmt.Printf("limit: %#+v\n", req.LimitReq) + // fmt.Printf("total: %v\n", appList.Total) + // t.Fatalf("len costAppList: %v, not equal getPageCount: %v", len(appList.Overviews), GetCurrentPageItemCount(int(appList.Total), 2, 1)) + //} + // + //t.Logf("len costAppList: %v", len(appList.Overviews)) + ////t.Logf("costAppList: %#+v", appList) + // + //// 转json + //if len(appList.Overviews) != 0 { + // b, err := json.MarshalIndent(appList, "", " ") + // if err != nil { + // t.Fatalf("failed to marshal cost app list: %v", err) + // } + // t.Logf("costoverview json: %s", string(b)) + //} + //t.Logf("success: %#+v", req.LimitReq) +} + +func GetCurrentPageItemCount(totalItems, pageSize, currentPage int) int { + if totalItems <= 0 || pageSize <= 0 || currentPage <= 0 { + return 0 + } + + if pageSize >= totalItems { + if currentPage == 1 { + return totalItems + } + return 0 + } + + totalPages := (totalItems + pageSize - 1) / pageSize + + if currentPage > totalPages { + return 0 + } + + if currentPage == totalPages { + return totalItems - (totalPages-1)*pageSize + } + + return pageSize +} + +func TestUnmarshal_Config(t *testing.T) { + cfg := &Config{ + LocalRegionDomain: "localRegionDomain", + Regions: []Region{ + { + Domain: "192.168.0.55.nip.io", + AccountSvc: "account-api.192.168.0.55.nip.io", + UID: "97925cb0-c8e2-4d52-8b39-d8bf0cbb414a", + Name: map[string]string{ + "zh": "区域A", + "en": "region-a", + }, + }, + { + Domain: "192.168.0.75.nip.io", + AccountSvc: "account-api.192.168.0.75.nip.io", + UID: "b373c0e9-7bf1-4d64-b863-bc604a4801ad", + Name: map[string]string{ + "zh": "区域B", + "en": "region-b", + }, + }, + }, + } + b, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + t.Logf("config json: \n%s", string(b)) +} + +func TestMongoDB_GetBasicCostDistribution(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := helper.GetCostAppListReq{ + Auth: &helper.Auth{ + Owner: "5uxfy8jl", + }, + //Namespace: "ns-hwhbg4vf", + AppType: "APP-STORE", + //AppName: "cronicle-ldokpaus", + LimitReq: helper.LimitReq{ + Page: 1, + PageSize: 5, + }, + } + appList, err := m.GetBasicCostDistribution(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("costAppList: %v", appList) +} + +func TestMongoDB_GetAppCostTimeRange(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := helper.GetCostAppListReq{ + Auth: &helper.Auth{ + Owner: "5uxfy8jl", + }, + //Namespace: "ns-hwhbg4vf", + //AppType: "APP-STORE", + AppType: "DB", + AppName: "test", + LimitReq: helper.LimitReq{ + Page: 1, + PageSize: 5, + }, + } + timeRange, err := m.GetAppCostTimeRange(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("costAppList: %v", timeRange) +} + +func TestMongoDB_GetAppCost1(t *testing.T) { + dbCTX := context.Background() + m, err := newAccountForTest(os.Getenv("MONGO_URI"), "", "") + if err != nil { + t.Fatalf("NewAccountInterface() error = %v", err) + return + } + defer func() { + if err = m.Disconnect(dbCTX); err != nil { + t.Errorf("failed to disconnect mongo: error = %v", err) + } + }() + req := &helper.AppCostsReq{ + UserBaseReq: helper.UserBaseReq{ + TimeRange: helper.TimeRange{ + StartTime: time.Now().Add(-24 * time.Hour * 30), + EndTime: time.Now(), + }, + Auth: &helper.Auth{ + Owner: "uy771xun", + }, + }, + //Namespace: "ns-hwhbg4vf", + //AppType: "APP", + //AppName: "hello-world", + Page: 1, + PageSize: 10, + } + + //for _, appType := range []string{"", "DB", "APP", "APP-STORE", "TERMINAL", "JOB"} { + // req.AppType = appType + // for i := 1; i <= 30; i++ { + // for j := 1; j <= 30; j++ { + // req.Page = i + // req.PageSize = j + // appList, err := m.GetAppCosts(req) + // if err != nil { + // t.Fatalf("failed to get cost app list: %v", err) + // } + // if len(appList.Costs) != GetCurrentPageItemCount(appList.TotalRecords, j, i) { + // fmt.Printf("page: %d, pageSize: %d\n", req.Page, req.PageSize) + // fmt.Printf("appType: %s\n", appType) + // fmt.Printf("total: %v\n", appList.TotalRecords) + // t.Fatalf("len costAppList: %v, not equal getPageCount: %v", len(appList.Costs), GetCurrentPageItemCount(appList.TotalRecords, j, i)) + // } + // + // t.Logf("len costAppList: %v", len(appList.Costs)) + // //t.Logf("costAppList: %#+v", appList) + // + // //// 转json + // //if len(appList.Costs) != 0 { + // // b, err := json.MarshalIndent(appList, "", " ") + // // if err != nil { + // // t.Fatalf("failed to marshal cost app list: %v", err) + // // } + // // t.Logf("costoverview json: %s", string(b)) + // //} + // t.Logf("success: apptype %s, page: %d, pagesize: %d, total: %d", req.AppType, req.Page, req.PageSize, appList.TotalRecords) + // } + // } + //} + + req.Page = 1 + req.PageSize = 10 + req.AppType = "APP-STORE" + req.AppName = "" + appList, err := m.GetAppCosts(req) + if err != nil { + t.Fatalf("failed to get cost app list: %v", err) + } + t.Logf("costAppList: %#+v", appList) +} + +func init() { + // set env + os.Setenv("MONGO_URI", "") +} diff --git a/service/account/deploy/Kubefile b/service/account/deploy/Kubefile index 7f8b67a9090..a5adccfe029 100644 --- a/service/account/deploy/Kubefile +++ b/service/account/deploy/Kubefile @@ -1,8 +1,11 @@ FROM scratch COPY registry registry COPY manifests manifests +COPY scripts scripts ENV DEFAULT_NAMESPACE account-system -ENV MONGO_URI mongodb://mongo:27017 +ENV cloudDomain="127.0.0.1.nip.io" +ENV cloudPort="" +ENV certSecretName="wildcard-cert" -CMD ["kubectl apply -f manifests/deploy.yaml -n $DEFAULT_NAMESPACE"] \ No newline at end of file +CMD ["bash scripts/init.sh"] \ No newline at end of file diff --git a/service/account/deploy/manifests/config.json b/service/account/deploy/manifests/config.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/service/account/deploy/manifests/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/service/account/deploy/manifests/deploy.yaml.tmpl b/service/account/deploy/manifests/deploy.yaml.tmpl index e58addc64cb..6e9b3622fda 100644 --- a/service/account/deploy/manifests/deploy.yaml.tmpl +++ b/service/account/deploy/manifests/deploy.yaml.tmpl @@ -56,6 +56,16 @@ spec: ports: - containerPort: 2333 imagePullPolicy: Always - volumeMounts: [] + volumeMounts: + - mountPath: /config/config.json + name: region-info + subPath: ./config/config.json + volumes: + - configMap: + defaultMode: 420 + items: + - key: config.json + path: ./config/config.json + name: region-info + name: region-info serviceAccountName: account-controller-manager - volumes: [] diff --git a/service/account/deploy/manifests/ingress.yaml.tmpl b/service/account/deploy/manifests/ingress.yaml.tmpl new file mode 100644 index 00000000000..10b221edef5 --- /dev/null +++ b/service/account/deploy/manifests/ingress.yaml.tmpl @@ -0,0 +1,37 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, DELETE, PATCH, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-origin: "https://{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}, https://*.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}" + nginx.ingress.kubernetes.io/cors-allow-credentials: "true" + nginx.ingress.kubernetes.io/cors-max-age: "600" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/configuration-snippet: | + more_clear_headers "X-Frame-Options:"; + more_set_headers "Content-Security-Policy: default-src * blob: data: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; img-src * data: blob: resource: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource: *.baidu.com *.bdstatic.com https://js.stripe.com; frame-src 'self' *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} mailto: tel: weixin: mtt: *.baidu.com https://js.stripe.com; frame-ancestors 'self' https://{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} https://*.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}"; + more_set_headers "X-Xss-Protection: 1; mode=block"; + higress.io/response-header-control-remove: X-Frame-Options + higress.io/response-header-control-update: | + Content-Security-Policy "default-src * blob: data: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; img-src * data: blob: resource: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource: *.baidu.com *.bdstatic.com https://js.stripe.com; frame-src 'self' *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} mailto: tel: weixin: mtt: *.baidu.com https://js.stripe.com; frame-ancestors 'self' https://{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} https://*.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}" + X-Xss-Protection "1; mode=block" + name: account-service + namespace: account-system +spec: + rules: + - host: account-api.{{ .cloudDomain }} + http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: account-service + port: + number: 2333 + tls: + - hosts: + - 'account-api.{{ .cloudDomain }}' + secretName: {{ .certSecretName }} \ No newline at end of file diff --git a/service/account/deploy/scripts/init.sh b/service/account/deploy/scripts/init.sh new file mode 100644 index 00000000000..2fe9cb450fb --- /dev/null +++ b/service/account/deploy/scripts/init.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -ex + +kubectl create cm -n account-system region-info --from-file=manifests/config.json || true + +kubectl apply -f manifests/deploy.yaml -n account-system + +if [[ -n "$cloudDomain" ]]; then + kubectl create -f manifests/ingress.yaml -n account-system || true +fi diff --git a/service/account/docs/docs.go b/service/account/docs/docs.go index 44100afcaff..a0c9eb26069 100644 --- a/service/account/docs/docs.go +++ b/service/account/docs/docs.go @@ -67,6 +67,315 @@ const docTemplate = `{ } } }, + "/account/v1alpha1/basic-cost-distribution": { + "post": { + "description": "Get basic cost distribution", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "BasicCostDistribution" + ], + "summary": "Get basic cost distribution", + "parameters": [ + { + "description": "Basic cost distribution request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get basic cost distribution", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse basic cost distribution request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get basic cost distribution", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/check-permission": { + "post": { + "description": "Check permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Permission" + ], + "summary": "Check permission", + "parameters": [ + { + "description": "Check permission request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.UserBaseReq" + } + } + ], + "responses": { + "200": { + "description": "successfully check permission", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse check permission request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to check permission", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-list": { + "post": { + "description": "Get cost app list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "CostAppList" + ], + "summary": "Get cost app list", + "parameters": [ + { + "description": "Cost app list request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get cost app list", + "schema": { + "$ref": "#/definitions/helper.CostAppListResp" + } + }, + "400": { + "description": "failed to parse cost app list request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get cost app list", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-time-range": { + "post": { + "description": "Get app cost time range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppCostTimeRange" + ], + "summary": "Get app cost time range", + "parameters": [ + { + "description": "App cost time range request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get app cost time range", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse app cost time range request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get app cost time range", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-type-list": { + "post": { + "description": "Get app type list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppTypeList" + ], + "summary": "Get app type list", + "responses": { + "200": { + "description": "successfully get app type list", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get app type list", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-overview": { + "post": { + "description": "Get cost overview", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "CostOverview" + ], + "summary": "Get cost overview", + "parameters": [ + { + "description": "Cost overview request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get cost overview", + "schema": { + "$ref": "#/definitions/helper.CostOverviewResp" + } + }, + "400": { + "description": "failed to parse cost overview request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get cost overview", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/costs": { "post": { "description": "Get user costs within a specified time range", @@ -606,6 +915,37 @@ const docTemplate = `{ } } }, + "/account/v1alpha1/regions": { + "post": { + "description": "Get regions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Regions" + ], + "summary": "Get regions", + "responses": { + "200": { + "description": "successfully get regions", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get regions", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/transfer": { "post": { "description": "Transfer amount", @@ -687,10 +1027,6 @@ const docTemplate = `{ }, "helper.AppCostsReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "appName": { "description": "@Summary App Name\n@Description App Name", @@ -714,6 +1050,11 @@ const docTemplate = `{ "type": "string", "example": "ns-admin" }, + "orderID": { + "description": "@Summary Order ID\n@Description Order ID\n@JSONSchema", + "type": "string", + "example": "order-id-1" + }, "owner": { "type": "string", "example": "admin" @@ -740,10 +1081,6 @@ const docTemplate = `{ }, "helper.Auth": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "kubeConfig": { "type": "string" @@ -758,6 +1095,88 @@ const docTemplate = `{ } } }, + "helper.CostApp": { + "type": "object", + "properties": { + "appName": { + "description": "@Summary App Name\n@Description App Name", + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "integer" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + } + } + }, + "helper.CostAppListResp": { + "type": "object", + "properties": { + "apps": { + "description": "@Summary Cost app list\n@Description Cost app list", + "type": "array", + "items": { + "$ref": "#/definitions/helper.CostApp" + } + }, + "total": { + "description": "@Summary Total\n@Description Total", + "type": "integer" + }, + "totalPage": { + "description": "@Summary Total page\n@Description Total page", + "type": "integer" + } + } + }, + "helper.CostOverview": { + "type": "object", + "properties": { + "amount": { + "description": "@Summary Amount\n@Description Amount", + "type": "integer" + }, + "appName": { + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "integer" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + }, + "regionDomain": { + "description": "@Summary Region domain\n@Description Region domain", + "type": "string", + "example": "region-domain-1" + } + } + }, + "helper.CostOverviewResp": { + "type": "object", + "properties": { + "overviews": { + "description": "@Summary Cost overview\n@Description Cost overview", + "type": "array", + "items": { + "$ref": "#/definitions/helper.CostOverview" + } + }, + "total": { + "description": "@Summary Total\n@Description Total", + "type": "integer" + }, + "totalPage": { + "description": "@Summary Total page\n@Description Total page", + "type": "integer" + } + } + }, "helper.ErrorMessage": { "type": "object", "properties": { @@ -767,6 +1186,50 @@ const docTemplate = `{ } } }, + "helper.GetCostAppListReq": { + "type": "object", + "properties": { + "appName": { + "description": "@Summary App Name\n@Description App Name", + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "string" + }, + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { @@ -792,10 +1255,6 @@ const docTemplate = `{ }, "helper.GetTransferRecordReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", @@ -840,10 +1299,6 @@ const docTemplate = `{ }, "helper.NamespaceBillingHistoryReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", @@ -888,8 +1343,6 @@ const docTemplate = `{ "helper.SetPaymentInvoiceReq": { "type": "object", "required": [ - "kubeConfig", - "owner", "paymentIDList" ], "properties": { @@ -920,8 +1373,6 @@ const docTemplate = `{ "helper.TransferAmountReq": { "type": "object", "required": [ - "kubeConfig", - "owner", "toUser" ], "properties": { @@ -954,10 +1405,6 @@ const docTemplate = `{ }, "helper.UserBaseReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", diff --git a/service/account/docs/swagger.json b/service/account/docs/swagger.json index d8f7bc5e2fa..00c01f7246a 100644 --- a/service/account/docs/swagger.json +++ b/service/account/docs/swagger.json @@ -60,6 +60,315 @@ } } }, + "/account/v1alpha1/basic-cost-distribution": { + "post": { + "description": "Get basic cost distribution", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "BasicCostDistribution" + ], + "summary": "Get basic cost distribution", + "parameters": [ + { + "description": "Basic cost distribution request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get basic cost distribution", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse basic cost distribution request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get basic cost distribution", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/check-permission": { + "post": { + "description": "Check permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Permission" + ], + "summary": "Check permission", + "parameters": [ + { + "description": "Check permission request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.UserBaseReq" + } + } + ], + "responses": { + "200": { + "description": "successfully check permission", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse check permission request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to check permission", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-list": { + "post": { + "description": "Get cost app list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "CostAppList" + ], + "summary": "Get cost app list", + "parameters": [ + { + "description": "Cost app list request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get cost app list", + "schema": { + "$ref": "#/definitions/helper.CostAppListResp" + } + }, + "400": { + "description": "failed to parse cost app list request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get cost app list", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-time-range": { + "post": { + "description": "Get app cost time range", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppCostTimeRange" + ], + "summary": "Get app cost time range", + "parameters": [ + { + "description": "App cost time range request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get app cost time range", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "failed to parse app cost time range request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get app cost time range", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-app-type-list": { + "post": { + "description": "Get app type list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppTypeList" + ], + "summary": "Get app type list", + "responses": { + "200": { + "description": "successfully get app type list", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get app type list", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/account/v1alpha1/cost-overview": { + "post": { + "description": "Get cost overview", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "CostOverview" + ], + "summary": "Get cost overview", + "parameters": [ + { + "description": "Cost overview request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/helper.GetCostAppListReq" + } + } + ], + "responses": { + "200": { + "description": "successfully get cost overview", + "schema": { + "$ref": "#/definitions/helper.CostOverviewResp" + } + }, + "400": { + "description": "failed to parse cost overview request", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "authenticate error", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get cost overview", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/costs": { "post": { "description": "Get user costs within a specified time range", @@ -599,6 +908,37 @@ } } }, + "/account/v1alpha1/regions": { + "post": { + "description": "Get regions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Regions" + ], + "summary": "Get regions", + "responses": { + "200": { + "description": "successfully get regions", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "failed to get regions", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/account/v1alpha1/transfer": { "post": { "description": "Transfer amount", @@ -680,10 +1020,6 @@ }, "helper.AppCostsReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "appName": { "description": "@Summary App Name\n@Description App Name", @@ -707,6 +1043,11 @@ "type": "string", "example": "ns-admin" }, + "orderID": { + "description": "@Summary Order ID\n@Description Order ID\n@JSONSchema", + "type": "string", + "example": "order-id-1" + }, "owner": { "type": "string", "example": "admin" @@ -733,10 +1074,6 @@ }, "helper.Auth": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "kubeConfig": { "type": "string" @@ -751,6 +1088,88 @@ } } }, + "helper.CostApp": { + "type": "object", + "properties": { + "appName": { + "description": "@Summary App Name\n@Description App Name", + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "integer" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + } + } + }, + "helper.CostAppListResp": { + "type": "object", + "properties": { + "apps": { + "description": "@Summary Cost app list\n@Description Cost app list", + "type": "array", + "items": { + "$ref": "#/definitions/helper.CostApp" + } + }, + "total": { + "description": "@Summary Total\n@Description Total", + "type": "integer" + }, + "totalPage": { + "description": "@Summary Total page\n@Description Total page", + "type": "integer" + } + } + }, + "helper.CostOverview": { + "type": "object", + "properties": { + "amount": { + "description": "@Summary Amount\n@Description Amount", + "type": "integer" + }, + "appName": { + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "integer" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + }, + "regionDomain": { + "description": "@Summary Region domain\n@Description Region domain", + "type": "string", + "example": "region-domain-1" + } + } + }, + "helper.CostOverviewResp": { + "type": "object", + "properties": { + "overviews": { + "description": "@Summary Cost overview\n@Description Cost overview", + "type": "array", + "items": { + "$ref": "#/definitions/helper.CostOverview" + } + }, + "total": { + "description": "@Summary Total\n@Description Total", + "type": "integer" + }, + "totalPage": { + "description": "@Summary Total page\n@Description Total page", + "type": "integer" + } + } + }, "helper.ErrorMessage": { "type": "object", "properties": { @@ -760,6 +1179,50 @@ } } }, + "helper.GetCostAppListReq": { + "type": "object", + "properties": { + "appName": { + "description": "@Summary App Name\n@Description App Name", + "type": "string" + }, + "appType": { + "description": "@Summary App type\n@Description App type", + "type": "string" + }, + "endTime": { + "type": "string", + "example": "2021-12-01T00:00:00Z" + }, + "kubeConfig": { + "type": "string" + }, + "namespace": { + "description": "@Summary Namespace\n@Description Namespace", + "type": "string" + }, + "owner": { + "type": "string", + "example": "admin" + }, + "page": { + "description": "@Summary Page\n@Description Page", + "type": "integer" + }, + "pageSize": { + "description": "@Summary Page Size\n@Description Page Size", + "type": "integer" + }, + "startTime": { + "type": "string", + "example": "2021-01-01T00:00:00Z" + }, + "userID": { + "type": "string", + "example": "admin" + } + } + }, "helper.GetPropertiesResp": { "type": "object", "properties": { @@ -785,10 +1248,6 @@ }, "helper.GetTransferRecordReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", @@ -833,10 +1292,6 @@ }, "helper.NamespaceBillingHistoryReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", @@ -881,8 +1336,6 @@ "helper.SetPaymentInvoiceReq": { "type": "object", "required": [ - "kubeConfig", - "owner", "paymentIDList" ], "properties": { @@ -913,8 +1366,6 @@ "helper.TransferAmountReq": { "type": "object", "required": [ - "kubeConfig", - "owner", "toUser" ], "properties": { @@ -947,10 +1398,6 @@ }, "helper.UserBaseReq": { "type": "object", - "required": [ - "kubeConfig", - "owner" - ], "properties": { "endTime": { "type": "string", diff --git a/service/account/docs/swagger.yaml b/service/account/docs/swagger.yaml index c4bb053ace1..2815f7bb30e 100644 --- a/service/account/docs/swagger.yaml +++ b/service/account/docs/swagger.yaml @@ -39,6 +39,13 @@ definitions: @Description Namespace example: ns-admin type: string + orderID: + description: |- + @Summary Order ID + @Description Order ID + @JSONSchema + example: order-id-1 + type: string owner: example: admin type: string @@ -60,9 +67,6 @@ definitions: userID: example: admin type: string - required: - - kubeConfig - - owner type: object helper.Auth: properties: @@ -74,9 +78,90 @@ definitions: userID: example: admin type: string - required: - - kubeConfig - - owner + type: object + helper.CostApp: + properties: + appName: + description: |- + @Summary App Name + @Description App Name + type: string + appType: + description: |- + @Summary App type + @Description App type + type: integer + namespace: + description: |- + @Summary Namespace + @Description Namespace + type: string + type: object + helper.CostAppListResp: + properties: + apps: + description: |- + @Summary Cost app list + @Description Cost app list + items: + $ref: '#/definitions/helper.CostApp' + type: array + total: + description: |- + @Summary Total + @Description Total + type: integer + totalPage: + description: |- + @Summary Total page + @Description Total page + type: integer + type: object + helper.CostOverview: + properties: + amount: + description: |- + @Summary Amount + @Description Amount + type: integer + appName: + type: string + appType: + description: |- + @Summary App type + @Description App type + type: integer + namespace: + description: |- + @Summary Namespace + @Description Namespace + type: string + regionDomain: + description: |- + @Summary Region domain + @Description Region domain + example: region-domain-1 + type: string + type: object + helper.CostOverviewResp: + properties: + overviews: + description: |- + @Summary Cost overview + @Description Cost overview + items: + $ref: '#/definitions/helper.CostOverview' + type: array + total: + description: |- + @Summary Total + @Description Total + type: integer + totalPage: + description: |- + @Summary Total page + @Description Total page + type: integer type: object helper.ErrorMessage: properties: @@ -84,6 +169,48 @@ definitions: example: authentication failure type: string type: object + helper.GetCostAppListReq: + properties: + appName: + description: |- + @Summary App Name + @Description App Name + type: string + appType: + description: |- + @Summary App type + @Description App type + type: string + endTime: + example: "2021-12-01T00:00:00Z" + type: string + kubeConfig: + type: string + namespace: + description: |- + @Summary Namespace + @Description Namespace + type: string + owner: + example: admin + type: string + page: + description: |- + @Summary Page + @Description Page + type: integer + pageSize: + description: |- + @Summary Page Size + @Description Page Size + type: integer + startTime: + example: "2021-01-01T00:00:00Z" + type: string + userID: + example: admin + type: string + type: object helper.GetPropertiesResp: properties: data: @@ -140,9 +267,6 @@ definitions: userID: example: admin type: string - required: - - kubeConfig - - owner type: object helper.NamespaceBillingHistoryReq: properties: @@ -166,9 +290,6 @@ definitions: userID: example: admin type: string - required: - - kubeConfig - - owner type: object helper.NamespaceBillingHistoryRespData: properties: @@ -202,8 +323,6 @@ definitions: example: admin type: string required: - - kubeConfig - - owner - paymentIDList type: object helper.TransferAmountReq: @@ -236,8 +355,6 @@ definitions: example: admin type: string required: - - kubeConfig - - owner - toUser type: object helper.UserBaseReq: @@ -256,9 +373,6 @@ definitions: userID: example: admin type: string - required: - - kubeConfig - - owner type: object host: localhost:2333 info: @@ -302,6 +416,215 @@ paths: summary: Get user account tags: - Account + /account/v1alpha1/basic-cost-distribution: + post: + consumes: + - application/json + description: Get basic cost distribution + parameters: + - description: Basic cost distribution request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetCostAppListReq' + produces: + - application/json + responses: + "200": + description: successfully get basic cost distribution + schema: + additionalProperties: true + type: object + "400": + description: failed to parse basic cost distribution request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get basic cost distribution + schema: + additionalProperties: true + type: object + summary: Get basic cost distribution + tags: + - BasicCostDistribution + /account/v1alpha1/check-permission: + post: + consumes: + - application/json + description: Check permission + parameters: + - description: Check permission request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.UserBaseReq' + produces: + - application/json + responses: + "200": + description: successfully check permission + schema: + additionalProperties: true + type: object + "400": + description: failed to parse check permission request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to check permission + schema: + additionalProperties: true + type: object + summary: Check permission + tags: + - Permission + /account/v1alpha1/cost-app-list: + post: + consumes: + - application/json + description: Get cost app list + parameters: + - description: Cost app list request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetCostAppListReq' + produces: + - application/json + responses: + "200": + description: successfully get cost app list + schema: + $ref: '#/definitions/helper.CostAppListResp' + "400": + description: failed to parse cost app list request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get cost app list + schema: + additionalProperties: true + type: object + summary: Get cost app list + tags: + - CostAppList + /account/v1alpha1/cost-app-time-range: + post: + consumes: + - application/json + description: Get app cost time range + parameters: + - description: App cost time range request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetCostAppListReq' + produces: + - application/json + responses: + "200": + description: successfully get app cost time range + schema: + additionalProperties: true + type: object + "400": + description: failed to parse app cost time range request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get app cost time range + schema: + additionalProperties: true + type: object + summary: Get app cost time range + tags: + - AppCostTimeRange + /account/v1alpha1/cost-app-type-list: + post: + consumes: + - application/json + description: Get app type list + produces: + - application/json + responses: + "200": + description: successfully get app type list + schema: + additionalProperties: true + type: object + "500": + description: failed to get app type list + schema: + additionalProperties: true + type: object + summary: Get app type list + tags: + - AppTypeList + /account/v1alpha1/cost-overview: + post: + consumes: + - application/json + description: Get cost overview + parameters: + - description: Cost overview request + in: body + name: request + required: true + schema: + $ref: '#/definitions/helper.GetCostAppListReq' + produces: + - application/json + responses: + "200": + description: successfully get cost overview + schema: + $ref: '#/definitions/helper.CostOverviewResp' + "400": + description: failed to parse cost overview request + schema: + additionalProperties: true + type: object + "401": + description: authenticate error + schema: + additionalProperties: true + type: object + "500": + description: failed to get cost overview + schema: + additionalProperties: true + type: object + summary: Get cost overview + tags: + - CostOverview /account/v1alpha1/costs: post: consumes: @@ -665,6 +988,27 @@ paths: summary: Get properties tags: - Properties + /account/v1alpha1/regions: + post: + consumes: + - application/json + description: Get regions + produces: + - application/json + responses: + "200": + description: successfully get regions + schema: + additionalProperties: true + type: object + "500": + description: failed to get regions + schema: + additionalProperties: true + type: object + summary: Get regions + tags: + - Regions /account/v1alpha1/transfer: post: consumes: diff --git a/service/account/go.mod b/service/account/go.mod index 6b4f30fa5d9..4e45208e52c 100644 --- a/service/account/go.mod +++ b/service/account/go.mod @@ -11,6 +11,7 @@ replace ( require ( github.com/dustin/go-humanize v1.0.1 github.com/gin-gonic/gin v1.9.1 + github.com/goccy/go-json v0.10.2 github.com/labring/sealos/controllers/pkg v0.0.0-00010101000000-000000000000 github.com/labring/sealos/service v0.0.0-00010101000000-000000000000 github.com/swaggo/files v1.0.1 @@ -25,11 +26,11 @@ require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dinoallo/sealos-networkmanager-protoapi v0.0.0-20230928031328-cf9649d6af49 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.11 // indirect @@ -37,14 +38,12 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect @@ -53,7 +52,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -61,7 +60,7 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/montanaflynn/stats v0.6.6 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -75,33 +74,33 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.16.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/tools v0.16.1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/grpc v1.57.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/postgres v1.5.4 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/api v0.28.4 // indirect - k8s.io/apimachinery v0.28.4 // indirect - k8s.io/client-go v0.28.4 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect - sigs.k8s.io/controller-runtime v0.13.0 // indirect + k8s.io/api v0.29.0 // indirect + k8s.io/apimachinery v0.29.0 // indirect + k8s.io/client-go v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/controller-runtime v0.17.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/service/account/go.sum b/service/account/go.sum index bfdf8f1c157..936df128e11 100644 --- a/service/account/go.sum +++ b/service/account/go.sum @@ -1,12 +1,12 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -18,22 +18,25 @@ github.com/dinoallo/sealos-networkmanager-protoapi v0.0.0-20230928031328-cf9649d github.com/dinoallo/sealos-networkmanager-protoapi v0.0.0-20230928031328-cf9649d6af49/go.mod h1:sbm1DAsayX+XsXCOC2CFAAU9JZhX0SPKwnybDjSd0Ls= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -50,6 +53,7 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -57,6 +61,7 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v2.20.0+incompatible h1:4Xh3bDzO29j4TWNOI+24ubc0vbVFMg2PMnXKxK54/CA= +github.com/go-task/slim-sprig v2.20.0+incompatible/go.mod h1:N/mhXZITr/EQAOErEHciKvO1bFei2Lld2Ym6h96pdy0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -72,15 +77,18 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -89,7 +97,6 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -101,14 +108,15 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -125,33 +133,29 @@ github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -166,6 +170,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= @@ -189,7 +194,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY= go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= @@ -202,12 +208,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -217,16 +226,16 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -237,13 +246,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -260,23 +269,22 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -286,7 +294,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -299,25 +306,26 @@ gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ= -sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= +sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= +sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/service/account/helper/auth.go b/service/account/helper/auth.go index d942fc8947a..612708c28bf 100644 --- a/service/account/helper/auth.go +++ b/service/account/helper/auth.go @@ -2,31 +2,30 @@ package helper import ( "fmt" - "strings" - "github.com/gin-gonic/gin" "github.com/labring/sealos/controllers/pkg/utils/logger" auth2 "github.com/labring/sealos/service/pkg/auth" ) -func AuthenticateWithBind(c *gin.Context) error { - auth := &Auth{} - err := c.ShouldBindJSON(auth) +func AuthenticateKC(auth Auth) error { + if auth.KubeConfig == "" { + return fmt.Errorf("kubeconfig must be set") + } + host, err := auth2.GetKcHost(auth.KubeConfig) if err != nil { - return fmt.Errorf("bind json error : %v", err) + return fmt.Errorf("kubeconfig failed %v", err) } - return Authenticate(*auth) -} - -func Authenticate(auth Auth) error { - if auth.KubeConfig == "" || auth.Owner == "" { - return fmt.Errorf("kubeconfig and owner must be set") + if err := auth2.CheckK8sHost(host); err != nil { + return fmt.Errorf("failed to check k8s host: %v", err) } - userNamespace := auth.Owner - if !strings.HasPrefix(userNamespace, "ns-") { - userNamespace = "ns-" + userNamespace + + user, err := auth2.GetKcUser(auth.KubeConfig) + if err != nil { + return fmt.Errorf("failed to get user: %v", err) } + + userNamespace := "ns-" + user // Identity authentication if err := auth2.Authenticate(userNamespace, auth.KubeConfig); err != nil { logger.Error("failed to auth %s: %v", userNamespace, err) diff --git a/service/account/helper/common.go b/service/account/helper/common.go index d94952e480e..326135f122f 100644 --- a/service/account/helper/common.go +++ b/service/account/helper/common.go @@ -1,23 +1,31 @@ package helper const ( - GROUP = "/account/v1alpha1" - GetAccount = "/account" - GetPayment = "/payment" - GetHistoryNamespaces = "/namespaces" - GetProperties = "/properties" - GetRechargeAmount = "/costs/recharge" - GetConsumptionAmount = "/costs/consumption" - GetPropertiesUsed = "/costs/properties" - GetAPPCosts = "/costs/app" - SetPaymentInvoice = "/payment/set-invoice" - GetUserCosts = "/costs" - SetTransfer = "/transfer" - GetTransfer = "/get-transfer" + GROUP = "/account/v1alpha1" + GetAccount = "/account" + GetPayment = "/payment" + GetHistoryNamespaces = "/namespaces" + GetProperties = "/properties" + GetRechargeAmount = "/costs/recharge" + GetConsumptionAmount = "/costs/consumption" + GetPropertiesUsed = "/costs/properties" + GetAPPCosts = "/costs/app" + SetPaymentInvoice = "/payment/set-invoice" + GetUserCosts = "/costs" + SetTransfer = "/transfer" + GetTransfer = "/get-transfer" + GetRegions = "/regions" + GetOverview = "/cost-overview" + GetAppList = "/cost-app-list" + GetAppTypeList = "/cost-app-type-list" + GetBasicCostDistribution = "/cost-basic-distribution" + GetAppCostTimeRange = "/cost-app-time-range" + CheckPermission = "/check-permission" ) // env const ( + ConfigPath = "/config/config.json" EnvMongoURI = "MONGO_URI" ENVGlobalCockroach = "GLOBAL_COCKROACH_URI" ENVLocalCockroach = "LOCAL_COCKROACH_URI" diff --git a/service/account/helper/request.go b/service/account/helper/request.go index 312a7f5e1fe..11ce0cc4f08 100644 --- a/service/account/helper/request.go +++ b/service/account/helper/request.go @@ -21,7 +21,7 @@ type NamespaceBillingHistoryReq struct { // @Summary Authentication information // @Description Authentication information // @JSONSchema required - Auth `json:",inline" bson:",inline"` + *Auth `json:",inline" bson:",inline"` // @Summary Type of the request (optional) // @Description Type of the request (optional) @@ -38,7 +38,7 @@ type SetPaymentInvoiceReq struct { // @Summary Authentication information // @Description Authentication information // @JSONSchema required - Auth `json:",inline" bson:",inline"` + *Auth `json:",inline" bson:",inline"` } type TransferAmountReq struct { @@ -55,7 +55,7 @@ type TransferAmountReq struct { // @Summary Authentication information // @Description Authentication information // @JSONSchema required - Auth `json:",inline" bson:",inline"` + *Auth `json:",inline" bson:",inline"` // @Summary Transfer all // @Description Transfer all amount @@ -76,7 +76,7 @@ type ConsumptionRecordReq struct { // @Summary Authentication information // @Description Authentication information // @JSONSchema required - Auth `json:",inline" bson:",inline"` + *Auth `json:",inline" bson:",inline"` // @Summary App type // @Description App type @@ -111,9 +111,9 @@ type TimeRange struct { } type Auth struct { - Owner string `json:"owner" bson:"owner" binding:"required" example:"admin"` + Owner string `json:"owner" bson:"owner" example:"admin"` UserID string `json:"userID" bson:"userID" example:"admin"` - KubeConfig string `json:"kubeConfig" bson:"kubeConfig" binding:"required"` + KubeConfig string `json:"kubeConfig" bson:"kubeConfig"` } func ParseNamespaceBillingHistoryReq(c *gin.Context) (*NamespaceBillingHistoryReq, error) { @@ -150,12 +150,7 @@ func ParseConsumptionRecordReq(c *gin.Context) (*ConsumptionRecordReq, error) { if err := c.ShouldBindJSON(consumptionRecord); err != nil { return nil, fmt.Errorf("bind json error: %v", err) } - if consumptionRecord.TimeRange.StartTime.Before(time.Now().Add(-6 * humanize.Month)) { - consumptionRecord.TimeRange.StartTime = time.Now().Add(-6 * humanize.Month) - } - if consumptionRecord.TimeRange.EndTime.After(time.Now()) { - consumptionRecord.TimeRange.EndTime = time.Now() - } + setDefaultTimeRange(&consumptionRecord.TimeRange) return consumptionRecord, nil } @@ -169,7 +164,7 @@ type UserBaseReq struct { // @Summary Authentication information // @Description Authentication information // @JSONSchema required - Auth `json:",inline" bson:",inline"` + *Auth `json:",inline" bson:",inline"` } func ParseUserBaseReq(c *gin.Context) (*UserBaseReq, error) { @@ -177,17 +172,17 @@ func ParseUserBaseReq(c *gin.Context) (*UserBaseReq, error) { if err := c.ShouldBindJSON(userCosts); err != nil { return nil, fmt.Errorf("bind json error: %v", err) } - if userCosts.TimeRange.StartTime.Before(time.Now().Add(-6 * humanize.Month)) { - userCosts.TimeRange.StartTime = time.Now().Add(-6 * humanize.Month) - } - if userCosts.TimeRange.EndTime.After(time.Now()) { - userCosts.TimeRange.EndTime = time.Now() - } + setDefaultTimeRange(&userCosts.TimeRange) userCosts.Owner = strings.TrimPrefix(userCosts.Owner, "ns-") return userCosts, nil } type AppCostsReq struct { + // @Summary Order ID + // @Description Order ID + // @JSONSchema + OrderID string `json:"orderID,omitempty" bson:"orderID" example:"order-id-1"` + UserBaseReq `json:",inline" bson:",inline"` // @Summary Namespace @@ -215,12 +210,7 @@ func ParseAppCostsReq(c *gin.Context) (*AppCostsReq, error) { if err := c.ShouldBindJSON(userCosts); err != nil { return nil, fmt.Errorf("bind json error: %v", err) } - if userCosts.TimeRange.StartTime.Before(time.Now().Add(-6 * humanize.Month)) { - userCosts.TimeRange.StartTime = time.Now().Add(-6 * humanize.Month) - } - if userCosts.TimeRange.EndTime.After(time.Now()) { - userCosts.TimeRange.EndTime = time.Now() - } + setDefaultTimeRange(&userCosts.TimeRange) userCosts.Owner = strings.TrimPrefix(userCosts.Owner, "ns-") return userCosts, nil } @@ -251,12 +241,124 @@ func ParseGetTransferRecordReq(c *gin.Context) (*GetTransferRecordReq, error) { if err := c.ShouldBindJSON(transferReq); err != nil { return nil, fmt.Errorf("bind json error: %v", err) } - if transferReq.TimeRange.StartTime.Before(time.Now().Add(-6 * humanize.Month)) { - transferReq.TimeRange.StartTime = time.Now().Add(-6 * humanize.Month) - } - if transferReq.TimeRange.EndTime.After(time.Now()) { - transferReq.TimeRange.EndTime = time.Now() - } + setDefaultTimeRange(&transferReq.TimeRange) transferReq.Owner = strings.TrimPrefix(transferReq.Owner, "ns-") return transferReq, nil } + +func ParseGetCostAppListReq(c *gin.Context) (*GetCostAppListReq, error) { + costAppList := &GetCostAppListReq{} + if err := c.ShouldBindJSON(costAppList); err != nil { + return nil, fmt.Errorf("bind json error: %v", err) + } + setDefaultTimeRange(&costAppList.TimeRange) + return costAppList, nil +} + +func setDefaultTimeRange(timeRange *TimeRange) { + if timeRange.StartTime.IsZero() { + timeRange.StartTime = time.Now().Add(-6 * humanize.Month) + } + if timeRange.EndTime.IsZero() { + timeRange.EndTime = time.Now() + } +} + +type CostOverviewResp struct { + // @Summary Cost overview + // @Description Cost overview + Overviews []CostOverview `json:"overviews" bson:"overviews"` + + // @Summary Limit response + // @Description Limit response + LimitResp `json:",inline" bson:",inline"` +} + +type CostOverview struct { + // @Summary Amount + // @Description Amount + Amount int64 `json:"amount" bson:"amount"` + + // @Summary Namespace + // @Description Namespace + Namespace string `json:"namespace" bson:"namespace"` + + // @Summary Region domain + // @Description Region domain + RegionDomain string `json:"regionDomain" bson:"regionDomain" example:"region-domain-1"` + + // @Summary App type + // @Description App type + AppType uint8 `json:"appType" bson:"appType"` + AppName string `json:"appName" bson:"appName"` +} + +type GetCostAppListReq struct { + // @Summary Authentication information + // @Description Authentication information + *Auth `json:",inline" bson:",inline"` + + // @Summary Namespace + // @Description Namespace + Namespace string `json:"namespace" bson:"namespace"` + + // @Summary App type + // @Description App type + AppType string `json:"appType" bson:"appType"` + + // @Summary App Name + // @Description App Name + AppName string `json:"appName" bson:"appName"` + + // @Summary Limit request + // @Description Limit request + LimitReq `json:",inline" bson:",inline"` +} + +type CostAppListResp struct { + // @Summary Cost app list + // @Description Cost app list + Apps []CostApp `json:"apps" bson:"apps"` + + // @Summary Limit response + // @Description Limit response + LimitResp `json:",inline" bson:",inline"` +} + +type CostApp struct { + // @Summary Namespace + // @Description Namespace + Namespace string `json:"namespace" bson:"namespace"` + + // @Summary App type + // @Description App type + AppType uint8 `json:"appType" bson:"appType"` + + // @Summary App Name + // @Description App Name + AppName string `json:"appName" bson:"appName"` +} + +type LimitReq struct { + // @Summary Page + // @Description Page + Page int `json:"page" bson:"page"` + + // @Summary Page Size + // @Description Page Size + PageSize int `json:"pageSize" bson:"pageSize"` + + // @Summary Time range + // @Description Time range + TimeRange `json:",inline" bson:",inline"` +} + +type LimitResp struct { + // @Summary Total + // @Description Total + Total int64 `json:"total" bson:"total"` + + // @Summary Total page + // @Description Total page + TotalPage int64 `json:"totalPage" bson:"totalPage"` +} diff --git a/service/account/router/router.go b/service/account/router/router.go index c441ffd438b..750a0a66b06 100644 --- a/service/account/router/router.go +++ b/service/account/router/router.go @@ -42,7 +42,14 @@ func RegisterPayRouter() { POST(helper.GetPropertiesUsed, api.GetPropertiesUsedAmount). POST(helper.SetPaymentInvoice, api.SetPaymentInvoice). POST(helper.SetTransfer, api.TransferAmount). - POST(helper.GetTransfer, api.GetTransfer) + POST(helper.GetTransfer, api.GetTransfer). + POST(helper.CheckPermission, api.CheckPermission). + POST(helper.GetRegions, api.GetRegions). + POST(helper.GetOverview, api.GetCostOverview). + POST(helper.GetAppList, api.GetCostAppList). + POST(helper.GetAppTypeList, api.GetAppTypeList). + POST(helper.GetBasicCostDistribution, api.GetBasicCostDistribution). + POST(helper.GetAppCostTimeRange, api.GetAppCostTimeRange) docs.SwaggerInfo.Host = env.GetEnvWithDefault("SWAGGER_HOST", "localhost:2333") router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) diff --git a/service/go.work.sum b/service/go.work.sum index cc8273d6ba5..637fd05c8dc 100644 --- a/service/go.work.sum +++ b/service/go.work.sum @@ -538,9 +538,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flowstack/go-jsonschema v0.1.1 h1:dCrjGJRXIlbDsLAgTJZTjhwUJnnxVWl1OgNyYh5nyDc= @@ -549,7 +546,6 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E= github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= @@ -559,16 +555,13 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -613,8 +606,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -626,8 +617,6 @@ github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JV github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= @@ -668,6 +657,7 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -679,8 +669,6 @@ github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHz github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -721,8 +709,6 @@ github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vyg github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -732,17 +718,16 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= @@ -840,6 +825,7 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7Zo github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/volcengine/volc-sdk-golang v1.0.159 h1:0EzJ49M+YJbaGiGxhmD2BvPbsZOgt6RWlgOqKP6IaoQ= github.com/volcengine/volc-sdk-golang v1.0.159/go.mod h1:iqWIQk0pkcDKEYpIG4vkocgHpeiAabfAK9g0Ob7lSxE= +github.com/wneessen/go-mail v0.4.2/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -923,7 +909,6 @@ go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= @@ -936,7 +921,6 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= @@ -949,7 +933,6 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -966,9 +949,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= @@ -976,16 +958,12 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -997,6 +975,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= @@ -1016,7 +995,6 @@ golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= @@ -1044,6 +1022,7 @@ google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdL google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -1060,7 +1039,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1074,7 +1052,7 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1107,7 +1085,6 @@ k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y= k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= @@ -1133,12 +1110,10 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/kms v0.27.2 h1:wCdmPCa3kubcVd3AssOeaVjLQSu45k5g/vruJ3iqwDU= k8s.io/kms v0.27.2/go.mod h1:dahSqjI05J55Fo5qipzvHSRbm20d7llrSeQjjl86A7c= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= @@ -1147,5 +1122,4 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISX sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y= sigs.k8s.io/controller-runtime v0.15.2 h1:9V7b7SDQSJ08IIsJ6CY1CE85Okhp87dyTMNDG0FS7f4= sigs.k8s.io/controller-runtime v0.15.2/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= -sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/service/pkg/auth/authenticate.go b/service/pkg/auth/authenticate.go index c923d3de7cc..972a6559b1b 100644 --- a/service/pkg/auth/authenticate.go +++ b/service/pkg/auth/authenticate.go @@ -25,6 +25,42 @@ var ( whiteListKubernetesHosts []string ) +func AddWhiteListKubernetesHosts(host string) { + whiteListKubernetesHosts = append(whiteListKubernetesHosts, host) +} + +func GetKcHost(kc string) (string, error) { + config, err := clientcmd.RESTConfigFromKubeConfig([]byte(kc)) + if err != nil { + return "", fmt.Errorf("kubeconfig failed %v", err) + } + return config.Host, nil +} + +func GetKcUser(kc string) (string, error) { + config, err := clientcmd.Load([]byte(kc)) + if err != nil { + return "", fmt.Errorf("kubeconfig failed %v", err) + } + for user := range config.AuthInfos { + return user, nil + } + return "", fmt.Errorf("no user found") +} + +func CheckK8sHost(host string) error { + if !IsWhitelistKubernetesHost(host) { + if k8shost := GetKubernetesHostFromEnv(); k8shost != "" { + if k8shost != host { + return fmt.Errorf("k8s host not match, expect %s, got %s", k8shost, host) + } + } else { + return ErrNoSealosHost + } + } + return nil +} + func Authenticate(ns, kc string) error { if ns == "" { return ErrNilNs @@ -60,7 +96,7 @@ func Authenticate(ns, kc string) error { return fmt.Errorf("ping apiserver is no ok: %v", string(res)) } - if err := CheckResourceAccess(client, ns, "get", "pods"); err != nil { + if err := CheckResourceAccess(client, ns, "update", "pods"); err != nil { // fmt.Println(err.Error()) return fmt.Errorf("check resource access error: %v", err) }