mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #1223 in YUNIONIO/onecloud from ~QUXUAN/onecloud:feature/qx-tag to release/2.8.0
* commit '43daa1ceb420d8b6176e92ee7c023d51c9c8e962': 用户自定义标签加前缀user: 同步公有云key加上前缀 限制key必须是中英文、数字或者_ 允许只传key进行过滤 取消key以_开头key限制 metadata返回列表使用复数 支持server disk 过滤 支持标签
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
R(&options.MetadataListOptions{}, "metadata-list", "List metadatas", func(s *mcclient.ClientSession, opts *options.MetadataListOptions) error {
|
||||
params, err := options.ListStructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Metadatas.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, []string{})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -85,6 +85,32 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.ServerMetadataOptions{}, "server-add-tag", "Set tag of a server", func(s *mcclient.ClientSession, opts *options.ServerMetadataOptions) error {
|
||||
params, err := opts.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Servers.PerformAction(s, opts.ID, "user-metadata", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.ServerMetadataOptions{}, "server-set-tag", "Set tag of a server", func(s *mcclient.ClientSession, opts *options.ServerMetadataOptions) error {
|
||||
params, err := opts.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Servers.PerformAction(s, opts.ID, "set-user-metadata", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.ServerCreateOptions{}, "server-create", "Create a server", func(s *mcclient.ClientSession, opts *options.ServerCreateOptions) error {
|
||||
params, err := opts.Params()
|
||||
if err != nil {
|
||||
|
||||
@@ -452,7 +452,9 @@ func ListItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok
|
||||
if err == nil && colSpec != nil && colSpec.IsNumeric() {
|
||||
orderBy = []string{"id"}
|
||||
} else {
|
||||
orderBy = []string{"created_at"}
|
||||
if manager.TableSpec().ColumnSpec("created_at") != nil {
|
||||
orderBy = []string{"created_at"}
|
||||
}
|
||||
}
|
||||
}
|
||||
order := sqlchemy.SQL_ORDER_DESC
|
||||
|
||||
+158
-23
@@ -8,14 +8,24 @@ import (
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/util/stringutils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
const (
|
||||
SYSTEM_ADMIN_PREFIX = "__sys_"
|
||||
SYSTEM_ADMIN_PREFIX = "_"
|
||||
CLOUD_TAG_PREFIX = "ext:"
|
||||
USER_TAG_PREFIX = "user:"
|
||||
|
||||
TAG_DELETE_RANGE_USER = "user"
|
||||
TAG_DELETE_RANGE_CLOUD = "cloud"
|
||||
TAG_DELETE_RANGE_SYS = "sys"
|
||||
TAG_DELETE_RANGE_ALL = "all"
|
||||
)
|
||||
|
||||
type SMetadataManager struct {
|
||||
@@ -25,16 +35,24 @@ type SMetadataManager struct {
|
||||
type SMetadata struct {
|
||||
SModelBase
|
||||
|
||||
Id string `width:"128" charset:"ascii" primary:"true"` // = Column(VARCHAR(128, charset='ascii'), primary_key=True)
|
||||
Key string `width:"64" charset:"ascii" primary:"true"` // = Column(VARCHAR(64, charset='ascii'), primary_key=True)
|
||||
Value string `charset:"utf8"` // = Column(TEXT(charset='utf8'), nullable=True)
|
||||
UpdatedAt time.Time `nullable:"false" updated_at:"true"` // = Column(DateTime, default=get_utcnow, nullable=False, onupdate=get_utcnow)
|
||||
Id string `width:"128" charset:"ascii" primary:"true" list:"user" get:"user"` // = Column(VARCHAR(128, charset='ascii'), primary_key=True)
|
||||
Key string `width:"64" charset:"utf8" primary:"true" list:"user" get:"user"` // = Column(VARCHAR(64, charset='ascii'), primary_key=True)
|
||||
Value string `charset:"utf8" list:"user" get:"user"` // = Column(TEXT(charset='utf8'), nullable=True)
|
||||
UpdatedAt time.Time `nullable:"false" updated_at:"true"` // = Column(DateTime, default=get_utcnow, nullable=False, onupdate=get_utcnow)
|
||||
Deleted bool `nullable:"false" default:"false" index:"true"`
|
||||
}
|
||||
|
||||
var Metadata *SMetadataManager
|
||||
var ResourceMap map[string]*SVirtualResourceBaseManager
|
||||
|
||||
func init() {
|
||||
Metadata = &SMetadataManager{SModelBaseManager: NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadata")}
|
||||
Metadata = &SMetadataManager{SModelBaseManager: NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadatas")}
|
||||
ResourceMap = map[string]*SVirtualResourceBaseManager{
|
||||
"disk": {SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(SVirtualResourceBase{}, "disks_tbl", "disk", "disks")},
|
||||
"server": {SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(SVirtualResourceBase{}, "guests_tbl", "server", "servers")},
|
||||
"eip": {SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(SVirtualResourceBase{}, "elasticips_tbl", "eip", "eips")},
|
||||
"snapshot": {SStatusStandaloneResourceBaseManager: NewStatusStandaloneResourceBaseManager(SVirtualResourceBase{}, "snapshots_tbl", "snpashot", "snpashots")},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SMetadata) GetId() string {
|
||||
@@ -53,6 +71,64 @@ func GetObjectIdstr(model IModel) string {
|
||||
return fmt.Sprintf("%s::%s", model.GetModelManager().Keyword(), model.GetId())
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) Query(fields ...string) *sqlchemy.SQuery {
|
||||
return manager.SModelBaseManager.Query(fields...).IsFalse("deleted")
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) RawQuery(fields ...string) *sqlchemy.SQuery {
|
||||
return manager.SModelBaseManager.Query(fields...)
|
||||
}
|
||||
|
||||
func (m *SMetadata) MarkDelete() error {
|
||||
m.Deleted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SMetadata) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return DeleteModel(ctx, userCred, m)
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
resources := jsonutils.GetQueryStringArray(query, "resources")
|
||||
if len(resources) == 0 {
|
||||
for resource := range ResourceMap {
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
}
|
||||
conditions := []sqlchemy.ICondition{}
|
||||
admin := jsonutils.QueryBoolean(query, "admin", false)
|
||||
for _, resource := range resources {
|
||||
if manager, ok := ResourceMap[resource]; ok {
|
||||
resourceView := manager.Query().SubQuery()
|
||||
prefix := sqlchemy.NewStringField(fmt.Sprintf("%s::", manager.Keyword()))
|
||||
field := sqlchemy.CONCAT(manager.Keyword(), prefix, resourceView.Field("id"))
|
||||
sq := resourceView.Query(field)
|
||||
if !admin || !IsAdminAllowList(userCred, manager) {
|
||||
ownerId := manager.GetOwnerId(userCred)
|
||||
if len(ownerId) > 0 {
|
||||
sq = manager.FilterByOwner(sq, ownerId)
|
||||
}
|
||||
}
|
||||
conditions = append(conditions, sqlchemy.In(q.Field("id"), sq))
|
||||
} else {
|
||||
return nil, httperrors.NewInputParameterError("Not support resource %s tag filter", resource)
|
||||
}
|
||||
}
|
||||
if len(conditions) > 0 {
|
||||
q = q.Filter(sqlchemy.OR(conditions...))
|
||||
}
|
||||
for args, prefix := range map[string]string{"sys_meta": SYSTEM_ADMIN_PREFIX, "cloud_meta": CLOUD_TAG_PREFIX, "user_meta": USER_TAG_PREFIX} {
|
||||
if jsonutils.QueryBoolean(query, args, false) {
|
||||
q = q.Filter(sqlchemy.Startswith(q.Field("key"), prefix))
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
/* @classmethod
|
||||
def get_object_idstr(cls, obj, keygen_func):
|
||||
idstr = None
|
||||
@@ -106,35 +182,42 @@ func (manager *SMetadataManager) RemoveAll(ctx context.Context, model IModel, us
|
||||
lockman.LockObject(ctx, model)
|
||||
defer lockman.ReleaseObject(ctx, model)
|
||||
|
||||
changes := []sMetadataChange{}
|
||||
records := make([]SMetadata, 0)
|
||||
q := manager.Query().Equals("id", idStr)
|
||||
err := FetchModelObjects(manager, q, &records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find metadata for %s fail: %s", idStr, err)
|
||||
}
|
||||
changes := make([]sMetadataChange, 0)
|
||||
for _, rec := range records {
|
||||
if len(rec.Value) > 0 {
|
||||
_, err := Update(&rec, func() error {
|
||||
rec.Value = ""
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
changes = append(changes, sMetadataChange{Key: rec.Key, OValue: rec.Value})
|
||||
}
|
||||
if err = rec.Delete(ctx, userCred); err != nil {
|
||||
log.Errorf("remove metadata %v error: %v", rec, err)
|
||||
continue
|
||||
}
|
||||
changes = append(changes, sMetadataChange{Key: rec.Key, OValue: rec.Value})
|
||||
}
|
||||
if len(changes) > 0 {
|
||||
OpsLog.LogEvent(model, ACT_DEL_METADATA, jsonutils.Marshal(changes), userCred)
|
||||
OpsLog.LogEvent(model, ACT_SET_METADATA, jsonutils.Marshal(changes), userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) SetValue(ctx context.Context, obj IModel, key string, value interface{}, userCred mcclient.TokenCredential) error {
|
||||
return manager.SetAll(ctx, obj, map[string]interface{}{key: value}, userCred)
|
||||
return manager.SetValuesWithLog(ctx, obj, map[string]interface{}{key: value}, userCred)
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store map[string]interface{}, userCred mcclient.TokenCredential) error {
|
||||
func (manager *SMetadataManager) SetValuesWithLog(ctx context.Context, obj IModel, store map[string]interface{}, userCred mcclient.TokenCredential) error {
|
||||
changes, err := manager.SetValues(ctx, obj, store, userCred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(changes) > 0 {
|
||||
OpsLog.LogEvent(obj, ACT_SET_METADATA, jsonutils.Marshal(changes), userCred)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) SetValues(ctx context.Context, obj IModel, store map[string]interface{}, userCred mcclient.TokenCredential) ([]sMetadataChange, error) {
|
||||
idStr := GetObjectIdstr(obj)
|
||||
|
||||
lockman.LockObject(ctx, obj)
|
||||
@@ -142,13 +225,17 @@ func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store m
|
||||
|
||||
changes := make([]sMetadataChange, 0)
|
||||
for key, value := range store {
|
||||
if strings.HasPrefix(key, SYSTEM_ADMIN_PREFIX) && (userCred == nil || !IsAdminAllowGetSpec(userCred, obj, "metadata")) {
|
||||
return nil, httperrors.NewForbiddenError("Ordinary users can't set the tags that begin with an underscore")
|
||||
}
|
||||
|
||||
valStr := stringutils.Interface2String(value)
|
||||
valStrLower := strings.ToLower(valStr)
|
||||
if valStrLower == "none" || valStrLower == "null" {
|
||||
valStr = ""
|
||||
}
|
||||
record := SMetadata{}
|
||||
err := manager.Query().Equals("id", idStr).Equals("key", key).First(&record)
|
||||
err := manager.RawQuery().Equals("id", idStr).Equals("key", key).First(&record) //避免之前设置的tag被删除后再次设置时出现Duplicate entry error
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
changes = append(changes, sMetadataChange{Key: key, NValue: valStr})
|
||||
@@ -157,22 +244,70 @@ func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store m
|
||||
record.Value = valStr
|
||||
err = manager.TableSpec().Insert(&record)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
deleted := record.Deleted
|
||||
_, err := Update(&record, func() error {
|
||||
record.Deleted = false
|
||||
record.Value = valStr
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if deleted {
|
||||
changes = append(changes, sMetadataChange{Key: key, NValue: valStr})
|
||||
} else {
|
||||
if record.Value != valStr {
|
||||
changes = append(changes, sMetadataChange{Key: key, OValue: record.Value, NValue: valStr})
|
||||
}
|
||||
}
|
||||
changes = append(changes, sMetadataChange{Key: key, OValue: record.Value, NValue: valStr})
|
||||
}
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store map[string]interface{}, userCred mcclient.TokenCredential, delRange string) error {
|
||||
changes, err := manager.SetValues(ctx, obj, store, userCred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idStr := GetObjectIdstr(obj)
|
||||
|
||||
lockman.LockObject(ctx, obj)
|
||||
defer lockman.ReleaseObject(ctx, obj)
|
||||
|
||||
keys := []string{}
|
||||
for key := range store {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
records := []SMetadata{}
|
||||
q := manager.Query().Equals("id", idStr)
|
||||
switch delRange {
|
||||
case TAG_DELETE_RANGE_USER:
|
||||
q = q.Like("key", USER_TAG_PREFIX+"%")
|
||||
case TAG_DELETE_RANGE_CLOUD:
|
||||
q = q.Like("key", CLOUD_TAG_PREFIX+"%")
|
||||
case TAG_DELETE_RANGE_SYS:
|
||||
q = q.Like("key", SYSTEM_ADMIN_PREFIX+"%")
|
||||
}
|
||||
q = q.Filter(sqlchemy.NOT(sqlchemy.In(q.Field("key"), keys)))
|
||||
if err := FetchModelObjects(manager, q, &records); err != nil {
|
||||
log.Errorf("failed to fetch metadata error: %v", err)
|
||||
}
|
||||
for _, rec := range records {
|
||||
if err := rec.Delete(ctx, userCred); err != nil {
|
||||
log.Errorf("failed to delete metadata error: %v", err)
|
||||
continue
|
||||
}
|
||||
changes = append(changes, sMetadataChange{Key: rec.Key, OValue: rec.Value})
|
||||
}
|
||||
if len(changes) > 0 {
|
||||
OpsLog.LogEvent(obj, ACT_SET_METADATA, jsonutils.Marshal(changes), userCred)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
@@ -103,6 +105,11 @@ func (manager *SStandaloneResourceBaseManager) FetchByExternalId(idStr string) (
|
||||
}
|
||||
}
|
||||
|
||||
type STagValue struct {
|
||||
value string
|
||||
exist bool
|
||||
}
|
||||
|
||||
func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
q, err := manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
|
||||
if err != nil {
|
||||
@@ -114,6 +121,62 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex
|
||||
q = q.Filter(sqlchemy.IsFalse(q.Field("is_emulated")))
|
||||
}
|
||||
|
||||
tags := map[string]STagValue{}
|
||||
if query.Contains("tags") {
|
||||
idx := 0
|
||||
for {
|
||||
key, _ := query.GetString("tags", fmt.Sprintf("%d", idx), "key")
|
||||
if len(key) == 0 {
|
||||
break
|
||||
}
|
||||
value := STagValue{exist: false}
|
||||
if query.Contains("tags", fmt.Sprintf("%d", idx), "value") {
|
||||
value.value, _ = query.GetString("tags", fmt.Sprintf("%d", idx), "value")
|
||||
value.exist = true
|
||||
}
|
||||
tags[key] = value
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
if len(tags) > 0 {
|
||||
metadataView := Metadata.Query("id")
|
||||
idx := 0
|
||||
for k, v := range tags {
|
||||
if idx == 0 {
|
||||
metadataView = metadataView.Equals("key", k)
|
||||
if v.exist {
|
||||
metadataView = metadataView.Equals("value", v.value)
|
||||
}
|
||||
} else {
|
||||
subMetataView := Metadata.Query().Equals("key", k)
|
||||
if v.exist {
|
||||
subMetataView = subMetataView.Equals("value", v.value)
|
||||
}
|
||||
sq := subMetataView.SubQuery()
|
||||
metadataView.Join(sq, sqlchemy.Equals(metadataView.Field("id"), sq.Field("id")))
|
||||
}
|
||||
idx++
|
||||
}
|
||||
metadataView = metadataView.Filter(sqlchemy.Like(metadataView.Field("id"), manager.Keyword()+"::%")).Distinct()
|
||||
resourceIds := []string{}
|
||||
rows, err := metadataView.Rows()
|
||||
if err != nil {
|
||||
log.Errorf("query metadata ids error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var metadataID string
|
||||
err = rows.Scan(&metadataID)
|
||||
if err != nil {
|
||||
log.Errorf("get metadata id scan error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resourceIds = append(resourceIds, strings.TrimLeft(metadataID, manager.Keyword()+"::"))
|
||||
}
|
||||
q = q.Filter(sqlchemy.In(q.Field("id"), resourceIds))
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
@@ -163,7 +226,15 @@ func (model *SStandaloneResourceBase) SetAllMetadata(ctx context.Context, dictst
|
||||
return httperrors.NewNotSufficientPrivilegeError("not allow to set system key %s", k)
|
||||
}
|
||||
}
|
||||
return Metadata.SetAll(ctx, model, dictstore, userCred)
|
||||
return Metadata.SetValuesWithLog(ctx, model, dictstore, userCred)
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) SetUserMetadataValues(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error {
|
||||
return Metadata.SetValuesWithLog(ctx, model, dictstore, userCred)
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) SetUserMetadataAll(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error {
|
||||
return Metadata.SetAll(ctx, model, dictstore, userCred, "user")
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) RemoveMetadata(ctx context.Context, key string, userCred mcclient.TokenCredential) error {
|
||||
@@ -212,6 +283,48 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) AllowPerformUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return IsAdminAllowPerform(userCred, model, "user-metadata")
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) PerformUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
dict, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewInputParameterError("input data not key value dict")
|
||||
}
|
||||
dictMap, err := dict.GetMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dictStore := make(map[string]interface{})
|
||||
for k, v := range dictMap {
|
||||
dictStore["user:"+k], _ = v.GetString()
|
||||
}
|
||||
err = model.SetUserMetadataValues(ctx, dictStore, userCred)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) AllowPerformSetUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return IsAdminAllowPerform(userCred, model, "set-user-metadata")
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) PerformSetUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
dict, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewInputParameterError("input data not key value dict")
|
||||
}
|
||||
dictMap, err := dict.GetMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dictStore := make(map[string]interface{})
|
||||
for k, v := range dictMap {
|
||||
dictStore["user:"+k], _ = v.GetString()
|
||||
}
|
||||
err = model.SetUserMetadataAll(ctx, dictStore, userCred)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (model *SStandaloneResourceBase) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := model.SResourceBase.GetCustomizeColumns(ctx, userCred, query)
|
||||
withMeta, _ := query.GetString("with_meta")
|
||||
|
||||
@@ -236,18 +236,7 @@ func (self *SManagedVirtualizationHostDriver) RequestAllocateDiskOnStorage(ctx c
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if metaData := iDisk.GetMetadata(); metaData != nil {
|
||||
meta := make(map[string]string)
|
||||
if err := metaData.Unmarshal(meta); err != nil {
|
||||
log.Errorf("Get disk %s Metadata error: %v", disk.Name, err)
|
||||
} else {
|
||||
for key, value := range meta {
|
||||
if err := disk.SetMetadata(ctx, key, value, task.GetUserCred()); err != nil {
|
||||
log.Errorf("set disk %s mata %s => %s error: %v", disk.Name, key, value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
models.SyncMetadata(ctx, task.GetUserCred(), disk, iDisk)
|
||||
|
||||
data := jsonutils.NewDict()
|
||||
data.Add(jsonutils.NewInt(int64(iDisk.GetDiskSizeMB())), "disk_size")
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
type IMetadataSetter interface {
|
||||
SetAllMetadata(ctx context.Context, meta map[string]interface{}, userCred mcclient.TokenCredential) error
|
||||
SetMetadata(ctx context.Context, key string, value interface{}, userCred mcclient.TokenCredential) error
|
||||
}
|
||||
|
||||
func syncMetadata(ctx context.Context, userCred mcclient.TokenCredential, model IMetadataSetter, remote cloudprovider.ICloudResource) error {
|
||||
@@ -22,8 +23,13 @@ func syncMetadata(ctx context.Context, userCred mcclient.TokenCredential, model
|
||||
log.Errorf("Get VM Metadata error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return model.SetAllMetadata(ctx, meta, userCred)
|
||||
for key, value := range meta {
|
||||
model.SetMetadata(ctx, "ext:"+key, value, userCred)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SyncMetadata(ctx context.Context, userCred mcclient.TokenCredential, model IMetadataSetter, remote cloudprovider.ICloudResource) error {
|
||||
return syncMetadata(ctx, userCred, model, remote)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ func InitHandlers(app *appsrv.Application) {
|
||||
db.RegisterModelManager(manager)
|
||||
}
|
||||
|
||||
metadatahandler := db.NewModelHandler(db.Metadata)
|
||||
dispatcher.AddModelDispatcher("", app, metadatahandler)
|
||||
|
||||
for _, manager := range []db.IModelManager{
|
||||
db.OpsLog,
|
||||
models.CloudaccountManager,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package modules
|
||||
|
||||
var (
|
||||
Metadatas ResourceManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Metadatas = NewComputeManager("metadata", "metadatas",
|
||||
[]string{"id", "key", "value"},
|
||||
[]string{})
|
||||
registerCompute(&Metadatas)
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package options
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
@@ -186,6 +188,7 @@ type BaseListOptions struct {
|
||||
ExportFile string `help:"Export to file" metavar:"<EXPORT_FILE_PATH>" json:"-"`
|
||||
ExportKeys string `help:"Export field keys"`
|
||||
ExportTexts string `help:"Export field displayname texts" json:"-"`
|
||||
Tags []string `help:"Tags info, eg: hypervisor=aliyun、os_type=Linux、os_version"`
|
||||
|
||||
Manager string `help:"List objects belonging to the cloud provider" json:"manager,omitempty"`
|
||||
Account string `help:"List objects belonging to the cloud account" json:"account,omitempty"`
|
||||
@@ -217,5 +220,23 @@ func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
params.Set("admin", jsonutils.JSONTrue)
|
||||
}
|
||||
}
|
||||
for idx, tag := range opts.Tags {
|
||||
tagInfo := strings.Split(tag, "=")
|
||||
if len(tagInfo) > 2 {
|
||||
return nil, fmt.Errorf("failed parse tags info %s", tag)
|
||||
}
|
||||
if len(tagInfo[0]) == 0 {
|
||||
return nil, fmt.Errorf("Not support empty key")
|
||||
}
|
||||
for _, k := range tagInfo[0] {
|
||||
if k != rune('_') && !unicode.IsLetter(k) && !unicode.IsDigit(k) {
|
||||
return nil, fmt.Errorf("Not support tag key with %s", string(k))
|
||||
}
|
||||
}
|
||||
params.Add(jsonutils.NewString(tagInfo[0]), fmt.Sprintf("tags.%d.key", idx))
|
||||
if len(tagInfo) == 2 {
|
||||
params.Add(jsonutils.NewString(tagInfo[1]), fmt.Sprintf("tags.%d.value", idx))
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package options
|
||||
|
||||
type MetadataListOptions struct {
|
||||
Resources []string `help:"list of resource e.g server、disk、eip、snapshot, empty will show all metadata"`
|
||||
SysMeta *bool `help:"Show sys metadata only"`
|
||||
CloudMeta *bool `help:"Show cloud metadata olny"`
|
||||
UserMeta *bool `help:"Show user metadata olny"`
|
||||
Admin *bool `help:"Show all metadata"`
|
||||
}
|
||||
@@ -350,3 +350,26 @@ type ServerLiveMigrateOptions struct {
|
||||
ID string `help:"ID of server" json:"-"`
|
||||
PreferHost string `help:"Server migration prefer host id or name" json:"prefer_host"`
|
||||
}
|
||||
|
||||
type ServerMetadataOptions struct {
|
||||
ID string `help:"ID or name of server" json:"-"`
|
||||
TAGS []string `help:"Tags info, eg: hypervisor=aliyun、os_type=Linux、os_version"`
|
||||
}
|
||||
|
||||
func (opts *ServerMetadataOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
params := jsonutils.NewDict()
|
||||
for _, tag := range opts.TAGS {
|
||||
info := strings.Split(tag, "=")
|
||||
if len(info) == 2 {
|
||||
if len(info[0]) == 0 {
|
||||
return nil, fmt.Errorf("invalidate tag info %s", tag)
|
||||
}
|
||||
params.Add(jsonutils.NewString(info[1]), info[0])
|
||||
} else if len(info) == 1 {
|
||||
params.Add(jsonutils.NewString(info[0]), info[0])
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalidate tag info %s", tag)
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user