vendor: initial vendor

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2019-03-22 13:31:59 -07:00
parent 4f2cc0e220
commit fd8fbf21e6
2584 changed files with 828696 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
package containerizedengine
import (
"context"
"io"
"github.com/containerd/containerd"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes/docker"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// NewClient returns a new containerizedengine client
// This client can be used to manage the lifecycle of
// dockerd running as a container on containerd.
func NewClient(sockPath string) (clitypes.ContainerizedClient, error) {
if sockPath == "" {
sockPath = containerdSockPath
}
cclient, err := containerd.New(sockPath)
if err != nil {
return nil, err
}
return &baseClient{
cclient: cclient,
}, nil
}
// Close will close the underlying clients
func (c *baseClient) Close() error {
return c.cclient.Close()
}
func (c *baseClient) pullWithAuth(ctx context.Context, imageName string, out clitypes.OutStream,
authConfig *types.AuthConfig) (containerd.Image, error) {
resolver := docker.NewResolver(docker.ResolverOptions{
Credentials: func(string) (string, string, error) {
return authConfig.Username, authConfig.Password, nil
},
})
ongoing := newJobs(imageName)
pctx, stopProgress := context.WithCancel(ctx)
progress := make(chan struct{})
bufin, bufout := io.Pipe()
go func() {
showProgress(pctx, ongoing, c.cclient.ContentStore(), bufout)
}()
go func() {
jsonmessage.DisplayJSONMessagesToStream(bufin, out, nil)
close(progress)
}()
h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
if desc.MediaType != images.MediaTypeDockerSchema1Manifest {
ongoing.add(desc)
}
return nil, nil
})
image, err := c.cclient.Pull(ctx, imageName,
containerd.WithResolver(resolver),
containerd.WithImageHandler(h),
containerd.WithPullUnpack)
stopProgress()
if err != nil {
return nil, err
}
<-progress
return image, nil
}

View File

@@ -0,0 +1,215 @@
package containerizedengine
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/remotes"
"github.com/docker/docker/pkg/jsonmessage"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
func showProgress(ctx context.Context, ongoing *jobs, cs content.Store, out io.WriteCloser) {
var (
ticker = time.NewTicker(100 * time.Millisecond)
start = time.Now()
enc = json.NewEncoder(out)
statuses = map[string]statusInfo{}
done bool
)
defer ticker.Stop()
outer:
for {
select {
case <-ticker.C:
resolved := "resolved"
if !ongoing.isResolved() {
resolved = "resolving"
}
statuses[ongoing.name] = statusInfo{
Ref: ongoing.name,
Status: resolved,
}
keys := []string{ongoing.name}
activeSeen := map[string]struct{}{}
if !done {
active, err := cs.ListStatuses(ctx, "")
if err != nil {
logrus.Debugf("active check failed: %s", err)
continue
}
// update status of active entries!
for _, active := range active {
statuses[active.Ref] = statusInfo{
Ref: active.Ref,
Status: "downloading",
Offset: active.Offset,
Total: active.Total,
StartedAt: active.StartedAt,
UpdatedAt: active.UpdatedAt,
}
activeSeen[active.Ref] = struct{}{}
}
}
err := updateNonActive(ctx, ongoing, cs, statuses, &keys, activeSeen, &done, start)
if err != nil {
continue outer
}
var ordered []statusInfo
for _, key := range keys {
ordered = append(ordered, statuses[key])
}
for _, si := range ordered {
jm := si.JSONMessage()
err := enc.Encode(jm)
if err != nil {
logrus.Debugf("failed to encode progress message: %s", err)
}
}
if done {
out.Close()
return
}
case <-ctx.Done():
done = true // allow ui to update once more
}
}
}
func updateNonActive(ctx context.Context, ongoing *jobs, cs content.Store, statuses map[string]statusInfo, keys *[]string, activeSeen map[string]struct{}, done *bool, start time.Time) error {
for _, j := range ongoing.jobs() {
key := remotes.MakeRefKey(ctx, j)
*keys = append(*keys, key)
if _, ok := activeSeen[key]; ok {
continue
}
status, ok := statuses[key]
if !*done && (!ok || status.Status == "downloading") {
info, err := cs.Info(ctx, j.Digest)
if err != nil {
if !errdefs.IsNotFound(err) {
logrus.Debugf("failed to get content info: %s", err)
return err
}
statuses[key] = statusInfo{
Ref: key,
Status: "waiting",
}
} else if info.CreatedAt.After(start) {
statuses[key] = statusInfo{
Ref: key,
Status: "done",
Offset: info.Size,
Total: info.Size,
UpdatedAt: info.CreatedAt,
}
} else {
statuses[key] = statusInfo{
Ref: key,
Status: "exists",
}
}
} else if *done {
if ok {
if status.Status != "done" && status.Status != "exists" {
status.Status = "done"
statuses[key] = status
}
} else {
statuses[key] = statusInfo{
Ref: key,
Status: "done",
}
}
}
}
return nil
}
type jobs struct {
name string
added map[digest.Digest]struct{}
descs []ocispec.Descriptor
mu sync.Mutex
resolved bool
}
func newJobs(name string) *jobs {
return &jobs{
name: name,
added: map[digest.Digest]struct{}{},
}
}
func (j *jobs) add(desc ocispec.Descriptor) {
j.mu.Lock()
defer j.mu.Unlock()
j.resolved = true
if _, ok := j.added[desc.Digest]; ok {
return
}
j.descs = append(j.descs, desc)
j.added[desc.Digest] = struct{}{}
}
func (j *jobs) jobs() []ocispec.Descriptor {
j.mu.Lock()
defer j.mu.Unlock()
var descs []ocispec.Descriptor
return append(descs, j.descs...)
}
func (j *jobs) isResolved() bool {
j.mu.Lock()
defer j.mu.Unlock()
return j.resolved
}
// statusInfo holds the status info for an upload or download
type statusInfo struct {
Ref string
Status string
Offset int64
Total int64
StartedAt time.Time
UpdatedAt time.Time
}
func (s statusInfo) JSONMessage() jsonmessage.JSONMessage {
// Shorten the ID to use up less width on the display
id := s.Ref
if strings.Contains(id, ":") {
split := strings.SplitN(id, ":", 2)
id = split[1]
}
id = fmt.Sprintf("%.12s", id)
return jsonmessage.JSONMessage{
ID: id,
Status: s.Status,
Progress: &jsonmessage.JSONProgress{
Current: s.Offset,
Total: s.Total,
},
}
}

View File

@@ -0,0 +1,49 @@
package containerizedengine
import (
"context"
"errors"
"github.com/containerd/containerd"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content"
)
const (
containerdSockPath = "/run/containerd/containerd.sock"
engineNamespace = "com.docker"
)
var (
// ErrEngineAlreadyPresent returned when engine already present and should not be
ErrEngineAlreadyPresent = errors.New("engine already present, use the update command to change versions")
// ErrEngineNotPresent returned when the engine is not present and should be
ErrEngineNotPresent = errors.New("engine not present")
// ErrMalformedConfigFileParam returned if the engine config file parameter is malformed
ErrMalformedConfigFileParam = errors.New("malformed --config-file param on engine")
// ErrEngineConfigLookupFailure returned if unable to lookup existing engine configuration
ErrEngineConfigLookupFailure = errors.New("unable to lookup existing engine configuration")
// ErrEngineShutdownTimeout returned if the engine failed to shutdown in time
ErrEngineShutdownTimeout = errors.New("timeout waiting for engine to exit")
)
type baseClient struct {
cclient containerdClient
}
// containerdClient abstracts the containerd client to aid in testability
type containerdClient interface {
Containers(ctx context.Context, filters ...string) ([]containerd.Container, error)
NewContainer(ctx context.Context, id string, opts ...containerd.NewContainerOpts) (containerd.Container, error)
Pull(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error)
GetImage(ctx context.Context, ref string) (containerd.Image, error)
Close() error
ContentStore() content.Store
ContainerService() containers.Store
Install(context.Context, containerd.Image, ...containerd.InstallOpts) error
Version(ctx context.Context) (containerd.Version, error)
}

View File

@@ -0,0 +1,183 @@
package containerizedengine
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces"
"github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// ActivateEngine will switch the image from the CE to EE image
func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
authConfig *types.AuthConfig) error {
// If the user didn't specify an image, determine the correct enterprise image to use
if opts.EngineImage == "" {
localMetadata, err := versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
if err != nil {
return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image")
}
engineImage := localMetadata.EngineImage
if engineImage == clitypes.EnterpriseEngineImage || engineImage == clitypes.CommunityEngineImage {
opts.EngineImage = clitypes.EnterpriseEngineImage
} else {
// Chop off the standard prefix and retain any trailing OS specific image details
// e.g., engine-community-dm -> engine-enterprise-dm
engineImage = strings.TrimPrefix(engineImage, clitypes.EnterpriseEngineImage)
engineImage = strings.TrimPrefix(engineImage, clitypes.CommunityEngineImage)
opts.EngineImage = clitypes.EnterpriseEngineImage + engineImage
}
}
ctx = namespaces.WithNamespace(ctx, engineNamespace)
return c.DoUpdate(ctx, opts, out, authConfig)
}
// DoUpdate performs the underlying engine update
func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
authConfig *types.AuthConfig) error {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
if opts.EngineVersion == "" {
// TODO - Future enhancement: This could be improved to be
// smart about figuring out the latest patch rev for the
// current engine version and automatically apply it so users
// could stay in sync by simply having a scheduled
// `docker engine update`
return fmt.Errorf("pick the version you want to update to with --version")
}
var localMetadata *clitypes.RuntimeMetadata
if opts.EngineImage == "" {
var err error
localMetadata, err = versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
if err != nil {
return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'")
}
opts.EngineImage = localMetadata.EngineImage
}
imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
// Look for desired image
image, err := c.cclient.GetImage(ctx, imageName)
if err != nil {
if errdefs.IsNotFound(err) {
image, err = c.pullWithAuth(ctx, imageName, out, authConfig)
if err != nil {
return errors.Wrapf(err, "unable to pull image %s", imageName)
}
} else {
return errors.Wrapf(err, "unable to check for image %s", imageName)
}
}
// Make sure we're safe to proceed
newMetadata, err := c.PreflightCheck(ctx, image)
if err != nil {
return err
}
if localMetadata != nil {
if localMetadata.Platform != newMetadata.Platform {
fmt.Fprintf(out, "\nNotice: you have switched to \"%s\". Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName))
}
}
if err := c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr")); err != nil {
return err
}
return versions.WriteRuntimeMetadata(opts.RuntimeMetadataDir, newMetadata)
}
// PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate
// If things look good, the RuntimeMetadata for the new image is returned and can be written out to the host
func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image) (*clitypes.RuntimeMetadata, error) {
var metadata clitypes.RuntimeMetadata
ic, err := image.Config(ctx)
if err != nil {
return nil, err
}
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil {
return nil, err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return nil, err
}
config = ociimage.Config
default:
return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType)
}
metadataString, ok := config.Labels["com.docker."+clitypes.RuntimeMetadataName]
if !ok {
return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), clitypes.RuntimeMetadataName)
}
err = json.Unmarshal([]byte(metadataString), &metadata)
if err != nil {
return nil, errors.Wrapf(err, "malformed runtime metadata file in %s", image.Name())
}
// Current CLI only supports host install runtime
if metadata.Runtime != "host_install" {
return nil, fmt.Errorf("unsupported daemon image: %s\nConsult the release notes at %s for upgrade instructions", metadata.Runtime, getReleaseNotesURL(image.Name()))
}
// Verify local containerd is new enough
localVersion, err := c.cclient.Version(ctx)
if err != nil {
return nil, err
}
if metadata.ContainerdMinVersion != "" {
lv, err := ver.NewVersion(localVersion.Version)
if err != nil {
return nil, err
}
mv, err := ver.NewVersion(metadata.ContainerdMinVersion)
if err != nil {
return nil, err
}
if lv.LessThan(mv) {
return nil, fmt.Errorf("local containerd is too old: %s - this engine version requires %s or newer.\nConsult the release notes at %s for upgrade instructions",
localVersion.Version, metadata.ContainerdMinVersion, getReleaseNotesURL(image.Name()))
}
} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
// All checks look OK, proceed with update
return &metadata, nil
}
// getReleaseNotesURL returns a release notes url
// If the image name does not contain a version tag, the base release notes URL is returned
func getReleaseNotesURL(imageName string) string {
versionTag := ""
distributionRef, err := reference.ParseNormalizedNamed(imageName)
if err == nil {
taggedRef, ok := distributionRef.(reference.NamedTagged)
if ok {
versionTag = taggedRef.Tag()
}
}
return fmt.Sprintf("%s/%s", clitypes.ReleaseNotePrefix, versionTag)
}