parameters.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "fmt"
  18. "yunion.io/x/jsonutils"
  19. "yunion.io/x/log"
  20. "yunion.io/x/pkg/errors"
  21. "yunion.io/x/pkg/util/rbacscope"
  22. "yunion.io/x/pkg/util/timeutils"
  23. "yunion.io/x/sqlchemy"
  24. api "yunion.io/x/onecloud/pkg/apis/yunionconf"
  25. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  28. "yunion.io/x/onecloud/pkg/cloudcommon/policy"
  29. "yunion.io/x/onecloud/pkg/httperrors"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. "yunion.io/x/onecloud/pkg/mcclient/auth"
  32. modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  33. "yunion.io/x/onecloud/pkg/util/logclient"
  34. "yunion.io/x/onecloud/pkg/yunionconf/options"
  35. )
  36. const (
  37. NAMESPACE_USER = api.NAMESPACE_USER
  38. NAMESPACE_SERVICE = api.NAMESPACE_SERVICE
  39. NAMESPACE_BUG_REPORT = api.NAMESPACE_BUG_REPORT
  40. )
  41. type SParameterManager struct {
  42. db.SResourceBaseManager
  43. }
  44. type SParameter struct {
  45. db.SResourceBase
  46. Id int64 `primary:"true" auto_increment:"true" list:"user"` // = Column(BigInteger, primary_key=True)
  47. CreatedBy string `width:"128" charset:"ascii" nullable:"false" create:"required" list:"user"` // Column(VARCHAR(length=128, charset='ascii'), nullable=False)
  48. UpdatedBy string `width:"128" charset:"ascii" nullable:"false" create:"required" update:"user" list:"user"` // Column(VARCHAR(length=128, charset='ascii'), nullable=False) "user"/ serviceName/ "admin"
  49. Namespace string `width:"64" charset:"ascii" default:"user" nullable:"false" create:"required" list:"admin"` // Column(VARCHAR(length=128, charset='ascii'), nullable=False) user_id / serviceid
  50. NamespaceId string `width:"128" charset:"ascii" nullable:"false" index:"true" create:"required" list:"admin"` // Column(VARCHAR(length=128, charset='ascii'), nullable=False)
  51. Name string `width:"128" charset:"ascii" nullable:"false" index:"true" create:"required" list:"user"` // Column(VARCHAR(length=128, charset='ascii'), nullable=false)
  52. Value jsonutils.JSONObject `charset:"utf8" create:"required" update:"user" update:"user" list:"user"` // Column(VARCHAR(charset='utf-8'))
  53. }
  54. var ParameterManager *SParameterManager
  55. func init() {
  56. ParameterManager = &SParameterManager{
  57. SResourceBaseManager: db.NewResourceBaseManager(
  58. SParameter{},
  59. "paramters_tbl",
  60. "parameter",
  61. "parameters",
  62. ),
  63. }
  64. ParameterManager.SetVirtualObject(ParameterManager)
  65. }
  66. func isAdminQuery(query jsonutils.JSONObject) bool {
  67. admin_fields := [3]string{"namespace_id", "user_id", "service_id"}
  68. for _, field := range admin_fields {
  69. if s, _ := query.GetString(field); len(s) > 0 {
  70. return true
  71. }
  72. }
  73. return false
  74. }
  75. func getUserId(ctx context.Context, user string) (string, error) {
  76. s := auth.GetAdminSession(ctx, options.Options.Region)
  77. userObj, err := modules.UsersV3.Get(s, user, nil)
  78. if err != nil {
  79. return "", err
  80. }
  81. uid, err := userObj.GetString("id")
  82. if err != nil {
  83. return "", err
  84. }
  85. return uid, nil
  86. }
  87. func getServiceId(ctx context.Context, service string) (string, error) {
  88. s := auth.GetAdminSession(ctx, options.Options.Region)
  89. serviceObj, err := modules.ServicesV3.Get(s, service, nil)
  90. if err != nil {
  91. return "", err
  92. }
  93. uid, err := serviceObj.GetString("id")
  94. if err != nil {
  95. return "", err
  96. }
  97. return uid, nil
  98. }
  99. func getNamespaceInContext(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (namespace string, namespaceId string, err error) {
  100. // 优先匹配上线文中的参数, /users/<user_id>/parameters /services/<service_id>/parameters
  101. if query != nil {
  102. if uid := jsonutils.GetAnyString(query, []string{"user", "user_id"}); len(uid) > 0 {
  103. uid, err := getUserId(ctx, uid)
  104. if err != nil {
  105. return "", "", err
  106. }
  107. return NAMESPACE_USER, uid, nil
  108. } else if sid := jsonutils.GetAnyString(query, []string{"service", "service_id"}); len(sid) > 0 {
  109. sid, err := getServiceId(ctx, sid)
  110. if err != nil {
  111. return "", "", err
  112. }
  113. return NAMESPACE_SERVICE, sid, nil
  114. }
  115. }
  116. // 匹配/parameters中的参数
  117. if uid := jsonutils.GetAnyString(data, []string{"user", "user_id"}); len(uid) > 0 {
  118. uid, err := getUserId(ctx, uid)
  119. if err != nil {
  120. return "", "", err
  121. }
  122. return NAMESPACE_USER, uid, nil
  123. } else if sid := jsonutils.GetAnyString(data, []string{"service", "service_id"}); len(sid) > 0 {
  124. sid, err := getServiceId(ctx, sid)
  125. if err != nil {
  126. return "", "", err
  127. }
  128. return NAMESPACE_SERVICE, sid, nil
  129. } else {
  130. return NAMESPACE_USER, userCred.GetUserId(), nil
  131. }
  132. }
  133. func getNamespace(ctx context.Context, userCred mcclient.TokenCredential, resource string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (string, string, error) {
  134. var namespace, namespace_id string
  135. if policy.PolicyManager.Allow(rbacscope.ScopeSystem, userCred, consts.GetServiceType(), resource, policy.PolicyActionList).Result.IsAllow() {
  136. if name, nameId, e := getNamespaceInContext(ctx, userCred, query, data); e != nil {
  137. return "", "", e
  138. } else {
  139. namespace = name
  140. namespace_id = nameId
  141. }
  142. } else {
  143. namespace = NAMESPACE_USER
  144. namespace_id = userCred.GetUserId()
  145. }
  146. return namespace, namespace_id, nil
  147. }
  148. func (manager *SParameterManager) CreateByInsertOrUpdate() bool {
  149. return false
  150. }
  151. func (manager *SParameterManager) NamespaceScope() rbacscope.TRbacScope {
  152. return rbacscope.ScopeUser
  153. }
  154. func (manager *SParameterManager) ResourceScope() rbacscope.TRbacScope {
  155. return rbacscope.ScopeUser
  156. }
  157. func (manager *SParameterManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  158. // check duplication
  159. name, _ := data.GetString("name")
  160. uid := userCred.GetUserId()
  161. if len(uid) == 0 {
  162. return nil, httperrors.NewUserNotFoundError("user not found")
  163. }
  164. namespace, namespace_id, e := getNamespace(ctx, userCred, manager.KeywordPlural(), query, data)
  165. if e != nil {
  166. return nil, e
  167. }
  168. // check duplication, 同一个namespace下,name不能 重复
  169. q := manager.Query().Equals("name", name).Equals("namespace_id", namespace_id)
  170. cnt, err := q.CountWithError()
  171. if err != nil {
  172. return nil, httperrors.NewInternalServerError("check name duplication fail %s", err)
  173. }
  174. if cnt > 0 {
  175. return nil, httperrors.NewDuplicateNameError("paramter %s has been created", name)
  176. }
  177. _, err = data.Get("value")
  178. if err != nil {
  179. return nil, err
  180. }
  181. data.Add(jsonutils.NewString(uid), "created_by")
  182. data.Add(jsonutils.NewString(uid), "updated_by")
  183. data.Add(jsonutils.NewString(namespace), "namespace")
  184. data.Add(jsonutils.NewString(namespace_id), "namespace_id")
  185. return data, nil
  186. }
  187. func (manager *SParameterManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  188. if owner != nil {
  189. switch scope {
  190. case rbacscope.ScopeUser:
  191. if len(owner.GetUserId()) > 0 {
  192. q = q.Equals("namespace_id", owner.GetUserId()).Equals("namespace", NAMESPACE_USER)
  193. }
  194. }
  195. }
  196. return q
  197. }
  198. func (manager *SParameterManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery {
  199. return q.Equals("id", idStr)
  200. }
  201. func (manager *SParameterManager) FilterByName(q *sqlchemy.SQuery, name string) *sqlchemy.SQuery {
  202. return q.Equals("name", name)
  203. }
  204. // 配置参数列表
  205. func (manager *SParameterManager) ListItemFilter(
  206. ctx context.Context,
  207. q *sqlchemy.SQuery,
  208. userCred mcclient.TokenCredential,
  209. query api.ParameterListInput,
  210. ) (*sqlchemy.SQuery, error) {
  211. var err error
  212. q, err = manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ResourceBaseListInput)
  213. if err != nil {
  214. return nil, errors.Wrap(err, "SResourceBaseManager.ListItemFilter")
  215. }
  216. if len(query.Name) > 0 {
  217. q = q.In("name", query.Name)
  218. }
  219. if db.IsAdminAllowList(userCred, manager).Result.IsAllow() {
  220. if id := query.NamespaceId; len(id) > 0 {
  221. q = q.Equals("namespace_id", id)
  222. } else if id := query.ServiceId; len(id) > 0 {
  223. if sid, err := getServiceId(ctx, id); err != nil {
  224. return q, err
  225. } else {
  226. q = q.Equals("namespace_id", sid).Equals("namespace", NAMESPACE_SERVICE)
  227. }
  228. } else if id := query.UserId; len(id) > 0 {
  229. if uid, err := getUserId(ctx, id); err != nil {
  230. return q, err
  231. } else {
  232. q = q.Equals("namespace_id", uid).Equals("namespace", NAMESPACE_USER)
  233. }
  234. }
  235. /*else {
  236. // not admin
  237. admin, _ := query.GetString("admin")
  238. if !utils.ToBool(admin) {
  239. q = q.Equals("namespace_id", userCred.GetUserId()).Equals("namespace", NAMESPACE_USER)
  240. }
  241. } */
  242. }
  243. return q, nil
  244. }
  245. func (manager *SParameterManager) OrderByExtraFields(
  246. ctx context.Context,
  247. q *sqlchemy.SQuery,
  248. userCred mcclient.TokenCredential,
  249. query api.ParameterListInput,
  250. ) (*sqlchemy.SQuery, error) {
  251. var err error
  252. q, err = manager.SResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ResourceBaseListInput)
  253. if err != nil {
  254. return nil, errors.Wrap(err, "SResourceBaseManager.OrderByExtraFielda")
  255. }
  256. return q, nil
  257. }
  258. func (manager *SParameterManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  259. var err error
  260. q, err = manager.SResourceBaseManager.QueryDistinctExtraField(q, field)
  261. if err == nil {
  262. return q, nil
  263. }
  264. return q, httperrors.ErrNotFound
  265. }
  266. func (model *SParameter) IsOwner(userCred mcclient.TokenCredential) bool {
  267. return model.CreatedBy == userCred.GetUserId() || (model.NamespaceId == userCred.GetUserId() && model.Namespace == NAMESPACE_USER)
  268. }
  269. func (model *SParameter) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
  270. uid := userCred.GetUserId()
  271. if len(uid) == 0 {
  272. return nil, httperrors.NewUserNotFoundError("user not found")
  273. }
  274. namespace, namespace_id, e := getNamespace(ctx, userCred, model.KeywordPlural(), query, data)
  275. if e != nil {
  276. return nil, e
  277. }
  278. _, err := data.Get("value")
  279. if err != nil {
  280. return nil, err
  281. }
  282. data.Add(jsonutils.NewString(uid), "updated_by")
  283. data.Add(jsonutils.NewString(namespace_id), "namespace_id")
  284. data.Add(jsonutils.NewString(namespace), "namespace")
  285. return data, nil
  286. }
  287. func (model *SParameter) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  288. return model.Delete(ctx, userCred)
  289. }
  290. func (model *SParameter) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  291. _, err := db.Update(model, func() error {
  292. model.Deleted = true
  293. model.DeletedAt = timeutils.UtcNow()
  294. return nil
  295. })
  296. if err != nil {
  297. log.Errorf("PendingDelete fail %s", err)
  298. }
  299. return err
  300. }
  301. func (model *SParameter) GetOwnerId() mcclient.IIdentityProvider {
  302. if model.Namespace == NAMESPACE_SERVICE {
  303. return nil
  304. }
  305. owner := db.SOwnerId{UserId: model.NamespaceId}
  306. return &owner
  307. }
  308. func (manager *SParameterManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
  309. return db.FetchUserInfo(ctx, data)
  310. }
  311. func (model *SParameter) GetId() string {
  312. return fmt.Sprintf("%d", model.Id)
  313. }
  314. func (model *SParameter) GetName() string {
  315. return model.Name
  316. }
  317. var bugReportEnable *bool = nil
  318. func (manager *SParameterManager) GetBugReportEnabled() bool {
  319. if bugReportEnable != nil {
  320. return *bugReportEnable
  321. }
  322. enabled := manager.Query().Equals("namespace", NAMESPACE_BUG_REPORT).Count() > 0
  323. bugReportEnable = &enabled
  324. return enabled
  325. }
  326. func (manager *SParameterManager) EnableBugReport(ctx context.Context) bool {
  327. if manager.GetBugReportEnabled() {
  328. return true
  329. }
  330. res := &SParameter{
  331. Namespace: NAMESPACE_BUG_REPORT,
  332. NamespaceId: NAMESPACE_BUG_REPORT,
  333. Name: NAMESPACE_BUG_REPORT,
  334. Value: jsonutils.NewDict(),
  335. CreatedBy: api.SERVICE_TYPE,
  336. }
  337. res.SetModelManager(manager, res)
  338. err := manager.TableSpec().Insert(ctx, res)
  339. if err != nil {
  340. return false
  341. }
  342. enabled := true
  343. bugReportEnable = &enabled
  344. return true
  345. }
  346. func (manager *SParameterManager) DisableBugReport(ctx context.Context) error {
  347. if !manager.GetBugReportEnabled() {
  348. return nil
  349. }
  350. _, err := sqlchemy.GetDB().Exec(
  351. fmt.Sprintf(
  352. "delete from %s where namespace = ?",
  353. manager.TableSpec().Name(),
  354. ), NAMESPACE_BUG_REPORT,
  355. )
  356. bugReportEnable = nil
  357. return err
  358. }
  359. func (manager *SParameterManager) FetchParameters(nsType string, nsId string, name string) ([]SParameter, error) {
  360. q := manager.Query()
  361. q = q.Equals("namespace", nsType)
  362. q = q.Equals("namespace_id", nsId)
  363. if len(name) > 0 {
  364. q = q.Equals("name", name)
  365. }
  366. params := make([]SParameter, 0)
  367. err := db.FetchModelObjects(manager, q, &params)
  368. if err != nil {
  369. return nil, errors.Wrap(err, "db.FetchModelObjects")
  370. }
  371. return params, nil
  372. }
  373. func (parameter *SParameter) GetShortDesc(ctx context.Context) *jsonutils.JSONDict {
  374. return jsonutils.Marshal(struct {
  375. Id int64
  376. Name string
  377. Namespace string
  378. NamespaceId string
  379. Value jsonutils.JSONObject
  380. }{
  381. Id: parameter.Id,
  382. Name: parameter.Name,
  383. Namespace: parameter.Namespace,
  384. NamespaceId: parameter.NamespaceId,
  385. Value: parameter.Value,
  386. }).(*jsonutils.JSONDict)
  387. }
  388. func (parameter *SParameter) PerformClone(
  389. ctx context.Context,
  390. userCred mcclient.TokenCredential,
  391. query jsonutils.JSONObject,
  392. input *api.ParameterCloneInput,
  393. ) (jsonutils.JSONObject, error) {
  394. if len(input.DestName) == 0 {
  395. input.DestName = parameter.Name
  396. }
  397. var nsType string
  398. var nsId string
  399. switch input.DestNs {
  400. case "user", "users":
  401. uid, err := getUserId(ctx, input.DestNsId)
  402. if err != nil {
  403. return nil, errors.Wrapf(err, "getDestUserId %s", input.DestNsId)
  404. }
  405. nsType = NAMESPACE_USER
  406. nsId = uid
  407. case "service", "services":
  408. sid, err := getServiceId(ctx, input.DestNsId)
  409. if err != nil {
  410. return nil, errors.Wrapf(err, "getDestServiceId %s", input.DestNsId)
  411. }
  412. nsType = NAMESPACE_SERVICE
  413. nsId = sid
  414. default:
  415. return nil, errors.Wrapf(errors.ErrNotSupported, "unsupported namespace %s/%s", input.DestNs, input.DestNsId)
  416. }
  417. lockman.LockClass(ctx, ParameterManager, nsId)
  418. defer lockman.ReleaseClass(ctx, ParameterManager, nsId)
  419. destParams, err := ParameterManager.FetchParameters(nsType, nsId, input.DestName)
  420. if err != nil {
  421. return nil, errors.Wrap(err, "FetchParameters")
  422. }
  423. switch len(destParams) {
  424. case 0:
  425. // create it
  426. if !policy.PolicyManager.Allow(rbacscope.ScopeSystem, userCred, consts.GetServiceType(), ParameterManager.KeywordPlural(), policy.PolicyActionCreate).Result.IsAllow() {
  427. return nil, httperrors.ErrNotSufficientPrivilege
  428. }
  429. newParam := SParameter{}
  430. newParam.SetModelManager(ParameterManager, &newParam)
  431. newParam.Namespace = nsType
  432. newParam.NamespaceId = nsId
  433. newParam.Name = input.DestName
  434. newParam.Value = parameter.Value
  435. newParam.CreatedBy = userCred.GetUserId()
  436. newParam.UpdatedBy = userCred.GetUserId()
  437. err := ParameterManager.TableSpec().Insert(ctx, &newParam)
  438. if err != nil {
  439. return nil, errors.Wrap(err, "Insert")
  440. }
  441. logclient.AddActionLogWithContext(ctx, &newParam, logclient.ACT_CREATE, newParam.GetShortDesc(ctx), userCred, true)
  442. return jsonutils.Marshal(&newParam), nil
  443. case 1:
  444. // update it
  445. if !policy.PolicyManager.Allow(rbacscope.ScopeSystem, userCred, consts.GetServiceType(), ParameterManager.KeywordPlural(), policy.PolicyActionUpdate).Result.IsAllow() {
  446. return nil, httperrors.ErrNotSufficientPrivilege
  447. }
  448. destParam := destParams[0]
  449. lockman.LockObject(ctx, &destParam)
  450. defer lockman.ReleaseObject(ctx, &destParam)
  451. var newValue jsonutils.JSONObject
  452. if parameter.Value != nil {
  453. switch srcVal := parameter.Value.(type) {
  454. case *jsonutils.JSONDict:
  455. if destParam.Value == nil {
  456. newValue = srcVal
  457. } else if destDict, ok := destParam.Value.(*jsonutils.JSONDict); ok {
  458. dest := jsonutils.NewDict()
  459. dest.Update(destDict)
  460. dest.Update(srcVal)
  461. newValue = dest
  462. } else {
  463. return nil, errors.Wrap(httperrors.ErrInvalidFormat, "cannot clone dictionary value to other type")
  464. }
  465. case *jsonutils.JSONArray:
  466. if destParam.Value == nil {
  467. newValue = srcVal
  468. } else if destArray, ok := destParam.Value.(*jsonutils.JSONArray); ok {
  469. dest := destArray.Copy()
  470. srcObjs, _ := srcVal.GetArray()
  471. dest.Add(srcObjs...)
  472. newValue = dest
  473. } else {
  474. return nil, errors.Wrap(httperrors.ErrInvalidFormat, "cannot clone array value to other type")
  475. }
  476. default:
  477. newValue = srcVal
  478. }
  479. } else {
  480. // null operation
  481. return nil, nil
  482. }
  483. diff, err := db.Update(&destParam, func() error {
  484. destParam.Value = newValue
  485. destParam.UpdatedBy = userCred.GetUserId()
  486. return nil
  487. })
  488. if err != nil {
  489. logclient.AddActionLogWithContext(ctx, &destParam, logclient.ACT_UPDATE, diff, userCred, false)
  490. return nil, errors.Wrap(err, "update")
  491. }
  492. logclient.AddActionLogWithContext(ctx, &destParam, logclient.ACT_UPDATE, diff, userCred, true)
  493. return jsonutils.Marshal(parameter), nil
  494. default:
  495. // error?
  496. return nil, errors.Wrapf(httperrors.ErrInternalError, "duplicate dest?")
  497. }
  498. }