1
0
Fork 0
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:
John Howard 2016-04-05 16:16:25 -07:00
commit fc9912fd00
15 changed files with 248 additions and 32 deletions

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
View 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
View 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
View 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
View 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
}
}

View file

@ -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

View file

@ -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

View file

@ -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
View 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")
}
}

View file

@ -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
}

View 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
}

View file

@ -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 {