github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/engine/docker/helper.go (about)

     1  package docker
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  	"text/template"
    18  
    19  	"github.com/docker/distribution/reference"
    20  	dockertypes "github.com/docker/docker/api/types"
    21  	"github.com/docker/docker/api/types/blkiodev"
    22  	dockercontainer "github.com/docker/docker/api/types/container"
    23  	dockerapi "github.com/docker/docker/client"
    24  	"github.com/docker/docker/pkg/archive"
    25  	"github.com/docker/docker/pkg/stdcopy"
    26  	"github.com/docker/docker/registry"
    27  	"github.com/docker/go-units"
    28  
    29  	corecluster "github.com/projecteru2/core/cluster"
    30  	"github.com/projecteru2/core/engine"
    31  	enginetypes "github.com/projecteru2/core/engine/types"
    32  	"github.com/projecteru2/core/log"
    33  	"github.com/projecteru2/core/types"
    34  	coretypes "github.com/projecteru2/core/types"
    35  	"github.com/projecteru2/core/utils"
    36  )
    37  
    38  type fuckDockerStream struct {
    39  	conn net.Conn
    40  	buf  io.Reader
    41  }
    42  
    43  func (f fuckDockerStream) Read(p []byte) (n int, err error) {
    44  	return f.buf.Read(p)
    45  }
    46  
    47  func (f fuckDockerStream) Close() error {
    48  	return f.conn.Close()
    49  }
    50  
    51  func mergeStream(stream io.ReadCloser) io.Reader {
    52  	outr, outw := io.Pipe()
    53  
    54  	go func() {
    55  		defer stream.Close()
    56  		_, err := stdcopy.StdCopy(outw, outw, stream)
    57  		_ = outw.CloseWithError(err)
    58  	}()
    59  
    60  	return outr
    61  }
    62  
    63  // FuckDockerStream will copy docker stream to stdout and err
    64  func FuckDockerStream(stream dockertypes.HijackedResponse) io.ReadCloser {
    65  	outr := mergeStream(io.NopCloser(stream.Reader))
    66  	return fuckDockerStream{stream.Conn, outr}
    67  }
    68  
    69  // make mount paths
    70  // 使用volumes, 参数格式跟docker一样
    71  // volumes:
    72  //   - "/foo-data:$SOMEENV/foodata:rw"
    73  func makeMountPaths(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions, resourceOpts *engine.VirtualizationResource) ([]string, map[string]struct{}) {
    74  	binds := []string{}
    75  	volumes := make(map[string]struct{})
    76  
    77  	var expandENV = func(env string) string {
    78  		envMap := map[string]string{}
    79  		for _, env := range opts.Env {
    80  			parts := strings.Split(env, "=")
    81  			envMap[parts[0]] = parts[1]
    82  		}
    83  		return envMap[env]
    84  	}
    85  
    86  	for _, path := range resourceOpts.Volumes {
    87  		expanded := os.Expand(path, expandENV)
    88  		parts := strings.Split(expanded, ":")
    89  		if len(parts) == 2 {
    90  			binds = append(binds, fmt.Sprintf("%s:%s:rw", parts[0], parts[1]))
    91  			volumes[parts[1]] = struct{}{}
    92  		} else if len(parts) >= 3 {
    93  			binds = append(binds, fmt.Sprintf("%s:%s:%s", parts[0], parts[1], parts[2]))
    94  			volumes[parts[1]] = struct{}{}
    95  			if len(parts) == 4 {
    96  				log.WithFunc("engine.docker.makeMountPaths").Warn(ctx, "docker engine not support volume with size limit")
    97  			}
    98  		}
    99  	}
   100  
   101  	return binds, volumes
   102  }
   103  
   104  func makeResourceSetting(cpu float64, memory int64, cpuMap map[string]int64, numaNode string, IOPSOptions map[string]string, remap bool) dockercontainer.Resources {
   105  	resource := dockercontainer.Resources{}
   106  
   107  	resource.CPUQuota = 0
   108  	resource.CPUShares = defaultCPUShare
   109  	resource.CPUPeriod = corecluster.CPUPeriodBase
   110  	if cpu > 0 {
   111  		resource.CPUQuota = int64(cpu * float64(corecluster.CPUPeriodBase))
   112  	} else if cpu == -1 {
   113  		resource.CPUQuota = -1
   114  	}
   115  
   116  	if len(cpuMap) > 0 {
   117  		cpuIDs := []string{}
   118  		for cpuID := range cpuMap {
   119  			cpuIDs = append(cpuIDs, cpuID)
   120  		}
   121  		resource.CpusetCpus = strings.Join(cpuIDs, ",")
   122  		// numaNode will empty or numaNode
   123  		resource.CpusetMems = numaNode
   124  
   125  		if remap {
   126  			resource.CPUShares = int64(1024)
   127  		} else {
   128  			// unrestrained cpu quota for binding
   129  			resource.CPUQuota = -1
   130  			// cpu share for fragile pieces
   131  			if _, divpart := math.Modf(cpu); divpart > 0 {
   132  				resource.CPUShares = int64(math.Round(float64(1024) * divpart))
   133  			}
   134  		}
   135  	}
   136  	resource.Memory = memory
   137  	resource.MemorySwap = memory
   138  	resource.MemoryReservation = memory / 2
   139  	if memory != 0 && memory/2 < int64(units.MiB*4) {
   140  		resource.MemoryReservation = int64(units.MiB * 4)
   141  	}
   142  
   143  	if len(IOPSOptions) > 0 {
   144  		var readIOPSDevices, writeIOPSDevices, readBPSDevices, writeBPSDevices []*blkiodev.ThrottleDevice
   145  		for device, options := range IOPSOptions {
   146  			parts := strings.Split(options, ":")
   147  			for len(parts) < 4 {
   148  				parts = append(parts, "0")
   149  			}
   150  			var readIOPS, writeIOPS, readBPS, writeBPS int64
   151  			readIOPS, _ = utils.ParseRAMInHuman(parts[0])
   152  			writeIOPS, _ = utils.ParseRAMInHuman(parts[1])
   153  			readBPS, _ = utils.ParseRAMInHuman(parts[2])
   154  			writeBPS, _ = utils.ParseRAMInHuman(parts[3])
   155  
   156  			readIOPSDevices = append(readIOPSDevices, &blkiodev.ThrottleDevice{
   157  				Path: device,
   158  				Rate: uint64(readIOPS),
   159  			})
   160  			writeIOPSDevices = append(writeIOPSDevices, &blkiodev.ThrottleDevice{
   161  				Path: device,
   162  				Rate: uint64(writeIOPS),
   163  			})
   164  			readBPSDevices = append(readBPSDevices, &blkiodev.ThrottleDevice{
   165  				Path: device,
   166  				Rate: uint64(readBPS),
   167  			})
   168  			writeBPSDevices = append(writeBPSDevices, &blkiodev.ThrottleDevice{
   169  				Path: device,
   170  				Rate: uint64(writeBPS),
   171  			})
   172  		}
   173  		resource.BlkioDeviceReadIOps = readIOPSDevices
   174  		resource.BlkioDeviceWriteIOps = writeIOPSDevices
   175  		resource.BlkioDeviceReadBps = readBPSDevices
   176  		resource.BlkioDeviceWriteBps = writeBPSDevices
   177  	}
   178  
   179  	return resource
   180  }
   181  
   182  // 只要一个image的前面, tag不要
   183  func normalizeImage(image string) string {
   184  	if strings.Contains(image, ":") {
   185  		t := strings.Split(image, ":")
   186  		return t[0]
   187  	}
   188  	return image
   189  }
   190  
   191  // image begin
   192  // MakeAuthConfigFromRemote Calculate encoded AuthConfig from registry and eru-core config
   193  // See https://github.com/docker/cli/blob/16cccc30f95c8163f0749eba5a2e80b807041342/cli/command/registry.go#L67
   194  func makeEncodedAuthConfigFromRemote(authConfigs map[string]coretypes.AuthConfig, remote string) (string, error) {
   195  	ref, err := reference.ParseNormalizedNamed(remote)
   196  	if err != nil {
   197  		return "", err
   198  	}
   199  
   200  	// Resolve the Repository name from fqn to RepositoryInfo
   201  	repoInfo, err := registry.ParseRepositoryInfo(ref)
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  
   206  	serverAddress := repoInfo.Index.Name
   207  	if authConfig, exists := authConfigs[serverAddress]; exists {
   208  		if encodedAuth, err := encodeAuthToBase64(authConfig); err == nil {
   209  			return encodedAuth, nil
   210  		}
   211  		return "", err
   212  	}
   213  	return "dummy", nil
   214  }
   215  
   216  // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
   217  // See https://github.com/docker/cli/blob/master/cli/command/registry.go#L41
   218  func encodeAuthToBase64(authConfig coretypes.AuthConfig) (string, error) {
   219  	buf, err := json.Marshal(authConfig)
   220  	if err != nil {
   221  		return "", err
   222  	}
   223  	return base64.URLEncoding.EncodeToString(buf), nil
   224  }
   225  
   226  // Image tag
   227  // 格式严格按照 Hub/HubPrefix/appname:tag 来
   228  func createImageTag(config types.DockerConfig, appname, tag string) string {
   229  	prefix := strings.Trim(config.Namespace, "/")
   230  	if prefix == "" {
   231  		return fmt.Sprintf("%s/%s:%s", config.Hub, appname, tag)
   232  	}
   233  	return fmt.Sprintf("%s/%s/%s:%s", config.Hub, prefix, appname, tag)
   234  }
   235  
   236  func makeCommonPart(build *enginetypes.Build) (string, error) {
   237  	tmpl := template.Must(template.New("common").Parse(commonTmpl))
   238  	out := bytes.Buffer{}
   239  	if err := tmpl.Execute(&out, build); err != nil {
   240  		return "", err
   241  	}
   242  	return out.String(), nil
   243  }
   244  
   245  func makeUserPart(opts *enginetypes.BuildContentOptions) (string, error) {
   246  	tmpl := template.Must(template.New("user").Parse(userTmpl))
   247  	out := bytes.Buffer{}
   248  	if err := tmpl.Execute(&out, opts); err != nil {
   249  		return "", err
   250  	}
   251  	return out.String(), nil
   252  }
   253  
   254  func makeMainPart(_ *enginetypes.BuildContentOptions, build *enginetypes.Build, from string, commands, copys []string) (string, error) {
   255  	var buildTmpl []string
   256  	common, err := makeCommonPart(build)
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  	buildTmpl = append(buildTmpl, from, common)
   261  	if len(copys) > 0 {
   262  		buildTmpl = append(buildTmpl, copys...)
   263  	}
   264  	if len(commands) > 0 {
   265  		buildTmpl = append(buildTmpl, commands...)
   266  	}
   267  	return strings.Join(buildTmpl, "\n"), nil
   268  }
   269  
   270  // Dockerfile
   271  func createDockerfile(dockerfile, buildDir string) error {
   272  	f, err := os.Create(filepath.Join(buildDir, "Dockerfile"))
   273  	if err != nil {
   274  		return err
   275  	}
   276  	defer f.Close()
   277  	_, err = f.WriteString(dockerfile)
   278  	return err
   279  }
   280  
   281  // CreateTarStream create a tar stream
   282  func CreateTarStream(path string) (io.ReadCloser, error) {
   283  	tarOpts := &archive.TarOptions{
   284  		ExcludePatterns: []string{},
   285  		IncludeFiles:    []string{"."},
   286  		Compression:     archive.Uncompressed,
   287  		NoLchown:        true,
   288  	}
   289  	return archive.TarWithOptions(path, tarOpts)
   290  }
   291  
   292  // GetIP Get hostIP
   293  func GetIP(ctx context.Context, daemonHost string) string {
   294  	u, err := url.Parse(daemonHost)
   295  	if err != nil {
   296  		log.WithFunc("engine.docker.GetIP").Errorf(ctx, err, "GetIP %s failed", daemonHost)
   297  		return ""
   298  	}
   299  	return u.Hostname()
   300  }
   301  
   302  func makeDockerClient(_ context.Context, config coretypes.Config, client *http.Client, endpoint string) (engine.API, error) {
   303  	cli, err := dockerapi.NewClientWithOpts(
   304  		dockerapi.WithHost(endpoint),
   305  		dockerapi.WithVersion(config.Docker.APIVersion),
   306  		dockerapi.WithHTTPClient(client))
   307  	if err != nil {
   308  		return nil, err
   309  	}
   310  	return &Engine{cli, config}, nil
   311  }
   312  
   313  func useCNI(labels map[string]string) bool {
   314  	for k, v := range labels {
   315  		if k == "cni" && v == "1" {
   316  			return true
   317  		}
   318  	}
   319  	return false
   320  }