image.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package docker
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "sync"
  10. "time"
  11. fw "git.buran.team/main/fairwind"
  12. archive "github.com/moby/go-archive"
  13. mobyregistry "github.com/moby/moby/api/types/registry"
  14. moby "github.com/moby/moby/client"
  15. )
  16. type Image struct {
  17. ctx context.Context
  18. log *fw.Log
  19. client *Docker
  20. registry *Registry
  21. tag string
  22. version string
  23. build bool
  24. push bool
  25. }
  26. func NewImage(ctx context.Context, log *fw.Log, client *Docker, registry *Registry, tag string, version string, build bool, push bool) *Image {
  27. return &Image{
  28. ctx: ctx,
  29. log: log,
  30. client: client,
  31. registry: registry,
  32. tag: tag,
  33. version: version,
  34. build: build,
  35. push: push,
  36. }
  37. }
  38. func (this *Image) Name() string {
  39. return fmt.Sprintf("%s/%s:%s", this.registry.address, this.tag, this.version)
  40. }
  41. func (this *Image) Build(timeout int, path string) ([]byte, error) {
  42. if (!this.build) {
  43. return []byte{}, nil
  44. }
  45. ctx, cancel := context.WithTimeout(this.ctx, time.Duration(timeout)*time.Millisecond)
  46. defer cancel()
  47. stream, err := archive.TarWithOptions(path, &archive.TarOptions{})
  48. if err != nil {
  49. return nil, fmt.Errorf("can't build image: %w", err)
  50. }
  51. defer stream.Close()
  52. response, err := this.client.Docker.ImageBuild(
  53. ctx,
  54. stream,
  55. moby.ImageBuildOptions{
  56. Dockerfile: "Dockerfile",
  57. Tags: []string{
  58. this.Name(),
  59. },
  60. },
  61. )
  62. if err != nil {
  63. return nil, fmt.Errorf("can't build image: %w", err)
  64. }
  65. defer response.Body.Close()
  66. var stdout bytes.Buffer
  67. _, err = io.Copy(&stdout, response.Body)
  68. if err != nil {
  69. return nil, fmt.Errorf("can't build image: %w", err)
  70. }
  71. return stdout.Bytes(), nil
  72. }
  73. func (this *Image) Push(timeout int) ([]byte, error) {
  74. if (!this.push) {
  75. return []byte{}, nil
  76. }
  77. ctx, cancel := context.WithTimeout(this.ctx, time.Duration(timeout)*time.Millisecond)
  78. defer cancel()
  79. credentials, err := credentialsToString(
  80. this.registry.login,
  81. this.registry.password,
  82. )
  83. if err != nil {
  84. return nil, fmt.Errorf("can't push image: %w", err)
  85. }
  86. response, err := this.client.Docker.ImagePush(
  87. ctx,
  88. this.Name(),
  89. moby.ImagePushOptions{
  90. RegistryAuth: credentials,
  91. },
  92. )
  93. if err != nil {
  94. return nil, fmt.Errorf("can't push image: %w", err)
  95. }
  96. var stdout bytes.Buffer
  97. var waitGroup sync.WaitGroup
  98. waitGroup.Add(1)
  99. go func() {
  100. defer waitGroup.Done()
  101. _, err := io.Copy(&stdout, response)
  102. if err != nil {
  103. // ...
  104. }
  105. }()
  106. err = response.Wait(ctx)
  107. if err != nil {
  108. return nil, fmt.Errorf("can't push image: %w", err)
  109. }
  110. waitGroup.Wait()
  111. return stdout.Bytes(), nil
  112. }
  113. func credentialsToString(login string, password string) (string, error) {
  114. encodedJSON, err := json.Marshal(
  115. &mobyregistry.AuthConfig{
  116. Username: login,
  117. Password: password,
  118. },
  119. )
  120. if err != nil {
  121. return "", fmt.Errorf("can't serialize credentials: %w", err)
  122. }
  123. authStr := base64.URLEncoding.EncodeToString(encodedJSON)
  124. return authStr, nil
  125. }