mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #21272 from Microsoft/jstarks/manifest_updates
Add os_version and os_features to Image
This commit is contained in:
commit
fc9912fd00
15 changed files with 248 additions and 32 deletions
|
@ -138,6 +138,8 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
|
|||
|
||||
var history []image.History
|
||||
rootFS := image.NewRootFS()
|
||||
osVersion := ""
|
||||
var osFeatures []string
|
||||
|
||||
if container.ImageID != "" {
|
||||
img, err := daemon.imageStore.Get(container.ImageID)
|
||||
|
@ -146,6 +148,8 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
|
|||
}
|
||||
history = img.History
|
||||
rootFS = img.RootFS
|
||||
osVersion = img.OSVersion
|
||||
osFeatures = img.OSFeatures
|
||||
}
|
||||
|
||||
l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID())
|
||||
|
@ -180,8 +184,10 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
|
|||
Author: c.Author,
|
||||
Created: h.Created,
|
||||
},
|
||||
RootFS: rootFS,
|
||||
History: history,
|
||||
RootFS: rootFS,
|
||||
History: history,
|
||||
OSFeatures: osFeatures,
|
||||
OSVersion: osVersion,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -110,10 +110,7 @@ func verifyDaemonSettings(config *Config) error {
|
|||
func checkSystem() error {
|
||||
// Validate the OS version. Note that docker.exe must be manifested for this
|
||||
// call to return the correct version.
|
||||
osv, err := system.GetOSVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
osv := system.GetOSVersion()
|
||||
if osv.MajorVersion < 10 {
|
||||
return fmt.Errorf("This version of Windows does not support the docker daemon")
|
||||
}
|
||||
|
@ -135,10 +132,7 @@ func configureMaxThreads(config *Config) error {
|
|||
|
||||
func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) {
|
||||
// TODO Windows: Remove this check once TP4 is no longer supported
|
||||
osv, err := system.GetOSVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
osv := system.GetOSVersion()
|
||||
|
||||
if osv.Build < 14260 {
|
||||
// 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
|
|||
}
|
||||
|
||||
// Convert imageData to valid image configuration
|
||||
for i := range imageInfos {
|
||||
name := strings.ToLower(imageInfos[i].Name)
|
||||
for _, info := range imageInfos {
|
||||
name := strings.ToLower(info.Name)
|
||||
|
||||
type registrar interface {
|
||||
RegisterDiffID(graphID string, size int64) (layer.Layer, error)
|
||||
|
@ -374,13 +368,13 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro
|
|||
if !ok {
|
||||
return errors.New("Layerstore doesn't support RegisterDiffID")
|
||||
}
|
||||
if _, err := r.RegisterDiffID(imageInfos[i].ID, imageInfos[i].Size); err != nil {
|
||||
if _, err := r.RegisterDiffID(info.ID, info.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
// layer is intentionally not released
|
||||
|
||||
rootFS := image.NewRootFS()
|
||||
rootFS.BaseLayer = filepath.Base(imageInfos[i].Path)
|
||||
rootFS.BaseLayer = filepath.Base(info.Path)
|
||||
|
||||
// Create history for base layer
|
||||
config, err := json.Marshal(&image.Image{
|
||||
|
@ -388,10 +382,12 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro
|
|||
DockerVersion: dockerversion.Version,
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
Created: imageInfos[i].CreatedTime,
|
||||
Created: info.CreatedTime,
|
||||
},
|
||||
RootFS: rootFS,
|
||||
History: []image.History{},
|
||||
RootFS: rootFS,
|
||||
History: []image.History{},
|
||||
OSVersion: info.OSVersion,
|
||||
OSFeatures: info.OSFeatures,
|
||||
})
|
||||
|
||||
named, err := reference.ParseNamed(name)
|
||||
|
@ -399,7 +395,7 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro
|
|||
return err
|
||||
}
|
||||
|
||||
ref, err := reference.WithTag(named, imageInfos[i].Version)
|
||||
ref, err := reference.WithTag(named, info.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -405,6 +405,8 @@ type CustomImageInfo struct {
|
|||
Path string
|
||||
Size int64
|
||||
CreatedTime time.Time
|
||||
OSVersion string `json:"-"`
|
||||
OSFeatures []string `json:"-"`
|
||||
}
|
||||
|
||||
// GetCustomImageInfos returns the image infos for window specific
|
||||
|
@ -445,6 +447,21 @@ func (d *Driver) GetCustomImageInfos() ([]CustomImageInfo, error) {
|
|||
}
|
||||
|
||||
imageData.ID = id
|
||||
|
||||
// For now, hard code that all base images except nanoserver depend on win32k support
|
||||
if imageData.Name != "nanoserver" {
|
||||
imageData.OSFeatures = append(imageData.OSFeatures, "win32k")
|
||||
}
|
||||
|
||||
versionData := strings.Split(imageData.Version, ".")
|
||||
if len(versionData) != 4 {
|
||||
logrus.Warn("Could not parse Windows version %s", imageData.Version)
|
||||
} else {
|
||||
// Include just major.minor.build, skip the fourth version field, which does not influence
|
||||
// OS compatibility.
|
||||
imageData.OSVersion = strings.Join(versionData[:3], ".")
|
||||
}
|
||||
|
||||
images = append(images, imageData)
|
||||
}
|
||||
|
||||
|
|
|
@ -628,7 +628,9 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
|
|||
// TODO(aaronl): The manifest list spec supports optional
|
||||
// "features" and "variant" fields. These are not yet used.
|
||||
// Once they are, their values should be interpreted here.
|
||||
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
|
||||
// TODO(jstarks): Once os.version and os.features are present,
|
||||
// pass these, too.
|
||||
if image.ValidateOSCompatibility(manifestDescriptor.Platform.OS, manifestDescriptor.Platform.Architecture, "", nil) == nil {
|
||||
manifestDigest = manifestDescriptor.Digest
|
||||
break
|
||||
}
|
||||
|
|
38
image/compat.go
Normal file
38
image/compat.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func archMatches(arch string) bool {
|
||||
// Special case x86_64 as an alias for amd64
|
||||
return arch == runtime.GOARCH || (arch == "x86_64" && runtime.GOARCH == "amd64")
|
||||
}
|
||||
|
||||
// ValidateOSCompatibility validates that an image with the given properties can run on this machine.
|
||||
func ValidateOSCompatibility(os string, arch string, osVersion string, osFeatures []string) error {
|
||||
if os != "" && os != runtime.GOOS {
|
||||
return fmt.Errorf("image is for OS %s, expected %s", os, runtime.GOOS)
|
||||
}
|
||||
if arch != "" && !archMatches(arch) {
|
||||
return fmt.Errorf("image is for architecture %s, expected %s", arch, runtime.GOARCH)
|
||||
}
|
||||
if osVersion != "" {
|
||||
thisOSVersion := getOSVersion()
|
||||
if thisOSVersion != osVersion {
|
||||
return fmt.Errorf("image is for OS version '%s', expected '%s'", osVersion, thisOSVersion)
|
||||
}
|
||||
}
|
||||
var missing []string
|
||||
for _, f := range osFeatures {
|
||||
if !hasOSFeature(f) {
|
||||
missing = append(missing, f)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("image requires missing OS features: %s", strings.Join(missing, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
28
image/compat_test.go
Normal file
28
image/compat_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateOSCompatibility(t *testing.T) {
|
||||
err := ValidateOSCompatibility(runtime.GOOS, runtime.GOARCH, getOSVersion(), nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = ValidateOSCompatibility("DOS", runtime.GOARCH, getOSVersion(), nil)
|
||||
if err == nil {
|
||||
t.Error("expected OS compat error")
|
||||
}
|
||||
|
||||
err = ValidateOSCompatibility(runtime.GOOS, "pdp-11", getOSVersion(), nil)
|
||||
if err == nil {
|
||||
t.Error("expected architecture compat error")
|
||||
}
|
||||
|
||||
err = ValidateOSCompatibility(runtime.GOOS, runtime.GOARCH, "98 SE", nil)
|
||||
if err == nil {
|
||||
t.Error("expected OS version compat error")
|
||||
}
|
||||
}
|
13
image/compat_unix.go
Normal file
13
image/compat_unix.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
// +build !windows
|
||||
|
||||
package image
|
||||
|
||||
func getOSVersion() string {
|
||||
// For Linux, images do not specify a version.
|
||||
return ""
|
||||
}
|
||||
|
||||
func hasOSFeature(_ string) bool {
|
||||
// Linux currently has no OS features
|
||||
return false
|
||||
}
|
27
image/compat_windows.go
Normal file
27
image/compat_windows.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// Windows OS features
|
||||
const (
|
||||
FeatureWin32k = "win32k" // The kernel windowing stack is required
|
||||
)
|
||||
|
||||
func getOSVersion() string {
|
||||
v := system.GetOSVersion()
|
||||
return fmt.Sprintf("%d.%d.%d", v.MajorVersion, v.MinorVersion, v.Build)
|
||||
}
|
||||
|
||||
func hasOSFeature(f string) bool {
|
||||
switch f {
|
||||
case FeatureWin32k:
|
||||
return system.HasWin32KSupport()
|
||||
default:
|
||||
// Unrecognized feature.
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -48,9 +48,11 @@ type V1Image struct {
|
|||
// Image stores the image configuration
|
||||
type Image struct {
|
||||
V1Image
|
||||
Parent ID `json:"parent,omitempty"`
|
||||
RootFS *RootFS `json:"rootfs,omitempty"`
|
||||
History []History `json:"history,omitempty"`
|
||||
Parent ID `json:"parent,omitempty"`
|
||||
RootFS *RootFS `json:"rootfs,omitempty"`
|
||||
History []History `json:"history,omitempty"`
|
||||
OSVersion string `json:"os.version,omitempty"`
|
||||
OSFeatures []string `json:"os.features,omitempty"`
|
||||
|
||||
// rawJSON caches the immutable JSON associated with this image.
|
||||
rawJSON []byte
|
||||
|
|
|
@ -127,6 +127,11 @@ func (is *store) Create(config []byte) (ID, error) {
|
|||
return "", errors.New("too many non-empty layers in History section")
|
||||
}
|
||||
|
||||
err = ValidateOSCompatibility(img.OS, img.Architecture, img.OSVersion, img.OSFeatures)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := is.fs.Set(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -3,6 +3,7 @@ package v1
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
@ -118,8 +119,15 @@ func MakeV1ConfigFromConfig(img *image.Image, v1ID, parentV1ID string, throwaway
|
|||
}
|
||||
|
||||
// Delete fields that didn't exist in old manifest
|
||||
delete(configAsMap, "rootfs")
|
||||
delete(configAsMap, "history")
|
||||
imageType := reflect.TypeOf(img).Elem()
|
||||
for i := 0; i < imageType.NumField(); i++ {
|
||||
f := imageType.Field(i)
|
||||
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
|
||||
// Parent is handled specially below.
|
||||
if jsonName != "" && jsonName != "parent" {
|
||||
delete(configAsMap, jsonName)
|
||||
}
|
||||
}
|
||||
configAsMap["id"] = rawJSON(v1ID)
|
||||
if parentV1ID != "" {
|
||||
configAsMap["parent"] = rawJSON(parentV1ID)
|
||||
|
|
55
image/v1/imagev1_test.go
Normal file
55
image/v1/imagev1_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/image"
|
||||
)
|
||||
|
||||
func TestMakeV1ConfigFromConfig(t *testing.T) {
|
||||
img := &image.Image{
|
||||
V1Image: image.V1Image{
|
||||
ID: "v2id",
|
||||
Parent: "v2parent",
|
||||
OS: "os",
|
||||
},
|
||||
OSVersion: "osversion",
|
||||
RootFS: &image.RootFS{
|
||||
Type: "layers",
|
||||
},
|
||||
}
|
||||
v2js, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Convert the image back in order to get RawJSON() support.
|
||||
img, err = image.NewFromJSON(v2js)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
js, err := MakeV1ConfigFromConfig(img, "v1id", "v1parent", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newimg := &image.Image{}
|
||||
err = json.Unmarshal(js, newimg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if newimg.V1Image.ID != "v1id" || newimg.Parent != "v1parent" {
|
||||
t.Error("ids should have changed", newimg.V1Image.ID, newimg.V1Image.Parent)
|
||||
}
|
||||
|
||||
if newimg.RootFS != nil {
|
||||
t.Error("rootfs should have been removed")
|
||||
}
|
||||
|
||||
if newimg.V1Image.OS != "os" {
|
||||
t.Error("os should have been preserved")
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
ntuserApiset = syscall.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0")
|
||||
)
|
||||
|
||||
// OSVersion is a wrapper for Windows version information
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||
type OSVersion struct {
|
||||
|
@ -17,17 +20,18 @@ type OSVersion struct {
|
|||
|
||||
// GetOSVersion gets the operating system version on Windows. Note that
|
||||
// docker.exe must be manifested to get the correct version information.
|
||||
func GetOSVersion() (OSVersion, error) {
|
||||
func GetOSVersion() OSVersion {
|
||||
var err error
|
||||
osv := OSVersion{}
|
||||
osv.Version, err = syscall.GetVersion()
|
||||
if err != nil {
|
||||
return osv, fmt.Errorf("Failed to call GetVersion()")
|
||||
// GetVersion never fails.
|
||||
panic(err)
|
||||
}
|
||||
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||
osv.Build = uint16(osv.Version >> 16)
|
||||
return osv, nil
|
||||
return osv
|
||||
}
|
||||
|
||||
// Unmount is a platform-specific helper function to call
|
||||
|
@ -58,3 +62,12 @@ func CommandLineToArgv(commandLine string) ([]string, error) {
|
|||
|
||||
return newArgs, nil
|
||||
}
|
||||
|
||||
// HasWin32KSupport determines whether containers that depend on win32k can
|
||||
// run on this machine. Win32k is the driver used to implement windowing.
|
||||
func HasWin32KSupport() bool {
|
||||
// For now, check for ntuser API support on the host. In the future, a host
|
||||
// may support win32k in containers even if the host does not support ntuser
|
||||
// APIs.
|
||||
return ntuserApiset.Load() == nil
|
||||
}
|
||||
|
|
9
pkg/system/syscall_windows_test.go
Normal file
9
pkg/system/syscall_windows_test.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package system
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHasWin32KSupport(t *testing.T) {
|
||||
s := HasWin32KSupport() // make sure this doesn't panic
|
||||
|
||||
t.Logf("win32k: %v", s) // will be different on different platforms -- informative only
|
||||
}
|
|
@ -57,10 +57,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|||
// console which supports ANSI emulation, or fall-back to the golang emulator
|
||||
// (github.com/azure/go-ansiterm).
|
||||
func useNativeConsole() bool {
|
||||
osv, err := system.GetOSVersion()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
osv := system.GetOSVersion()
|
||||
|
||||
// Native console is not available before major version 10
|
||||
if osv.MajorVersion < 10 {
|
||||
|
|
Loading…
Add table
Reference in a new issue