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:
邱剑
2019-03-19 15:59:46 +08:00
12 changed files with 400 additions and 39 deletions
+22
View File
@@ -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
})
}
+26
View File
@@ -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 {
+3 -1
View File
@@ -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
View File
@@ -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)
}
+114 -1
View File
@@ -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")
+1 -12
View File
@@ -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")
+8 -2
View File
@@ -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)
}
+3
View File
@@ -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,
+12
View File
@@ -0,0 +1,12 @@
package modules
var (
Metadatas ResourceManager
)
func init() {
Metadatas = NewComputeManager("metadata", "metadatas",
[]string{"id", "key", "value"},
[]string{})
registerCompute(&Metadatas)
}
+21
View File
@@ -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
}
+9
View File
@@ -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"`
}
+23
View File
@@ -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
}