github.com/containerd/nerdctl@v1.7.7/pkg/infoutil/infoutil.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package infoutil
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"os/exec"
    24  	"runtime"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/Masterminds/semver/v3"
    29  	"github.com/containerd/containerd"
    30  	ptypes "github.com/containerd/containerd/protobuf/types"
    31  	"github.com/containerd/containerd/services/introspection"
    32  	"github.com/containerd/log"
    33  	"github.com/containerd/nerdctl/pkg/buildkitutil"
    34  	"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
    35  	"github.com/containerd/nerdctl/pkg/inspecttypes/native"
    36  	"github.com/containerd/nerdctl/pkg/logging"
    37  	"github.com/containerd/nerdctl/pkg/version"
    38  )
    39  
    40  func NativeDaemonInfo(ctx context.Context, client *containerd.Client) (*native.DaemonInfo, error) {
    41  	introService := client.IntrospectionService()
    42  	plugins, err := introService.Plugins(ctx, nil)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	server, err := introService.Server(ctx, &ptypes.Empty{})
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	versionService := client.VersionService()
    51  	version, err := versionService.Version(ctx, &ptypes.Empty{})
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	daemonInfo := &native.DaemonInfo{
    57  		Plugins: plugins,
    58  		Server:  server,
    59  		Version: version,
    60  	}
    61  	return daemonInfo, nil
    62  }
    63  
    64  func Info(ctx context.Context, client *containerd.Client, snapshotter, cgroupManager string) (*dockercompat.Info, error) {
    65  	daemonVersion, err := client.Version(ctx)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	introService := client.IntrospectionService()
    70  	daemonIntro, err := introService.Server(ctx, &ptypes.Empty{})
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	snapshotterPlugins, err := GetSnapshotterNames(ctx, introService)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	var info dockercompat.Info
    80  	info.ID = daemonIntro.UUID
    81  	// Storage drivers and logging drivers are not really Server concept for nerdctl, but mimics `docker info` output
    82  	info.Driver = snapshotter
    83  	info.Plugins.Log = logging.Drivers()
    84  	info.Plugins.Storage = snapshotterPlugins
    85  	info.SystemTime = time.Now().Format(time.RFC3339Nano)
    86  	info.LoggingDriver = "json-file" // hard-coded
    87  	info.CgroupDriver = cgroupManager
    88  	info.CgroupVersion = CgroupsVersion()
    89  	info.KernelVersion = UnameR()
    90  	info.OperatingSystem = DistroName()
    91  	info.OSType = runtime.GOOS
    92  	info.Architecture = UnameM()
    93  	info.Name, err = os.Hostname()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	info.ServerVersion = daemonVersion.Version
    98  	fulfillPlatformInfo(&info)
    99  	return &info, nil
   100  }
   101  
   102  func GetSnapshotterNames(ctx context.Context, introService introspection.Service) ([]string, error) {
   103  	var names []string
   104  	plugins, err := introService.Plugins(ctx, nil)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	for _, p := range plugins.Plugins {
   109  		if strings.HasPrefix(p.Type, "io.containerd.snapshotter.") && p.InitErr == nil {
   110  			names = append(names, p.ID)
   111  		}
   112  	}
   113  	return names, nil
   114  }
   115  
   116  func ClientVersion() dockercompat.ClientVersion {
   117  	return dockercompat.ClientVersion{
   118  		Version:   version.GetVersion(),
   119  		GitCommit: version.GetRevision(),
   120  		GoVersion: runtime.Version(),
   121  		Os:        runtime.GOOS,
   122  		Arch:      runtime.GOARCH,
   123  		Components: []dockercompat.ComponentVersion{
   124  			buildctlVersion(),
   125  		},
   126  	}
   127  }
   128  
   129  func ServerVersion(ctx context.Context, client *containerd.Client) (*dockercompat.ServerVersion, error) {
   130  	daemonVersion, err := client.Version(ctx)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	v := &dockercompat.ServerVersion{
   136  		Components: []dockercompat.ComponentVersion{
   137  			{
   138  				Name:    "containerd",
   139  				Version: daemonVersion.Version,
   140  				Details: map[string]string{"GitCommit": daemonVersion.Revision},
   141  			},
   142  			runcVersion(),
   143  		},
   144  	}
   145  	return v, nil
   146  }
   147  
   148  func ServerSemVer(ctx context.Context, client *containerd.Client) (*semver.Version, error) {
   149  	v, err := client.Version(ctx)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	sv, err := semver.NewVersion(v.Version)
   154  	if err != nil {
   155  		return nil, fmt.Errorf("failed to parse the containerd version %q: %w", v.Version, err)
   156  	}
   157  	return sv, nil
   158  }
   159  
   160  func buildctlVersion() dockercompat.ComponentVersion {
   161  	buildctlBinary, err := buildkitutil.BuildctlBinary()
   162  	if err != nil {
   163  		log.L.Warnf("unable to determine buildctl version: %s", err.Error())
   164  		return dockercompat.ComponentVersion{Name: "buildctl"}
   165  	}
   166  
   167  	stdout, err := exec.Command(buildctlBinary, "--version").Output()
   168  	if err != nil {
   169  		log.L.Warnf("unable to determine buildctl version: %s", err.Error())
   170  		return dockercompat.ComponentVersion{Name: "buildctl"}
   171  	}
   172  
   173  	v, err := parseBuildctlVersion(stdout)
   174  	if err != nil {
   175  		log.L.Warn(err)
   176  		return dockercompat.ComponentVersion{Name: "buildctl"}
   177  	}
   178  	return *v
   179  }
   180  
   181  func parseBuildctlVersion(buildctlVersionStdout []byte) (*dockercompat.ComponentVersion, error) {
   182  	fields := strings.Fields(strings.TrimSpace(string(buildctlVersionStdout)))
   183  	var v *dockercompat.ComponentVersion
   184  	switch len(fields) {
   185  	case 4:
   186  		v = &dockercompat.ComponentVersion{
   187  			Name:    fields[0],
   188  			Version: fields[2],
   189  			Details: map[string]string{"GitCommit": fields[3]},
   190  		}
   191  	case 3:
   192  		v = &dockercompat.ComponentVersion{
   193  			Name:    fields[0],
   194  			Version: fields[2],
   195  		}
   196  	default:
   197  		return nil, fmt.Errorf("unable to determine buildctl version, got %q", string(buildctlVersionStdout))
   198  	}
   199  	if v.Name != "buildctl" {
   200  		return nil, fmt.Errorf("unable to determine buildctl version, got %q", string(buildctlVersionStdout))
   201  	}
   202  	return v, nil
   203  }
   204  
   205  func runcVersion() dockercompat.ComponentVersion {
   206  	stdout, err := exec.Command("runc", "--version").Output()
   207  	if err != nil {
   208  		log.L.Warnf("unable to determine runc version: %s", err.Error())
   209  		return dockercompat.ComponentVersion{Name: "runc"}
   210  	}
   211  	v, err := parseRuncVersion(stdout)
   212  	if err != nil {
   213  		log.L.Warn(err)
   214  		return dockercompat.ComponentVersion{Name: "runc"}
   215  	}
   216  	return *v
   217  }
   218  
   219  func parseRuncVersion(runcVersionStdout []byte) (*dockercompat.ComponentVersion, error) {
   220  	var versionList = strings.Split(strings.TrimSpace(string(runcVersionStdout)), "\n")
   221  	firstLine := strings.Fields(versionList[0])
   222  	if len(firstLine) != 3 || firstLine[0] != "runc" {
   223  		return nil, fmt.Errorf("unable to determine runc version, got: %s", string(runcVersionStdout))
   224  	}
   225  	version := firstLine[2]
   226  
   227  	details := map[string]string{}
   228  	for _, detailsLine := range versionList[1:] {
   229  		detail := strings.SplitN(detailsLine, ":", 2)
   230  		if len(detail) != 2 {
   231  			log.L.Warnf("unable to determine one of runc details, got: %s, %d", detail, len(detail))
   232  			continue
   233  		}
   234  		switch strings.TrimSpace(detail[0]) {
   235  		case "commit":
   236  			details["GitCommit"] = strings.TrimSpace(detail[1])
   237  		}
   238  	}
   239  
   240  	return &dockercompat.ComponentVersion{
   241  		Name:    "runc",
   242  		Version: version,
   243  		Details: details,
   244  	}, nil
   245  }
   246  
   247  // BlockIOWeight return whether Block IO weight is supported or not
   248  func BlockIOWeight(cgroupManager string) bool {
   249  	var info dockercompat.Info
   250  	info.CgroupVersion = CgroupsVersion()
   251  	info.CgroupDriver = cgroupManager
   252  	mobySysInfo := mobySysInfo(&info)
   253  	// blkio weight is not available on cgroup v1 since kernel 5.0.
   254  	// On cgroup v2, blkio weight is implemented using io.weight
   255  	return mobySysInfo.BlkioWeight
   256  }