<commit_before>package client

import (
	"encoding/json"
	"fmt"
	"net/http"
	"sync"

	"github.com/micro/go-micro/broker"
	c "github.com/micro/go-micro/context"
	"github.com/micro/go-micro/errors"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	rpc "github.com/youtube/vitess/go/rpcplus"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
)

type headerRoundTripper struct {
	r http.RoundTripper
}

type rpcClient struct {
	once sync.Once
	opts options
}

func newRpcClient(opt ...Option) Client {
	opts := options{
		codecs: make(map[string]CodecFunc),
	}

	for _, o := range opt {
		o(&opts)
	}

	if len(opts.contentType) == 0 {
		opts.contentType = defaultContentType
	}

	if opts.transport == nil {
		opts.transport = transport.DefaultTransport
	}

	if opts.broker == nil {
		opts.broker = broker.DefaultBroker
	}

	var once sync.Once

	return &rpcClient{
		once: once,
		opts: opts,
	}
}

func (t *headerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	r.Header.Set("X-Client-Version", "1.0")
	return t.r.RoundTrip(r)
}

func (r *rpcClient) codecFunc(contentType string) (codecFunc, error) {
	if cf, ok := r.opts.codecs[contentType]; ok {
		return codecWrap(cf), nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (r *rpcClient) call(ctx context.Context, address string, request Request, response interface{}) error {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := c.GetMetadata(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	msg.Header["Content-Type"] = request.ContentType()

	cf, err := r.codecFunc(request.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.transport.Dial(address)
	if err != nil {
		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}
	defer c.Close()

	client := rpc.NewClientWithCodec(newRpcPlusCodec(msg, c, cf))
	err = client.Call(ctx, request.Method(), request.Request(), response)
	if err != nil {
		return err
	}
	return client.Close()
}

func (r *rpcClient) stream(ctx context.Context, address string, request Request, responseChan interface{}) (Streamer, error) {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := c.GetMetadata(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	msg.Header["Content-Type"] = request.ContentType()

	cf, err := r.codecFunc(request.ContentType())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.transport.Dial(address, transport.WithStream())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}

	client := rpc.NewClientWithCodec(newRpcPlusCodec(msg, c, cf))
	call := client.StreamGo(request.Method(), request.Request(), responseChan)

	return &rpcStream{
		request: request,
		call:    call,
		client:  client,
	}, nil
}

func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}) error {
	return r.call(ctx, address, request, response)
}

// TODO: Call(..., opts *Options) error {
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}) error {
	service, err := registry.GetService(request.Service())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	node, err := nodeSelector(service)
	if err != nil {
		return err
	}

	address := node.Address
	if node.Port > 0 {
		address = fmt.Sprintf("%s:%d", address, node.Port)
	}

	return r.call(ctx, address, request, response)
}

func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, responseChan interface{}) (Streamer, error) {
	return r.stream(ctx, address, request, responseChan)
}

func (r *rpcClient) Stream(ctx context.Context, request Request, responseChan interface{}) (Streamer, error) {
	service, err := registry.GetService(request.Service())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	node, err := nodeSelector(service)
	if err != nil {
		return nil, err
	}

	address := node.Address
	if node.Port > 0 {
		address = fmt.Sprintf("%s:%d", address, node.Port)
	}

	return r.stream(ctx, address, request, responseChan)
}

func (r *rpcClient) Publish(ctx context.Context, p Publication) error {
	md, ok := c.GetMetadata(ctx)
	if !ok {
		md = make(map[string]string)
	}
	md["Content-Type"] = p.ContentType()

	// encode message body
	var body []byte

	switch p.ContentType() {
	case "application/octet-stream":
		b, err := proto.Marshal(p.Message().(proto.Message))
		if err != nil {
			return err
		}
		body = b
	case "application/json":
		b, err := json.Marshal(p.Message())
		if err != nil {
			return err
		}
		body = b
	}

	r.once.Do(func() {
		r.opts.broker.Connect()
	})

	return r.opts.broker.Publish(p.Topic(), &broker.Message{
		Header: md,
		Body:   body,
	})
}

func (r *rpcClient) NewPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, r.opts.contentType)
}

func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, "application/octet-stream")
}
func (r *rpcClient) NewRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, r.opts.contentType)
}

func (r *rpcClient) NewProtoRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, "application/octet-stream")
}

func (r *rpcClient) NewJsonRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, "application/json")
}
<commit_msg>Remove unused roundtripper code<commit_after>package client

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/micro/go-micro/broker"
	c "github.com/micro/go-micro/context"
	"github.com/micro/go-micro/errors"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	rpc "github.com/youtube/vitess/go/rpcplus"

	"github.com/golang/protobuf/proto"
	"golang.org/x/net/context"
)

type rpcClient struct {
	once sync.Once
	opts options
}

func newRpcClient(opt ...Option) Client {
	opts := options{
		codecs: make(map[string]CodecFunc),
	}

	for _, o := range opt {
		o(&opts)
	}

	if len(opts.contentType) == 0 {
		opts.contentType = defaultContentType
	}

	if opts.transport == nil {
		opts.transport = transport.DefaultTransport
	}

	if opts.broker == nil {
		opts.broker = broker.DefaultBroker
	}

	var once sync.Once

	return &rpcClient{
		once: once,
		opts: opts,
	}
}

func (r *rpcClient) codecFunc(contentType string) (codecFunc, error) {
	if cf, ok := r.opts.codecs[contentType]; ok {
		return codecWrap(cf), nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (r *rpcClient) call(ctx context.Context, address string, request Request, response interface{}) error {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := c.GetMetadata(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	msg.Header["Content-Type"] = request.ContentType()

	cf, err := r.codecFunc(request.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.transport.Dial(address)
	if err != nil {
		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}
	defer c.Close()

	client := rpc.NewClientWithCodec(newRpcPlusCodec(msg, c, cf))
	err = client.Call(ctx, request.Method(), request.Request(), response)
	if err != nil {
		return err
	}
	return client.Close()
}

func (r *rpcClient) stream(ctx context.Context, address string, request Request, responseChan interface{}) (Streamer, error) {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := c.GetMetadata(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	msg.Header["Content-Type"] = request.ContentType()

	cf, err := r.codecFunc(request.ContentType())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.transport.Dial(address, transport.WithStream())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}

	client := rpc.NewClientWithCodec(newRpcPlusCodec(msg, c, cf))
	call := client.StreamGo(request.Method(), request.Request(), responseChan)

	return &rpcStream{
		request: request,
		call:    call,
		client:  client,
	}, nil
}

func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}) error {
	return r.call(ctx, address, request, response)
}

// TODO: Call(..., opts *Options) error {
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}) error {
	service, err := registry.GetService(request.Service())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	node, err := nodeSelector(service)
	if err != nil {
		return err
	}

	address := node.Address
	if node.Port > 0 {
		address = fmt.Sprintf("%s:%d", address, node.Port)
	}

	return r.call(ctx, address, request, response)
}

func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, responseChan interface{}) (Streamer, error) {
	return r.stream(ctx, address, request, responseChan)
}

func (r *rpcClient) Stream(ctx context.Context, request Request, responseChan interface{}) (Streamer, error) {
	service, err := registry.GetService(request.Service())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	node, err := nodeSelector(service)
	if err != nil {
		return nil, err
	}

	address := node.Address
	if node.Port > 0 {
		address = fmt.Sprintf("%s:%d", address, node.Port)
	}

	return r.stream(ctx, address, request, responseChan)
}

func (r *rpcClient) Publish(ctx context.Context, p Publication) error {
	md, ok := c.GetMetadata(ctx)
	if !ok {
		md = make(map[string]string)
	}
	md["Content-Type"] = p.ContentType()

	// encode message body
	var body []byte

	switch p.ContentType() {
	case "application/octet-stream":
		b, err := proto.Marshal(p.Message().(proto.Message))
		if err != nil {
			return err
		}
		body = b
	case "application/json":
		b, err := json.Marshal(p.Message())
		if err != nil {
			return err
		}
		body = b
	}

	r.once.Do(func() {
		r.opts.broker.Connect()
	})

	return r.opts.broker.Publish(p.Topic(), &broker.Message{
		Header: md,
		Body:   body,
	})
}

func (r *rpcClient) NewPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, r.opts.contentType)
}

func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, "application/octet-stream")
}
func (r *rpcClient) NewRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, r.opts.contentType)
}

func (r *rpcClient) NewProtoRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, "application/octet-stream")
}

func (r *rpcClient) NewJsonRequest(service, method string, request interface{}) Request {
	return newRpcRequest(service, method, request, "application/json")
}
<|endoftext|>


<commit_before>package manifest

import (
	"encoding/json"
	"time"

	"github.com/containers/image/types"
	"github.com/opencontainers/image-spec/specs-go"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

// OCI1 is a manifest.Manifest implementation for OCI images.
// The underlying data from imgspecv1.Manifest is also available.
type OCI1 struct {
	imgspecv1.Manifest
}

// OCI1FromManifest creates an OCI1 manifest instance from a manifest blob.
func OCI1FromManifest(manifest []byte) (*OCI1, error) {
	oci1 := OCI1{}
	if err := json.Unmarshal(manifest, &oci1); err != nil {
		return nil, err
	}
	return &oci1, nil
}

// OCI1FromComponents creates an OCI1 manifest instance from the supplied data.
func OCI1FromComponents(config imgspecv1.Descriptor, layers []imgspecv1.Descriptor) *OCI1 {
	return &OCI1{
		imgspecv1.Manifest{
			Versioned: specs.Versioned{SchemaVersion: 2},
			Config:    config,
			Layers:    layers,
		},
	}
}

// OCI1Clone creates a copy of the supplied OCI1 manifest.
func OCI1Clone(src *OCI1) *OCI1 {
	return &OCI1{
		Manifest: src.Manifest,
	}
}

// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
func (m *OCI1) ConfigInfo() types.BlobInfo {
	return types.BlobInfo{Digest: m.Config.Digest, Size: m.Config.Size, Annotations: m.Config.Annotations}
}

// LayerInfos returns a list of BlobInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
func (m *OCI1) LayerInfos() []types.BlobInfo {
	blobs := []types.BlobInfo{}
	for _, layer := range m.Layers {
		blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs, MediaType: layer.MediaType})
	}
	return blobs
}

// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
	if len(m.Layers) != len(layerInfos) {
		return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.Layers), len(layerInfos))
	}
	original := m.Layers
	m.Layers = make([]imgspecv1.Descriptor, len(layerInfos))
	for i, info := range layerInfos {
		m.Layers[i].MediaType = original[i].MediaType
		m.Layers[i].Digest = info.Digest
		m.Layers[i].Size = info.Size
		m.Layers[i].Annotations = info.Annotations
		m.Layers[i].URLs = info.URLs
	}
	return nil
}

// Serialize returns the manifest in a blob format.
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
func (m *OCI1) Serialize() ([]byte, error) {
	return json.Marshal(*m)
}

// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
	config, err := configGetter(m.ConfigInfo())
	if err != nil {
		return nil, err
	}
	v1 := &imgspecv1.Image{}
	if err := json.Unmarshal(config, v1); err != nil {
		return nil, err
	}
	created := time.Time{}
	if v1.Created != nil {
		created = *v1.Created
	}
	i := &types.ImageInspectInfo{
		Tag:           "",
		Created:       created,
		DockerVersion: "",
		Labels:        v1.Config.Labels,
		Architecture:  v1.Architecture,
		Os:            v1.OS,
		Layers:        []string{},
	}
	return i, nil
}
<commit_msg>manifest.OCI.Inspect(): try to preserve DockerVersion<commit_after>package manifest

import (
	"encoding/json"
	"time"

	"github.com/containers/image/types"
	"github.com/opencontainers/image-spec/specs-go"
	imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

// OCI1 is a manifest.Manifest implementation for OCI images.
// The underlying data from imgspecv1.Manifest is also available.
type OCI1 struct {
	imgspecv1.Manifest
}

// OCI1FromManifest creates an OCI1 manifest instance from a manifest blob.
func OCI1FromManifest(manifest []byte) (*OCI1, error) {
	oci1 := OCI1{}
	if err := json.Unmarshal(manifest, &oci1); err != nil {
		return nil, err
	}
	return &oci1, nil
}

// OCI1FromComponents creates an OCI1 manifest instance from the supplied data.
func OCI1FromComponents(config imgspecv1.Descriptor, layers []imgspecv1.Descriptor) *OCI1 {
	return &OCI1{
		imgspecv1.Manifest{
			Versioned: specs.Versioned{SchemaVersion: 2},
			Config:    config,
			Layers:    layers,
		},
	}
}

// OCI1Clone creates a copy of the supplied OCI1 manifest.
func OCI1Clone(src *OCI1) *OCI1 {
	return &OCI1{
		Manifest: src.Manifest,
	}
}

// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
func (m *OCI1) ConfigInfo() types.BlobInfo {
	return types.BlobInfo{Digest: m.Config.Digest, Size: m.Config.Size, Annotations: m.Config.Annotations}
}

// LayerInfos returns a list of BlobInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
// The Digest field is guaranteed to be provided; Size may be -1.
// WARNING: The list may contain duplicates, and they are semantically relevant.
func (m *OCI1) LayerInfos() []types.BlobInfo {
	blobs := []types.BlobInfo{}
	for _, layer := range m.Layers {
		blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs, MediaType: layer.MediaType})
	}
	return blobs
}

// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
	if len(m.Layers) != len(layerInfos) {
		return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.Layers), len(layerInfos))
	}
	original := m.Layers
	m.Layers = make([]imgspecv1.Descriptor, len(layerInfos))
	for i, info := range layerInfos {
		m.Layers[i].MediaType = original[i].MediaType
		m.Layers[i].Digest = info.Digest
		m.Layers[i].Size = info.Size
		m.Layers[i].Annotations = info.Annotations
		m.Layers[i].URLs = info.URLs
	}
	return nil
}

// Serialize returns the manifest in a blob format.
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
func (m *OCI1) Serialize() ([]byte, error) {
	return json.Marshal(*m)
}

// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
	config, err := configGetter(m.ConfigInfo())
	if err != nil {
		return nil, err
	}
	v1 := &imgspecv1.Image{}
	if err := json.Unmarshal(config, v1); err != nil {
		return nil, err
	}
	d1 := &Schema2V1Image{}
	json.Unmarshal(config, d1)
	created := time.Time{}
	if v1.Created != nil {
		created = *v1.Created
	}
	i := &types.ImageInspectInfo{
		Tag:           "",
		Created:       created,
		DockerVersion: d1.DockerVersion,
		Labels:        v1.Config.Labels,
		Architecture:  v1.Architecture,
		Os:            v1.OS,
		Layers:        []string{},
	}
	return i, nil
}
<|endoftext|>


<commit_before>package stats

import (
	"runtime"
	"time"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/mem"

	shelpers "github.com/hashicorp/nomad/helper/stats"
)

// HostStats represents resource usage stats of the host running a Nomad client
type HostStats struct {
	Memory           *MemoryStats
	CPU              []*CPUStats
	DiskStats        []*DiskStats
	Uptime           uint64
	Timestamp        int64
	CPUTicksConsumed float64
}

// MemoryStats represnts stats related to virtual memory usage
type MemoryStats struct {
	Total     uint64
	Available uint64
	Used      uint64
	Free      uint64
}

// CPUStats represents stats related to cpu usage
type CPUStats struct {
	CPU    string
	User   float64
	System float64
	Idle   float64
	Total  float64
}

// DiskStats represents stats related to disk usage
type DiskStats struct {
	Device            string
	Mountpoint        string
	Size              uint64
	Used              uint64
	Available         uint64
	UsedPercent       float64
	InodesUsedPercent float64
}

// HostStatsCollector collects host resource usage stats
type HostStatsCollector struct {
	clkSpeed        float64
	numCores        int
	statsCalculator map[string]*HostCpuStatsCalculator
}

// NewHostStatsCollector returns a HostStatsCollector
func NewHostStatsCollector() *HostStatsCollector {
	numCores := runtime.NumCPU()
	statsCalculator := make(map[string]*HostCpuStatsCalculator)
	collector := &HostStatsCollector{
		statsCalculator: statsCalculator,
		numCores:        numCores,
	}
	return collector
}

// Collect collects stats related to resource usage of a host
func (h *HostStatsCollector) Collect() (*HostStats, error) {
	hs := &HostStats{Timestamp: time.Now().UTC().UnixNano()}
	if memStats, err := mem.VirtualMemory(); err == nil {
		ms := &MemoryStats{
			Total:     memStats.Total,
			Available: memStats.Available,
			Used:      memStats.Used,
			Free:      memStats.Free,
		}
		hs.Memory = ms
	}

	ticksConsumed := 0.0
	if cpuStats, err := cpu.Times(true); err == nil {
		cs := make([]*CPUStats, len(cpuStats))
		for idx, cpuStat := range cpuStats {
			cs[idx] = &CPUStats{
				CPU:    cpuStat.CPU,
				User:   cpuStat.User,
				System: cpuStat.System,
				Idle:   cpuStat.Idle,
			}
			percentCalculator, ok := h.statsCalculator[cpuStat.CPU]
			if !ok {
				percentCalculator = NewHostCpuStatsCalculator()
				h.statsCalculator[cpuStat.CPU] = percentCalculator
			}
			idle, user, system, total := percentCalculator.Calculate(cpuStat)
			cs[idx].Idle = idle
			cs[idx].System = system
			cs[idx].User = user
			cs[idx].Total = total
			ticksConsumed += (total / 100) * shelpers.TotalTicksAvailable()
		}
		hs.CPU = cs
		hs.CPUTicksConsumed = ticksConsumed
	}

	if partitions, err := disk.Partitions(false); err == nil {
		var diskStats []*DiskStats
		for _, partition := range partitions {
			if usage, err := disk.Usage(partition.Mountpoint); err == nil {
				ds := DiskStats{
					Device:            partition.Device,
					Mountpoint:        partition.Mountpoint,
					Size:              usage.Total,
					Used:              usage.Used,
					Available:         usage.Free,
					UsedPercent:       usage.UsedPercent,
					InodesUsedPercent: usage.InodesUsedPercent,
				}
				diskStats = append(diskStats, &ds)
			}
		}
		hs.DiskStats = diskStats
	}

	if uptime, err := host.Uptime(); err == nil {
		hs.Uptime = uptime
	}
	return hs, nil
}

// HostCpuStatsCalculator calculates cpu usage percentages
type HostCpuStatsCalculator struct {
	prevIdle   float64
	prevUser   float64
	prevSystem float64
	prevBusy   float64
	prevTotal  float64
}

// NewHostCpuStatsCalculator returns a HostCpuStatsCalculator
func NewHostCpuStatsCalculator() *HostCpuStatsCalculator {
	return &HostCpuStatsCalculator{}
}

// Calculate calculates the current cpu usage percentages
func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, user float64, system float64, total float64) {
	currentIdle := times.Idle
	currentUser := times.User
	currentSystem := times.System
	currentTotal := times.Total()

	deltaTotal := currentTotal - h.prevTotal
	idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100
	user = ((currentUser - h.prevUser) / deltaTotal) * 100
	system = ((currentSystem - h.prevSystem) / deltaTotal) * 100

	currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
		times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen

	total = ((currentBusy - h.prevBusy) / deltaTotal) * 100

	h.prevIdle = currentIdle
	h.prevUser = currentUser
	h.prevSystem = currentSystem
	h.prevTotal = currentTotal
	h.prevBusy = currentBusy

	return
}
<commit_msg>Fixed the calculation of the host node ticks<commit_after>package stats

import (
	"runtime"
	"time"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/mem"

	shelpers "github.com/hashicorp/nomad/helper/stats"
)

// HostStats represents resource usage stats of the host running a Nomad client
type HostStats struct {
	Memory           *MemoryStats
	CPU              []*CPUStats
	DiskStats        []*DiskStats
	Uptime           uint64
	Timestamp        int64
	CPUTicksConsumed float64
}

// MemoryStats represnts stats related to virtual memory usage
type MemoryStats struct {
	Total     uint64
	Available uint64
	Used      uint64
	Free      uint64
}

// CPUStats represents stats related to cpu usage
type CPUStats struct {
	CPU    string
	User   float64
	System float64
	Idle   float64
	Total  float64
}

// DiskStats represents stats related to disk usage
type DiskStats struct {
	Device            string
	Mountpoint        string
	Size              uint64
	Used              uint64
	Available         uint64
	UsedPercent       float64
	InodesUsedPercent float64
}

// HostStatsCollector collects host resource usage stats
type HostStatsCollector struct {
	clkSpeed        float64
	numCores        int
	statsCalculator map[string]*HostCpuStatsCalculator
}

// NewHostStatsCollector returns a HostStatsCollector
func NewHostStatsCollector() *HostStatsCollector {
	numCores := runtime.NumCPU()
	statsCalculator := make(map[string]*HostCpuStatsCalculator)
	collector := &HostStatsCollector{
		statsCalculator: statsCalculator,
		numCores:        numCores,
	}
	return collector
}

// Collect collects stats related to resource usage of a host
func (h *HostStatsCollector) Collect() (*HostStats, error) {
	hs := &HostStats{Timestamp: time.Now().UTC().UnixNano()}
	if memStats, err := mem.VirtualMemory(); err == nil {
		ms := &MemoryStats{
			Total:     memStats.Total,
			Available: memStats.Available,
			Used:      memStats.Used,
			Free:      memStats.Free,
		}
		hs.Memory = ms
	}

	ticksConsumed := 0.0
	if cpuStats, err := cpu.Times(true); err == nil {
		cs := make([]*CPUStats, len(cpuStats))
		for idx, cpuStat := range cpuStats {
			cs[idx] = &CPUStats{
				CPU:    cpuStat.CPU,
				User:   cpuStat.User,
				System: cpuStat.System,
				Idle:   cpuStat.Idle,
			}
			percentCalculator, ok := h.statsCalculator[cpuStat.CPU]
			if !ok {
				percentCalculator = NewHostCpuStatsCalculator()
				h.statsCalculator[cpuStat.CPU] = percentCalculator
			}
			idle, user, system, total := percentCalculator.Calculate(cpuStat)
			cs[idx].Idle = idle
			cs[idx].System = system
			cs[idx].User = user
			cs[idx].Total = total
			ticksConsumed += (total / 100) * (shelpers.TotalTicksAvailable() / float64(len(cpuStats)))
		}
		hs.CPU = cs
		hs.CPUTicksConsumed = ticksConsumed
	}

	if partitions, err := disk.Partitions(false); err == nil {
		var diskStats []*DiskStats
		for _, partition := range partitions {
			if usage, err := disk.Usage(partition.Mountpoint); err == nil {
				ds := DiskStats{
					Device:            partition.Device,
					Mountpoint:        partition.Mountpoint,
					Size:              usage.Total,
					Used:              usage.Used,
					Available:         usage.Free,
					UsedPercent:       usage.UsedPercent,
					InodesUsedPercent: usage.InodesUsedPercent,
				}
				diskStats = append(diskStats, &ds)
			}
		}
		hs.DiskStats = diskStats
	}

	if uptime, err := host.Uptime(); err == nil {
		hs.Uptime = uptime
	}
	return hs, nil
}

// HostCpuStatsCalculator calculates cpu usage percentages
type HostCpuStatsCalculator struct {
	prevIdle   float64
	prevUser   float64
	prevSystem float64
	prevBusy   float64
	prevTotal  float64
}

// NewHostCpuStatsCalculator returns a HostCpuStatsCalculator
func NewHostCpuStatsCalculator() *HostCpuStatsCalculator {
	return &HostCpuStatsCalculator{}
}

// Calculate calculates the current cpu usage percentages
func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, user float64, system float64, total float64) {
	currentIdle := times.Idle
	currentUser := times.User
	currentSystem := times.System
	currentTotal := times.Total()

	deltaTotal := currentTotal - h.prevTotal
	idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100
	user = ((currentUser - h.prevUser) / deltaTotal) * 100
	system = ((currentSystem - h.prevSystem) / deltaTotal) * 100

	currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
		times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen

	total = ((currentBusy - h.prevBusy) / deltaTotal) * 100

	h.prevIdle = currentIdle
	h.prevUser = currentUser
	h.prevSystem = currentSystem
	h.prevTotal = currentTotal
	h.prevBusy = currentBusy

	return
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package merge

import (
	"fmt"

	"sigs.k8s.io/structured-merge-diff/fieldpath"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// Converter is an interface to the conversion logic. The converter
// needs to be able to convert objects from one version to another.
type Converter interface {
	Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error)
	IsMissingVersionError(error) bool
}

// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
	Converter Converter
}

func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
	if managers == nil {
		managers = fieldpath.ManagedFields{}
	}
	conflicts := fieldpath.ManagedFields{}
	removed := fieldpath.ManagedFields{}
	type Versioned struct {
		oldObject typed.TypedValue
		newObject typed.TypedValue
	}
	versions := map[fieldpath.APIVersion]Versioned{
		version: Versioned{
			oldObject: oldObject,
			newObject: newObject,
		},
	}

	for manager, managerSet := range managers {
		if manager == workflow {
			continue
		}
		versioned, ok := versions[managerSet.APIVersion]
		if !ok {
			var err error
			versioned.oldObject, err = s.Converter.Convert(oldObject, managerSet.APIVersion)
			if err != nil {
				if s.Converter.IsMissingVersionError(err) {
					delete(managers, manager)
					continue
				}
				return nil, fmt.Errorf("failed to convert old object: %v", err)
			}
			versioned.newObject, err = s.Converter.Convert(newObject, managerSet.APIVersion)
			if err != nil {
				if s.Converter.IsMissingVersionError(err) {
					delete(managers, manager)
					continue
				}
				return nil, fmt.Errorf("failed to convert new object: %v", err)
			}
			versions[managerSet.APIVersion] = versioned
		}
		compare, err := versioned.oldObject.Compare(versioned.newObject)
		if err != nil {
			return nil, fmt.Errorf("failed to compare objects: %v", err)
		}

		conflictSet := managerSet.Intersection(compare.Modified.Union(compare.Added))
		if !conflictSet.Empty() {
			conflicts[manager] = &fieldpath.VersionedSet{
				Set:        conflictSet,
				APIVersion: managerSet.APIVersion,
			}
		}

		if !compare.Removed.Empty() {
			removed[manager] = &fieldpath.VersionedSet{
				Set:        compare.Removed,
				APIVersion: managerSet.APIVersion,
			}
		}
	}

	if !force && len(conflicts) != 0 {
		return nil, ConflictsFromManagers(conflicts)
	}

	for manager, conflictSet := range conflicts {
		managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set)
	}

	for manager, removedSet := range removed {
		managers[manager].Set = managers[manager].Set.Difference(removedSet.Set)
	}

	for manager := range managers {
		if managers[manager].Set.Empty() {
			delete(managers, manager)
		}
	}

	return managers, nil
}

// Update is the method you should call once you've merged your final
// object on CREATE/UPDATE/PATCH verbs. newObject must be the object
// that you intend to persist (after applying the patch if this is for a
// PATCH call), and liveObject must be the original object (empty if
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
	var err error
	managers, err = s.update(liveObject, newObject, version, managers, manager, true)
	if err != nil {
		return fieldpath.ManagedFields{}, err
	}
	compare, err := liveObject.Compare(newObject)
	if err != nil {
		return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
	}
	if _, ok := managers[manager]; !ok {
		managers[manager] = &fieldpath.VersionedSet{
			Set: fieldpath.NewSet(),
		}
	}
	managers[manager].Set = managers[manager].Set.Union(compare.Modified).Union(compare.Added).Difference(compare.Removed)
	managers[manager].APIVersion = version
	if managers[manager].Set.Empty() {
		delete(managers, manager)
	}
	return managers, nil
}

// Apply should be called when Apply is run, given the current object as
// well as the configuration that is applied. This will merge the object
// and return it.
func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) {
	newObject, err := liveObject.Merge(configObject)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
	}
	set, err := configObject.ToFieldSet()
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
	}
	appliedSet := &fieldpath.VersionedSet{
		Set:        set,
		APIVersion: version,
		Applied:    true,
	}
	newObject, err = s.prune(newObject, managers, manager, appliedSet)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
	}
	managers, err = s.update(liveObject, newObject, version, managers, manager, force)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, err
	}
	managers[manager] = appliedSet
	if managers[manager].Set.Empty() {
		delete(managers, manager)
	}
	return newObject, managers, nil
}

func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, appliedSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
	lastSet := managers[applyingManager]
	if lastSet == nil || lastSet.Set.Empty() {
		return merged, nil
	}
	convertedMerged, err := s.Converter.Convert(merged, lastSet.APIVersion)
	if err != nil {
		if s.Converter.IsMissingVersionError(err) {
			return merged, nil
		}
		return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
	}
	pruned := convertedMerged.RemoveItems(lastSet.Set)
	pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager, appliedSet)
	if err != nil {
		return nil, fmt.Errorf("failed add back owned items: %v", err)
	}
	pruned, err = s.addBackDanglingItems(convertedMerged, pruned, lastSet)
	if err != nil {
		return nil, fmt.Errorf("failed add back dangling items: %v", err)
	}
	return s.Converter.Convert(pruned, appliedSet.APIVersion)
}

func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string, appliedSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
	var err error
	managersAtVersion := map[fieldpath.APIVersion][]string{}
	for manager, managerSet := range managedFields {
		if !managerSet.Applied {
			continue
		}
		if manager == applyingManager {
			managerSet = appliedSet
		}
		managersAtVersion[managerSet.APIVersion] = append(managersAtVersion[managerSet.APIVersion], manager)
	}
	for version, managers := range managersAtVersion {
		merged, err = s.Converter.Convert(merged, version)
		if err != nil {
			if s.Converter.IsMissingVersionError(err) {
				continue
			}
			return nil, fmt.Errorf("failed to convert merged object at version %v: %v", version, err)
		}
		pruned, err = s.Converter.Convert(pruned, version)
		if err != nil {
			if s.Converter.IsMissingVersionError(err) {
				continue
			}
			return nil, fmt.Errorf("failed to convert pruned object at version %v: %v", version, err)
		}
		mergedSet, err := merged.ToFieldSet()
		if err != nil {
			return nil, fmt.Errorf("failed to create field set from merged object at version %v: %v", version, err)
		}
		prunedSet, err := pruned.ToFieldSet()
		if err != nil {
			return nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
		}
		toAddBack := prunedSet
		for _, manager := range managers {
			managerSet := managedFields[manager]
			if manager == applyingManager {
				managerSet = appliedSet
			}
			toAddBack = toAddBack.Union(managerSet.Set)
		}
		pruned = merged.RemoveItems(mergedSet.Difference(toAddBack))
	}
	return pruned, nil
}

func (s *Updater) addBackDanglingItems(merged, pruned typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
	convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion)
	if err != nil {
		if s.Converter.IsMissingVersionError(err) {
			return merged, nil
		}
		return nil, fmt.Errorf("failed to convert pruned object to last applied version: %v", err)
	}
	prunedSet, err := convertedPruned.ToFieldSet()
	if err != nil {
		return nil, fmt.Errorf("failed to create field set from pruned object in last applied version: %v", err)
	}
	mergedSet, err := merged.ToFieldSet()
	if err != nil {
		return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
	}
	return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(lastSet.Set)), nil
}
<commit_msg>Simplify logic<commit_after>/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package merge

import (
	"fmt"

	"sigs.k8s.io/structured-merge-diff/fieldpath"
	"sigs.k8s.io/structured-merge-diff/typed"
)

// Converter is an interface to the conversion logic. The converter
// needs to be able to convert objects from one version to another.
type Converter interface {
	Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error)
	IsMissingVersionError(error) bool
}

// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
	Converter Converter
}

func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
	conflicts := fieldpath.ManagedFields{}
	removed := fieldpath.ManagedFields{}
	type Versioned struct {
		oldObject typed.TypedValue
		newObject typed.TypedValue
	}
	versions := map[fieldpath.APIVersion]Versioned{
		version: Versioned{
			oldObject: oldObject,
			newObject: newObject,
		},
	}

	for manager, managerSet := range managers {
		if manager == workflow {
			continue
		}
		versioned, ok := versions[managerSet.APIVersion]
		if !ok {
			var err error
			versioned.oldObject, err = s.Converter.Convert(oldObject, managerSet.APIVersion)
			if err != nil {
				if s.Converter.IsMissingVersionError(err) {
					delete(managers, manager)
					continue
				}
				return nil, fmt.Errorf("failed to convert old object: %v", err)
			}
			versioned.newObject, err = s.Converter.Convert(newObject, managerSet.APIVersion)
			if err != nil {
				if s.Converter.IsMissingVersionError(err) {
					delete(managers, manager)
					continue
				}
				return nil, fmt.Errorf("failed to convert new object: %v", err)
			}
			versions[managerSet.APIVersion] = versioned
		}
		compare, err := versioned.oldObject.Compare(versioned.newObject)
		if err != nil {
			return nil, fmt.Errorf("failed to compare objects: %v", err)
		}

		conflictSet := managerSet.Intersection(compare.Modified.Union(compare.Added))
		if !conflictSet.Empty() {
			conflicts[manager] = &fieldpath.VersionedSet{
				Set:        conflictSet,
				APIVersion: managerSet.APIVersion,
			}
		}

		if !compare.Removed.Empty() {
			removed[manager] = &fieldpath.VersionedSet{
				Set:        compare.Removed,
				APIVersion: managerSet.APIVersion,
			}
		}
	}

	if !force && len(conflicts) != 0 {
		return nil, ConflictsFromManagers(conflicts)
	}

	for manager, conflictSet := range conflicts {
		managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set)
	}

	for manager, removedSet := range removed {
		managers[manager].Set = managers[manager].Set.Difference(removedSet.Set)
	}

	for manager := range managers {
		if managers[manager].Set.Empty() {
			delete(managers, manager)
		}
	}

	return managers, nil
}

// Update is the method you should call once you've merged your final
// object on CREATE/UPDATE/PATCH verbs. newObject must be the object
// that you intend to persist (after applying the patch if this is for a
// PATCH call), and liveObject must be the original object (empty if
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
	var err error
	managers = shallowCopyManagers(managers)
	managers, err = s.update(liveObject, newObject, version, managers, manager, true)
	if err != nil {
		return fieldpath.ManagedFields{}, err
	}
	compare, err := liveObject.Compare(newObject)
	if err != nil {
		return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
	}
	if _, ok := managers[manager]; !ok {
		managers[manager] = &fieldpath.VersionedSet{
			Set: fieldpath.NewSet(),
		}
	}
	managers[manager].Set = managers[manager].Set.Union(compare.Modified).Union(compare.Added).Difference(compare.Removed)
	managers[manager].APIVersion = version
	if managers[manager].Set.Empty() {
		delete(managers, manager)
	}
	return managers, nil
}

// Apply should be called when Apply is run, given the current object as
// well as the configuration that is applied. This will merge the object
// and return it.
func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) {
	managers = shallowCopyManagers(managers)
	newObject, err := liveObject.Merge(configObject)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
	}
	lastSet := managers[manager]
	set, err := configObject.ToFieldSet()
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
	}
	managers[manager] = &fieldpath.VersionedSet{
		Set:        set,
		APIVersion: version,
		Applied:    true,
	}
	newObject, err = s.prune(newObject, managers, manager, lastSet)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
	}
	managers, err = s.update(liveObject, newObject, version, managers, manager, force)
	if err != nil {
		return nil, fieldpath.ManagedFields{}, err
	}
	return newObject, managers, nil
}

func shallowCopyManagers(managers fieldpath.ManagedFields) fieldpath.ManagedFields {
	newManagers := fieldpath.ManagedFields{}
	for manager, set := range managers {
		newManagers[manager] = set
	}
	return newManagers
}

func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
	if lastSet == nil || lastSet.Set.Empty() {
		return merged, nil
	}
	convertedMerged, err := s.Converter.Convert(merged, lastSet.APIVersion)
	if err != nil {
		if s.Converter.IsMissingVersionError(err) {
			return merged, nil
		}
		return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
	}
	pruned := convertedMerged.RemoveItems(lastSet.Set)
	pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
	if err != nil {
		return nil, fmt.Errorf("failed add back owned items: %v", err)
	}
	pruned, err = s.addBackDanglingItems(convertedMerged, pruned, lastSet)
	if err != nil {
		return nil, fmt.Errorf("failed add back dangling items: %v", err)
	}
	return s.Converter.Convert(pruned, managers[applyingManager].APIVersion)
}

func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (typed.TypedValue, error) {
	var err error
	managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
	for _, managerSet := range managedFields {
		if managerSet.Applied {
			if _, ok := managedAtVersion[managerSet.APIVersion]; !ok {
				managedAtVersion[managerSet.APIVersion] = fieldpath.NewSet()
			}
			managedAtVersion[managerSet.APIVersion] = managedAtVersion[managerSet.APIVersion].Union(managerSet.Set)
		}
	}
	for version, managed := range managedAtVersion {
		merged, err = s.Converter.Convert(merged, version)
		if err != nil {
			if s.Converter.IsMissingVersionError(err) {
				continue
			}
			return nil, fmt.Errorf("failed to convert merged object at version %v: %v", version, err)
		}
		pruned, err = s.Converter.Convert(pruned, version)
		if err != nil {
			if s.Converter.IsMissingVersionError(err) {
				continue
			}
			return nil, fmt.Errorf("failed to convert pruned object at version %v: %v", version, err)
		}
		mergedSet, err := merged.ToFieldSet()
		if err != nil {
			return nil, fmt.Errorf("failed to create field set from merged object at version %v: %v", version, err)
		}
		prunedSet, err := pruned.ToFieldSet()
		if err != nil {
			return nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
		}
		pruned = merged.RemoveItems(mergedSet.Difference(prunedSet.Union(managed)))
	}
	return pruned, nil
}

func (s *Updater) addBackDanglingItems(merged, pruned typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
	convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion)
	if err != nil {
		if s.Converter.IsMissingVersionError(err) {
			return merged, nil
		}
		return nil, fmt.Errorf("failed to convert pruned object to last applied version: %v", err)
	}
	prunedSet, err := convertedPruned.ToFieldSet()
	if err != nil {
		return nil, fmt.Errorf("failed to create field set from pruned object in last applied version: %v", err)
	}
	mergedSet, err := merged.ToFieldSet()
	if err != nil {
		return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
	}
	return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(lastSet.Set)), nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/command/commands"
	cliconfig "github.com/docker/cli/cli/config"
	"github.com/docker/cli/cli/debug"
	cliflags "github.com/docker/cli/cli/flags"
	"github.com/docker/docker/api/types/versions"
	"github.com/docker/docker/client"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
	opts := cliflags.NewClientOptions()
	var flags *pflag.FlagSet

	cmd := &cobra.Command{
		Use:              "docker [OPTIONS] COMMAND [ARG...]",
		Short:            "A self-sufficient runtime for containers",
		SilenceUsage:     true,
		SilenceErrors:    true,
		TraverseChildren: true,
		Args:             noArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return command.ShowHelp(dockerCli.Err())(cmd, args)
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			// flags must be the top-level command flags, not cmd.Flags()
			opts.Common.SetDefaultOptions(flags)
			dockerPreRun(opts)
			if err := dockerCli.Initialize(opts); err != nil {
				return err
			}
			return isSupported(cmd, dockerCli)
		},
		Version:               fmt.Sprintf("%s, build %s", cli.Version, cli.GitCommit),
		DisableFlagsInUseLine: true,
	}
	cli.SetupRootCommand(cmd)

	flags = cmd.Flags()
	flags.BoolP("version", "v", false, "Print version information and quit")
	flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
	opts.Common.InstallFlags(flags)

	setFlagErrorFunc(dockerCli, cmd, flags, opts)

	setHelpFunc(dockerCli, cmd, flags, opts)

	cmd.SetOutput(dockerCli.Out())
	commands.AddCommands(cmd, dockerCli)

	disableFlagsInUseLine(cmd)
	setValidateArgs(dockerCli, cmd, flags, opts)

	return cmd
}

func disableFlagsInUseLine(cmd *cobra.Command) {
	visitAll(cmd, func(ccmd *cobra.Command) {
		// do not add a `[flags]` to the end of the usage line.
		ccmd.DisableFlagsInUseLine = true
	})
}

func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	// When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate
	// output if the feature is not supported.
	// As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc
	// is called.
	flagErrorFunc := cmd.FlagErrorFunc()
	cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
		if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
			return err
		}
		if err := isSupported(cmd, dockerCli); err != nil {
			return err
		}
		return flagErrorFunc(cmd, err)
	})
}

func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	defaultHelpFunc := cmd.HelpFunc()
	cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
		if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
			ccmd.Println(err)
			return
		}
		if err := isSupported(ccmd, dockerCli); err != nil {
			ccmd.Println(err)
			return
		}
		if err := hideUnsupportedFeatures(ccmd, dockerCli); err != nil {
			ccmd.Println(err)
			return
		}
		defaultHelpFunc(ccmd, args)
	})
}

func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	// The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook.
	// As a result, here we replace the existing Args validation func to a wrapper,
	// where the wrapper will check to see if the feature is supported or not.
	// The Args validation error will only be returned if the feature is supported.
	visitAll(cmd, func(ccmd *cobra.Command) {
		// if there is no tags for a command or any of its parent,
		// there is no need to wrap the Args validation.
		if !hasTags(ccmd) {
			return
		}

		if ccmd.Args == nil {
			return
		}

		cmdArgs := ccmd.Args
		ccmd.Args = func(cmd *cobra.Command, args []string) error {
			if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
				return err
			}
			if err := isSupported(cmd, dockerCli); err != nil {
				return err
			}
			return cmdArgs(cmd, args)
		}
	})
}

func initializeDockerCli(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *cliflags.ClientOptions) error {
	if dockerCli.Client() != nil {
		return nil
	}
	// when using --help, PersistentPreRun is not called, so initialization is needed.
	// flags must be the top-level command flags, not cmd.Flags()
	opts.Common.SetDefaultOptions(flags)
	dockerPreRun(opts)
	return dockerCli.Initialize(opts)
}

// visitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
	for _, cmd := range root.Commands() {
		visitAll(cmd, fn)
	}
	fn(root)
}

func noArgs(cmd *cobra.Command, args []string) error {
	if len(args) == 0 {
		return nil
	}
	return fmt.Errorf(
		"docker: '%s' is not a docker command.\nSee 'docker --help'", args[0])
}

func main() {
	dockerCli, err := command.NewDockerCli()
	if err != nil {
		fmt.Fprintln(dockerCli.Err(), err)
		os.Exit(1)
	}
	logrus.SetOutput(dockerCli.Err())

	cmd := newDockerCommand(dockerCli)

	if err := cmd.Execute(); err != nil {
		if sterr, ok := err.(cli.StatusError); ok {
			if sterr.Status != "" {
				fmt.Fprintln(dockerCli.Err(), sterr.Status)
			}
			// StatusError should only be used for errors, and all errors should
			// have a non-zero exit status, so never exit with 0
			if sterr.StatusCode == 0 {
				os.Exit(1)
			}
			os.Exit(sterr.StatusCode)
		}
		fmt.Fprintln(dockerCli.Err(), err)
		os.Exit(1)
	}
}

func dockerPreRun(opts *cliflags.ClientOptions) {
	cliflags.SetLogLevel(opts.Common.LogLevel)

	if opts.ConfigDir != "" {
		cliconfig.SetDir(opts.ConfigDir)
	}

	if opts.Common.Debug {
		debug.Enable()
	}
}

type versionDetails interface {
	Client() client.APIClient
	ClientInfo() command.ClientInfo
	ServerInfo() command.ServerInfo
}

func hideFeatureFlag(f *pflag.Flag, hasFeature bool, annotation string) {
	if hasFeature {
		return
	}
	if _, ok := f.Annotations[annotation]; ok {
		f.Hidden = true
	}
}

func hideFeatureSubCommand(subcmd *cobra.Command, hasFeature bool, annotation string) {
	if hasFeature {
		return
	}
	if _, ok := subcmd.Annotations[annotation]; ok {
		subcmd.Hidden = true
	}
}

func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental
	hasBuildKit, err := command.BuildKitEnabled(details.ServerInfo())
	if err != nil {
		return err
	}

	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		hideFeatureFlag(f, hasExperimental, "experimental")
		hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
		hideFeatureFlag(f, hasBuildKit, "buildkit")
		hideFeatureFlag(f, !hasBuildKit, "no-buildkit")
		// hide flags not supported by the server
		if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
			f.Hidden = true
		}
		// root command shows all top-level flags
		if cmd.Parent() != nil {
			if commands, ok := f.Annotations["top-level"]; ok {
				f.Hidden = !findCommand(cmd, commands)
			}
		}
	})

	for _, subcmd := range cmd.Commands() {
		hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
		hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
		hideFeatureSubCommand(subcmd, hasBuildKit, "buildkit")
		hideFeatureSubCommand(subcmd, !hasBuildKit, "no-buildkit")
		// hide subcommands not supported by the server
		if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
			subcmd.Hidden = true
		}
		if v, ok := subcmd.Annotations["ostype"]; ok && v != osType {
			subcmd.Hidden = true
		}
	}
	return nil
}

// Checks if a command or one of its ancestors is in the list
func findCommand(cmd *cobra.Command, commands []string) bool {
	if cmd == nil {
		return false
	}
	for _, c := range commands {
		if c == cmd.Name() {
			return true
		}
	}
	return findCommand(cmd.Parent(), commands)
}

func isSupported(cmd *cobra.Command, details versionDetails) error {
	if err := areSubcommandsSupported(cmd, details); err != nil {
		return err
	}
	return areFlagsSupported(cmd, details)
}

func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental

	errs := []string{}

	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		if f.Changed {
			if !isVersionSupported(f, clientVersion) {
				errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion))
				return
			}
			if !isOSTypeSupported(f, osType) {
				errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType))
				return
			}
			if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
				errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
			}
			if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
				errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
			}
			// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
		}
	})
	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "\n"))
	}
	return nil
}

// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental

	// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
	for curr := cmd; curr != nil; curr = curr.Parent() {
		if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
			return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
		}
		if os, ok := curr.Annotations["ostype"]; ok && os != osType {
			return fmt.Errorf("%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", cmd.CommandPath(), os, osType)
		}
		if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
			return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
		}
		if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
			return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
		}
	}
	return nil
}

func getFlagAnnotation(f *pflag.Flag, annotation string) string {
	if value, ok := f.Annotations[annotation]; ok && len(value) == 1 {
		return value[0]
	}
	return ""
}

func isVersionSupported(f *pflag.Flag, clientVersion string) bool {
	if v := getFlagAnnotation(f, "version"); v != "" {
		return versions.GreaterThanOrEqualTo(clientVersion, v)
	}
	return true
}

func isOSTypeSupported(f *pflag.Flag, osType string) bool {
	if v := getFlagAnnotation(f, "ostype"); v != "" && osType != "" {
		return osType == v
	}
	return true
}

// hasTags return true if any of the command's parents has tags
func hasTags(cmd *cobra.Command) bool {
	for curr := cmd; curr != nil; curr = curr.Parent() {
		if len(curr.Annotations) > 0 {
			return true
		}
	}

	return false
}
<commit_msg>Fix using a nil dockerCli if an error occurred during cli creation, using the standard error stream instead.<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/docker/cli/cli"
	"github.com/docker/cli/cli/command"
	"github.com/docker/cli/cli/command/commands"
	cliconfig "github.com/docker/cli/cli/config"
	"github.com/docker/cli/cli/debug"
	cliflags "github.com/docker/cli/cli/flags"
	"github.com/docker/docker/api/types/versions"
	"github.com/docker/docker/client"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
	opts := cliflags.NewClientOptions()
	var flags *pflag.FlagSet

	cmd := &cobra.Command{
		Use:              "docker [OPTIONS] COMMAND [ARG...]",
		Short:            "A self-sufficient runtime for containers",
		SilenceUsage:     true,
		SilenceErrors:    true,
		TraverseChildren: true,
		Args:             noArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return command.ShowHelp(dockerCli.Err())(cmd, args)
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			// flags must be the top-level command flags, not cmd.Flags()
			opts.Common.SetDefaultOptions(flags)
			dockerPreRun(opts)
			if err := dockerCli.Initialize(opts); err != nil {
				return err
			}
			return isSupported(cmd, dockerCli)
		},
		Version:               fmt.Sprintf("%s, build %s", cli.Version, cli.GitCommit),
		DisableFlagsInUseLine: true,
	}
	cli.SetupRootCommand(cmd)

	flags = cmd.Flags()
	flags.BoolP("version", "v", false, "Print version information and quit")
	flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
	opts.Common.InstallFlags(flags)

	setFlagErrorFunc(dockerCli, cmd, flags, opts)

	setHelpFunc(dockerCli, cmd, flags, opts)

	cmd.SetOutput(dockerCli.Out())
	commands.AddCommands(cmd, dockerCli)

	disableFlagsInUseLine(cmd)
	setValidateArgs(dockerCli, cmd, flags, opts)

	return cmd
}

func disableFlagsInUseLine(cmd *cobra.Command) {
	visitAll(cmd, func(ccmd *cobra.Command) {
		// do not add a `[flags]` to the end of the usage line.
		ccmd.DisableFlagsInUseLine = true
	})
}

func setFlagErrorFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	// When invoking `docker stack --nonsense`, we need to make sure FlagErrorFunc return appropriate
	// output if the feature is not supported.
	// As above cli.SetupRootCommand(cmd) have already setup the FlagErrorFunc, we will add a pre-check before the FlagErrorFunc
	// is called.
	flagErrorFunc := cmd.FlagErrorFunc()
	cmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
		if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
			return err
		}
		if err := isSupported(cmd, dockerCli); err != nil {
			return err
		}
		return flagErrorFunc(cmd, err)
	})
}

func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	defaultHelpFunc := cmd.HelpFunc()
	cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
		if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
			ccmd.Println(err)
			return
		}
		if err := isSupported(ccmd, dockerCli); err != nil {
			ccmd.Println(err)
			return
		}
		if err := hideUnsupportedFeatures(ccmd, dockerCli); err != nil {
			ccmd.Println(err)
			return
		}
		defaultHelpFunc(ccmd, args)
	})
}

func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
	// The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook.
	// As a result, here we replace the existing Args validation func to a wrapper,
	// where the wrapper will check to see if the feature is supported or not.
	// The Args validation error will only be returned if the feature is supported.
	visitAll(cmd, func(ccmd *cobra.Command) {
		// if there is no tags for a command or any of its parent,
		// there is no need to wrap the Args validation.
		if !hasTags(ccmd) {
			return
		}

		if ccmd.Args == nil {
			return
		}

		cmdArgs := ccmd.Args
		ccmd.Args = func(cmd *cobra.Command, args []string) error {
			if err := initializeDockerCli(dockerCli, flags, opts); err != nil {
				return err
			}
			if err := isSupported(cmd, dockerCli); err != nil {
				return err
			}
			return cmdArgs(cmd, args)
		}
	})
}

func initializeDockerCli(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *cliflags.ClientOptions) error {
	if dockerCli.Client() != nil {
		return nil
	}
	// when using --help, PersistentPreRun is not called, so initialization is needed.
	// flags must be the top-level command flags, not cmd.Flags()
	opts.Common.SetDefaultOptions(flags)
	dockerPreRun(opts)
	return dockerCli.Initialize(opts)
}

// visitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
	for _, cmd := range root.Commands() {
		visitAll(cmd, fn)
	}
	fn(root)
}

func noArgs(cmd *cobra.Command, args []string) error {
	if len(args) == 0 {
		return nil
	}
	return fmt.Errorf(
		"docker: '%s' is not a docker command.\nSee 'docker --help'", args[0])
}

func main() {
	dockerCli, err := command.NewDockerCli()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	logrus.SetOutput(dockerCli.Err())

	cmd := newDockerCommand(dockerCli)

	if err := cmd.Execute(); err != nil {
		if sterr, ok := err.(cli.StatusError); ok {
			if sterr.Status != "" {
				fmt.Fprintln(dockerCli.Err(), sterr.Status)
			}
			// StatusError should only be used for errors, and all errors should
			// have a non-zero exit status, so never exit with 0
			if sterr.StatusCode == 0 {
				os.Exit(1)
			}
			os.Exit(sterr.StatusCode)
		}
		fmt.Fprintln(dockerCli.Err(), err)
		os.Exit(1)
	}
}

func dockerPreRun(opts *cliflags.ClientOptions) {
	cliflags.SetLogLevel(opts.Common.LogLevel)

	if opts.ConfigDir != "" {
		cliconfig.SetDir(opts.ConfigDir)
	}

	if opts.Common.Debug {
		debug.Enable()
	}
}

type versionDetails interface {
	Client() client.APIClient
	ClientInfo() command.ClientInfo
	ServerInfo() command.ServerInfo
}

func hideFeatureFlag(f *pflag.Flag, hasFeature bool, annotation string) {
	if hasFeature {
		return
	}
	if _, ok := f.Annotations[annotation]; ok {
		f.Hidden = true
	}
}

func hideFeatureSubCommand(subcmd *cobra.Command, hasFeature bool, annotation string) {
	if hasFeature {
		return
	}
	if _, ok := subcmd.Annotations[annotation]; ok {
		subcmd.Hidden = true
	}
}

func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental
	hasBuildKit, err := command.BuildKitEnabled(details.ServerInfo())
	if err != nil {
		return err
	}

	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		hideFeatureFlag(f, hasExperimental, "experimental")
		hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
		hideFeatureFlag(f, hasBuildKit, "buildkit")
		hideFeatureFlag(f, !hasBuildKit, "no-buildkit")
		// hide flags not supported by the server
		if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
			f.Hidden = true
		}
		// root command shows all top-level flags
		if cmd.Parent() != nil {
			if commands, ok := f.Annotations["top-level"]; ok {
				f.Hidden = !findCommand(cmd, commands)
			}
		}
	})

	for _, subcmd := range cmd.Commands() {
		hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
		hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
		hideFeatureSubCommand(subcmd, hasBuildKit, "buildkit")
		hideFeatureSubCommand(subcmd, !hasBuildKit, "no-buildkit")
		// hide subcommands not supported by the server
		if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
			subcmd.Hidden = true
		}
		if v, ok := subcmd.Annotations["ostype"]; ok && v != osType {
			subcmd.Hidden = true
		}
	}
	return nil
}

// Checks if a command or one of its ancestors is in the list
func findCommand(cmd *cobra.Command, commands []string) bool {
	if cmd == nil {
		return false
	}
	for _, c := range commands {
		if c == cmd.Name() {
			return true
		}
	}
	return findCommand(cmd.Parent(), commands)
}

func isSupported(cmd *cobra.Command, details versionDetails) error {
	if err := areSubcommandsSupported(cmd, details); err != nil {
		return err
	}
	return areFlagsSupported(cmd, details)
}

func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental

	errs := []string{}

	cmd.Flags().VisitAll(func(f *pflag.Flag) {
		if f.Changed {
			if !isVersionSupported(f, clientVersion) {
				errs = append(errs, fmt.Sprintf("\"--%s\" requires API version %s, but the Docker daemon API version is %s", f.Name, getFlagAnnotation(f, "version"), clientVersion))
				return
			}
			if !isOSTypeSupported(f, osType) {
				errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", f.Name, getFlagAnnotation(f, "ostype"), osType))
				return
			}
			if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
				errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
			}
			if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
				errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
			}
			// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
		}
	})
	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "\n"))
	}
	return nil
}

// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
	clientVersion := details.Client().ClientVersion()
	osType := details.ServerInfo().OSType
	hasExperimental := details.ServerInfo().HasExperimental
	hasExperimentalCLI := details.ClientInfo().HasExperimental

	// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
	for curr := cmd; curr != nil; curr = curr.Parent() {
		if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
			return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
		}
		if os, ok := curr.Annotations["ostype"]; ok && os != osType {
			return fmt.Errorf("%s is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s", cmd.CommandPath(), os, osType)
		}
		if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
			return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
		}
		if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
			return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
		}
	}
	return nil
}

func getFlagAnnotation(f *pflag.Flag, annotation string) string {
	if value, ok := f.Annotations[annotation]; ok && len(value) == 1 {
		return value[0]
	}
	return ""
}

func isVersionSupported(f *pflag.Flag, clientVersion string) bool {
	if v := getFlagAnnotation(f, "version"); v != "" {
		return versions.GreaterThanOrEqualTo(clientVersion, v)
	}
	return true
}

func isOSTypeSupported(f *pflag.Flag, osType string) bool {
	if v := getFlagAnnotation(f, "ostype"); v != "" && osType != "" {
		return osType == v
	}
	return true
}

// hasTags return true if any of the command's parents has tags
func hasTags(cmd *cobra.Command) bool {
	for curr := cmd; curr != nil; curr = curr.Parent() {
		if len(curr.Annotations) > 0 {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/ziutek/gst"
	"github.com/zyxar/doubanfm"
)

const (
	OpAgain  = ""
	OpPlay   = "p"
	OpLoop   = "x"
	OpNext   = "n"
	OpSkip   = "s"
	OpTrash  = "t"
	OpLike   = "r"
	OpUnlike = "u"
	OpList   = "l"
	OpSong   = "c"
	OpLogin  = "z"
	OpId     = "i"
	OpHelp   = "h"
	OpExit   = "q"
	OpChann  = "0"
	PROMPT   = "DoubanFM> "
)

var (
	userId  string
	helpStr string
)

func init() {
	flag.StringVar(&userId, "login", "", "login id")
	helpStr = `Operation list:
             p: Pause or play
             n: Next, next song
             x: Loop, loop playback
             s: Skip, skip current playlist
             t: Trash, never play
             r: Like
             u: Unlike
             c: Current playing info
             l: Playlist
             0: Channel list
             N: Change to Channel N, N stands for channel number, see channel list
             z: Login, Account login
             i: Current Id
             q: Quit
             h: Show this help
`
}

func main() {
	flag.Parse()
	var err error
	if err = mkHomeDir(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	player, err := newPlayer()
	if err != nil {
		fmt.Println("\r>>>>>>>>> Player error:", err)
		os.Exit(1)
	}

	term := newTerm(PROMPT)
	var quit = func(code int) {
		term.Restore()
		fmt.Println("\r>>>>>>>>> Bye!")
		os.Exit(code)
	}

	session := doubanfm.NewSession()
	var logon = func(uid string) {
		term := newTerm("Douban Id:")
		defer term.Restore()
		var err error
		if uid == "" {
			uid, err = term.ReadLine()
			if err != nil {
				fmt.Println("\r>>>>>>>>> Access denied:", err)
				return
			}
		}
		uid = strings.TrimSpace(uid)
		if len(uid) == 0 {
			fmt.Println("\r>>>>>>>>> Access denied: empty id")
			return
		}

		tokenFn := filepath.Join(homeDir, uid)
		if err = session.LoadFile(tokenFn); err == nil {
			fmt.Println("\r>>>>>>>>> Token loaded from", tokenFn)
			fmt.Println("\r>>>>>>>>> Access acquired:")
			fmt.Println(session.Id())
			return
		}

		if err = session.LoginAs(uid, func() (string, error) {
			return term.ReadPassword("Password: ")
		}); err != nil {
			fmt.Println("\r>>>>>>>>> Access denied:", err)
		} else {
			fmt.Println("\r>>>>>>>>> Access acquired:")
			fmt.Println(session.Id())
			if err = session.SaveFile(tokenFn); err != nil {
				fmt.Println("\r>>>>>>>>> Token saving error:", err)
			} else {
				fmt.Println("\r>>>>>>>>> Token saved as", tokenFn)
			}
		}
	}

	var playSong = func(song *doubanfm.Song) {
		if song != nil {
			fmt.Printf("\rPLAYING>> %s - %s\n", song.Title, song.Artist)
			player.Play(song.Url)
		}
	}

	player.init(func(bus *gst.Bus, msg *gst.Message) {
		switch msg.GetType() {
		case gst.MESSAGE_EOS:
			if session.Loop {
				playSong(session.NextSong())
				return
			}
			session.FetchSongs(doubanfm.End)
			if session.SongNum() == 0 {
				session.FetchSongs(doubanfm.Last)
			}
			playSong(session.NextSong())
		case gst.MESSAGE_ERROR:
			s, param := msg.GetStructure()
			fmt.Println("[gstreamer]", msg.GetType(), s, param)
			player.Stop()
		}
	})

	if err = session.FetchChannels(); err != nil {
		fmt.Println("\r>>>>>>>>> Error in fetching channels:", err)
	}
	if userId != "" {
		logon(userId)
		if err = session.FetchMyChannels(); err != nil {
			fmt.Println("\r>>>>>>>>> Error in fetching channels:", err)
		}
	}

	if session.RandomChannel() == nil {
		fmt.Println("\r>>>>>>>>> No channel in list.")
		quit(1)
	} else {
		fmt.Println(session.Channel().String())
	}

	if err = session.FetchSongs(doubanfm.New); err != nil {
		fmt.Println("\r>>>>>>>>> Error in fetching songs:", err)
		quit(1)
	}
	playSong(session.NextSong())

	var op string
	var prevOp = OpNext
	var paused bool = false
	for {
		op, err = term.ReadLine()
		if err == io.EOF {
			fmt.Println()
			quit(0)
		} else if err != nil {
			fmt.Println(err)
			continue
		}
		op = strings.ToLower(strings.TrimSpace(op))
	PREV:
		switch op {
		case OpAgain:
			op = prevOp
			goto PREV
		case OpPlay:
			paused = !paused
			if paused {
				player.Pause()
			} else {
				player.Resume()
			}
		case OpLoop:
			session.Loop = !session.Loop
		case OpNext:
			if session.SongNum() == 0 {
				session.FetchSongs(doubanfm.Last)
			}
			playSong(session.NextSong())
		case OpSkip:
			session.FetchSongs(doubanfm.Skip)
			playSong(session.NextSong())
		case OpTrash:
			session.FetchSongs(doubanfm.Bypass)
			playSong(session.NextSong())
		case OpLike:
			session.FetchSongs(doubanfm.Like)
			session.Song().Like = 1
		case OpUnlike:
			session.FetchSongs(doubanfm.Unlike)
			session.Song().Like = 0
		case OpLogin:
			logon("")
			continue
		case OpId:
			fmt.Println(session.Id())
		case OpList:
			for _, song := range session.Songs() {
				fmt.Printf("%s - %s\n", song.Title, song.Artist)
			}
		case OpSong:
			fmt.Println(session.Song())
		case OpExit:
			quit(0)
		case OpChann:
			session.PrintChannels()
		case OpHelp:
			help()
		default:
			if ch, err := strconv.Atoi(op); err == nil {
				err = session.SetChannel(ch)
				if err != nil {
					fmt.Println("\r>>>>>>>>>", err)
					prevOp = OpHelp
					continue
				}
				fmt.Println(session.Channel().String())
				playSong(session.NextSong())
			} else {
				fmt.Println("\r>>>>>>>>> No such operation:", op)
				help()
				prevOp = OpHelp
				continue
			}
		}
		prevOp = op
	}
	term.Restore()
}

func help() {
	fmt.Println(helpStr)
}
<commit_msg>signal handler<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/ziutek/gst"
	"github.com/zyxar/doubanfm"
)

const (
	OpAgain  = ""
	OpPlay   = "p"
	OpLoop   = "x"
	OpNext   = "n"
	OpSkip   = "s"
	OpTrash  = "t"
	OpLike   = "r"
	OpUnlike = "u"
	OpList   = "l"
	OpSong   = "c"
	OpLogin  = "z"
	OpId     = "i"
	OpHelp   = "h"
	OpExit   = "q"
	OpChann  = "0"
	PROMPT   = "DoubanFM> "
)

var (
	userId  string
	helpStr string
)

func init() {
	flag.StringVar(&userId, "login", "", "login id")
	helpStr = `Operation list:
             p: Pause or play
             n: Next, next song
             x: Loop, loop playback
             s: Skip, skip current playlist
             t: Trash, never play
             r: Like
             u: Unlike
             c: Current playing info
             l: Playlist
             0: Channel list
             N: Change to Channel N, N stands for channel number, see channel list
             z: Login, Account login
             i: Current Id
             q: Quit
             h: Show this help
`
}

func main() {
	flag.Parse()
	var err error
	if err = mkHomeDir(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	player, err := newPlayer()
	if err != nil {
		fmt.Println("\r>>>>>>>>> Player error:", err)
		os.Exit(1)
	}

	term := newTerm(PROMPT)
	var quit = func(code int) {
		term.Restore()
		fmt.Println("\r>>>>>>>>> Bye!")
		os.Exit(code)
	}

	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1)
		for s := range c {
			fmt.Printf("\r>>>>>>>>> %v caught, exit\n", s)
			quit(1)
			break
		}
	}()

	session := doubanfm.NewSession()
	var logon = func(uid string) {
		term := newTerm("Douban Id:")
		defer term.Restore()
		var err error
		if uid == "" {
			uid, err = term.ReadLine()
			if err != nil {
				fmt.Println("\r>>>>>>>>> Access denied:", err)
				return
			}
		}
		uid = strings.TrimSpace(uid)
		if len(uid) == 0 {
			fmt.Println("\r>>>>>>>>> Access denied: empty id")
			return
		}

		tokenFn := filepath.Join(homeDir, uid)
		if err = session.LoadFile(tokenFn); err == nil {
			fmt.Println("\r>>>>>>>>> Token loaded from", tokenFn)
			fmt.Println("\r>>>>>>>>> Access acquired:")
			fmt.Println(session.Id())
			return
		}

		if err = session.LoginAs(uid, func() (string, error) {
			return term.ReadPassword("Password: ")
		}); err != nil {
			fmt.Println("\r>>>>>>>>> Access denied:", err)
		} else {
			fmt.Println("\r>>>>>>>>> Access acquired:")
			fmt.Println(session.Id())
			if err = session.SaveFile(tokenFn); err != nil {
				fmt.Println("\r>>>>>>>>> Token saving error:", err)
			} else {
				fmt.Println("\r>>>>>>>>> Token saved as", tokenFn)
			}
		}
	}

	var playSong = func(song *doubanfm.Song) {
		if song != nil {
			fmt.Printf("\rPLAYING>> %s - %s\n", song.Title, song.Artist)
			player.Play(song.Url)
		}
	}

	player.init(func(bus *gst.Bus, msg *gst.Message) {
		switch msg.GetType() {
		case gst.MESSAGE_EOS:
			if session.Loop {
				playSong(session.NextSong())
				return
			}
			session.FetchSongs(doubanfm.End)
			if session.SongNum() == 0 {
				session.FetchSongs(doubanfm.Last)
			}
			playSong(session.NextSong())
		case gst.MESSAGE_ERROR:
			s, param := msg.GetStructure()
			fmt.Println("[gstreamer]", msg.GetType(), s, param)
			player.Stop()
		}
	})

	if err = session.FetchChannels(); err != nil {
		fmt.Println("\r>>>>>>>>> Error in fetching channels:", err)
	}
	if userId != "" {
		logon(userId)
		if err = session.FetchMyChannels(); err != nil {
			fmt.Println("\r>>>>>>>>> Error in fetching channels:", err)
		}
	}

	if session.RandomChannel() == nil {
		fmt.Println("\r>>>>>>>>> No channel in list.")
		quit(1)
	} else {
		fmt.Println(session.Channel().String())
	}

	if err = session.FetchSongs(doubanfm.New); err != nil {
		fmt.Println("\r>>>>>>>>> Error in fetching songs:", err)
		quit(1)
	}
	playSong(session.NextSong())

	var op string
	var prevOp = OpNext
	var paused bool = false
	for {
		op, err = term.ReadLine()
		if err == io.EOF {
			fmt.Println()
			quit(0)
		} else if err != nil {
			fmt.Println(err)
			continue
		}
		op = strings.ToLower(strings.TrimSpace(op))
	PREV:
		switch op {
		case OpAgain:
			op = prevOp
			goto PREV
		case OpPlay:
			paused = !paused
			if paused {
				player.Pause()
			} else {
				player.Resume()
			}
		case OpLoop:
			session.Loop = !session.Loop
		case OpNext:
			if session.SongNum() == 0 {
				session.FetchSongs(doubanfm.Last)
			}
			playSong(session.NextSong())
		case OpSkip:
			session.FetchSongs(doubanfm.Skip)
			playSong(session.NextSong())
		case OpTrash:
			session.FetchSongs(doubanfm.Bypass)
			playSong(session.NextSong())
		case OpLike:
			session.FetchSongs(doubanfm.Like)
			session.Song().Like = 1
		case OpUnlike:
			session.FetchSongs(doubanfm.Unlike)
			session.Song().Like = 0
		case OpLogin:
			logon("")
			continue
		case OpId:
			fmt.Println(session.Id())
		case OpList:
			for _, song := range session.Songs() {
				fmt.Printf("%s - %s\n", song.Title, song.Artist)
			}
		case OpSong:
			fmt.Println(session.Song())
		case OpExit:
			quit(0)
		case OpChann:
			session.PrintChannels()
		case OpHelp:
			help()
		default:
			if ch, err := strconv.Atoi(op); err == nil {
				err = session.SetChannel(ch)
				if err != nil {
					fmt.Println("\r>>>>>>>>>", err)
					prevOp = OpHelp
					continue
				}
				fmt.Println(session.Channel().String())
				playSong(session.NextSong())
			} else {
				fmt.Println("\r>>>>>>>>> No such operation:", op)
				help()
				prevOp = OpHelp
				continue
			}
		}
		prevOp = op
	}
	term.Restore()
}

func help() {
	fmt.Println(helpStr)
}
<|endoftext|>