images.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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 image
  15. import (
  16. "fmt"
  17. "io"
  18. "os"
  19. "strings"
  20. "github.com/cheggaaa/pb/v3"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/util/printutils"
  24. "yunion.io/x/onecloud/cmd/climc/shell"
  25. imageapi "yunion.io/x/onecloud/pkg/apis/image"
  26. "yunion.io/x/onecloud/pkg/mcclient"
  27. "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  28. modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
  29. "yunion.io/x/onecloud/pkg/mcclient/options"
  30. "yunion.io/x/onecloud/pkg/mcclient/options/glance"
  31. )
  32. type ImageOptionalOptions struct {
  33. Format string `help:"Image format" choices:"raw|qcow2|iso|vmdk|docker|vhd|tgz"`
  34. Protected bool `help:"Prevent image from being deleted"`
  35. Unprotected bool `help:"Allow image to be deleted"`
  36. Standard bool `help:"Mark image as a standard image"`
  37. Nonstandard bool `help:"Mark image as a non-standard image"`
  38. Public bool `help:"make image public"`
  39. MinDisk int64 `help:"Disk size after expanded, in MB" metavar:"MIN_DISK_SIZE_MB"`
  40. MinRam int64 `help:"Minimal memory size required" metavar:"MIN_RAM_MB"`
  41. VirtualSize int64 `help:"Disk size after expanded, in MB"`
  42. Size int64 `help:"Disk size, in MB"`
  43. Location string `help:"Image location"`
  44. Status string `help:"Image status" choices:"killed|active|queued"`
  45. OwnerProject string `help:"Owner project Id or Name"`
  46. OwnerProjectDomain string `help:"Owner project Domain"`
  47. OsType string `help:"Type of OS" choices:"Windows|Linux|Freebsd|Android|macOS|VMWare"`
  48. OsDist string `help:"Distribution name of OS" metavar:"OS_DISTRIBUTION"`
  49. OsVersion string `help:"Version of OS"`
  50. OsCodename string `help:"Codename of OS"`
  51. OsArch string `help:"Os hardware architecture" choices:"x86|x86_64|aarch32|aarch64|riscv32|riscv64"`
  52. OsLang string `help:"OS Language" choices:"zh_CN|en_US"`
  53. Preference int64 `help:"Disk preferences"`
  54. Notes string `help:"Notes about the image"`
  55. Hypervisor []string `help:"Prefer hypervisor type" choices:"kvm|esxi|baremetal|container|openstack|ctyun"`
  56. DiskDriver string `help:"Perfer disk driver" choices:"virtio|scsi|pvscsi|ide|sata"`
  57. NetDriver string `help:"Preferred network driver" choices:"virtio|e1000|vmxnet3"`
  58. DisableUsbKbd bool `help:"Disable usb keyboard on this image(for hypervisor kvm)"`
  59. BootMode string `help:"UEFI support" choices:"UEFI|BIOS"`
  60. VdiProtocol string `help:"VDI protocol" choices:"vnc|spice"`
  61. // for container usage
  62. UsedByPostOverlay bool `help:"Used by container post-overlay"`
  63. InternalPathMap []string `help:"Internal path map, e.g. 'com.taobao.taobao/10.42.12/data/data/com.taobao.taobao:/data/data/com.taobao.taobao'"`
  64. }
  65. func addImageOptionalOptions(s *mcclient.ClientSession, params *jsonutils.JSONDict, args ImageOptionalOptions) error {
  66. if len(args.Format) > 0 {
  67. params.Add(jsonutils.NewString(args.Format), "disk-format")
  68. }
  69. if args.Protected && !args.Unprotected {
  70. params.Add(jsonutils.NewString("true"), "protected")
  71. } else if !args.Protected && args.Unprotected {
  72. params.Add(jsonutils.NewString("false"), "protected")
  73. }
  74. if args.Standard && !args.Nonstandard {
  75. params.Add(jsonutils.JSONTrue, "is_standard")
  76. } else if !args.Standard && args.Nonstandard {
  77. params.Add(jsonutils.JSONFalse, "is_standard")
  78. }
  79. if args.Public {
  80. params.Add(jsonutils.JSONTrue, "is_public")
  81. }
  82. if args.MinDisk > 0 {
  83. params.Add(jsonutils.NewString(fmt.Sprintf("%d", args.MinDisk)), "min_disk")
  84. }
  85. if args.MinRam > 0 {
  86. params.Add(jsonutils.NewString(fmt.Sprintf("%d", args.MinRam)), "min_ram")
  87. }
  88. if args.Size > 0 {
  89. params.Add(jsonutils.NewString(fmt.Sprintf("%d", args.Size*1024*1024)), "size")
  90. }
  91. if args.VirtualSize > 0 {
  92. params.Add(jsonutils.NewString(fmt.Sprintf("%d", args.VirtualSize)), "min_disk")
  93. }
  94. if len(args.Location) > 0 {
  95. params.Add(jsonutils.NewString(args.Location), "location")
  96. }
  97. if len(args.Status) > 0 {
  98. params.Add(jsonutils.NewString(args.Status), "status")
  99. }
  100. if len(args.OwnerProject) > 0 {
  101. projectId, e := identity.Projects.GetId(s, args.OwnerProject, nil)
  102. if e != nil {
  103. return e
  104. }
  105. params.Add(jsonutils.NewString(projectId), "owner")
  106. }
  107. keyProperties := "properties"
  108. if len(args.OsType) > 0 {
  109. params.Add(jsonutils.NewString(args.OsType), keyProperties, "os_type")
  110. }
  111. if len(args.OsDist) > 0 {
  112. params.Add(jsonutils.NewString(args.OsDist), keyProperties, "os_distribution")
  113. }
  114. if len(args.OsVersion) > 0 {
  115. params.Add(jsonutils.NewString(args.OsVersion), keyProperties, "os_version")
  116. }
  117. if len(args.OsCodename) > 0 {
  118. params.Add(jsonutils.NewString(args.OsCodename), keyProperties, "os_codename")
  119. }
  120. if len(args.OsArch) > 0 {
  121. params.Add(jsonutils.NewString(args.OsArch), keyProperties, "os_arch")
  122. params.Add(jsonutils.NewString(args.OsArch), "os_arch")
  123. }
  124. if len(args.OsLang) > 0 {
  125. params.Add(jsonutils.NewString(args.OsLang), keyProperties, "os_language")
  126. }
  127. if args.Preference > 0 {
  128. params.Add(jsonutils.NewString(fmt.Sprintf("%d", args.Preference)), "properties", "preference")
  129. }
  130. if len(args.Notes) > 0 {
  131. params.Add(jsonutils.NewString(args.Notes), keyProperties, "notes")
  132. }
  133. if len(args.DiskDriver) > 0 {
  134. params.Add(jsonutils.NewString(args.DiskDriver), keyProperties, "disk_driver")
  135. }
  136. if len(args.NetDriver) > 0 {
  137. params.Add(jsonutils.NewString(args.NetDriver), keyProperties, "net_driver")
  138. }
  139. if len(args.Hypervisor) > 0 {
  140. params.Add(jsonutils.NewString(strings.Join(args.Hypervisor, ",")), "properties", "hypervisor")
  141. }
  142. if args.DisableUsbKbd {
  143. params.Add(jsonutils.NewString("true"), keyProperties, "disable_usb_kbd")
  144. }
  145. if args.BootMode == "UEFI" {
  146. params.Add(jsonutils.JSONTrue, keyProperties, "uefi_support")
  147. } else if args.BootMode == "BIOS" {
  148. params.Add(jsonutils.JSONFalse, keyProperties, "uefi_support")
  149. }
  150. if len(args.VdiProtocol) > 0 {
  151. params.Add(jsonutils.NewString(args.VdiProtocol), keyProperties, "vdi_protocol")
  152. }
  153. if args.UsedByPostOverlay {
  154. params.Add(jsonutils.NewString("true"), keyProperties, imageapi.IMAGE_USED_BY_POST_OVERLAY)
  155. }
  156. if len(args.InternalPathMap) > 0 {
  157. dirMap := make(map[string]string)
  158. for _, dir := range args.InternalPathMap {
  159. parts := strings.Split(dir, ":")
  160. if len(parts) != 2 {
  161. return errors.Errorf("internal dir format error: %s", dir)
  162. }
  163. dirMap[parts[0]] = parts[1]
  164. }
  165. params.Add(jsonutils.NewString(jsonutils.Marshal(dirMap).String()), keyProperties, imageapi.IMAGE_INTERNAL_PATH_MAP)
  166. }
  167. return nil
  168. }
  169. func init() {
  170. cmd := shell.NewResourceCmd(&modules.Images)
  171. cmd.List(&glance.ImageListOptions{})
  172. cmd.GetProperty(&glance.ImageStatusStatisticsOptions{})
  173. cmd.Perform("user-metadata", &options.ResourceMetadataOptions{})
  174. cmd.Perform("set-user-metadata", &options.ResourceMetadataOptions{})
  175. cmd.Perform("class-metadata", &options.ResourceMetadataOptions{})
  176. cmd.Perform("set-class-metadata", &options.ResourceMetadataOptions{})
  177. type ImageOperationOptions struct {
  178. ID []string `help:"Image id or name" metavar:"IMAGE"`
  179. }
  180. type ImageShowOptions struct {
  181. ID []string `help:"Image id or name" metavar:"IMAGE"`
  182. Format string `help:"Image format"`
  183. Torrent bool `help:"show torrent information"`
  184. }
  185. R(&ImageShowOptions{}, "image-show", "Show details of a image", func(s *mcclient.ClientSession, args *ImageShowOptions) error {
  186. params := jsonutils.NewDict()
  187. if len(args.Format) > 0 {
  188. params.Add(jsonutils.NewString(args.Format), "format")
  189. if args.Torrent {
  190. params.Add(jsonutils.JSONTrue, "torrent")
  191. }
  192. }
  193. if len(args.ID) == 0 {
  194. return fmt.Errorf("No image ID provided")
  195. } else if len(args.ID) == 1 {
  196. result, e := modules.Images.Get(s, args.ID[0], params)
  197. if e != nil {
  198. return e
  199. }
  200. printObject(result)
  201. } else {
  202. sr := modules.Images.BatchGet(s, args.ID, params)
  203. printBatchResults(sr, modules.Images.GetColumns(s))
  204. }
  205. return nil
  206. })
  207. type ImageUpdateOptions struct {
  208. ID []string `help:"ID or Name of Image"`
  209. Name string `help:"New name of the image"`
  210. Description string `help:"Description of image"`
  211. ImageOptionalOptions
  212. }
  213. R(&ImageUpdateOptions{}, "image-update", "Update images meta infomation", func(s *mcclient.ClientSession, args *ImageUpdateOptions) error {
  214. params := jsonutils.NewDict()
  215. if len(args.Name) > 0 {
  216. params.Add(jsonutils.NewString(args.Name), "name")
  217. }
  218. if len(args.Description) > 0 {
  219. params.Add(jsonutils.NewString(args.Description), "description")
  220. }
  221. err := addImageOptionalOptions(s, params, args.ImageOptionalOptions)
  222. if err != nil {
  223. return err
  224. }
  225. result := modules.Images.BatchUpdate(s, args.ID, params)
  226. printBatchResults(result, modules.Images.GetColumns(s))
  227. return nil
  228. })
  229. type ImageDetailOptions struct {
  230. ID string `help:"Image ID or name"`
  231. }
  232. type ImageDeleteOptions struct {
  233. ID []string `help:"Image ID or name" json:"-"`
  234. OverridePendingDelete *bool `help:"Delete image directly instead of pending delete" short-token:"f"`
  235. }
  236. R(&ImageDeleteOptions{}, "image-delete", "Delete a image", func(s *mcclient.ClientSession, args *ImageDeleteOptions) error {
  237. params, err := options.StructToParams(args)
  238. if err != nil {
  239. return err
  240. }
  241. ret := modules.Images.BatchDeleteWithParam(s, args.ID, params, nil)
  242. printBatchResults(ret, modules.Images.GetColumns(s))
  243. return nil
  244. })
  245. type ImageCancelDeleteOptions struct {
  246. ID string `help:"Image id or name" metavar:"IMAGE"`
  247. }
  248. R(&ImageCancelDeleteOptions{}, "image-cancel-delete", "Cancel pending delete images", func(s *mcclient.ClientSession, args *ImageCancelDeleteOptions) error {
  249. if image, e := modules.Images.PerformAction(s, args.ID, "cancel-delete", nil); e != nil {
  250. return e
  251. } else {
  252. printObject(image)
  253. }
  254. return nil
  255. })
  256. type ImageUploadOptions struct {
  257. NAME string `help:"Image Name"`
  258. FILE string `help:"The local image filename to Upload"`
  259. EncryptKey string `help:"encrypt key id"`
  260. ImageOptionalOptions
  261. }
  262. R(&ImageUploadOptions{}, "image-upload", "Upload a local image", func(s *mcclient.ClientSession, args *ImageUploadOptions) error {
  263. params := jsonutils.NewDict()
  264. params.Add(jsonutils.NewString(args.NAME), "name")
  265. if len(args.EncryptKey) > 0 {
  266. params.Add(jsonutils.NewString(args.EncryptKey), "encrypt_key_id")
  267. }
  268. err := addImageOptionalOptions(s, params, args.ImageOptionalOptions)
  269. if err != nil {
  270. return err
  271. }
  272. f, err := os.Open(args.FILE)
  273. if err != nil {
  274. return err
  275. }
  276. defer f.Close()
  277. finfo, err := f.Stat()
  278. if err != nil {
  279. return err
  280. }
  281. size := finfo.Size()
  282. bar := pb.Full.Start64(size)
  283. barReader := bar.NewProxyReader(f)
  284. img, err := modules.Images.Upload(s, params, barReader, size)
  285. if err != nil {
  286. return err
  287. }
  288. printObject(img)
  289. return nil
  290. })
  291. type ImageImportOptions struct {
  292. ImageOptionalOptions
  293. NAME string `help:"Image Name"`
  294. COPYFROM string `help:"Image external location url"`
  295. CompressFormat string
  296. EncryptKey string `help:"encrypt key id"`
  297. }
  298. R(&ImageImportOptions{}, "image-import", "Import a external image", func(s *mcclient.ClientSession, args *ImageImportOptions) error {
  299. params := jsonutils.NewDict()
  300. params.Add(jsonutils.NewString(args.NAME), "name")
  301. if len(args.Format) == 0 {
  302. return fmt.Errorf("Please specify image format")
  303. }
  304. if len(args.EncryptKey) > 0 {
  305. params.Add(jsonutils.NewString(args.EncryptKey), "encrypt_key_id")
  306. }
  307. err := addImageOptionalOptions(s, params, args.ImageOptionalOptions)
  308. if err != nil {
  309. return err
  310. }
  311. params.Add(jsonutils.NewString(args.COPYFROM), "copy_from")
  312. params.Add(jsonutils.NewString(args.CompressFormat), "compress_format")
  313. img, err := modules.Images.Create(s, params)
  314. if err != nil {
  315. return err
  316. }
  317. printObject(img)
  318. return nil
  319. })
  320. type ImageDownloadOptions struct {
  321. ID string `help:"ID or Name of image"`
  322. Output string `help:"Destination file, if omitted, output to stdout"`
  323. Format string `help:"Image format"`
  324. Torrent bool `help:"show torrent information"`
  325. }
  326. R(&ImageDownloadOptions{}, "image-download", "Download image data to a file or stdout", func(s *mcclient.ClientSession, args *ImageDownloadOptions) error {
  327. imgId, err := modules.Images.GetId(s, args.ID, nil)
  328. if err != nil {
  329. return err
  330. }
  331. var sink io.Writer
  332. showProgress := false
  333. if len(args.Output) > 0 {
  334. f, err := os.Create(args.Output)
  335. if err != nil {
  336. return err
  337. }
  338. defer f.Close()
  339. sink = f
  340. showProgress = true
  341. } else {
  342. sink = os.Stdout
  343. }
  344. meta, src, size, err := modules.Images.Download(s, imgId, args.Format, args.Torrent)
  345. if err != nil {
  346. return err
  347. }
  348. if !showProgress {
  349. _, err = io.Copy(sink, src)
  350. if err != nil {
  351. return err
  352. }
  353. } else {
  354. bar := pb.Full.Start64(size)
  355. barReader := bar.NewProxyReader(src)
  356. _, err = io.Copy(sink, barReader)
  357. if err != nil {
  358. return err
  359. }
  360. }
  361. if len(args.Output) > 0 {
  362. printObject(meta)
  363. fmt.Println("Image size: ", size)
  364. }
  365. return nil
  366. })
  367. R(&ImageOperationOptions{}, "image-private", "Make a image private", func(s *mcclient.ClientSession, args *ImageOperationOptions) error {
  368. if len(args.ID) == 0 {
  369. return fmt.Errorf("No image ID provided")
  370. } else if len(args.ID) == 1 {
  371. result, err := modules.Images.PerformAction(s, args.ID[0], "private", nil)
  372. if err != nil {
  373. return err
  374. }
  375. printObject(result)
  376. } else {
  377. results := modules.Images.BatchPerformAction(s, args.ID, "private", nil)
  378. printBatchResults(results, modules.Images.GetColumns(s))
  379. }
  380. return nil
  381. })
  382. type ImagePublicOptions struct {
  383. ID []string `help:"ID or name of image" json:"-"`
  384. Scope string `help:"sharing scope" choices:"system|domain|project"`
  385. SharedProjects []string `help:"Share to projects"`
  386. SharedDomains []string `help:"Share to domains"`
  387. }
  388. R(&ImagePublicOptions{}, "image-public", "Make a image public", func(s *mcclient.ClientSession, args *ImagePublicOptions) error {
  389. params := jsonutils.Marshal(args)
  390. if len(args.ID) == 0 {
  391. return fmt.Errorf("No image ID provided")
  392. } else if len(args.ID) == 1 {
  393. result, err := modules.Images.PerformAction(s, args.ID[0], "public", params)
  394. if err != nil {
  395. return err
  396. }
  397. printObject(result)
  398. } else {
  399. results := modules.Images.BatchPerformAction(s, args.ID, "public", params)
  400. printBatchResults(results, modules.Images.GetColumns(s))
  401. }
  402. return nil
  403. })
  404. R(&ImageOperationOptions{}, "image-subformats", "Show all format status of a image", func(s *mcclient.ClientSession, args *ImageOperationOptions) error {
  405. for i := range args.ID {
  406. result, err := modules.Images.GetSpecific(s, args.ID[i], "subformats", nil)
  407. if err != nil {
  408. fmt.Println("Fail to fetch subformats for", args.ID[i])
  409. continue
  410. }
  411. arrays, _ := result.(*jsonutils.JSONArray).GetArray()
  412. listResult := printutils.ListResult{Data: arrays}
  413. printList(&listResult, []string{})
  414. }
  415. return nil
  416. })
  417. R(&ImageOperationOptions{}, "image-mark-standard", "Mark image standard", func(s *mcclient.ClientSession, args *ImageOperationOptions) error {
  418. params := jsonutils.NewDict()
  419. params.Add(jsonutils.JSONTrue, "is_standard")
  420. results := modules.Images.BatchPerformAction(s, args.ID, "mark-standard", params)
  421. printBatchResults(results, modules.Images.GetColumns(s))
  422. return nil
  423. })
  424. R(&ImageOperationOptions{}, "image-mark-unstandard", "Mark image not standard", func(s *mcclient.ClientSession, args *ImageOperationOptions) error {
  425. params := jsonutils.NewDict()
  426. params.Add(jsonutils.JSONFalse, "is_standard")
  427. results := modules.Images.BatchPerformAction(s, args.ID, "mark-standard", params)
  428. printBatchResults(results, modules.Images.GetColumns(s))
  429. return nil
  430. })
  431. type ImageChangeOwnerOptions struct {
  432. ID string `help:"Image to change owner"`
  433. PROJECT string `help:"Project ID or change"`
  434. RawId bool `help:"User raw ID, instead of name"`
  435. }
  436. R(&ImageChangeOwnerOptions{}, "image-change-owner", "Change owner project of an image", func(s *mcclient.ClientSession, opts *ImageChangeOwnerOptions) error {
  437. params := jsonutils.NewDict()
  438. if opts.RawId {
  439. projid, err := identity.Projects.GetId(s, opts.PROJECT, nil)
  440. if err != nil {
  441. return err
  442. }
  443. params.Add(jsonutils.NewString(projid), "tenant")
  444. params.Add(jsonutils.JSONTrue, "raw_id")
  445. } else {
  446. params.Add(jsonutils.NewString(opts.PROJECT), "tenant")
  447. }
  448. srv, err := modules.Images.PerformAction(s, opts.ID, "change-owner", params)
  449. if err != nil {
  450. return err
  451. }
  452. printObject(srv)
  453. return nil
  454. })
  455. type ImageProbeOptions struct {
  456. ID string `help:"ID or name of image to probe"`
  457. }
  458. R(&ImageProbeOptions{}, "image-probe", "Start image probe task", func(s *mcclient.ClientSession, opts *ImageProbeOptions) error {
  459. img, err := modules.Images.PerformAction(s, opts.ID, "probe", nil)
  460. if err != nil {
  461. return err
  462. }
  463. printObject(img)
  464. return nil
  465. })
  466. }