Add os_version and os_features to Image
| ... | ... |
@@ -138,6 +138,8 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 138 | 138 |
|
| 139 | 139 |
var history []image.History |
| 140 | 140 |
rootFS := image.NewRootFS() |
| 141 |
+ osVersion := "" |
|
| 142 |
+ var osFeatures []string |
|
| 141 | 143 |
|
| 142 | 144 |
if container.ImageID != "" {
|
| 143 | 145 |
img, err := daemon.imageStore.Get(container.ImageID) |
| ... | ... |
@@ -146,6 +148,8 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 146 | 146 |
} |
| 147 | 147 |
history = img.History |
| 148 | 148 |
rootFS = img.RootFS |
| 149 |
+ osVersion = img.OSVersion |
|
| 150 |
+ osFeatures = img.OSFeatures |
|
| 149 | 151 |
} |
| 150 | 152 |
|
| 151 | 153 |
l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) |
| ... | ... |
@@ -180,8 +184,10 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str |
| 180 | 180 |
Author: c.Author, |
| 181 | 181 |
Created: h.Created, |
| 182 | 182 |
}, |
| 183 |
- RootFS: rootFS, |
|
| 184 |
- History: history, |
|
| 183 |
+ RootFS: rootFS, |
|
| 184 |
+ History: history, |
|
| 185 |
+ OSFeatures: osFeatures, |
|
| 186 |
+ OSVersion: osVersion, |
|
| 185 | 187 |
}) |
| 186 | 188 |
|
| 187 | 189 |
if err != nil {
|
| ... | ... |
@@ -110,10 +110,7 @@ func verifyDaemonSettings(config *Config) error {
|
| 110 | 110 |
func checkSystem() error {
|
| 111 | 111 |
// Validate the OS version. Note that docker.exe must be manifested for this |
| 112 | 112 |
// call to return the correct version. |
| 113 |
- osv, err := system.GetOSVersion() |
|
| 114 |
- if err != nil {
|
|
| 115 |
- return err |
|
| 116 |
- } |
|
| 113 |
+ osv := system.GetOSVersion() |
|
| 117 | 114 |
if osv.MajorVersion < 10 {
|
| 118 | 115 |
return fmt.Errorf("This version of Windows does not support the docker daemon")
|
| 119 | 116 |
} |
| ... | ... |
@@ -135,10 +132,7 @@ func configureMaxThreads(config *Config) error {
|
| 135 | 135 |
|
| 136 | 136 |
func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) {
|
| 137 | 137 |
// TODO Windows: Remove this check once TP4 is no longer supported |
| 138 |
- osv, err := system.GetOSVersion() |
|
| 139 |
- if err != nil {
|
|
| 140 |
- return nil, err |
|
| 141 |
- } |
|
| 138 |
+ osv := system.GetOSVersion() |
|
| 142 | 139 |
|
| 143 | 140 |
if osv.Build < 14260 {
|
| 144 | 141 |
// Set the name of the virtual switch if not specified by -b on daemon start |
| ... | ... |
@@ -364,8 +358,8 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro |
| 364 | 364 |
} |
| 365 | 365 |
|
| 366 | 366 |
// Convert imageData to valid image configuration |
| 367 |
- for i := range imageInfos {
|
|
| 368 |
- name := strings.ToLower(imageInfos[i].Name) |
|
| 367 |
+ for _, info := range imageInfos {
|
|
| 368 |
+ name := strings.ToLower(info.Name) |
|
| 369 | 369 |
|
| 370 | 370 |
type registrar interface {
|
| 371 | 371 |
RegisterDiffID(graphID string, size int64) (layer.Layer, error) |
| ... | ... |
@@ -374,13 +368,13 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro |
| 374 | 374 |
if !ok {
|
| 375 | 375 |
return errors.New("Layerstore doesn't support RegisterDiffID")
|
| 376 | 376 |
} |
| 377 |
- if _, err := r.RegisterDiffID(imageInfos[i].ID, imageInfos[i].Size); err != nil {
|
|
| 377 |
+ if _, err := r.RegisterDiffID(info.ID, info.Size); err != nil {
|
|
| 378 | 378 |
return err |
| 379 | 379 |
} |
| 380 | 380 |
// layer is intentionally not released |
| 381 | 381 |
|
| 382 | 382 |
rootFS := image.NewRootFS() |
| 383 |
- rootFS.BaseLayer = filepath.Base(imageInfos[i].Path) |
|
| 383 |
+ rootFS.BaseLayer = filepath.Base(info.Path) |
|
| 384 | 384 |
|
| 385 | 385 |
// Create history for base layer |
| 386 | 386 |
config, err := json.Marshal(&image.Image{
|
| ... | ... |
@@ -388,10 +382,12 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro |
| 388 | 388 |
DockerVersion: dockerversion.Version, |
| 389 | 389 |
Architecture: runtime.GOARCH, |
| 390 | 390 |
OS: runtime.GOOS, |
| 391 |
- Created: imageInfos[i].CreatedTime, |
|
| 391 |
+ Created: info.CreatedTime, |
|
| 392 | 392 |
}, |
| 393 |
- RootFS: rootFS, |
|
| 394 |
- History: []image.History{},
|
|
| 393 |
+ RootFS: rootFS, |
|
| 394 |
+ History: []image.History{},
|
|
| 395 |
+ OSVersion: info.OSVersion, |
|
| 396 |
+ OSFeatures: info.OSFeatures, |
|
| 395 | 397 |
}) |
| 396 | 398 |
|
| 397 | 399 |
named, err := reference.ParseNamed(name) |
| ... | ... |
@@ -399,7 +395,7 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro |
| 399 | 399 |
return err |
| 400 | 400 |
} |
| 401 | 401 |
|
| 402 |
- ref, err := reference.WithTag(named, imageInfos[i].Version) |
|
| 402 |
+ ref, err := reference.WithTag(named, info.Version) |
|
| 403 | 403 |
if err != nil {
|
| 404 | 404 |
return err |
| 405 | 405 |
} |
| ... | ... |
@@ -405,6 +405,8 @@ type CustomImageInfo struct {
|
| 405 | 405 |
Path string |
| 406 | 406 |
Size int64 |
| 407 | 407 |
CreatedTime time.Time |
| 408 |
+ OSVersion string `json:"-"` |
|
| 409 |
+ OSFeatures []string `json:"-"` |
|
| 408 | 410 |
} |
| 409 | 411 |
|
| 410 | 412 |
// GetCustomImageInfos returns the image infos for window specific |
| ... | ... |
@@ -445,6 +447,21 @@ func (d *Driver) GetCustomImageInfos() ([]CustomImageInfo, error) {
|
| 445 | 445 |
} |
| 446 | 446 |
|
| 447 | 447 |
imageData.ID = id |
| 448 |
+ |
|
| 449 |
+ // For now, hard code that all base images except nanoserver depend on win32k support |
|
| 450 |
+ if imageData.Name != "nanoserver" {
|
|
| 451 |
+ imageData.OSFeatures = append(imageData.OSFeatures, "win32k") |
|
| 452 |
+ } |
|
| 453 |
+ |
|
| 454 |
+ versionData := strings.Split(imageData.Version, ".") |
|
| 455 |
+ if len(versionData) != 4 {
|
|
| 456 |
+ logrus.Warn("Could not parse Windows version %s", imageData.Version)
|
|
| 457 |
+ } else {
|
|
| 458 |
+ // Include just major.minor.build, skip the fourth version field, which does not influence |
|
| 459 |
+ // OS compatibility. |
|
| 460 |
+ imageData.OSVersion = strings.Join(versionData[:3], ".") |
|
| 461 |
+ } |
|
| 462 |
+ |
|
| 448 | 463 |
images = append(images, imageData) |
| 449 | 464 |
} |
| 450 | 465 |
|
| ... | ... |
@@ -628,7 +628,9 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf |
| 628 | 628 |
// TODO(aaronl): The manifest list spec supports optional |
| 629 | 629 |
// "features" and "variant" fields. These are not yet used. |
| 630 | 630 |
// Once they are, their values should be interpreted here. |
| 631 |
- if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
|
|
| 631 |
+ // TODO(jstarks): Once os.version and os.features are present, |
|
| 632 |
+ // pass these, too. |
|
| 633 |
+ if image.ValidateOSCompatibility(manifestDescriptor.Platform.OS, manifestDescriptor.Platform.Architecture, "", nil) == nil {
|
|
| 632 | 634 |
manifestDigest = manifestDescriptor.Digest |
| 633 | 635 |
break |
| 634 | 636 |
} |
| 635 | 637 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+package image |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "runtime" |
|
| 5 |
+ "strings" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func archMatches(arch string) bool {
|
|
| 9 |
+ // Special case x86_64 as an alias for amd64 |
|
| 10 |
+ return arch == runtime.GOARCH || (arch == "x86_64" && runtime.GOARCH == "amd64") |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+// ValidateOSCompatibility validates that an image with the given properties can run on this machine. |
|
| 14 |
+func ValidateOSCompatibility(os string, arch string, osVersion string, osFeatures []string) error {
|
|
| 15 |
+ if os != "" && os != runtime.GOOS {
|
|
| 16 |
+ return fmt.Errorf("image is for OS %s, expected %s", os, runtime.GOOS)
|
|
| 17 |
+ } |
|
| 18 |
+ if arch != "" && !archMatches(arch) {
|
|
| 19 |
+ return fmt.Errorf("image is for architecture %s, expected %s", arch, runtime.GOARCH)
|
|
| 20 |
+ } |
|
| 21 |
+ if osVersion != "" {
|
|
| 22 |
+ thisOSVersion := getOSVersion() |
|
| 23 |
+ if thisOSVersion != osVersion {
|
|
| 24 |
+ return fmt.Errorf("image is for OS version '%s', expected '%s'", osVersion, thisOSVersion)
|
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ var missing []string |
|
| 28 |
+ for _, f := range osFeatures {
|
|
| 29 |
+ if !hasOSFeature(f) {
|
|
| 30 |
+ missing = append(missing, f) |
|
| 31 |
+ } |
|
| 32 |
+ } |
|
| 33 |
+ if len(missing) > 0 {
|
|
| 34 |
+ return fmt.Errorf("image requires missing OS features: %s", strings.Join(missing, ", "))
|
|
| 35 |
+ } |
|
| 36 |
+ return nil |
|
| 37 |
+} |
| 0 | 38 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,28 @@ |
| 0 |
+package image |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "runtime" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestValidateOSCompatibility(t *testing.T) {
|
|
| 8 |
+ err := ValidateOSCompatibility(runtime.GOOS, runtime.GOARCH, getOSVersion(), nil) |
|
| 9 |
+ if err != nil {
|
|
| 10 |
+ t.Error(err) |
|
| 11 |
+ } |
|
| 12 |
+ |
|
| 13 |
+ err = ValidateOSCompatibility("DOS", runtime.GOARCH, getOSVersion(), nil)
|
|
| 14 |
+ if err == nil {
|
|
| 15 |
+ t.Error("expected OS compat error")
|
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ err = ValidateOSCompatibility(runtime.GOOS, "pdp-11", getOSVersion(), nil) |
|
| 19 |
+ if err == nil {
|
|
| 20 |
+ t.Error("expected architecture compat error")
|
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ err = ValidateOSCompatibility(runtime.GOOS, runtime.GOARCH, "98 SE", nil) |
|
| 24 |
+ if err == nil {
|
|
| 25 |
+ t.Error("expected OS version compat error")
|
|
| 26 |
+ } |
|
| 27 |
+} |
| 0 | 28 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package image |
|
| 3 |
+ |
|
| 4 |
+func getOSVersion() string {
|
|
| 5 |
+ // For Linux, images do not specify a version. |
|
| 6 |
+ return "" |
|
| 7 |
+} |
|
| 8 |
+ |
|
| 9 |
+func hasOSFeature(_ string) bool {
|
|
| 10 |
+ // Linux currently has no OS features |
|
| 11 |
+ return false |
|
| 12 |
+} |
| 0 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package image |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/pkg/system" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Windows OS features |
|
| 9 |
+const ( |
|
| 10 |
+ FeatureWin32k = "win32k" // The kernel windowing stack is required |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func getOSVersion() string {
|
|
| 14 |
+ v := system.GetOSVersion() |
|
| 15 |
+ return fmt.Sprintf("%d.%d.%d", v.MajorVersion, v.MinorVersion, v.Build)
|
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func hasOSFeature(f string) bool {
|
|
| 19 |
+ switch f {
|
|
| 20 |
+ case FeatureWin32k: |
|
| 21 |
+ return system.HasWin32KSupport() |
|
| 22 |
+ default: |
|
| 23 |
+ // Unrecognized feature. |
|
| 24 |
+ return false |
|
| 25 |
+ } |
|
| 26 |
+} |
| ... | ... |
@@ -48,9 +48,11 @@ type V1Image struct {
|
| 48 | 48 |
// Image stores the image configuration |
| 49 | 49 |
type Image struct {
|
| 50 | 50 |
V1Image |
| 51 |
- Parent ID `json:"parent,omitempty"` |
|
| 52 |
- RootFS *RootFS `json:"rootfs,omitempty"` |
|
| 53 |
- History []History `json:"history,omitempty"` |
|
| 51 |
+ Parent ID `json:"parent,omitempty"` |
|
| 52 |
+ RootFS *RootFS `json:"rootfs,omitempty"` |
|
| 53 |
+ History []History `json:"history,omitempty"` |
|
| 54 |
+ OSVersion string `json:"os.version,omitempty"` |
|
| 55 |
+ OSFeatures []string `json:"os.features,omitempty"` |
|
| 54 | 56 |
|
| 55 | 57 |
// rawJSON caches the immutable JSON associated with this image. |
| 56 | 58 |
rawJSON []byte |
| ... | ... |
@@ -127,6 +127,11 @@ func (is *store) Create(config []byte) (ID, error) {
|
| 127 | 127 |
return "", errors.New("too many non-empty layers in History section")
|
| 128 | 128 |
} |
| 129 | 129 |
|
| 130 |
+ err = ValidateOSCompatibility(img.OS, img.Architecture, img.OSVersion, img.OSFeatures) |
|
| 131 |
+ if err != nil {
|
|
| 132 |
+ return "", err |
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 130 | 135 |
dgst, err := is.fs.Set(config) |
| 131 | 136 |
if err != nil {
|
| 132 | 137 |
return "", err |
| ... | ... |
@@ -3,6 +3,7 @@ package v1 |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 |
+ "reflect" |
|
| 6 | 7 |
"regexp" |
| 7 | 8 |
"strings" |
| 8 | 9 |
|
| ... | ... |
@@ -118,8 +119,15 @@ func MakeV1ConfigFromConfig(img *image.Image, v1ID, parentV1ID string, throwaway |
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 | 120 |
// Delete fields that didn't exist in old manifest |
| 121 |
- delete(configAsMap, "rootfs") |
|
| 122 |
- delete(configAsMap, "history") |
|
| 121 |
+ imageType := reflect.TypeOf(img).Elem() |
|
| 122 |
+ for i := 0; i < imageType.NumField(); i++ {
|
|
| 123 |
+ f := imageType.Field(i) |
|
| 124 |
+ jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
|
|
| 125 |
+ // Parent is handled specially below. |
|
| 126 |
+ if jsonName != "" && jsonName != "parent" {
|
|
| 127 |
+ delete(configAsMap, jsonName) |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 123 | 130 |
configAsMap["id"] = rawJSON(v1ID) |
| 124 | 131 |
if parentV1ID != "" {
|
| 125 | 132 |
configAsMap["parent"] = rawJSON(parentV1ID) |
| 126 | 133 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,55 @@ |
| 0 |
+package v1 |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "testing" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/image" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestMakeV1ConfigFromConfig(t *testing.T) {
|
|
| 10 |
+ img := &image.Image{
|
|
| 11 |
+ V1Image: image.V1Image{
|
|
| 12 |
+ ID: "v2id", |
|
| 13 |
+ Parent: "v2parent", |
|
| 14 |
+ OS: "os", |
|
| 15 |
+ }, |
|
| 16 |
+ OSVersion: "osversion", |
|
| 17 |
+ RootFS: &image.RootFS{
|
|
| 18 |
+ Type: "layers", |
|
| 19 |
+ }, |
|
| 20 |
+ } |
|
| 21 |
+ v2js, err := json.Marshal(img) |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ t.Fatal(err) |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ // Convert the image back in order to get RawJSON() support. |
|
| 27 |
+ img, err = image.NewFromJSON(v2js) |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ t.Fatal(err) |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ js, err := MakeV1ConfigFromConfig(img, "v1id", "v1parent", false) |
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ t.Fatal(err) |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ newimg := &image.Image{}
|
|
| 38 |
+ err = json.Unmarshal(js, newimg) |
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ t.Fatal(err) |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ if newimg.V1Image.ID != "v1id" || newimg.Parent != "v1parent" {
|
|
| 44 |
+ t.Error("ids should have changed", newimg.V1Image.ID, newimg.V1Image.Parent)
|
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ if newimg.RootFS != nil {
|
|
| 48 |
+ t.Error("rootfs should have been removed")
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if newimg.V1Image.OS != "os" {
|
|
| 52 |
+ t.Error("os should have been preserved")
|
|
| 53 |
+ } |
|
| 54 |
+} |
| ... | ... |
@@ -1,11 +1,14 @@ |
| 1 | 1 |
package system |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 | 4 |
"syscall" |
| 6 | 5 |
"unsafe" |
| 7 | 6 |
) |
| 8 | 7 |
|
| 8 |
+var ( |
|
| 9 |
+ ntuserApiset = syscall.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0")
|
|
| 10 |
+) |
|
| 11 |
+ |
|
| 9 | 12 |
// OSVersion is a wrapper for Windows version information |
| 10 | 13 |
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx |
| 11 | 14 |
type OSVersion struct {
|
| ... | ... |
@@ -17,17 +20,18 @@ type OSVersion struct {
|
| 17 | 17 |
|
| 18 | 18 |
// GetOSVersion gets the operating system version on Windows. Note that |
| 19 | 19 |
// docker.exe must be manifested to get the correct version information. |
| 20 |
-func GetOSVersion() (OSVersion, error) {
|
|
| 20 |
+func GetOSVersion() OSVersion {
|
|
| 21 | 21 |
var err error |
| 22 | 22 |
osv := OSVersion{}
|
| 23 | 23 |
osv.Version, err = syscall.GetVersion() |
| 24 | 24 |
if err != nil {
|
| 25 |
- return osv, fmt.Errorf("Failed to call GetVersion()")
|
|
| 25 |
+ // GetVersion never fails. |
|
| 26 |
+ panic(err) |
|
| 26 | 27 |
} |
| 27 | 28 |
osv.MajorVersion = uint8(osv.Version & 0xFF) |
| 28 | 29 |
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF) |
| 29 | 30 |
osv.Build = uint16(osv.Version >> 16) |
| 30 |
- return osv, nil |
|
| 31 |
+ return osv |
|
| 31 | 32 |
} |
| 32 | 33 |
|
| 33 | 34 |
// Unmount is a platform-specific helper function to call |
| ... | ... |
@@ -58,3 +62,12 @@ func CommandLineToArgv(commandLine string) ([]string, error) {
|
| 58 | 58 |
|
| 59 | 59 |
return newArgs, nil |
| 60 | 60 |
} |
| 61 |
+ |
|
| 62 |
+// HasWin32KSupport determines whether containers that depend on win32k can |
|
| 63 |
+// run on this machine. Win32k is the driver used to implement windowing. |
|
| 64 |
+func HasWin32KSupport() bool {
|
|
| 65 |
+ // For now, check for ntuser API support on the host. In the future, a host |
|
| 66 |
+ // may support win32k in containers even if the host does not support ntuser |
|
| 67 |
+ // APIs. |
|
| 68 |
+ return ntuserApiset.Load() == nil |
|
| 69 |
+} |
| 61 | 70 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,9 @@ |
| 0 |
+package system |
|
| 1 |
+ |
|
| 2 |
+import "testing" |
|
| 3 |
+ |
|
| 4 |
+func TestHasWin32KSupport(t *testing.T) {
|
|
| 5 |
+ s := HasWin32KSupport() // make sure this doesn't panic |
|
| 6 |
+ |
|
| 7 |
+ t.Logf("win32k: %v", s) // will be different on different platforms -- informative only
|
|
| 8 |
+} |
| ... | ... |
@@ -57,10 +57,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
| 57 | 57 |
// console which supports ANSI emulation, or fall-back to the golang emulator |
| 58 | 58 |
// (github.com/azure/go-ansiterm). |
| 59 | 59 |
func useNativeConsole() bool {
|
| 60 |
- osv, err := system.GetOSVersion() |
|
| 61 |
- if err != nil {
|
|
| 62 |
- return false |
|
| 63 |
- } |
|
| 60 |
+ osv := system.GetOSVersion() |
|
| 64 | 61 |
|
| 65 | 62 |
// Native console is not available before major version 10 |
| 66 | 63 |
if osv.MajorVersion < 10 {
|