mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #42683 from thaJeztah/remove_lcow_step6
Remove LCOW (step 6)
This commit is contained in:
commit
51b06c6795
14 changed files with 170 additions and 308 deletions
|
@ -116,7 +116,7 @@ func TestFromScratch(t *testing.T) {
|
|||
}
|
||||
err := initializeStage(sb, cmd)
|
||||
|
||||
if runtime.GOOS == "windows" && !system.LCOWSupported() {
|
||||
if runtime.GOOS == "windows" {
|
||||
assert.Check(t, is.Error(err, "Linux containers are not supported on this system"))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -264,12 +264,6 @@ func (container *Container) WriteHostConfig() (*containertypes.HostConfig, error
|
|||
|
||||
// SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir
|
||||
func (container *Container) SetupWorkingDirectory(rootIdentity idtools.Identity) error {
|
||||
// TODO: LCOW Support. This will need revisiting.
|
||||
// We will need to do remote filesystem operations here.
|
||||
if container.OS != runtime.GOOS {
|
||||
return nil
|
||||
}
|
||||
|
||||
if container.Config.WorkingDir == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -727,14 +721,14 @@ func getConfigTargetPath(r *swarmtypes.ConfigReference) string {
|
|||
// CreateDaemonEnvironment creates a new environment variable slice for this container.
|
||||
func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
|
||||
// Setup environment
|
||||
os := container.OS
|
||||
if os == "" {
|
||||
os = runtime.GOOS
|
||||
ctrOS := container.OS
|
||||
if ctrOS == "" {
|
||||
ctrOS = runtime.GOOS
|
||||
}
|
||||
|
||||
// Figure out what size slice we need so we can allocate this all at once.
|
||||
envSize := len(container.Config.Env)
|
||||
if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") {
|
||||
if runtime.GOOS != "windows" {
|
||||
envSize += 2 + len(linkedEnv)
|
||||
}
|
||||
if tty {
|
||||
|
@ -743,7 +737,7 @@ func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string
|
|||
|
||||
env := make([]string, 0, envSize)
|
||||
if runtime.GOOS != "windows" {
|
||||
env = append(env, "PATH="+system.DefaultPathEnv(os))
|
||||
env = append(env, "PATH="+system.DefaultPathEnv(ctrOS))
|
||||
env = append(env, "HOSTNAME="+container.Config.Hostname)
|
||||
if tty {
|
||||
env = append(env, "TERM=xterm")
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/runconfig"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/selinux/go-selinux"
|
||||
|
@ -113,19 +114,17 @@ func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr
|
|||
img *image.Image
|
||||
imgID image.ID
|
||||
err error
|
||||
os = runtime.GOOS
|
||||
)
|
||||
|
||||
os := runtime.GOOS
|
||||
if opts.params.Config.Image != "" {
|
||||
img, err = daemon.imageService.GetImage(opts.params.Config.Image, opts.params.Platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if img.OS != "" {
|
||||
os = img.OS
|
||||
}
|
||||
os = img.OperatingSystem()
|
||||
imgID = img.ID()
|
||||
if isWindows && img.OS == "linux" {
|
||||
if !system.IsOSSupported(os) {
|
||||
return nil, errors.New("operating system on which parent image was created is not Windows")
|
||||
}
|
||||
} else if isWindows {
|
||||
|
|
|
@ -3,7 +3,6 @@ package daemon // import "github.com/docker/docker/daemon"
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/container"
|
||||
|
@ -14,20 +13,10 @@ import (
|
|||
|
||||
// createContainerOSSpecificSettings performs host-OS specific container create functionality
|
||||
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
|
||||
|
||||
if container.OS == runtime.GOOS {
|
||||
if containertypes.Isolation.IsDefault(hostConfig.Isolation) {
|
||||
// Make sure the host config has the default daemon isolation if not specified by caller.
|
||||
if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) {
|
||||
hostConfig.Isolation = daemon.defaultIsolation
|
||||
}
|
||||
} else {
|
||||
// LCOW must be a Hyper-V container as you can't run a shared kernel when one
|
||||
// is a Windows kernel, the other is a Linux kernel.
|
||||
if containertypes.Isolation.IsProcess(containertypes.Isolation(hostConfig.Isolation)) {
|
||||
return fmt.Errorf("process isolation is invalid for Linux containers on Windows")
|
||||
}
|
||||
hostConfig.Isolation = "hyperv"
|
||||
}
|
||||
parser := volumemounts.NewParser()
|
||||
for spec := range config.Volumes {
|
||||
|
||||
|
|
|
@ -510,17 +510,11 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
|
|||
// conditionalUnmountOnCleanup is a platform specific helper function called
|
||||
// during the cleanup of a container to unmount.
|
||||
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
|
||||
|
||||
// Bail out now for Linux containers
|
||||
if system.LCOWSupported() && container.OS != "windows" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if daemon.runAsHyperVContainer(container.HostConfig) {
|
||||
// We do not unmount if a Hyper-V container
|
||||
if !daemon.runAsHyperVContainer(container.HostConfig) {
|
||||
return daemon.Unmount(container)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return daemon.Unmount(container)
|
||||
}
|
||||
|
||||
func driverOptions(config *config.Config) []nwconfig.Option {
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
|
@ -13,7 +12,6 @@ import (
|
|||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/oci"
|
||||
"github.com/docker/docker/oci/caps"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
@ -33,8 +31,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !system.IsOSSupported(img.OperatingSystem()) {
|
||||
return nil, system.ErrNotSupportedOperatingSystem
|
||||
}
|
||||
|
||||
s := oci.DefaultOSSpec(img.OS)
|
||||
s := oci.DefaultSpec()
|
||||
|
||||
linkedEnv, err := daemon.setupLinkedContainers(c)
|
||||
if err != nil {
|
||||
|
@ -116,11 +117,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
if !mount.Writable {
|
||||
m.Options = append(m.Options, "ro")
|
||||
}
|
||||
if img.OS != runtime.GOOS {
|
||||
m.Type = "bind"
|
||||
m.Options = append(m.Options, "rbind")
|
||||
m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
|
||||
}
|
||||
s.Mounts = append(s.Mounts, m)
|
||||
}
|
||||
|
||||
|
@ -200,21 +196,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
NetworkSharedContainerName: networkSharedContainerID,
|
||||
}
|
||||
|
||||
switch img.OS {
|
||||
case "windows":
|
||||
if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "linux":
|
||||
if !system.LCOWSupported() {
|
||||
return nil, fmt.Errorf("Linux containers on Windows are not supported")
|
||||
}
|
||||
if err := daemon.createSpecLinuxFields(c, &s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported platform %q", img.OS)
|
||||
}
|
||||
|
||||
if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
||||
if b, err := json.Marshal(&s); err == nil {
|
||||
|
@ -222,7 +206,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return (*specs.Spec)(&s), nil
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// Sets the Windows-specific fields of the OCI spec
|
||||
|
@ -370,41 +354,6 @@ func (daemon *Daemon) setWindowsCredentialSpec(c *container.Container, s *specs.
|
|||
return nil
|
||||
}
|
||||
|
||||
// Sets the Linux-specific fields of the OCI spec
|
||||
// TODO: LCOW Support. We need to do a lot more pulling in what can
|
||||
// be pulled in from oci_linux.go.
|
||||
func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) error {
|
||||
s.Root = &specs.Root{
|
||||
Path: "rootfs",
|
||||
Readonly: c.HostConfig.ReadonlyRootfs,
|
||||
}
|
||||
|
||||
s.Hostname = c.Config.Hostname
|
||||
setLinuxDomainname(c, s)
|
||||
|
||||
if len(s.Process.Cwd) == 0 {
|
||||
s.Process.Cwd = `/`
|
||||
}
|
||||
s.Process.Args = append([]string{c.Path}, c.Args...)
|
||||
|
||||
// Note these are against the UVM.
|
||||
setResourcesInSpec(c, s, true) // LCOW is Hyper-V only
|
||||
|
||||
capabilities, err := caps.TweakCapabilities(caps.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged)
|
||||
if err != nil {
|
||||
return fmt.Errorf("linux spec capabilities: %v", err)
|
||||
}
|
||||
if err := oci.SetCapabilities(s, capabilities); err != nil {
|
||||
return fmt.Errorf("linux spec capabilities: %v", err)
|
||||
}
|
||||
devPermissions, err := oci.AppendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)
|
||||
if err != nil {
|
||||
return fmt.Errorf("linux runtime spec devices: %v", err)
|
||||
}
|
||||
s.Linux.Resources.Devices = devPermissions
|
||||
return nil
|
||||
}
|
||||
|
||||
func setResourcesInSpec(c *container.Container, s *specs.Spec, isHyperV bool) {
|
||||
// In s.Windows.Resources
|
||||
cpuShares := uint16(c.HostConfig.CPUShares)
|
||||
|
|
|
@ -3,7 +3,6 @@ package distribution // import "github.com/docker/docker/distribution"
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
|
@ -21,6 +20,7 @@ import (
|
|||
"github.com/docker/libtrust"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Config stores configuration for communicating
|
||||
|
@ -155,20 +155,12 @@ func (s *imageConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// fail immediately on Windows when downloading a non-Windows image
|
||||
// and vice versa. Exception on Windows if Linux Containers are enabled.
|
||||
if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" && !system.LCOWSupported() {
|
||||
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
|
||||
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
|
||||
}
|
||||
|
||||
os := unmarshalledConfig.OS
|
||||
if os == "" {
|
||||
os = runtime.GOOS
|
||||
}
|
||||
if !system.IsOSSupported(os) {
|
||||
return nil, system.ErrNotSupportedOperatingSystem
|
||||
return nil, errors.Wrapf(system.ErrNotSupportedOperatingSystem, "image operating system %q cannot be used on this platform", os)
|
||||
}
|
||||
return &specs.Platform{OS: os, Architecture: unmarshalledConfig.Architecture, Variant: unmarshalledConfig.Variant, OSVersion: unmarshalledConfig.OSVersion}, nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
|
@ -487,6 +486,14 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
|
|||
}
|
||||
|
||||
func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
||||
if platform != nil {
|
||||
// Early bath if the requested OS doesn't match that of the configuration.
|
||||
// This avoids doing the download, only to potentially fail later.
|
||||
if !system.IsOSSupported(platform.OS) {
|
||||
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", runtime.GOOS, platform.OS)
|
||||
}
|
||||
}
|
||||
|
||||
var verifiedManifest *schema1.Manifest
|
||||
verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
|
||||
if err != nil {
|
||||
|
@ -541,44 +548,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|||
descriptors = append(descriptors, layerDescriptor)
|
||||
}
|
||||
|
||||
// The v1 manifest itself doesn't directly contain an OS. However,
|
||||
// the history does, but unfortunately that's a string, so search through
|
||||
// all the history until hopefully we find one which indicates the OS.
|
||||
// supertest2014/nyan is an example of a registry image with schemav1.
|
||||
configOS := runtime.GOOS
|
||||
if system.LCOWSupported() {
|
||||
type config struct {
|
||||
Os string `json:"os,omitempty"`
|
||||
}
|
||||
for _, v := range verifiedManifest.History {
|
||||
var c config
|
||||
if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
|
||||
if c.Os != "" {
|
||||
configOS = c.Os
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In the situation that the API call didn't specify an OS explicitly, but
|
||||
// we support the operating system, switch to that operating system.
|
||||
// eg FROM supertest2014/nyan with no platform specifier, and docker build
|
||||
// with no --platform= flag under LCOW.
|
||||
requestedOS := ""
|
||||
if platform != nil {
|
||||
requestedOS = platform.OS
|
||||
} else if system.IsOSSupported(configOS) {
|
||||
requestedOS = configOS
|
||||
}
|
||||
|
||||
// Early bath if the requested OS doesn't match that of the configuration.
|
||||
// This avoids doing the download, only to potentially fail later.
|
||||
if !strings.EqualFold(configOS, requestedOS) {
|
||||
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
|
||||
}
|
||||
|
||||
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, configOS, descriptors, p.config.ProgressOutput)
|
||||
resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, runtime.GOOS, descriptors, p.config.ProgressOutput)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ Write-Host -ForegroundColor Red "-----------------------------------------------
|
|||
# DOCKER_STORAGE_OPTS comma-separated list of optional storage driver options for the daemon under test
|
||||
# examples:
|
||||
# DOCKER_STORAGE_OPTS="size=40G"
|
||||
# DOCKER_STORAGE_OPTS="lcow.globalmode=false,lcow.kernel=kernel.efi"
|
||||
#
|
||||
# SKIP_VALIDATION_TESTS if defined skips the validation tests
|
||||
#
|
||||
|
@ -191,7 +190,7 @@ Function Nuke-Everything {
|
|||
Stop-Process -name "tail" -Force -ErrorAction SilentlyContinue 2>&1 | Out-Null
|
||||
|
||||
# Detach any VHDs
|
||||
gwmi msvm_mountedstorageimage -namespace root/virtualization/v2 -ErrorAction SilentlyContinue | foreach-object {$_.DetachVirtualHardDisk() }
|
||||
gwmi msvm_mountedstorageimage -namespace root/virtualization/v2 -ErrorAction SilentlyContinue | foreach-Object {$_.DetachVirtualHardDisk() }
|
||||
|
||||
# Stop any compute processes
|
||||
Get-ComputeProcess | Stop-ComputeProcess -Force
|
||||
|
@ -602,10 +601,10 @@ Try {
|
|||
}
|
||||
|
||||
# Arguments: Allow setting optional storage-driver options
|
||||
# example usage: DOCKER_STORAGE_OPTS="lcow.globalmode=false,lcow.kernel=kernel.efi"
|
||||
# example usage: DDOCKER_STORAGE_OPTS="size=40G"
|
||||
if (-not ("$env:DOCKER_STORAGE_OPTS" -eq "")) {
|
||||
Write-Host -ForegroundColor Green "INFO: Running the daemon under test with storage-driver options ${env:DOCKER_STORAGE_OPTS}"
|
||||
$env:DOCKER_STORAGE_OPTS.Split(",") | ForEach {
|
||||
$env:DOCKER_STORAGE_OPTS.Split(",") | ForEach-Object {
|
||||
$dutArgs += "--storage-opt $_"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,9 +217,7 @@ func (s *saveSession) save(outStream io.Writer) error {
|
|||
|
||||
for _, l := range imageDescr.layers {
|
||||
// IMPORTANT: We use path, not filepath here to ensure the layers
|
||||
// in the manifest use Unix-style forward-slashes. Otherwise, a
|
||||
// Linux image saved from LCOW won't be able to be imported on
|
||||
// LCOL.
|
||||
// in the manifest use Unix-style forward-slashes.
|
||||
layers = append(layers, path.Join(l, legacyLayerFileName))
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,7 @@ func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
|||
|
||||
// DefaultSpec returns the default spec used by docker for the current Platform
|
||||
func DefaultSpec() specs.Spec {
|
||||
return DefaultOSSpec(runtime.GOOS)
|
||||
}
|
||||
|
||||
// DefaultOSSpec returns the spec for a given OS
|
||||
func DefaultOSSpec(osName string) specs.Spec {
|
||||
if osName == "windows" {
|
||||
if runtime.GOOS == "windows" {
|
||||
return DefaultWindowsSpec()
|
||||
}
|
||||
return DefaultLinuxSpec()
|
||||
|
@ -37,7 +32,7 @@ func DefaultWindowsSpec() specs.Spec {
|
|||
|
||||
// DefaultLinuxSpec create a default spec for running Linux containers
|
||||
func DefaultLinuxSpec() specs.Spec {
|
||||
s := specs.Spec{
|
||||
return specs.Spec{
|
||||
Version: specs.Version,
|
||||
Process: &specs.Process{
|
||||
Capabilities: &specs.LinuxCapabilities{
|
||||
|
@ -48,8 +43,7 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
},
|
||||
},
|
||||
Root: &specs.Root{},
|
||||
}
|
||||
s.Mounts = []specs.Mount{
|
||||
Mounts: []specs.Mount{
|
||||
{
|
||||
Destination: "/proc",
|
||||
Type: "proc",
|
||||
|
@ -92,9 +86,8 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
Source: "shm",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777"},
|
||||
},
|
||||
}
|
||||
|
||||
s.Linux = &specs.Linux{
|
||||
},
|
||||
Linux: &specs.Linux{
|
||||
MaskedPaths: []string{
|
||||
"/proc/asound",
|
||||
"/proc/acpi",
|
||||
|
@ -183,12 +176,6 @@ func DefaultLinuxSpec() specs.Spec {
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// For LCOW support, populate a blank Windows spec
|
||||
if runtime.GOOS == "windows" {
|
||||
s.Windows = &specs.Windows{}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -374,9 +374,6 @@ func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.Read
|
|||
return rebased
|
||||
}
|
||||
|
||||
// TODO @gupta-ak. These might have to be changed in the future to be
|
||||
// continuity driver aware as well to support LCOW.
|
||||
|
||||
// CopyResource performs an archive copy from the given source path to the
|
||||
// given destination path. The source path MUST exist and the destination
|
||||
// path's parent directory must exist.
|
||||
|
|
|
@ -28,8 +28,7 @@ type ContainerFS interface {
|
|||
// Driver combines both continuity's Driver and PathDriver interfaces with a Platform
|
||||
// field to determine the OS.
|
||||
type Driver interface {
|
||||
// OS returns the OS where the rootfs is located. Essentially,
|
||||
// runtime.GOOS for everything aside from LCOW, which is "linux"
|
||||
// OS returns the OS where the rootfs is located. Essentially, runtime.GOOS.
|
||||
OS() string
|
||||
|
||||
// Architecture returns the hardware architecture where the
|
||||
|
|
|
@ -4,11 +4,6 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||
func LCOWSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOSSupported determines if an operating system is supported by the host.
|
||||
func IsOSSupported(os string) bool {
|
||||
return strings.EqualFold(runtime.GOOS, os)
|
Loading…
Reference in a new issue