2018-09-14 13:53:56 -04:00
|
|
|
package versions
|
2018-03-19 18:56:51 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-09-28 17:06:28 -04:00
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2018-09-14 13:53:56 -04:00
|
|
|
"path"
|
2018-09-28 17:06:28 -04:00
|
|
|
"path/filepath"
|
2018-03-19 18:56:51 -04:00
|
|
|
"sort"
|
|
|
|
|
|
|
|
registryclient "github.com/docker/cli/cli/registry/client"
|
2018-09-11 08:46:30 -04:00
|
|
|
clitypes "github.com/docker/cli/types"
|
2018-03-19 18:56:51 -04:00
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
ver "github.com/hashicorp/go-version"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2018-09-28 17:06:28 -04:00
|
|
|
const (
|
|
|
|
// defaultRuntimeMetadataDir is the location where the metadata file is stored
|
|
|
|
defaultRuntimeMetadataDir = "/var/lib/docker-engine"
|
|
|
|
)
|
|
|
|
|
2018-03-19 18:56:51 -04:00
|
|
|
// GetEngineVersions reports the versions of the engine that are available
|
2018-09-28 17:06:28 -04:00
|
|
|
func GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, registryPrefix, imageName, versionString string) (clitypes.AvailableVersions, error) {
|
|
|
|
|
|
|
|
if imageName == "" {
|
|
|
|
var err error
|
|
|
|
localMetadata, err := GetCurrentRuntimeMetadata("")
|
|
|
|
if err != nil {
|
|
|
|
return clitypes.AvailableVersions{}, err
|
|
|
|
}
|
|
|
|
imageName = localMetadata.EngineImage
|
|
|
|
}
|
|
|
|
imageRef, err := reference.ParseNormalizedNamed(path.Join(registryPrefix, imageName))
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
2018-09-11 08:46:30 -04:00
|
|
|
return clitypes.AvailableVersions{}, err
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
tags, err := registryClient.GetTags(ctx, imageRef)
|
|
|
|
if err != nil {
|
2018-09-11 08:46:30 -04:00
|
|
|
return clitypes.AvailableVersions{}, err
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
2018-09-28 17:06:28 -04:00
|
|
|
return parseTags(tags, versionString)
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
2018-09-11 08:46:30 -04:00
|
|
|
func parseTags(tags []string, currentVersion string) (clitypes.AvailableVersions, error) {
|
|
|
|
var ret clitypes.AvailableVersions
|
2018-03-19 18:56:51 -04:00
|
|
|
currentVer, err := ver.NewVersion(currentVersion)
|
|
|
|
if err != nil {
|
|
|
|
return ret, errors.Wrapf(err, "failed to parse existing version %s", currentVersion)
|
|
|
|
}
|
2018-09-11 08:46:30 -04:00
|
|
|
downgrades := []clitypes.DockerVersion{}
|
|
|
|
patches := []clitypes.DockerVersion{}
|
|
|
|
upgrades := []clitypes.DockerVersion{}
|
2018-03-19 18:56:51 -04:00
|
|
|
currentSegments := currentVer.Segments()
|
|
|
|
for _, tag := range tags {
|
|
|
|
tmp, err := ver.NewVersion(tag)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("Unable to parse %s: %s", tag, err)
|
|
|
|
continue
|
|
|
|
}
|
2018-09-11 08:46:30 -04:00
|
|
|
testVersion := clitypes.DockerVersion{Version: *tmp, Tag: tag}
|
2018-03-19 18:56:51 -04:00
|
|
|
if testVersion.LessThan(currentVer) {
|
|
|
|
downgrades = append(downgrades, testVersion)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
testSegments := testVersion.Segments()
|
|
|
|
// lib always provides min 3 segments
|
|
|
|
if testSegments[0] == currentSegments[0] &&
|
|
|
|
testSegments[1] == currentSegments[1] {
|
|
|
|
patches = append(patches, testVersion)
|
|
|
|
} else {
|
|
|
|
upgrades = append(upgrades, testVersion)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Slice(downgrades, func(i, j int) bool {
|
|
|
|
return downgrades[i].Version.LessThan(&downgrades[j].Version)
|
|
|
|
})
|
|
|
|
sort.Slice(patches, func(i, j int) bool {
|
|
|
|
return patches[i].Version.LessThan(&patches[j].Version)
|
|
|
|
})
|
|
|
|
sort.Slice(upgrades, func(i, j int) bool {
|
|
|
|
return upgrades[i].Version.LessThan(&upgrades[j].Version)
|
|
|
|
})
|
|
|
|
ret.Downgrades = downgrades
|
|
|
|
ret.Patches = patches
|
|
|
|
ret.Upgrades = upgrades
|
|
|
|
return ret, nil
|
|
|
|
}
|
2018-09-28 17:06:28 -04:00
|
|
|
|
|
|
|
// GetCurrentRuntimeMetadata loads the current daemon runtime metadata information from the local host
|
|
|
|
func GetCurrentRuntimeMetadata(metadataDir string) (*clitypes.RuntimeMetadata, error) {
|
|
|
|
if metadataDir == "" {
|
|
|
|
metadataDir = defaultRuntimeMetadataDir
|
|
|
|
}
|
|
|
|
filename := filepath.Join(metadataDir, clitypes.RuntimeMetadataName+".json")
|
|
|
|
|
|
|
|
data, err := ioutil.ReadFile(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var res clitypes.RuntimeMetadata
|
|
|
|
err = json.Unmarshal(data, &res)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "malformed runtime metadata file %s", filename)
|
|
|
|
}
|
|
|
|
return &res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteRuntimeMetadata stores the metadata on the local system
|
|
|
|
func WriteRuntimeMetadata(metadataDir string, metadata *clitypes.RuntimeMetadata) error {
|
|
|
|
if metadataDir == "" {
|
|
|
|
metadataDir = defaultRuntimeMetadataDir
|
|
|
|
}
|
|
|
|
filename := filepath.Join(metadataDir, clitypes.RuntimeMetadataName+".json")
|
|
|
|
|
|
|
|
data, err := json.Marshal(metadata)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Remove(filename)
|
|
|
|
return ioutil.WriteFile(filename, data, 0644)
|
|
|
|
}
|