docker.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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/moby/client"
  13. "github.com/moby/moby/client/pkg/jsonmessage"
  14. "github.com/moby/term"
  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.Create()
  136. if err != nil {
  137. return -1, err
  138. }
  139. err = t.Start()
  140. if err != nil {
  141. return -1, err
  142. }
  143. res, err := t.Wait(timeout)
  144. errs := t.Stop()
  145. if errs != nil {
  146. destErr.Write([]byte(errs.Error()))
  147. }
  148. errs = t.Logs(destOut, destErr)
  149. if errs != nil {
  150. destErr.Write([]byte(errs.Error()))
  151. }
  152. errs = t.Remove()
  153. if errs != nil {
  154. destErr.Write([]byte(errs.Error()))
  155. }
  156. return res, err
  157. }
  158. func (t *dockerContainer) Logs(destOut io.Writer, destErr io.Writer) error {
  159. cli, err := t.docker.client()
  160. if err != nil {
  161. return err
  162. }
  163. defer cli.Close()
  164. res, err := cli.ContainerLogs(t.docker.ctx, t.id, client.ContainerLogsOptions{
  165. ShowStdout: true,
  166. ShowStderr: true,
  167. // Details: true,
  168. })
  169. if err != nil {
  170. return err
  171. }
  172. defer res.Close()
  173. _, err = stdcopy.StdCopy(destOut, destErr, res)
  174. if err != nil {
  175. return err
  176. }
  177. return nil
  178. }
  179. func (t *dockerContainer) AddPort(containerPort, address, hostPort string) error {
  180. cp, err := network.ParsePort(containerPort)
  181. if err != nil {
  182. return err
  183. }
  184. addr, err := netip.ParseAddr(address)
  185. if err != nil {
  186. return err
  187. }
  188. if t.hostconfig.PortBindings == nil {
  189. t.hostconfig.PortBindings = network.PortMap{}
  190. }
  191. t.hostconfig.PortBindings[cp] = []network.PortBinding{
  192. {
  193. HostPort: hostPort,
  194. HostIP: addr,
  195. },
  196. }
  197. return nil
  198. }
  199. func (t *dockerContainer) State() (container.ContainerState, error) {
  200. cli, err := t.docker.client()
  201. if err != nil {
  202. return "", err
  203. }
  204. c, err := cli.ContainerInspect(context.Background(), t.id, client.ContainerInspectOptions{})
  205. if err != nil {
  206. return "", err
  207. }
  208. return c.Container.State.Status, nil
  209. }
  210. func (t *dockerContainer) Wait(timeout time.Duration) (int64, error) {
  211. cli, err := t.docker.client()
  212. if err != nil {
  213. return -1, err
  214. }
  215. defer cli.Close()
  216. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  217. wait := cli.ContainerWait(ctx, t.id, client.ContainerWaitOptions{})
  218. select {
  219. case err := <-wait.Error:
  220. if err != nil {
  221. return -1, err
  222. }
  223. case res := <-wait.Result:
  224. return res.StatusCode, nil
  225. }
  226. return -1, nil
  227. }
  228. func (t *dockerContainer) Create() error {
  229. cli, err := t.docker.client()
  230. if err != nil {
  231. return err
  232. }
  233. defer cli.Close()
  234. create, err := cli.ContainerCreate(context.Background(), client.ContainerCreateOptions{
  235. Name: t.name,
  236. Config: t.config,
  237. HostConfig: t.hostconfig,
  238. NetworkingConfig: t.network,
  239. Platform: &v1.Platform{},
  240. })
  241. if err != nil {
  242. return err
  243. }
  244. t.id = create.ID
  245. return nil
  246. }
  247. func (t *dockerContainer) Start() error {
  248. cli, err := t.docker.client()
  249. if err != nil {
  250. return err
  251. }
  252. defer cli.Close()
  253. _, err = cli.ContainerStart(t.docker.ctx, t.id, client.ContainerStartOptions{})
  254. if err != nil {
  255. return err
  256. }
  257. return nil
  258. }
  259. func (t *dockerContainer) Stop() error {
  260. cli, err := t.docker.client()
  261. if err != nil {
  262. return err
  263. }
  264. defer cli.Close()
  265. _, err = cli.ContainerStop(t.docker.ctx, t.id, client.ContainerStopOptions{})
  266. if err != nil {
  267. return err
  268. }
  269. return nil
  270. }
  271. func (t *dockerContainer) Remove() error {
  272. cli, err := t.docker.client()
  273. if err != nil {
  274. return err
  275. }
  276. defer cli.Close()
  277. _, err = cli.ContainerRemove(t.docker.ctx, t.id,
  278. client.ContainerRemoveOptions{
  279. // RemoveLinks: true,
  280. RemoveVolumes: true,
  281. Force: true,
  282. })
  283. if err != nil {
  284. return err
  285. }
  286. t.id = ""
  287. return nil
  288. }
  289. func (t *docker) Image() *dockerImage {
  290. return &dockerImage{
  291. docker: t,
  292. }
  293. }
  294. // RegistryAuth is the base64 encoded credentials for the registry
  295. func (t *dockerImage) Auth(RegistryAuth string) { t.auth = RegistryAuth }
  296. func (t *dockerImage) Build(tag, path, dockerfile string) error {
  297. cli, err := t.docker.client()
  298. if err != nil {
  299. return err
  300. }
  301. defer cli.Close()
  302. tar, err := archive.TarWithOptions(path, &archive.TarOptions{})
  303. if err != nil {
  304. return err
  305. }
  306. opts := client.ImageBuildOptions{
  307. Dockerfile: dockerfile,
  308. Tags: []string{tag},
  309. Remove: true,
  310. }
  311. buildResponse, err := cli.ImageBuild(t.docker.ctx, tar, opts)
  312. if err != nil {
  313. return err
  314. }
  315. defer buildResponse.Body.Close()
  316. if log != nil {
  317. fd, isTerminal := term.GetFdInfo(log)
  318. return jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, log, fd, isTerminal, nil)
  319. }
  320. return nil
  321. }
  322. func (t *dockerImage) Tag(source, target string) error {
  323. cli, err := t.docker.client()
  324. if err != nil {
  325. return err
  326. }
  327. defer cli.Close()
  328. _, err = cli.ImageTag(t.docker.ctx, client.ImageTagOptions{
  329. Source: source,
  330. Target: target,
  331. })
  332. if err != nil {
  333. return err
  334. }
  335. return nil
  336. }
  337. func (t *dockerImage) Push(image string) error {
  338. cli, err := t.docker.client()
  339. if err != nil {
  340. return err
  341. }
  342. defer cli.Close()
  343. res, err := cli.ImagePush(t.docker.ctx, image, client.ImagePushOptions{
  344. RegistryAuth: t.auth,
  345. })
  346. if err != nil {
  347. return err
  348. }
  349. defer res.Close()
  350. if log != nil {
  351. fd, isTerminal := term.GetFdInfo(log)
  352. return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil)
  353. }
  354. return nil
  355. }
  356. func (t *dockerImage) Remove(image string) error {
  357. cli, err := t.docker.client()
  358. if err != nil {
  359. return err
  360. }
  361. defer cli.Close()
  362. _, err = cli.ImageRemove(t.docker.ctx, image, client.ImageRemoveOptions{})
  363. if err != nil {
  364. return err
  365. }
  366. return nil
  367. }
  368. func (t *dockerImage) Pull(image string, timeout time.Duration) error {
  369. cli, err := t.docker.client()
  370. if err != nil {
  371. return err
  372. }
  373. defer cli.Close()
  374. ctx, _ := context.WithTimeout(t.docker.ctx, timeout)
  375. res, err := cli.ImagePull(ctx, image, client.ImagePullOptions{
  376. RegistryAuth: t.auth,
  377. })
  378. if err != nil {
  379. return err
  380. }
  381. defer res.Close()
  382. if log != nil {
  383. fd, isTerminal := term.GetFdInfo(log)
  384. return jsonmessage.DisplayJSONMessagesStream(res, log, fd, isTerminal, nil)
  385. } else {
  386. err = res.Wait(ctx)
  387. if err != nil {
  388. return err
  389. }
  390. }
  391. return nil
  392. }