package docker import ( "context" "encoding/json" "fmt" "goseg/config" "goseg/structs" "log/slog" "os" "github.com/docker/docker/api/types" "github.com/docker/docker/client" ) var ( logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) ) func GetShipStatus(patps []string) (map[string]string, error) { statuses := make(map[string]string) cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { errmsg := fmt.Sprintf("Error getting Docker info: %v", err) logger.Error(errmsg) return statuses, err } else { containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) if err != nil { errmsg := fmt.Sprintf("Error getting containers: %v", err) logger.Error(errmsg) return statuses, err } else { for _, pier := range patps { found := false for _, container := range containers { for _, name := range container.Names { fasPier := "/" + pier if name == fasPier { statuses[pier] = container.Status found = true break } } if found { break } } if !found { statuses[pier] = "not found" } } } return statuses, nil } } // return the name of a container's network func GetContainerNetwork(name string) (string, error) { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return "", err } defer cli.Close() containerJSON, err := cli.ContainerInspect(context.Background(), name) if err != nil { return "", err } for networkName := range containerJSON.NetworkSettings.Networks { return networkName, nil } return "", fmt.Errorf("container is not attached to any network") } // return the disk and memory usage for a container func GetContainerStats(containerName string) (structs.ContainerStats, error) { var res structs.ContainerStats cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return res, err } defer cli.Close() statsResp, err := cli.ContainerStats(context.Background(), containerName, false) if err != nil { return res, err } defer statsResp.Body.Close() var stat types.StatsJSON if err := json.NewDecoder(statsResp.Body).Decode(&stat); err != nil { return res, err } memUsage := stat.MemoryStats.Usage inspectResp, err := cli.ContainerInspect(context.Background(), containerName) if err != nil { return res, err } diskUsage := int64(0) if inspectResp.SizeRw != nil { diskUsage = *inspectResp.SizeRw } return structs.ContainerStats{ MemoryUsage: memUsage, DiskUsage: diskUsage, }, nil } // start a container by name + tag // not for booting new ships func StartContainer(containerName, containerType string) error { ctx := context.Background() cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return err } // Placeholder: Get the desired tag and hash from your config containerInfo, err := getCurrentContainerInfo() if err != nil { errMsg := fmt.Errorf("Couldn't get %s container info: %v", containerName, err) logger.Error(errMsg) return err } desiredTag := containerInfo["tag"] desiredHash := containerInfo["hash"] desiredRepo := containerInfo["repo"] if desiredTag == "" || desiredHash == "" { err = fmt.Errorf("Version info has not been retrieved!") logger.Error(err) return err } // Check if the desired image is available locally images, err := cli.ImageList(ctx, types.ImageListOptions{}) if err != nil { return err } imageExistsLocally := false for _, img := range images { for _, tag := range img.RepoTags { if tag == containerType+":"+desiredTag && img.ID == desiredHash { imageExistsLocally = true break } } if imageExistsLocally { break } } if !imageExistsLocally { // pull the image if it doesn't exist locally _, err = cli.ImagePull(ctx, desiredRepo+":"+desiredTag, types.ImagePullOptions{}) if err != nil { return err } } switch { case existingContainer == nil: // if the container does not exist, create and start it _, err := cli.ContainerCreate(ctx, &container.Config{ Image: containerType + ":" + containerTag, }, nil, nil, nil, containerName) if err != nil { return err } err = cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{}) if err != nil { return err } msg := fmt.Sprintf("%s started with image %s:%s", containerName, containerType, containerTag) logger.Info(msg) case existingContainer.State == "exited": // if the container exists but is stopped, start it err := cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{}) if err != nil { return err } msg := fmt.Sprintf("Started stopped container %s", containerName) logger.Info(msg) default: // if container is running, check the image tag currentImage := existingContainer.Image currentTag := strings.Split(currentImage, ":")[1] if currentTag != containerTag { // if the tags don't match, recreate the container with the new tag err := cli.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{Force: true}) if err != nil { return err } _, err = cli.ContainerCreate(ctx, &container.Config{ Image: containerType + ":" + containerTag, }, nil, nil, nil, containerName) if err != nil { return err } err = cli.ContainerStart(ctx, containerName, types.ContainerStartOptions{}) if err != nil { return err } msg := fmt.Sprintf("Restarted %s with image %s:%s", containerName, containerType, containerTag) logger.Info(msg) } else { msg := fmt.Sprintf("%s is already running with the correct tag: %s", containerName, containerTag) logger.Info(msg) } } return nil } // convert the version info back into json then a map lol // so we can easily get the correct repo/release channel/tag/hash func getCurrentContainerInfo(containerType string) (map[string]string, error) { var res map[string]string conf := config.Conf() releaseChannel := conf.UpdateBranch arch := config.Architecture hashLabel := arch + "_sha256" versionInfo := config.VersionInfo jsonData, err := json.Marshal(VersionInfo) if err != nil { return res, err } // Convert JSON to map var m map[string]interface{} err = json.Unmarshal(jsonData, &m) if err != nil { return res, err } res["tag"] = m["groundseg"][releaseChannel][containerType]["tag"] res["hash"] = m["groundseg"][releaseChannel][containerType][hashLabel] res["repo"] = m["groundseg"][releaseChannel][containerType]["repo"] return res, nil }