| package client | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "net/url" | |
| "path" | |
| "sort" | |
| "strings" | |
| cerrdefs "github.com/containerd/errdefs" | |
| "github.com/moby/moby/api/types/container" | |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | |
| ) | |
| // ContainerCreate creates a new container based on the given configuration. | |
| // It can be associated with a name, but it's not mandatory. | |
| func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) { | |
| cfg := options.Config | |
| if cfg == nil { | |
| cfg = &container.Config{} | |
| } | |
| if options.Image != "" { | |
| if cfg.Image != "" { | |
| return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("either Image or config.Image should be set") | |
| } | |
| newCfg := *cfg | |
| newCfg.Image = options.Image | |
| cfg = &newCfg | |
| } | |
| if cfg.Image == "" { | |
| return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config.Image or Image is required") | |
| } | |
| var response container.CreateResponse | |
| if options.HostConfig != nil { | |
| options.HostConfig.CapAdd = normalizeCapabilities(options.HostConfig.CapAdd) | |
| options.HostConfig.CapDrop = normalizeCapabilities(options.HostConfig.CapDrop) | |
| } | |
| query := url.Values{} | |
| if options.Platform != nil { | |
| if p := formatPlatform(*options.Platform); p != "unknown" { | |
| query.Set("platform", p) | |
| } | |
| } | |
| if options.Name != "" { | |
| query.Set("name", options.Name) | |
| } | |
| body := container.CreateRequest{ | |
| Config: cfg, | |
| HostConfig: options.HostConfig, | |
| NetworkingConfig: options.NetworkingConfig, | |
| } | |
| resp, err := cli.post(ctx, "/containers/create", query, body, nil) | |
| defer ensureReaderClosed(resp) | |
| if err != nil { | |
| return ContainerCreateResult{}, err | |
| } | |
| err = json.NewDecoder(resp.Body).Decode(&response) | |
| return ContainerCreateResult{ID: response.ID, Warnings: response.Warnings}, err | |
| } | |
| // formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7"). | |
| // | |
| // It is a fork of [platforms.Format], and does not yet support "os.version", | |
| // as [platforms.FormatAll] does. | |
| // | |
| // [platforms.Format]: https://github.com/containerd/platforms/blob/v1.0.0-rc.1/platforms.go#L309-L316 | |
| // [platforms.FormatAll]: https://github.com/containerd/platforms/blob/v1.0.0-rc.1/platforms.go#L318-L330 | |
| func formatPlatform(platform ocispec.Platform) string { | |
| if platform.OS == "" { | |
| return "unknown" | |
| } | |
| return path.Join(platform.OS, platform.Architecture, platform.Variant) | |
| } | |
| // allCapabilities is a magic value for "all capabilities" | |
| const allCapabilities = "ALL" | |
| // normalizeCapabilities normalizes capabilities to their canonical form, | |
| // removes duplicates, and sorts the results. | |
| // | |
| // It is similar to [caps.NormalizeLegacyCapabilities], | |
| // but performs no validation based on supported capabilities. | |
| // | |
| // [caps.NormalizeLegacyCapabilities]: https://github.com/moby/moby/blob/v28.3.2/oci/caps/utils.go#L56 | |
| func normalizeCapabilities(caps []string) []string { | |
| var normalized []string | |
| unique := make(map[string]struct{}) | |
| for _, c := range caps { | |
| c = normalizeCap(c) | |
| if _, ok := unique[c]; ok { | |
| continue | |
| } | |
| unique[c] = struct{}{} | |
| normalized = append(normalized, c) | |
| } | |
| sort.Strings(normalized) | |
| return normalized | |
| } | |
| // normalizeCap normalizes a capability to its canonical format by upper-casing | |
| // and adding a "CAP_" prefix (if not yet present). It also accepts the "ALL" | |
| // magic-value. | |
| func normalizeCap(capability string) string { | |
| capability = strings.ToUpper(capability) | |
| if capability == allCapabilities { | |
| return capability | |
| } | |
| if !strings.HasPrefix(capability, "CAP_") { | |
| capability = "CAP_" + capability | |
| } | |
| return capability | |
| } |