From 703ce13f67198ef7f7825e0fd6e4e4c3417e6738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 11 Mar 2019 16:01:32 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/metadata.go | 35 +++++++++++++++ pkg/cloudcommon/db/db_dispatcher.go | 4 +- pkg/cloudcommon/db/{ => metadata}/metadata.go | 44 +++++++++++++------ pkg/cloudcommon/db/standalone.go | 32 +++++++++----- pkg/compute/service/handlers.go | 3 ++ pkg/mcclient/modules/mod_metadatas.go | 12 +++++ 6 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 cmd/climc/shell/metadata.go rename pkg/cloudcommon/db/{ => metadata}/metadata.go (76%) create mode 100644 pkg/mcclient/modules/mod_metadatas.go diff --git a/cmd/climc/shell/metadata.go b/cmd/climc/shell/metadata.go new file mode 100644 index 0000000000..03cc22cd2a --- /dev/null +++ b/cmd/climc/shell/metadata.go @@ -0,0 +1,35 @@ +package shell + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +func init() { + type MetadataListOptions struct { + Resources []string `help:"list of resource e.g server、disk"` + IsSys bool `help:"list sys metadata"` + } + + R(&MetadataListOptions{}, "metadata-list", "List metadatas", func(s *mcclient.ClientSession, opts *MetadataListOptions) error { + params := jsonutils.NewDict() + resources := jsonutils.NewArray() + for _, resource := range opts.Resources { + resources.Add(jsonutils.NewString(resource)) + } + if resources.Length() > 0 { + params.Add(resources, "resources") + } + if opts.IsSys { + params.Add(jsonutils.JSONTrue, "is_sys") + } + result, err := modules.Metadatas.List(s, params) + if err != nil { + return err + } + printList(result, []string{}) + return nil + }) +} diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index 4c9df13e94..cdbb4dd92c 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -334,6 +334,7 @@ func Query2List(manager IModelManager, ctx context.Context, userCred mcclient.To return nil, err } results := make([]jsonutils.JSONObject, 0) + q.DebugQuery() rows, err := q.Rows() if err != nil { return nil, err @@ -452,7 +453,8 @@ 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"} + orderBy = []string{"id"} + //orderBy = []string{"created_at"} } } order := sqlchemy.SQL_ORDER_DESC diff --git a/pkg/cloudcommon/db/metadata.go b/pkg/cloudcommon/db/metadata/metadata.go similarity index 76% rename from pkg/cloudcommon/db/metadata.go rename to pkg/cloudcommon/db/metadata/metadata.go index f49c80fa0d..c2f50069d7 100644 --- a/pkg/cloudcommon/db/metadata.go +++ b/pkg/cloudcommon/db/metadata/metadata.go @@ -1,4 +1,4 @@ -package db +package metadata import ( "context" @@ -9,7 +9,9 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/util/stringutils" + "yunion.io/x/sqlchemy" + "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -19,22 +21,22 @@ const ( ) type SMetadataManager struct { - SModelBaseManager + db.SModelBaseManager } type SMetadata struct { - SModelBase + db.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:"ascii" 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) } var Metadata *SMetadataManager func init() { - Metadata = &SMetadataManager{SModelBaseManager: NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadata")} + Metadata = &SMetadataManager{SModelBaseManager: db.NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadata")} } func (m *SMetadata) GetId() string { @@ -53,6 +55,22 @@ func GetObjectIdstr(model IModel) string { return fmt.Sprintf("%s::%s", model.GetModelManager().Keyword(), model.GetId()) } +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) { + // queryDict, ok := query.(*jsonutils.JSONDict) + // if !ok { + // return nil, fmt.Errorf("invalid querystring format") + // } + resources := jsonutils.GetQueryStringArray(query, "resrouces") + if len(resources) > 0 { + //q = models.DiskManager.Query() + } + return q, nil +} + /* @classmethod def get_object_idstr(cls, obj, keygen_func): idstr = None @@ -64,7 +82,7 @@ if idstr is None: raise Exception('get_object_idstr: failed to generate obj ID') return idstr */ -func (manager *SMetadataManager) GetStringValue(model IModel, key string, userCred mcclient.TokenCredential) string { +func (manager *SMetadataManager) GetStringValue(model db.IModel, key string, userCred mcclient.TokenCredential) string { if strings.HasPrefix(key, SYSTEM_ADMIN_PREFIX) && (userCred == nil || !IsAdminAllowGetSpec(userCred, model, "metadata")) { return "" } @@ -77,7 +95,7 @@ func (manager *SMetadataManager) GetStringValue(model IModel, key string, userCr return "" } -func (manager *SMetadataManager) GetJsonValue(model IModel, key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { +func (manager *SMetadataManager) GetJsonValue(model db.IModel, key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { if strings.HasPrefix(key, SYSTEM_ADMIN_PREFIX) && (userCred == nil || !IsAdminAllowGetSpec(userCred, model, "metadata")) { return nil } @@ -115,7 +133,7 @@ func (manager *SMetadataManager) RemoveAll(ctx context.Context, model IModel, us changes := make([]sMetadataChange, 0) for _, rec := range records { if len(rec.Value) > 0 { - _, err := Update(&rec, func() error { + _, err := db.Update(&rec, func() error { rec.Value = "" return nil }) @@ -163,7 +181,7 @@ func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store m return err } } else { - _, err := Update(&record, func() error { + _, err := db.Update(&record, func() error { record.Value = valStr return nil }) @@ -194,7 +212,7 @@ func (manager *SMetadataManager) GetAll(obj IModel, keys []string, userCred mccl for _, rec := range records { if len(rec.Value) > 0 { if strings.HasPrefix(rec.Key, SYSTEM_ADMIN_PREFIX) { - if userCred != nil && IsAdminAllowGetSpec(userCred, obj, "metadata") { + if userCred != nil && db.IsAdminAllowGetSpec(userCred, obj, "metadata") { key := rec.Key[len(SYSTEM_ADMIN_PREFIX):] ret[key] = rec.Value } diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 46320a863b..8befa91185 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -3,6 +3,7 @@ package db import ( "context" "database/sql" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -11,6 +12,7 @@ import ( "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" + "yunion.io/x/onecloud/pkg/cloudcommon/db/metadata" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" @@ -143,23 +145,23 @@ func (model *SStandaloneResourceBase) GetShortDesc(ctx context.Context) *jsonuti * userCred: optional */ func (model *SStandaloneResourceBase) GetMetadata(key string, userCred mcclient.TokenCredential) string { - return Metadata.GetStringValue(model, key, userCred) + return metadata.Metadata.GetStringValue(model, key, userCred) } func (model *SStandaloneResourceBase) GetMetadataJson(key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { - return Metadata.GetJsonValue(model, key, userCred) + return metadata.Metadata.GetJsonValue(model, key, userCred) } func (model *SStandaloneResourceBase) SetMetadata(ctx context.Context, key string, value interface{}, userCred mcclient.TokenCredential) error { - if Metadata.IsSystemAdminKey(key) && !IsAdminAllowPerform(userCred, model, "metadata") { + if metadata.Metadata.IsSystemAdminKey(key) && !IsAdminAllowPerform(userCred, model, "metadata") { return httperrors.NewNotSufficientPrivilegeError("cannot set system key") } - return Metadata.SetValue(ctx, model, key, value, userCred) + return metadata.Metadata.SetValue(ctx, model, key, value, userCred) } func (model *SStandaloneResourceBase) SetAllMetadata(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error { for k := range dictstore { - if Metadata.IsSystemAdminKey(k) && !IsAdminAllowPerform(userCred, model, "metadata") { + if metadata.Metadata.IsSystemAdminKey(k) && !IsAdminAllowPerform(userCred, model, "metadata") { return httperrors.NewNotSufficientPrivilegeError("not allow to set system key %s", k) } } @@ -167,15 +169,15 @@ func (model *SStandaloneResourceBase) SetAllMetadata(ctx context.Context, dictst } func (model *SStandaloneResourceBase) RemoveMetadata(ctx context.Context, key string, userCred mcclient.TokenCredential) error { - return Metadata.SetValue(ctx, model, key, "", userCred) + return metadata.Metadata.SetValue(ctx, model, key, "", userCred) } func (model *SStandaloneResourceBase) RemoveAllMetadata(ctx context.Context, userCred mcclient.TokenCredential) error { - return Metadata.RemoveAll(ctx, model, userCred) + return metadata.Metadata.RemoveAll(ctx, model, userCred) } func (model *SStandaloneResourceBase) GetAllMetadata(userCred mcclient.TokenCredential) (map[string]string, error) { - return Metadata.GetAll(model, nil, userCred) + return metadata.Metadata.GetAll(model, nil, userCred) } func (model *SStandaloneResourceBase) AllowGetDetailsMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -184,7 +186,7 @@ func (model *SStandaloneResourceBase) AllowGetDetailsMetadata(ctx context.Contex func (model *SStandaloneResourceBase) GetDetailsMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { fields := jsonutils.GetQueryStringArray(query, "field") - val, err := Metadata.GetAll(model, fields, userCred) + val, err := metadata.Metadata.GetAll(model, fields, userCred) if err != nil { return nil, err } @@ -206,17 +208,27 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC } dictStore := make(map[string]interface{}) for k, v := range dictMap { + if err := model.ValidateMetadataKey(k); err != nil { + return nil, err + } dictStore[k], _ = v.GetString() } err = model.SetAllMetadata(ctx, dictStore, userCred) return nil, err } +func (model *SStandaloneResourceBase) ValidateMetadataKey(key string) error { + if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "ext:") || strings.Contains(key, ":=") { + return httperrors.NewInputParameterError("key cannot start with _ or ext: and not contain :=") + } + return nil +} + 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") if utils.ToBool(withMeta) { - jsonMeta, err := Metadata.GetAll(model, nil, userCred) + jsonMeta, err := metadata.Metadata.GetAll(model, nil, userCred) if err == nil { extra.Add(jsonutils.Marshal(jsonMeta), "metadata") } else { diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 0ebeea59c1..5f4aee8bb9 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -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, diff --git a/pkg/mcclient/modules/mod_metadatas.go b/pkg/mcclient/modules/mod_metadatas.go new file mode 100644 index 0000000000..ffc3217474 --- /dev/null +++ b/pkg/mcclient/modules/mod_metadatas.go @@ -0,0 +1,12 @@ +package modules + +var ( + Metadatas ResourceManager +) + +func init() { + Metadatas = NewComputeManager("metadata", "metadata", + []string{"id", "key", "value"}, + []string{}) + registerCompute(&Metadatas) +} From 2d0cc14835b9c7b744c814266b82c47aa4616376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 11 Mar 2019 17:53:23 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E6=94=AF=E6=8C=81server=20disk=20=E8=BF=87?= =?UTF-8?q?=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/metadata.go | 23 ++---- cmd/climc/shell/servers.go | 17 +++++ pkg/cloudcommon/db/db_dispatcher.go | 6 +- pkg/cloudcommon/db/{metadata => }/metadata.go | 68 +++++++++++++----- pkg/cloudcommon/db/standalone.go | 71 +++++++++++++++---- pkg/mcclient/options/base.go | 11 +++ pkg/mcclient/options/metadata.go | 7 ++ 7 files changed, 151 insertions(+), 52 deletions(-) rename pkg/cloudcommon/db/{metadata => }/metadata.go (69%) create mode 100644 pkg/mcclient/options/metadata.go diff --git a/cmd/climc/shell/metadata.go b/cmd/climc/shell/metadata.go index 03cc22cd2a..be57c5b386 100644 --- a/cmd/climc/shell/metadata.go +++ b/cmd/climc/shell/metadata.go @@ -1,29 +1,16 @@ package shell import ( - "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" ) func init() { - type MetadataListOptions struct { - Resources []string `help:"list of resource e.g server、disk"` - IsSys bool `help:"list sys metadata"` - } - - R(&MetadataListOptions{}, "metadata-list", "List metadatas", func(s *mcclient.ClientSession, opts *MetadataListOptions) error { - params := jsonutils.NewDict() - resources := jsonutils.NewArray() - for _, resource := range opts.Resources { - resources.Add(jsonutils.NewString(resource)) - } - if resources.Length() > 0 { - params.Add(resources, "resources") - } - if opts.IsSys { - params.Add(jsonutils.JSONTrue, "is_sys") + 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 { diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index f88cf61154..7acdcbf424 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -85,6 +85,23 @@ func init() { return nil }) + type ServerSetMetadataOptions struct { + ID string `help:"ID or name of server" json:"-"` + KEY string `help:"Metadata key"` + VALUE string `help:"Metadata Value"` + } + + R(&ServerSetMetadataOptions{}, "server-set-metadata", "Set metadata of a server", func(s *mcclient.ClientSession, opts *ServerSetMetadataOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(opts.VALUE), opts.KEY) + result, err := modules.Servers.PerformAction(s, opts.ID, "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 { diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index cdbb4dd92c..1375de94d1 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -334,7 +334,6 @@ func Query2List(manager IModelManager, ctx context.Context, userCred mcclient.To return nil, err } results := make([]jsonutils.JSONObject, 0) - q.DebugQuery() rows, err := q.Rows() if err != nil { return nil, err @@ -453,8 +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{"id"} - //orderBy = []string{"created_at"} + if manager.TableSpec().ColumnSpec("created_at") != nil { + orderBy = []string{"created_at"} + } } } order := sqlchemy.SQL_ORDER_DESC diff --git a/pkg/cloudcommon/db/metadata/metadata.go b/pkg/cloudcommon/db/metadata.go similarity index 69% rename from pkg/cloudcommon/db/metadata/metadata.go rename to pkg/cloudcommon/db/metadata.go index c2f50069d7..692c494504 100644 --- a/pkg/cloudcommon/db/metadata/metadata.go +++ b/pkg/cloudcommon/db/metadata.go @@ -1,4 +1,4 @@ -package metadata +package db import ( "context" @@ -11,8 +11,8 @@ import ( "yunion.io/x/pkg/util/stringutils" "yunion.io/x/sqlchemy" - "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -21,22 +21,29 @@ const ( ) type SMetadataManager struct { - db.SModelBaseManager + SModelBaseManager } type SMetadata struct { - db.SModelBase + SModelBase 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:"ascii" primary:"true" list:"user" get:"user"` // = Column(VARCHAR(64, 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) } var Metadata *SMetadataManager +var ResourceMap map[string]*SVirtualResourceBaseManager func init() { - Metadata = &SMetadataManager{SModelBaseManager: db.NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadata")} + Metadata = &SMetadataManager{SModelBaseManager: NewModelBaseManager(SMetadata{}, "metadata_tbl", "metadata", "metadata")} + 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 { @@ -60,13 +67,38 @@ func (manager *SMetadataManager) AllowListItems(ctx context.Context, userCred mc } func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - // queryDict, ok := query.(*jsonutils.JSONDict) - // if !ok { - // return nil, fmt.Errorf("invalid querystring format") - // } - resources := jsonutils.GetQueryStringArray(query, "resrouces") - if len(resources) > 0 { - //q = models.DiskManager.Query() + 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() + field := sqlchemy.CONCAT(manager.Keyword(), fmt.Sprintf("%s::", manager.Keyword()), 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...)) + } + if !jsonutils.QueryBoolean(query, "with_sys", false) { + q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), "_"))) + } + if !jsonutils.QueryBoolean(query, "with_cloud", false) { + q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), "ext:"))) } return q, nil } @@ -82,7 +114,7 @@ if idstr is None: raise Exception('get_object_idstr: failed to generate obj ID') return idstr */ -func (manager *SMetadataManager) GetStringValue(model db.IModel, key string, userCred mcclient.TokenCredential) string { +func (manager *SMetadataManager) GetStringValue(model IModel, key string, userCred mcclient.TokenCredential) string { if strings.HasPrefix(key, SYSTEM_ADMIN_PREFIX) && (userCred == nil || !IsAdminAllowGetSpec(userCred, model, "metadata")) { return "" } @@ -95,7 +127,7 @@ func (manager *SMetadataManager) GetStringValue(model db.IModel, key string, use return "" } -func (manager *SMetadataManager) GetJsonValue(model db.IModel, key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { +func (manager *SMetadataManager) GetJsonValue(model IModel, key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { if strings.HasPrefix(key, SYSTEM_ADMIN_PREFIX) && (userCred == nil || !IsAdminAllowGetSpec(userCred, model, "metadata")) { return nil } @@ -133,7 +165,7 @@ func (manager *SMetadataManager) RemoveAll(ctx context.Context, model IModel, us changes := make([]sMetadataChange, 0) for _, rec := range records { if len(rec.Value) > 0 { - _, err := db.Update(&rec, func() error { + _, err := Update(&rec, func() error { rec.Value = "" return nil }) @@ -181,7 +213,7 @@ func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store m return err } } else { - _, err := db.Update(&record, func() error { + _, err := Update(&record, func() error { record.Value = valStr return nil }) @@ -212,7 +244,7 @@ func (manager *SMetadataManager) GetAll(obj IModel, keys []string, userCred mccl for _, rec := range records { if len(rec.Value) > 0 { if strings.HasPrefix(rec.Key, SYSTEM_ADMIN_PREFIX) { - if userCred != nil && db.IsAdminAllowGetSpec(userCred, obj, "metadata") { + if userCred != nil && IsAdminAllowGetSpec(userCred, obj, "metadata") { key := rec.Key[len(SYSTEM_ADMIN_PREFIX):] ret[key] = rec.Value } diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 8befa91185..6c8767fb3c 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -3,6 +3,7 @@ package db import ( "context" "database/sql" + "fmt" "strings" "yunion.io/x/jsonutils" @@ -12,7 +13,6 @@ import ( "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" - "yunion.io/x/onecloud/pkg/cloudcommon/db/metadata" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" @@ -116,6 +116,51 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex q = q.Filter(sqlchemy.IsFalse(q.Field("is_emulated"))) } + tags := map[string]string{} + if query.Contains("tags") { + idx := 0 + for { + key, _ := query.GetString("tags", fmt.Sprintf("%d", idx), "key") + if len(key) == 0 { + break + } + value, _ := query.GetString("tags", fmt.Sprintf("%d", idx), "value") + 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).Equals("value", v) + } else { + subMetataView := Metadata.Query().Equals("key", k).Equals("value", v).SubQuery() + metadataView.Join(subMetataView, sqlchemy.Equals(metadataView.Field("id"), subMetataView.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.Replace(metadataID, manager.Keyword()+"::", "", -1)) + } + q = q.Filter(sqlchemy.In(q.Field("id"), resourceIds)) + } return q, nil } @@ -145,23 +190,23 @@ func (model *SStandaloneResourceBase) GetShortDesc(ctx context.Context) *jsonuti * userCred: optional */ func (model *SStandaloneResourceBase) GetMetadata(key string, userCred mcclient.TokenCredential) string { - return metadata.Metadata.GetStringValue(model, key, userCred) + return Metadata.GetStringValue(model, key, userCred) } func (model *SStandaloneResourceBase) GetMetadataJson(key string, userCred mcclient.TokenCredential) jsonutils.JSONObject { - return metadata.Metadata.GetJsonValue(model, key, userCred) + return Metadata.GetJsonValue(model, key, userCred) } func (model *SStandaloneResourceBase) SetMetadata(ctx context.Context, key string, value interface{}, userCred mcclient.TokenCredential) error { - if metadata.Metadata.IsSystemAdminKey(key) && !IsAdminAllowPerform(userCred, model, "metadata") { + if Metadata.IsSystemAdminKey(key) && !IsAdminAllowPerform(userCred, model, "metadata") { return httperrors.NewNotSufficientPrivilegeError("cannot set system key") } - return metadata.Metadata.SetValue(ctx, model, key, value, userCred) + return Metadata.SetValue(ctx, model, key, value, userCred) } func (model *SStandaloneResourceBase) SetAllMetadata(ctx context.Context, dictstore map[string]interface{}, userCred mcclient.TokenCredential) error { for k := range dictstore { - if metadata.Metadata.IsSystemAdminKey(k) && !IsAdminAllowPerform(userCred, model, "metadata") { + if Metadata.IsSystemAdminKey(k) && !IsAdminAllowPerform(userCred, model, "metadata") { return httperrors.NewNotSufficientPrivilegeError("not allow to set system key %s", k) } } @@ -169,15 +214,15 @@ func (model *SStandaloneResourceBase) SetAllMetadata(ctx context.Context, dictst } func (model *SStandaloneResourceBase) RemoveMetadata(ctx context.Context, key string, userCred mcclient.TokenCredential) error { - return metadata.Metadata.SetValue(ctx, model, key, "", userCred) + return Metadata.SetValue(ctx, model, key, "", userCred) } func (model *SStandaloneResourceBase) RemoveAllMetadata(ctx context.Context, userCred mcclient.TokenCredential) error { - return metadata.Metadata.RemoveAll(ctx, model, userCred) + return Metadata.RemoveAll(ctx, model, userCred) } func (model *SStandaloneResourceBase) GetAllMetadata(userCred mcclient.TokenCredential) (map[string]string, error) { - return metadata.Metadata.GetAll(model, nil, userCred) + return Metadata.GetAll(model, nil, userCred) } func (model *SStandaloneResourceBase) AllowGetDetailsMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { @@ -186,7 +231,7 @@ func (model *SStandaloneResourceBase) AllowGetDetailsMetadata(ctx context.Contex func (model *SStandaloneResourceBase) GetDetailsMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { fields := jsonutils.GetQueryStringArray(query, "field") - val, err := metadata.Metadata.GetAll(model, fields, userCred) + val, err := Metadata.GetAll(model, fields, userCred) if err != nil { return nil, err } @@ -218,8 +263,8 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC } func (model *SStandaloneResourceBase) ValidateMetadataKey(key string) error { - if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "ext:") || strings.Contains(key, ":=") { - return httperrors.NewInputParameterError("key cannot start with _ or ext: and not contain :=") + if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "ext:") || strings.ContainsAny(key, `:=#&?$/\`) { + return httperrors.NewInputParameterError(`key cannot start with _ or ext: and not contain :=#&?$/\`) } return nil } @@ -228,7 +273,7 @@ func (model *SStandaloneResourceBase) GetCustomizeColumns(ctx context.Context, u extra := model.SResourceBase.GetCustomizeColumns(ctx, userCred, query) withMeta, _ := query.GetString("with_meta") if utils.ToBool(withMeta) { - jsonMeta, err := metadata.Metadata.GetAll(model, nil, userCred) + jsonMeta, err := Metadata.GetAll(model, nil, userCred) if err == nil { extra.Add(jsonutils.Marshal(jsonMeta), "metadata") } else { diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index 07900fe7b4..488c502e1d 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -186,6 +186,8 @@ type BaseListOptions struct { ExportFile string `help:"Export to file" metavar:"" json:"-"` ExportKeys string `help:"Export field keys"` ExportTexts string `help:"Export field displayname texts" json:"-"` + TagsKey []string `help:"Tag key" json:"-"` + TagsValue []string `help:"Tag key" json:"-"` 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 +219,14 @@ func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) { params.Set("admin", jsonutils.JSONTrue) } } + if len(opts.TagsKey) > 0 { + if len(opts.TagsKey) != len(opts.TagsValue) { + return nil, fmt.Errorf("tags key length not equal value length") + } + for i := 0; i < len(opts.TagsKey); i++ { + params.Add(jsonutils.NewString(opts.TagsKey[i]), fmt.Sprintf("tags.%d.key", i)) + params.Add(jsonutils.NewString(opts.TagsValue[i]), fmt.Sprintf("tags.%d.value", i)) + } + } return params, nil } diff --git a/pkg/mcclient/options/metadata.go b/pkg/mcclient/options/metadata.go new file mode 100644 index 0000000000..24d0f70cf6 --- /dev/null +++ b/pkg/mcclient/options/metadata.go @@ -0,0 +1,7 @@ +package options + +type MetadataListOptions struct { + Resources []string `help:"list of resource e.g server、disk、eip、snapshot, empty will show all metadata"` + WithSys *bool `help:"With sys metadata"` + WithCloud *bool `help:"With cloud metadata"` +} From 9bcaf1ec844a621d47634fed55a84d6f51dfb7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Wed, 13 Mar 2019 12:07:08 +0800 Subject: [PATCH 3/8] =?UTF-8?q?metadata=E8=BF=94=E5=9B=9E=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BD=BF=E7=94=A8=E5=A4=8D=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cloudcommon/db/metadata.go | 6 ++++-- pkg/cloudcommon/db/standalone.go | 2 +- pkg/mcclient/modules/mod_metadatas.go | 2 +- pkg/mcclient/options/base.go | 6 +++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/cloudcommon/db/metadata.go b/pkg/cloudcommon/db/metadata.go index 692c494504..4d23b2068f 100644 --- a/pkg/cloudcommon/db/metadata.go +++ b/pkg/cloudcommon/db/metadata.go @@ -37,7 +37,7 @@ 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")}, @@ -78,7 +78,8 @@ func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy for _, resource := range resources { if manager, ok := ResourceMap[resource]; ok { resourceView := manager.Query().SubQuery() - field := sqlchemy.CONCAT(manager.Keyword(), fmt.Sprintf("%s::", manager.Keyword()), resourceView.Field("id")) + 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) @@ -94,6 +95,7 @@ func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy if len(conditions) > 0 { q = q.Filter(sqlchemy.OR(conditions...)) } + q.DebugQuery() if !jsonutils.QueryBoolean(query, "with_sys", false) { q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), "_"))) } diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 6c8767fb3c..7123088815 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -157,7 +157,7 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex log.Errorf("get metadata id scan error: %v", err) return nil, err } - resourceIds = append(resourceIds, strings.Replace(metadataID, manager.Keyword()+"::", "", -1)) + resourceIds = append(resourceIds, strings.TrimLeft(metadataID, manager.Keyword()+"::")) } q = q.Filter(sqlchemy.In(q.Field("id"), resourceIds)) } diff --git a/pkg/mcclient/modules/mod_metadatas.go b/pkg/mcclient/modules/mod_metadatas.go index ffc3217474..06bcec6974 100644 --- a/pkg/mcclient/modules/mod_metadatas.go +++ b/pkg/mcclient/modules/mod_metadatas.go @@ -5,7 +5,7 @@ var ( ) func init() { - Metadatas = NewComputeManager("metadata", "metadata", + Metadatas = NewComputeManager("metadata", "metadatas", []string{"id", "key", "value"}, []string{}) registerCompute(&Metadatas) diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index 488c502e1d..e52733a73c 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -219,10 +219,10 @@ func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) { params.Set("admin", jsonutils.JSONTrue) } } + if len(opts.TagsKey) != len(opts.TagsValue) { + return nil, fmt.Errorf("tags key length not equal value length") + } if len(opts.TagsKey) > 0 { - if len(opts.TagsKey) != len(opts.TagsValue) { - return nil, fmt.Errorf("tags key length not equal value length") - } for i := 0; i < len(opts.TagsKey); i++ { params.Add(jsonutils.NewString(opts.TagsKey[i]), fmt.Sprintf("tags.%d.key", i)) params.Add(jsonutils.NewString(opts.TagsValue[i]), fmt.Sprintf("tags.%d.value", i)) From 46d5120a35fa0a3eac6cd7e3e37326979a3b80e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Wed, 13 Mar 2019 16:03:29 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=8F=96=E6=B6=88key=E4=BB=A5=5F=E5=BC=80?= =?UTF-8?q?=E5=A4=B4key=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cloudcommon/db/metadata.go | 12 ++++++++---- pkg/cloudcommon/db/standalone.go | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/cloudcommon/db/metadata.go b/pkg/cloudcommon/db/metadata.go index 4d23b2068f..16f29e801f 100644 --- a/pkg/cloudcommon/db/metadata.go +++ b/pkg/cloudcommon/db/metadata.go @@ -17,7 +17,8 @@ import ( ) const ( - SYSTEM_ADMIN_PREFIX = "__sys_" + SYSTEM_ADMIN_PREFIX = "_" + CLOUD_TAG_PREFIX = "ext:" ) type SMetadataManager struct { @@ -95,12 +96,11 @@ func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy if len(conditions) > 0 { q = q.Filter(sqlchemy.OR(conditions...)) } - q.DebugQuery() if !jsonutils.QueryBoolean(query, "with_sys", false) { - q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), "_"))) + q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), SYSTEM_ADMIN_PREFIX))) } if !jsonutils.QueryBoolean(query, "with_cloud", false) { - q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), "ext:"))) + q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), CLOUD_TAG_PREFIX))) } return q, nil } @@ -194,6 +194,10 @@ 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 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" { diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 7123088815..09fcd2e85a 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -263,8 +263,8 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC } func (model *SStandaloneResourceBase) ValidateMetadataKey(key string) error { - if strings.HasPrefix(key, "_") || strings.HasPrefix(key, "ext:") || strings.ContainsAny(key, `:=#&?$/\`) { - return httperrors.NewInputParameterError(`key cannot start with _ or ext: and not contain :=#&?$/\`) + if strings.HasPrefix(key, CLOUD_TAG_PREFIX) || strings.ContainsAny(key, `:=#&?$/\`) { + return httperrors.NewInputParameterError(`key cannot start with %s and not contain :=#&?$/\`, CLOUD_TAG_PREFIX) } return nil } From 5d5cd9833b722ae7264f57192a136a46da6633a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Thu, 14 Mar 2019 15:19:37 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=85=81=E8=AE=B8=E5=8F=AA=E4=BC=A0key?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cloudcommon/db/standalone.go | 26 +++++++++++++++++++++----- pkg/mcclient/options/base.go | 7 +++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 09fcd2e85a..45bbfd5716 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -105,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 { @@ -116,7 +121,7 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex q = q.Filter(sqlchemy.IsFalse(q.Field("is_emulated"))) } - tags := map[string]string{} + tags := map[string]STagValue{} if query.Contains("tags") { idx := 0 for { @@ -124,7 +129,11 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex if len(key) == 0 { break } - value, _ := query.GetString("tags", fmt.Sprintf("%d", idx), "value") + 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++ } @@ -135,10 +144,17 @@ func (manager *SStandaloneResourceBaseManager) ListItemFilter(ctx context.Contex idx := 0 for k, v := range tags { if idx == 0 { - metadataView = metadataView.Equals("key", k).Equals("value", v) + metadataView = metadataView.Equals("key", k) + if v.exist { + metadataView = metadataView.Equals("value", v.value) + } } else { - subMetataView := Metadata.Query().Equals("key", k).Equals("value", v).SubQuery() - metadataView.Join(subMetataView, sqlchemy.Equals(metadataView.Field("id"), subMetataView.Field("id"))) + 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++ } diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index e52733a73c..af622c7347 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -219,13 +219,12 @@ func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) { params.Set("admin", jsonutils.JSONTrue) } } - if len(opts.TagsKey) != len(opts.TagsValue) { - return nil, fmt.Errorf("tags key length not equal value length") - } if len(opts.TagsKey) > 0 { for i := 0; i < len(opts.TagsKey); i++ { params.Add(jsonutils.NewString(opts.TagsKey[i]), fmt.Sprintf("tags.%d.key", i)) - params.Add(jsonutils.NewString(opts.TagsValue[i]), fmt.Sprintf("tags.%d.value", i)) + if len(opts.TagsValue) > i { + params.Add(jsonutils.NewString(opts.TagsValue[i]), fmt.Sprintf("tags.%d.value", i)) + } } } return params, nil From ee62f5d1537261e7cddfd301f7994bab607ee569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 15 Mar 2019 14:03:40 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E9=99=90=E5=88=B6key=E5=BF=85=E9=A1=BB?= =?UTF-8?q?=E6=98=AF=E4=B8=AD=E8=8B=B1=E6=96=87=E3=80=81=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E6=88=96=E8=80=85=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cloudcommon/db/standalone.go | 7 +++++-- pkg/mcclient/options/base.go | 25 ++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 45bbfd5716..88702015c3 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "strings" + "unicode" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -279,8 +280,10 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC } func (model *SStandaloneResourceBase) ValidateMetadataKey(key string) error { - if strings.HasPrefix(key, CLOUD_TAG_PREFIX) || strings.ContainsAny(key, `:=#&?$/\`) { - return httperrors.NewInputParameterError(`key cannot start with %s and not contain :=#&?$/\`, CLOUD_TAG_PREFIX) + for _, k := range []rune(key) { + if k != rune('_') && !unicode.IsLetter(k) && !unicode.IsDigit(k) { + return httperrors.NewInputParameterError(`Not support tag key with %s`, string(k)) + } } return nil } diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index af622c7347..f6fdb6cabd 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -3,7 +3,9 @@ package options import ( "fmt" "reflect" + "strings" "time" + "unicode" "yunion.io/x/jsonutils" "yunion.io/x/pkg/gotypes" @@ -186,8 +188,7 @@ type BaseListOptions struct { ExportFile string `help:"Export to file" metavar:"" json:"-"` ExportKeys string `help:"Export field keys"` ExportTexts string `help:"Export field displayname texts" json:"-"` - TagsKey []string `help:"Tag key" json:"-"` - TagsValue []string `help:"Tag key" 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"` @@ -219,13 +220,23 @@ func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) { params.Set("admin", jsonutils.JSONTrue) } } - if len(opts.TagsKey) > 0 { - for i := 0; i < len(opts.TagsKey); i++ { - params.Add(jsonutils.NewString(opts.TagsKey[i]), fmt.Sprintf("tags.%d.key", i)) - if len(opts.TagsValue) > i { - params.Add(jsonutils.NewString(opts.TagsValue[i]), fmt.Sprintf("tags.%d.value", i)) + 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 } From b49623a7e590a204d436726320c2cf6ec8811b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 18 Mar 2019 11:07:49 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=85=AC=E6=9C=89?= =?UTF-8?q?=E4=BA=91key=E5=8A=A0=E4=B8=8A=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/compute/hostdrivers/managedvirtual.go | 2 +- pkg/compute/models/syncutils.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/compute/hostdrivers/managedvirtual.go b/pkg/compute/hostdrivers/managedvirtual.go index 2061231381..a7f501d75e 100644 --- a/pkg/compute/hostdrivers/managedvirtual.go +++ b/pkg/compute/hostdrivers/managedvirtual.go @@ -242,7 +242,7 @@ func (self *SManagedVirtualizationHostDriver) RequestAllocateDiskOnStorage(ctx c 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 { + if err := disk.SetMetadata(ctx, "ext:"+key, value, task.GetUserCred()); err != nil { log.Errorf("set disk %s mata %s => %s error: %v", disk.Name, key, value, err) } } diff --git a/pkg/compute/models/syncutils.go b/pkg/compute/models/syncutils.go index e2a2300e95..155fed2e73 100644 --- a/pkg/compute/models/syncutils.go +++ b/pkg/compute/models/syncutils.go @@ -20,6 +20,7 @@ func getSyncOwnerProjectId(manager db.IModelManager, userCred mcclient.TokenCred 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 { @@ -31,8 +32,9 @@ 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 } From 43daa1ceb420d8b6176e92ee7c023d51c9c8e962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 18 Mar 2019 14:27:23 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=A0=87=E7=AD=BE=E5=8A=A0=E5=89=8D=E7=BC=80user:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/servers.go | 27 +++-- pkg/cloudcommon/db/metadata.go | 125 ++++++++++++++++++---- pkg/cloudcommon/db/standalone.go | 59 ++++++++-- pkg/compute/hostdrivers/managedvirtual.go | 13 +-- pkg/compute/models/syncutils.go | 4 + pkg/mcclient/options/metadata.go | 6 +- pkg/mcclient/options/servers.go | 23 ++++ 7 files changed, 200 insertions(+), 57 deletions(-) diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index 7acdcbf424..9ad2743bb5 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -85,16 +85,25 @@ func init() { return nil }) - type ServerSetMetadataOptions struct { - ID string `help:"ID or name of server" json:"-"` - KEY string `help:"Metadata key"` - VALUE string `help:"Metadata Value"` - } + 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(&ServerSetMetadataOptions{}, "server-set-metadata", "Set metadata of a server", func(s *mcclient.ClientSession, opts *ServerSetMetadataOptions) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(opts.VALUE), opts.KEY) - result, err := modules.Servers.PerformAction(s, opts.ID, "metadata", params) + 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 } diff --git a/pkg/cloudcommon/db/metadata.go b/pkg/cloudcommon/db/metadata.go index 16f29e801f..bd9589d30e 100644 --- a/pkg/cloudcommon/db/metadata.go +++ b/pkg/cloudcommon/db/metadata.go @@ -8,6 +8,7 @@ import ( "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/util/stringutils" "yunion.io/x/sqlchemy" @@ -19,6 +20,12 @@ import ( const ( 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 { @@ -32,6 +39,7 @@ type SMetadata struct { 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 @@ -63,6 +71,23 @@ 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 } @@ -96,11 +121,10 @@ func (manager *SMetadataManager) ListItemFilter(ctx context.Context, q *sqlchemy if len(conditions) > 0 { q = q.Filter(sqlchemy.OR(conditions...)) } - if !jsonutils.QueryBoolean(query, "with_sys", false) { - q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), SYSTEM_ADMIN_PREFIX))) - } - if !jsonutils.QueryBoolean(query, "with_cloud", false) { - q = q.Filter(sqlchemy.NOT(sqlchemy.Startswith(q.Field("key"), CLOUD_TAG_PREFIX))) + 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 } @@ -158,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) @@ -195,7 +226,7 @@ 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 httperrors.NewForbiddenError("Ordinary users can't set the tags that begin with an underscore") + return nil, httperrors.NewForbiddenError("Ordinary users can't set the tags that begin with an underscore") } valStr := stringutils.Interface2String(value) @@ -204,7 +235,7 @@ func (manager *SMetadataManager) SetAll(ctx context.Context, obj IModel, store m 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}) @@ -213,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) } diff --git a/pkg/cloudcommon/db/standalone.go b/pkg/cloudcommon/db/standalone.go index 88702015c3..bca40d119d 100644 --- a/pkg/cloudcommon/db/standalone.go +++ b/pkg/cloudcommon/db/standalone.go @@ -5,7 +5,6 @@ import ( "database/sql" "fmt" "strings" - "unicode" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -227,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 { @@ -270,22 +277,52 @@ func (model *SStandaloneResourceBase) PerformMetadata(ctx context.Context, userC } dictStore := make(map[string]interface{}) for k, v := range dictMap { - if err := model.ValidateMetadataKey(k); err != nil { - return nil, err - } dictStore[k], _ = v.GetString() } err = model.SetAllMetadata(ctx, dictStore, userCred) return nil, err } -func (model *SStandaloneResourceBase) ValidateMetadataKey(key string) error { - for _, k := range []rune(key) { - if k != rune('_') && !unicode.IsLetter(k) && !unicode.IsDigit(k) { - return httperrors.NewInputParameterError(`Not support tag key with %s`, string(k)) - } +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") } - return nil + 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 { diff --git a/pkg/compute/hostdrivers/managedvirtual.go b/pkg/compute/hostdrivers/managedvirtual.go index a7f501d75e..6cad743d4f 100644 --- a/pkg/compute/hostdrivers/managedvirtual.go +++ b/pkg/compute/hostdrivers/managedvirtual.go @@ -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, "ext:"+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") diff --git a/pkg/compute/models/syncutils.go b/pkg/compute/models/syncutils.go index 155fed2e73..04fba8db97 100644 --- a/pkg/compute/models/syncutils.go +++ b/pkg/compute/models/syncutils.go @@ -38,3 +38,7 @@ func syncMetadata(ctx context.Context, userCred mcclient.TokenCredential, model } return nil } + +func SyncMetadata(ctx context.Context, userCred mcclient.TokenCredential, model IMetadataSetter, remote cloudprovider.ICloudResource) error { + return syncMetadata(ctx, userCred, model, remote) +} diff --git a/pkg/mcclient/options/metadata.go b/pkg/mcclient/options/metadata.go index 24d0f70cf6..1116dbb0f3 100644 --- a/pkg/mcclient/options/metadata.go +++ b/pkg/mcclient/options/metadata.go @@ -2,6 +2,8 @@ package options type MetadataListOptions struct { Resources []string `help:"list of resource e.g server、disk、eip、snapshot, empty will show all metadata"` - WithSys *bool `help:"With sys metadata"` - WithCloud *bool `help:"With cloud 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"` } diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 362fcf9990..afebec0138 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -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 +}