* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain n copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
******************************************************************/
package docker
import (
"context"
_ "embed"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/registry"
dockerapi "github.com/docker/docker/client"
dockerConfig "github.com/docker/docker/daemon/config"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/wait"
"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/executor/exec"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
)
type DockerClient interface {
Pull(image ImageRef) error
Push(image ImageRef) (readCloser io.ReadCloser, err error)
Run(cs ContainerSpec) error
EnsureImageExists(image ImageRef) error
EnsureContainerRun(containerId string) (bool, error)
RemoveContainer(id string) error
Ping() error
}
type Client struct {
Client *dockerapi.Client
ctx context.Context
}
type ImageRef struct {
Image string `json:"image"`
Username string `json:"username"`
Password string `json:"password"`
}
type ContainerSpec struct {
ContainerConfig *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
ContainerName string
}
type DockerDaemonConfig struct {
CgroupDriver string
LowLevelRuntime string
DataRoot string
EnableTls bool
TlsHost string
InsecureRegistries []string
}
const (
dockerSock = "/var/run/docker.sock"
DockerDaemonConfigFilePath = "/etc/docker/daemon.json"
OverrideDockerConfig = `
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
`
DefaultDirFileMode = 0755
DefaultFileMode = 0644
DataRootDirFileMode = 0711
DockerReadyTimeoutMinutes = 2
DockerReadyPollInterval = 5 * time.Second
MinSplitParts = 2
)
var (
runtimeKeyPathMap = map[string]string{
"runc": "/usr/local/sbin/runc",
"richrunc": "/usr/local/beyondvm/runc",
"kata": "",
}
tlsCertScript string
)
func NewDockerClient() (DockerClient, error) {
if !utils.Exists(dockerSock) {
return nil, errors.New("Docker service does not exist. ")
}
ctx := context.Background()
cli, err := dockerapi.NewClientWithOpts(dockerapi.FromEnv, dockerapi.WithAPIVersionNegotiation())
if err != nil {
log.Error("get container runtime client err:", err)
return nil, err
}
c := &Client{
Client: cli,
ctx: ctx,
}
if c.Ping() != nil {
return nil, errors.New("docker service is not running")
}
return c, nil
}
func (c *Client) Close() error {
return c.Client.Close()
}
func (c *Client) Ping() error {
p, err := c.Client.Ping(c.ctx)
if err == nil {
log.Debugf("docker api version: %s", p.APIVersion)
}
return err
}
func (c *Client) Pull(img ImageRef) error {
imagePullOptions := image.PullOptions{}
if len(img.Username) != 0 && len(img.Password) != 0 {
authConfig := registry.AuthConfig{
Username: img.Username,
Password: img.Password,
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
log.Errorf(" encoded docker RegistryAuth err: %v", err)
return err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
imagePullOptions.RegistryAuth = authStr
}
reader, err := c.Client.ImagePull(c.ctx, img.Image, imagePullOptions)
if err != nil {
log.Errorf("docker pull image %s error %v", img.Image, err)
return err
}
written, err := io.Copy(os.Stdout, reader)
if err != nil {
return err
}
if written < 0 {
return errors.New("unexpected negative byte count")
}
return nil
}
func (c *Client) Push(img ImageRef) (io.ReadCloser, error) {
imagePushOptions := image.PushOptions{}
if len(img.Username) != 0 && len(img.Password) != 0 {
authConfig := registry.AuthConfig{
Username: img.Username,
Password: img.Password,
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
log.Errorf(" encoded docker RegistryAuth err: %v", err)
return nil, err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
imagePushOptions.RegistryAuth = authStr
}
closer, err := c.Client.ImagePush(c.ctx, img.Image, imagePushOptions)
if err != nil {
return nil, err
}
return closer, nil
}
func (c *Client) Run(cs ContainerSpec) error {
resp, err := c.Client.ContainerCreate(c.ctx, cs.ContainerConfig, cs.HostConfig,
cs.NetworkingConfig, cs.Platform, cs.ContainerName)
if err != nil {
log.Error(err)
}
if err := c.Client.ContainerStart(c.ctx, resp.ID, container.StartOptions{}); err != nil {
if err != nil {
log.Error(err)
}
}
log.Infof("container ID %s", resp.ID)
return nil
}
func (c *Client) EnsureImageExists(image ImageRef) error {
imageInspect, _, _ := c.Client.ImageInspectWithRaw(c.ctx, image.Image)
if imageInspect.ID == "" {
log.Infof("Image %s not found, pulling...", image.Image)
err := c.Pull(image)
if err != nil {
return err
}
}
return nil
}
func (c *Client) EnsureContainerRun(containerId string) (bool, error) {
containerInfo, _ := c.Client.ContainerInspect(c.ctx, containerId)
if containerInfo.ContainerJSONBase != nil {
if containerInfo.State.Running {
return true, nil
}
err := c.Client.ContainerStart(c.ctx, containerInfo.ID, container.StartOptions{})
if err == nil {
return true, nil
}
err = c.Client.ContainerRemove(c.ctx, containerInfo.ID, container.RemoveOptions{Force: true})
if err != nil {
return false, err
}
}
return false, nil
}
func (c *Client) RemoveContainer(id string) error {
err := c.Client.ContainerRemove(c.ctx, id, container.RemoveOptions{Force: true})
if err != nil {
return err
}
return nil
}
func GetDockerDaemonConfig(path string) (*dockerConfig.Config, error) {
if path == "" {
path = DockerDaemonConfigFilePath
}
cfg := &dockerConfig.Config{}
if !utils.Exists(path) {
if !utils.Exists(filepath.Dir(path)) {
err := os.MkdirAll(filepath.Dir(path), DefaultDirFileMode)
if err != nil {
return nil, errors.Wrapf(err, "create docker daemon config dir %s failed", path)
}
}
_, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, DefaultFileMode)
if err != nil {
return nil, errors.Wrapf(err, "create docker daemon config file %s failed", path)
}
return cfg, nil
}
f, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "read docker daemon config file %s failed", path)
}
err = json.Unmarshal(f, &cfg)
if err != nil {
return nil, errors.Wrapf(err, "unmarshal docker daemon config file %s failed", path)
}
if err = dockerConfig.Validate(cfg); err != nil {
return nil, errors.Wrapf(err, "validate docker daemon config file %s failed", path)
}
return cfg, nil
}
func ConfigDockerDaemon(params DockerDaemonConfig) (err error) {
cgroupDriver := params.CgroupDriver
lowLevelRuntime := params.LowLevelRuntime
dataRoot := params.DataRoot
enableTls := params.EnableTls
tlsHost := params.TlsHost
insecureRegis := params.InsecureRegistries
if err := OverrideDockerService(); err != nil {
return errors.New("override docker service failed")
}
if !utils.Exists("/etc/docker") {
if err = os.MkdirAll("/etc/docker", DefaultDirFileMode); err != nil {
return errors.Errorf("create /etc/docker dir err: %v", err)
}
}
if err = BaseConfig(); err != nil {
return errors.Errorf("config docker base err: %v", err)
}
if len(insecureRegis) > 0 || insecureRegis != nil {
if err = ConfigInsecureRegistries(insecureRegis); err != nil {
return errors.Errorf("config docker insecure registries err: %v", err)
}
}
if cgroupDriver != "" {
if err = ConfigCgroupDriver(cgroupDriver); err != nil {
return errors.Errorf("config docker cgroup driver err: %v", err)
}
}
if lowLevelRuntime != "" {
if err = ConfigRuntime(lowLevelRuntime); err != nil {
return errors.Errorf("config docker low level runtime err: %v", err)
}
}
if dataRoot != "" {
if !utils.Exists(dataRoot) {
if err = os.MkdirAll(dataRoot, DataRootDirFileMode); err != nil {
return err
}
}
if err = ConfigDataRoot(dataRoot); err != nil {
return errors.Errorf("config docker data root err: %v", err)
}
}
if enableTls {
if err = ConfigDockerTls(tlsHost); err != nil {
return errors.Errorf("config docker tls err: %v", err)
}
}
return nil
}
func OverrideDockerService() error {
log.Info("override docker service")
if !utils.Exists("/etc/systemd/system/docker.service.d") {
err := os.MkdirAll("/etc/systemd/system/docker.service.d", DefaultDirFileMode)
if err != nil {
return err
}
}
err := os.WriteFile("/etc/systemd/system/docker.service.d/docker.conf", []byte(OverrideDockerConfig), DefaultFileMode)
if err != nil {
return err
}
return nil
}
func ConfigInsecureRegistries(registries []string) error {
registries = cleanRegistries(registries)
if len(registries) == 0 {
return nil
}
log.Infof("config docker insecure registries, registries: %v", registries)
if !utils.Exists(DockerDaemonConfigFilePath) {
return createDockerConfigWithRegistries(registries)
}
return updateDockerConfigWithRegistries(registries)
}
func cleanRegistries(registries []string) []string {
for i := len(registries) - 1; i >= 0; i-- {
if registries[i] == "" {
registries = append(registries[:i], registries[i+1:]...)
}
}
return registries
}
func createDockerConfigWithRegistries(registries []string) error {
if err := ensureDockerConfigFileExists(); err != nil {
return err
}
cfg := map[string]interface{}{
"insecure-registries": registries,
}
return writeDockerConfigToFile(cfg)
}
func updateDockerConfigWithRegistries(registries []string) error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
var cfg interface{}
if len(f) == 0 {
cfg = map[string]interface{}{
"insecure-registries": registries,
}
} else {
err = json.Unmarshal(f, &cfg)
if err != nil {
return errors.Wrapf(err, "unmarshal docker daemon config failed")
}
}
if v, ok := cfg.(map[string]interface{}); ok {
registries = handleInsecureRegistries(v, registries)
}
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
if err = os.WriteFile(DockerDaemonConfigFilePath, b, DefaultFileMode); err != nil {
return err
}
return nil
}
func handleInsecureRegistries(v map[string]interface{}, registries []string) []string {
if v == nil {
return registries
}
if _, ok := v["insecure-registries"]; ok {
return handleExistingInsecureRegistries(v, registries)
} else {
v["insecure-registries"] = registries
}
return registries
}
func handleExistingInsecureRegistries(v map[string]interface{}, registries []string) []string {
if v == nil {
log.Warnf("Received nil map for insecure registries configuration")
return registries
}
if vr, ok := v["insecure-registries"].([]interface{}); ok {
var configRegistries []string
for _, r := range vr {
if t, ok := r.(string); ok {
configRegistries = append(configRegistries, t)
} else {
log.Warnf("Registry configuration is not a string, got: %T", r)
}
}
registries = append(registries, configRegistries...)
registries = utils.UniqueStringSlice(registries)
v["insecure-registries"] = registries
}
return registries
}
func ConfigCgroupDriver(driver string) error {
log.Infof("config docker cgroup driver, driver: %s", driver)
exceptDriver := fmt.Sprintf("native.cgroupdriver=%s", driver)
log.Infof("ensure docker cgroup driver is %s, if not, change it in %s", driver, DockerDaemonConfigFilePath)
if !utils.Exists(DockerDaemonConfigFilePath) {
return createDockerConfigWithCgroupDriver(exceptDriver)
}
return updateDockerConfigWithCgroupDriver(driver, exceptDriver)
}
func createDockerConfigWithCgroupDriver(exceptDriver string) error {
if err := ensureDockerConfigFileExists(); err != nil {
return err
}
cfg := map[string]interface{}{
"exec-opts": []string{exceptDriver},
}
return writeDockerConfig(cfg)
}
func updateDockerConfigWithCgroupDriver(driver, exceptDriver string) error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
var cfg interface{}
if len(f) == 0 {
cfg = map[string]interface{}{
"exec-opts": []string{exceptDriver},
}
} else {
err = json.Unmarshal(f, &cfg)
if err != nil {
return errors.Wrapf(err, "unmarshal docker daemon config failed")
}
}
if v, ok := cfg.(map[string]interface{}); ok {
if shouldReturn := handleExecOpts(v, driver, exceptDriver); shouldReturn {
return nil
}
}
return writeDockerConfigSimple(cfg)
}
func handleExecOpts(v map[string]interface{}, driver, exceptDriver string) bool {
if v == nil {
return false
}
if opts, ok := v["exec-opts"].([]interface{}); ok {
for i, opt := range opts {
if shouldReturn := processOpt(opt, opts, i, driver, exceptDriver); shouldReturn {
return true
}
}
} else {
v["exec-opts"] = []string{exceptDriver}
}
return false
}
func processOpt(opt interface{}, opts []interface{}, i int, driver, exceptDriver string) bool {
if t, ok := opt.(string); ok {
if t == exceptDriver {
log.Debugf("docker cgroup driver is %s, no need to change", driver)
return true
}
if strings.Contains(t, "native.cgroupdriver") {
updateCgroupDriverValue(t, opts, i, driver)
}
} else {
log.Warnf("Docker exec-opts configuration is not a string, got: %T", opt)
}
return false
}
func updateCgroupDriverValue(t string, opts []interface{}, i int, driver string) {
op := strings.Split(t, "=")
if len(op) >= MinSplitParts {
op[1] = driver
opts[i] = strings.Join(op, "=")
}
}
func ConfigRuntime(runtime string) error {
log.Infof("config docker runtime, runtime: %s", runtime)
log.Infof("ensure docker default runtime is %s, if not, change it in %s", runtime, DockerDaemonConfigFilePath)
if !utils.Exists(DockerDaemonConfigFilePath) {
return createDockerRuntimeConfig(runtime)
}
return updateDockerRuntimeConfig(runtime)
}
func createDockerRuntimeConfig(runtime string) error {
log.Infof("docker daemon config file %s not found, create it", DockerDaemonConfigFilePath)
_, err := os.OpenFile(DockerDaemonConfigFilePath, os.O_RDONLY|os.O_CREATE, DefaultFileMode)
if err != nil {
return errors.Wrapf(err, "create docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
cfg := buildRuntimeConfig(runtime)
return writeDockerConfig(cfg)
}
func updateDockerRuntimeConfig(runtime string) error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
cfg := parseConfigOrBuildNew(runtime, f)
if v, ok := cfg.(map[string]interface{}); ok {
updateRuntimeConfiguration(v, runtime)
}
return writeDockerConfig(cfg)
}
func buildRuntimeConfig(runtime string) map[string]interface{} {
if runtime == "runc" {
return map[string]interface{}{
"default-runtime": runtime,
}
}
return map[string]interface{}{
"default-runtime": runtime,
"runtimes": map[string]interface{}{
runtime: map[string]interface{}{
"path": runtimeKeyPathMap[runtime],
},
},
}
}
func parseConfigOrBuildNew(runtime string, f []byte) interface{} {
var cfg interface{}
if len(f) == 0 {
return buildRuntimeConfig(runtime)
}
err := json.Unmarshal(f, &cfg)
if err != nil {
return buildRuntimeConfig(runtime)
}
return cfg
}
func updateRuntimeConfiguration(v map[string]interface{}, runtime string) {
if v == nil {
return
}
if defaultRuntime, ok := v["default-runtime"]; ok {
if defaultRuntime == runtime {
log.Debugf("docker runtime is %s, no need to change", runtime)
} else {
v["default-runtime"] = runtime
}
} else {
v["default-runtime"] = runtime
}
if runtimes, ok := v["runtimes"].(map[string]interface{}); ok {
if _, ok := runtimes[runtime]; ok {
log.Debugf("runtime %s is exists, no need to add", runtime)
}
if runtime != "runc" {
runtimes[runtime] = map[string]interface{}{
"path": runtimeKeyPathMap[runtime],
}
}
} else {
if runtime != "runc" {
v["runtimes"] = map[string]interface{}{
runtime: map[string]interface{}{
"path": runtimeKeyPathMap[runtime],
},
}
}
}
}
func writeDockerConfigToFile(cfg interface{}) error {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return errors.Wrapf(err, "marshal docker daemon config failed")
}
err = os.WriteFile(DockerDaemonConfigFilePath, data, DefaultFileMode)
if err != nil {
return errors.Wrapf(err, "write docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
return nil
}
func writeDockerConfig(cfg interface{}) error {
return writeDockerConfigToFile(cfg)
}
func writeDockerConfigSimple(cfg interface{}) error {
b, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(DockerDaemonConfigFilePath, b, DefaultFileMode); err != nil {
return err
}
return nil
}
func ConfigDataRoot(dataRoot string) error {
log.Infof("config docker data root, data root: %s", dataRoot)
if !utils.Exists(DockerDaemonConfigFilePath) {
return createDockerDataRootConfig(dataRoot)
}
return updateDockerDataRootConfig(dataRoot)
}
func createDockerDataRootConfig(dataRoot string) error {
if err := ensureDockerConfigFileExists(); err != nil {
return err
}
cfg := map[string]interface{}{
"data-root": dataRoot,
}
return writeDockerConfig(cfg)
}
func updateDockerDataRootConfig(dataRoot string) error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
cfg, err := parseOrCreateConfig(f, dataRoot)
if err != nil {
return err
}
cfg = processConfigDataRoot(cfg, dataRoot)
return writeDockerConfigSimple(cfg)
}
func parseOrCreateConfig(f []byte, dataRoot string) (interface{}, error) {
var cfg interface{}
if len(f) == 0 {
cfg = map[string]interface{}{
"data-root": dataRoot,
}
} else {
err := json.Unmarshal(f, &cfg)
if err != nil {
return nil, errors.Wrapf(err, "unmarshal docker daemon config failed")
}
}
return cfg, nil
}
func processConfigDataRoot(cfg interface{}, dataRoot string) interface{} {
if v, ok := cfg.(map[string]interface{}); ok && v != nil {
if root, ok := v["data-root"]; ok {
if root == dataRoot {
log.Debugf("docker data-root is %s, no need to change", dataRoot)
}
} else {
v["data-root"] = dataRoot
}
}
return cfg
}
func ConfigDockerTls(tlsHost string) error {
log.Infof("config docker tls, tls host: %s", tlsHost)
if tlsHost == "" {
tlsHost = "127.0.0.1"
}
if err := setupTLSCerts(tlsHost); err != nil {
return err
}
if !utils.Exists(DockerDaemonConfigFilePath) {
return createDockerTLSConfig()
}
return updateDockerTLSConfig()
}
func setupTLSCerts(tlsHost string) error {
if !utils.Exists("/etc/docker/certs") {
err := os.MkdirAll("/etc/docker/certs", DefaultDirFileMode)
if err != nil {
return errors.Wrapf(err, "create docker certs dir failed")
}
}
err := os.WriteFile("/etc/docker/certs/tlscert.sh", []byte(tlsCertScript), DefaultDirFileMode)
if err != nil {
return errors.Wrapf(err, "write tlscert.sh failed")
}
executor := &exec.CommandExecutor{}
output, err := executor.ExecuteCommandWithCombinedOutput("/bin/sh", "-c",
"cd /etc/docker/certs && ./tlscert.sh "+tlsHost)
if err != nil {
return errors.Wrapf(err, "generate docker tls cert failed, output: %s, err: %v", output, err)
}
output, err = executor.ExecuteCommandWithCombinedOutput("/bin/sh", "-c",
"echo 'export DOCKER_CONFIG=/etc/docker/certs' >> /etc/profile")
if err != nil {
log.Warnf("export DOCKER_CONFIG=/etc/docker/certs to /etc/profile failed, output: %s, err: %v", output, err)
}
log.Debugf("export DOCKER_CONFIG=/etc/docker/certs to /etc/profile, output: %s", output)
output, err = executor.ExecuteCommandWithCombinedOutput("/bin/bash", "-c", "source /etc/profile")
if err != nil {
log.Warnf("source /etc/profile failed, output: %s, err: %v", output, err)
}
return nil
}
func createDockerTLSConfig() error {
if err := ensureDockerConfigFileExists(); err != nil {
return err
}
cfg := buildTLSConfig()
return writeDockerConfig(cfg)
}
func updateDockerTLSConfig() error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
var cfg interface{}
if len(f) == 0 {
cfg = buildTLSConfig()
return writeDockerConfig(cfg)
} else {
err = json.Unmarshal(f, &cfg)
if err != nil {
return errors.Wrapf(err, "unmarshal docker daemon config failed")
}
}
if v, ok := cfg.(map[string]interface{}); ok {
updateTLSConfig(v)
}
return writeDockerConfigSimple(cfg)
}
func buildTLSConfig() map[string]interface{} {
return map[string]interface{}{
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/certs/ca.pem",
"tlscert": "/etc/docker/certs/server-cert.pem",
"tlskey": "/etc/docker/certs/server-key.pem",
"hosts": []string{"tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"},
}
}
func updateTLSConfig(v map[string]interface{}) {
if v == nil {
return
}
v["tls"] = true
v["tlsverify"] = true
v["tlscacert"] = "/etc/docker/certs/ca.pem"
v["tlscert"] = "/etc/docker/certs/server-cert.pem"
v["tlskey"] = "/etc/docker/certs/server-key.pem"
if hosts := v["hosts"]; hosts != nil {
if h, ok := hosts.([]interface{}); ok {
newHosts := updateHostsList(h)
v["hosts"] = newHosts
}
} else {
v["hosts"] = []interface{}{
"unix:///var/run/docker.sock",
"tcp://0.0.0.0:2376",
}
}
}
func updateHostsList(hosts []interface{}) []interface{} {
hasUnixSocket := false
hasTcpEndpoint := false
for _, host := range hosts {
if hostStr, ok := host.(string); ok {
if hostStr == "unix:///var/run/docker.sock" {
hasUnixSocket = true
} else if hostStr == "tcp://0.0.0.0:2376" {
hasTcpEndpoint = true
} else {
}
} else {
log.Warnf("Docker hosts configuration is not a string, got: %T", host)
}
}
newHosts := hosts
if !hasUnixSocket {
newHosts = append(newHosts, "unix:///var/run/docker.sock")
}
if !hasTcpEndpoint {
newHosts = append(newHosts, "tcp://0.0.0.0:2376")
}
return newHosts
}
func ensureDockerConfigFileExists() error {
if !utils.Exists(filepath.Dir(DockerDaemonConfigFilePath)) {
err := os.MkdirAll(filepath.Dir(DockerDaemonConfigFilePath), DefaultDirFileMode)
if err != nil {
return errors.Wrapf(err, "create docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
}
log.Infof("docker daemon config file %s not found, create it", DockerDaemonConfigFilePath)
_, err := os.OpenFile(DockerDaemonConfigFilePath, os.O_RDONLY|os.O_CREATE, DefaultFileMode)
if err != nil {
return errors.Wrapf(err, "create docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
return nil
}
func BaseConfig() error {
if !utils.Exists(DockerDaemonConfigFilePath) {
return createBaseConfig()
}
return updateBaseConfig()
}
func createBaseConfig() error {
if err := ensureDockerConfigFileExists(); err != nil {
return err
}
cfg := buildBaseConfig()
return writeDockerConfig(cfg)
}
func updateBaseConfig() error {
f, err := os.ReadFile(DockerDaemonConfigFilePath)
if err != nil {
return errors.Wrapf(err, "read docker daemon config file %s failed", DockerDaemonConfigFilePath)
}
var cfg interface{}
if len(f) == 0 {
cfg = buildBaseConfig()
} else {
err = json.Unmarshal(f, &cfg)
if err != nil {
return errors.Wrapf(err, "unmarshal docker daemon config failed")
}
}
if v, ok := cfg.(map[string]interface{}); ok {
applyBaseConfigDefaults(v)
}
return writeDockerConfigSimple(cfg)
}
func buildBaseConfig() map[string]interface{} {
return map[string]interface{}{
"log-driver": "json-file",
"log-opts": map[string]interface{}{
"max-size": "100m",
},
}
}
func applyBaseConfigDefaults(v map[string]interface{}) {
if v == nil {
return
}
if _, ok := v["log-driver"]; !ok {
v["log-driver"] = "json-file"
}
if _, ok := v["log-opts"]; !ok {
v["log-opts"] = map[string]interface{}{
"max-size": "100m",
}
} else {
if logOpts, ok := v["log-opts"].(map[string]interface{}); ok {
if _, ok := logOpts["max-size"]; !ok {
logOpts["max-size"] = "100m"
}
}
}
}
func WaitDockerReady() error {
ctx, cancel := context.WithTimeout(context.Background(), DockerReadyTimeoutMinutes*time.Minute)
defer cancel()
err := wait.PollImmediateUntil(DockerReadyPollInterval, func() (bool, error) {
log.Debugf("Waiting for Docker to be ready")
_, err := NewDockerClient()
if err == nil {
return true, nil
}
log.Warnf("Docker is not available: %v", err)
return false, nil
}, ctx.Done())
if err != nil {
log.Errorf("Failed to wait Docker available: %v", err)
return errors.Wrapf(err, "failed to wait Docker available")
}
return nil
}