docker.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. package docker
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "goseg/config"
  7. "goseg/structs"
  8. "log/slog"
  9. "os"
  10. "strings"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/api/types/container"
  14. )
  15. var (
  16. logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
  17. )
  18. func GetShipStatus(patps []string) (map[string]string, error) {
  19. statuses := make(map[string]string)
  20. cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  21. if err != nil {
  22. errmsg := fmt.Sprintf("Error getting Docker info: %v", err)
  23. logger.Error(errmsg)
  24. return statuses, err
  25. } else {
  26. containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
  27. if err != nil {
  28. errmsg := fmt.Sprintf("Error getting containers: %v", err)
  29. logger.Error(errmsg)
  30. return statuses, err
  31. } else {
  32. for _, pier := range patps {
  33. found := false
  34. for _, container := range containers {
  35. for _, name := range container.Names {
  36. fasPier := "/" + pier
  37. if name == fasPier {
  38. statuses[pier] = container.Status
  39. found = true
  40. break
  41. }
  42. }
  43. if found {
  44. break
  45. }
  46. }
  47. if !found {
  48. statuses[pier] = "not found"
  49. }
  50. }
  51. }
  52. return statuses, nil
  53. }
  54. }
  55. // return the name of a container's network
  56. func GetContainerNetwork(name string) (string, error) {
  57. cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  58. if err != nil {
  59. return "", err
  60. }
  61. defer cli.Close()
  62. containerJSON, err := cli.ContainerInspect(context.Background(), name)
  63. if err != nil {
  64. return "", err
  65. }
  66. for networkName := range containerJSON.NetworkSettings.Networks {
  67. return networkName, nil
  68. }
  69. return "", fmt.Errorf("container is not attached to any network")
  70. }
  71. // return the disk and memory usage for a container
  72. func GetContainerStats(containerName string) (structs.ContainerStats, error) {
  73. var res structs.ContainerStats
  74. cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  75. if err != nil {
  76. return res, err
  77. }
  78. defer cli.Close()
  79. statsResp, err := cli.ContainerStats(context.Background(), containerName, false)
  80. if err != nil {
  81. return res, err
  82. }
  83. defer statsResp.Body.Close()
  84. var stat types.StatsJSON
  85. if err := json.NewDecoder(statsResp.Body).Decode(&stat); err != nil {
  86. return res, err
  87. }
  88. memUsage := stat.MemoryStats.Usage
  89. inspectResp, err := cli.ContainerInspect(context.Background(), containerName)
  90. if err != nil {
  91. return res, err
  92. }
  93. diskUsage := int64(0)
  94. if inspectResp.SizeRw != nil {
  95. diskUsage = *inspectResp.SizeRw
  96. }
  97. return structs.ContainerStats{
  98. MemoryUsage: memUsage,
  99. DiskUsage: diskUsage,
  100. }, nil
  101. }
  102. // start a container by name + tag
  103. // not for booting new ships
  104. func StartContainer(containerName string, containerType string) error {
  105. ctx := context.Background()
  106. cli, err := client.NewClientWithOpts(client.FromEnv)
  107. if err != nil {
  108. return err
  109. }
  110. // get the desired tag and hash from config
  111. containerInfo, err := getLatestContainerInfo(containerType)
  112. if err != nil {
  113. return err
  114. }
  115. // check if container exists
  116. containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
  117. if err != nil {
  118. return err
  119. }
  120. var existingContainer *types.Container = nil
  121. for _, container := range containers {
  122. for _, name := range container.Names {
  123. if name == "/"+containerName {
  124. existingContainer = &container
  125. break
  126. }
  127. }
  128. if existingContainer != nil {
  129. break
  130. }
  131. }
  132. desiredTag := containerInfo["tag"]
  133. desiredHash := containerInfo["hash"]
  134. desiredRepo := containerInfo["repo"]
  135. if desiredTag == "" || desiredHash == "" {
  136. err = fmt.Errorf("Version info has not been retrieved!")
  137. return err
  138. }
  139. // check if the desired image is available locally
  140. images, err := cli.ImageList(ctx, types.ImageListOptions{})
  141. if err != nil {
  142. return err
  143. }
  144. imageExistsLocally := false
  145. for _, img := range images {
  146. for _, tag := range img.RepoTags {
  147. if tag == containerType+":"+desiredTag && img.ID == desiredHash {
  148. imageExistsLocally = true
  149. break
  150. }
  151. }
  152. if imageExistsLocally {
  153. break
  154. }
  155. }
  156. if !imageExistsLocally {
  157. // pull the image if it doesn't exist locally
  158. _, err = cli.ImagePull(ctx, desiredRepo+":"+desiredTag, types.ImagePullOptions{})
  159. if err != nil {
  160. return err
  161. }
  162. }
  163. switch {
  164. case existingContainer == nil:
  165. // if the container does not exist, create and start it
  166. _, err := cli.ContainerCreate(ctx, &container.Config{
  167. Image: containerType + ":" + desiredTag,
  168. }, nil, nil, nil, containerName)
  169. if err != nil {
  170. return err
  171. }
  172. err = cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{})
  173. if err != nil {
  174. return err
  175. }
  176. msg := fmt.Sprintf("%s started with image %s:%s", containerName, containerType, desiredTag)
  177. logger.Info(msg)
  178. case existingContainer.State == "exited":
  179. // if the container exists but is stopped, start it
  180. err := cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{})
  181. if err != nil {
  182. return err
  183. }
  184. msg := fmt.Sprintf("Started stopped container %s", containerName)
  185. logger.Info(msg)
  186. default:
  187. // if container is running, check the image tag
  188. currentImage := existingContainer.Image
  189. currentTag := strings.Split(currentImage, ":")[1]
  190. if currentTag != desiredTag {
  191. // if the tags don't match, recreate the container with the new tag
  192. err := cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{Force: true})
  193. if err != nil {
  194. return err
  195. }
  196. _, err = cli.ContainerCreate(ctx, &container.Config{
  197. Image: containerType + ":" + desiredTag,
  198. }, nil, nil, nil, containerName)
  199. if err != nil {
  200. return err
  201. }
  202. err = cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{})
  203. if err != nil {
  204. return err
  205. }
  206. msg := fmt.Sprintf("Restarted %s with image %s:%s", containerName, containerType, desiredTag)
  207. logger.Info(msg)
  208. } else {
  209. msg := fmt.Sprintf("%s is already running with the correct tag: %s", containerName, desiredTag)
  210. logger.Info(msg)
  211. }
  212. }
  213. return nil
  214. }
  215. // convert the version info back into json then a map lol
  216. // so we can easily get the correct repo/release channel/tag/hash
  217. func getLatestContainerInfo(containerType string) (map[string]string, error) {
  218. var res map[string]string
  219. conf := config.Conf()
  220. releaseChannel := conf.UpdateBranch
  221. arch := config.Architecture
  222. hashLabel := arch + "_sha256"
  223. versionInfo := config.VersionInfo
  224. jsonData, err := json.Marshal(versionInfo)
  225. if err != nil {
  226. return res, err
  227. }
  228. // Convert JSON to map
  229. var m map[string]interface{}
  230. err = json.Unmarshal(jsonData, &m)
  231. if err != nil {
  232. return res, err
  233. }
  234. groundseg, ok := m["groundseg"].(map[string]interface{})
  235. if !ok {
  236. return nil, fmt.Errorf("groundseg is not a map")
  237. }
  238. channel, ok := groundseg[releaseChannel].(map[string]interface{})
  239. if !ok {
  240. return nil, fmt.Errorf("%s is not a map", releaseChannel)
  241. }
  242. containerData, ok := channel[containerType].(map[string]interface{})
  243. if !ok {
  244. return nil, fmt.Errorf("%s data is not a map", containerType)
  245. }
  246. tag, ok := containerData["tag"].(string)
  247. if !ok {
  248. return nil, fmt.Errorf("'tag' is not a string")
  249. }
  250. hashValue, ok := containerData[hashLabel].(string)
  251. if !ok {
  252. return nil, fmt.Errorf("'%s' is not a string", hashLabel)
  253. }
  254. repo, ok := containerData["repo"].(string)
  255. if !ok {
  256. return nil, fmt.Errorf("'repo' is not a string")
  257. }
  258. res = make(map[string]string)
  259. res["tag"] = tag
  260. res["hash"] = hashValue
  261. res["repo"] = repo
  262. return res, nil
  263. }
  264. // stop a container with the name
  265. func StopContainerByName(containerName string) error {
  266. ctx := context.Background()
  267. cli, err := client.NewClientWithOpts(client.FromEnv)
  268. if err != nil {
  269. return err
  270. }
  271. // fetch all containers incl stopped
  272. containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
  273. if err != nil {
  274. return err
  275. }
  276. for _, container := range containers {
  277. for _, name := range container.Names {
  278. if name == "/"+containerName {
  279. // Stop the container
  280. if err := cli.ContainerStop(ctx, container.ID, nil); err != nil {
  281. return fmt.Errorf("failed to stop container %s: %v", containerName, err)
  282. }
  283. logger.Info(fmt.Printf("Successfully stopped container %s\n", containerName))
  284. return nil
  285. }
  286. }
  287. }
  288. return fmt.Errorf("container with name %s not found", containerName)
  289. }