docker.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. package tools
  2. import (
  3. "context"
  4. "io"
  5. "net/netip"
  6. "strings"
  7. "time"
  8. "github.com/moby/go-archive"
  9. "github.com/moby/moby/api/pkg/stdcopy"
  10. "github.com/moby/moby/api/types/container"
  11. "github.com/moby/moby/api/types/network"
  12. "github.com/moby/term"
  13. "github.com/moby/moby/client"
  14. "github.com/moby/moby/client/pkg/jsonmessage"
  15. v1 "github.com/opencontainers/image-spec/specs-go/v1"
  16. )
  17. func Docker() *docker {
  18. ctx := context.Background()
  19. return &docker{
  20. ctx: ctx,
  21. }
  22. }
  23. type (
  24. docker struct {
  25. ctx context.Context
  26. }
  27. dockerImage struct {
  28. docker *docker
  29. auth string
  30. }
  31. dockerContainer struct {
  32. docker *docker
  33. id string
  34. name string
  35. config *container.Config
  36. hostconfig *container.HostConfig
  37. network *network.NetworkingConfig
  38. }
  39. )
  40. func (t *docker) client() (*client.Client, error) {
  41. cli, err := client.New(client.FromEnv)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return cli, nil
  46. }
  47. func (t *docker) ContainerList() ([]*dockerContainer, error) {
  48. cli, err := t.client()
  49. if err != nil {
  50. return nil, err
  51. }
  52. defer cli.Close()
  53. containers, err := cli.ContainerList(context.Background(), client.ContainerListOptions{All: true})
  54. if err != nil {
  55. return nil, err
  56. }
  57. res := []*dockerContainer{}
  58. for _, ctr := range containers.Items {
  59. c, err := t.ContainerGetByID(ctr.ID)
  60. if err != nil {
  61. return nil, err
  62. }
  63. res = append(res, c)
  64. }
  65. return res, nil
  66. }
  67. func (t *docker) ContainerGetByID(id string) (*dockerContainer, error) {
  68. cli, err := t.client()
  69. if err != nil {
  70. return nil, err
  71. }
  72. defer cli.Close()
  73. c, err := cli.ContainerInspect(context.Background(), id, client.ContainerInspectOptions{})
  74. if err != nil {
  75. return nil, err
  76. }
  77. return &dockerContainer{
  78. docker: t,
  79. id: id,
  80. name: strings.TrimPrefix(c.Container.Name, "/"),
  81. config: c.Container.Config,
  82. hostconfig: c.Container.HostConfig,
  83. network: &network.NetworkingConfig{
  84. EndpointsConfig: map[string]*network.EndpointSettings{},
  85. },
  86. }, nil
  87. }
  88. func (t *docker) ContainerGetByName(name string) (*dockerContainer, error) {
  89. cli, err := t.client()
  90. if err != nil {
  91. return nil, err
  92. }
  93. defer cli.Close()
  94. filters := client.Filters{}
  95. filters.Add("name", name)
  96. containers, err := cli.ContainerList(context.Background(),
  97. client.ContainerListOptions{
  98. Filters: filters,
  99. })
  100. if err != nil {
  101. return nil, err
  102. }
  103. var id string
  104. for _, ctr := range containers.Items {
  105. for _, n := range ctr.Names {
  106. if strings.TrimPrefix(n, "/") == name {
  107. id = ctr.ID
  108. }
  109. }
  110. }
  111. return t.ContainerGetByID(id)
  112. }
  113. func (t *docker) ContainerNew(name string) *dockerContainer {
  114. return &dockerContainer{
  115. name: name,
  116. docker: t,
  117. config: &container.Config{
  118. ExposedPorts: network.PortSet{},
  119. },
  120. hostconfig: &container.HostConfig{
  121. AutoRemove: false,
  122. },
  123. network: &network.NetworkingConfig{
  124. EndpointsConfig: map[string]*network.EndpointSettings{},
  125. },
  126. }
  127. }
  128. func (t *dockerContainer) ID() string { return t.id }
  129. func (t *dockerContainer) Name() string { return t.name }
  130. func (t *dockerContainer) Config() *container.Config { return t.config }
  131. func (t *dockerContainer) HostConfig() *container.HostConfig { return t.hostconfig }
  132. func (t *dockerContainer) NetworkConfig() *network.NetworkingConfig { return t.network }
  133. func (t *dockerContainer) Exec() {}
  134. func (t *dockerContainer) Run(destOut io.Writer, destErr io.Writer, timeout time.Duration) (int64, error) {
  135. err := t.Start()
  136. if err != nil {
  137. return -1, err
  138. }
  139. res, err := t.Wait(timeout)
  140. errs := t.Stop()
  141. if errs != nil {
  142. destErr.Write([]byte(errs.Error()))
  143. }
  144. errs = t.Logs(destOut, destErr)
  145. if errs != nil {
  146. destErr.Write([]byte(errs.Error()))
  147. }
  148. errs = t.Remove()
  149. if errs != nil {
  150. destErr.Write([]byte(errs.Error()))
  151. }
  152. return res, err
  153. }
  154. func (t *dockerContainer) Logs(destOut io.Writer, destErr io.Writer) error {
  155. cli, err := t.docker.client()
  156. if err != nil {
  157. return err
  158. }
  159. defer cli.Close()
  160. res, err := cli.ContainerLogs(t.docker.ctx, t.id, client.ContainerLogsOptions{
  161. ShowStdout: true,
  162. ShowStderr: true,
  163. // Details: true,
  164. })
  165. if err != nil {
  166. return err
  167. }
  168. defer res.Close()
  169. _, err = stdcopy.StdCopy(destOut, destErr, res)
  170. if err != nil {
  171. return err
  172. }
  173. return nil
  174. }
  175. func (t *dockerContainer) AddPort(containerPort, address, hostPort string) error {
  176. cp, err := network.ParsePort(containerPort)
  177. if err != nil {
  178. return err
  179. }
  180. addr, err := netip.ParseAddr(address)
  181. if err != nil {
  182. return err
  183. }
  184. if t.hostconfig.PortBindings == nil {
  185. t.hostconfig.PortBindings = network.PortMap{}
  186. }
  187. t.hostconfig.PortBindings[cp] = []network.PortBinding{
  188. {
  189. HostPort: hostPort,
  190. HostIP: addr,
  191. },
  192. }
  193. return nil
  194. }
  195. func (t *dockerContainer) State() (container.ContainerState, error) {
  196. cli, err := t.docker.client()
  197. if err != nil {
  198. return "", err
  199. }
  200. c, err := cli.ContainerInspect(context.Background(), t.id, client.ContainerInspectOptions{})
  201. if err != nil {
  202. return "", err
  203. }
  204. return c.Container.State.Status, nil
  205. }
  206. func (t *dockerContainer) Wait(timeout time.Duration) (int64, error) {
  207. cli, err := t.docker.client()
  208. if err != nil {
  209. return -1, err
  210. }
  211. defer cli.Close()
  212. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  213. wait := cli.ContainerWait(ctx, t.id, client.ContainerWaitOptions{})
  214. select {
  215. case err := <-wait.Error:
  216. if err != nil {
  217. return -1, err
  218. }
  219. case res := <-wait.Result:
  220. return res.StatusCode, nil
  221. }
  222. return -1, nil
  223. }
  224. func (t *dockerContainer) Create() error {
  225. cli, err := t.docker.client()
  226. if err != nil {
  227. return err
  228. }
  229. defer cli.Close()
  230. create, err := cli.ContainerCreate(context.Background(), client.ContainerCreateOptions{
  231. Name: t.name,
  232. Config: t.config,
  233. HostConfig: t.hostconfig,
  234. NetworkingConfig: t.network,
  235. Platform: &v1.Platform{},
  236. })
  237. if err != nil {
  238. return err
  239. }
  240. t.id = create.ID
  241. return nil
  242. }
  243. func (t *dockerContainer) Start() error {
  244. cli, err := t.docker.client()
  245. if err != nil {
  246. return err
  247. }
  248. defer cli.Close()
  249. _, err = cli.ContainerStart(t.docker.ctx, t.id, client.ContainerStartOptions{})
  250. if err != nil {
  251. return err
  252. }
  253. return nil
  254. }
  255. func (t *dockerContainer) Stop() error {
  256. cli, err := t.docker.client()
  257. if err != nil {
  258. return err
  259. }
  260. defer cli.Close()
  261. _, err = cli.ContainerStop(t.docker.ctx, t.id, client.ContainerStopOptions{})
  262. if err != nil {
  263. return err
  264. }
  265. return nil
  266. }
  267. func (t *dockerContainer) Remove() error {
  268. cli, err := t.docker.client()
  269. if err != nil {
  270. return err
  271. }
  272. defer cli.Close()
  273. _, err = cli.ContainerRemove(t.docker.ctx, t.id,
  274. client.ContainerRemoveOptions{
  275. // RemoveLinks: true,
  276. RemoveVolumes: true,
  277. Force: true,
  278. })
  279. if err != nil {
  280. return err
  281. }
  282. t.id = ""
  283. return nil
  284. }
  285. func (t *docker) Image() *dockerImage {
  286. return &dockerImage{
  287. docker: t,
  288. }
  289. }
  290. // RegistryAuth is the base64 encoded credentials for the registry
  291. func (t *dockerImage) Auth(RegistryAuth string) { t.auth = RegistryAuth }
  292. func (t *dockerImage) Build(tag, path, dockerfile string) error {
  293. cli, err := t.docker.client()
  294. if err != nil {
  295. return err
  296. }
  297. defer cli.Close()
  298. tar, err := archive.TarWithOptions(path, &archive.TarOptions{})
  299. if err != nil {
  300. return err
  301. }
  302. opts := client.ImageBuildOptions{
  303. Dockerfile: dockerfile,
  304. Tags: []string{tag},
  305. Remove: true,
  306. }
  307. buildResponse, err := cli.ImageBuild(t.docker.ctx, tar, opts)
  308. if err != nil {
  309. return err
  310. }
  311. defer buildResponse.Body.Close()
  312. if log != nil {
  313. fd, isTerminal := term.GetFdInfo(log)
  314. return jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, log, fd, isTerminal, nil)
  315. }
  316. return nil
  317. }
  318. func (t *dockerImage) Tag(source, target string) error {
  319. cli, err := t.docker.client()
  320. if err != nil {
  321. return err
  322. }
  323. defer cli.Close()
  324. _, err = cli.ImageTag(t.docker.ctx, client.ImageTagOptions{
  325. Source: source,
  326. Target: target,
  327. })
  328. if err != nil {
  329. return err
  330. }
  331. return nil
  332. }
  333. func (t *dockerImage) Push(image string) error {
  334. cli, err := t.docker.client()
  335. if err != nil {
  336. return err
  337. }
  338. defer cli.Close()
  339. res, err := cli.ImagePush(t.docker.ctx, image, client.ImagePushOptions{
  340. RegistryAuth: t.auth,
  341. })
  342. if err != nil {
  343. return err
  344. }
  345. defer res.Close()
  346. if log != nil {
  347. fd, isTerminal := term.GetFdInfo(log)
  348. return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil)
  349. }
  350. return nil
  351. }
  352. func (t *dockerImage) Remove(image string) error {
  353. cli, err := t.docker.client()
  354. if err != nil {
  355. return err
  356. }
  357. defer cli.Close()
  358. _, err = cli.ImageRemove(t.docker.ctx, image, client.ImageRemoveOptions{})
  359. if err != nil {
  360. return err
  361. }
  362. return nil
  363. }
  364. func (t *dockerImage) Pull(image string, timeout time.Duration) error {
  365. cli, err := t.docker.client()
  366. if err != nil {
  367. return err
  368. }
  369. defer cli.Close()
  370. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  371. res, err := cli.ImagePull(ctx, image, client.ImagePullOptions{
  372. RegistryAuth: t.auth,
  373. })
  374. if err != nil {
  375. return err
  376. }
  377. defer res.Close()
  378. if log != nil {
  379. fd, isTerminal := term.GetFdInfo(log)
  380. return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil)
  381. } else {
  382. err = res.Wait(ctx)
  383. if err != nil {
  384. return err
  385. }
  386. }
  387. return nil
  388. }