<commit_before>package docker_registry

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/remote"
)

func DimgTags(reference string) ([]string, error) {
	return tagsByDappDimgLabel(reference, "true")
}

func DimgstageTags(reference string) ([]string, error) {
	return tagsByDappDimgLabel(reference, "false")
}

func tagsByDappDimgLabel(reference, labelValue string) ([]string, error) {
	var dimgTags []string

	allTags, err := tags(reference)
	if err != nil {
		return nil, err
	}

	for _, tag := range allTags {
		tagReference := strings.Join([]string{reference, tag}, ":")
		i, _, err := getImage(tagReference)
		if err != nil {
			return nil, err
		}

		configFile, err := i.ConfigFile()
		if err != nil {
			return nil, err
		}

		for k, v := range configFile.Config.Labels {
			if k == "dapp-dimg" && v == labelValue {
				dimgTags = append(dimgTags, tag)
				break
			}
		}
	}

	return dimgTags, nil
}

func tags(reference string) ([]string, error) {
	repo, err := name.NewRepository(reference, name.WeakValidation)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", reference, err)
	}

	auth, err := authn.DefaultKeychain.Resolve(repo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting creds for %q: %v", repo, err)
	}

	tags, err := remote.List(repo, auth, http.DefaultTransport)
	if err != nil {
		return nil, fmt.Errorf("reading tags for %q: %v", repo, err)
	}

	return tags, nil
}

func ImageId(reference string) (string, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return "", err
	}

	manifest, err := i.Manifest()
	if err != nil {
		return "", err
	}

	return manifest.Config.Digest.String(), nil
}

func ImageParentId(reference string) (string, error) {
	configFile, err := ImageConfigFile(reference)
	if err != nil {
		return "", err
	}

	return configFile.ContainerConfig.Image, nil
}

func ImageConfigFile(reference string) (v1.ConfigFile, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return v1.ConfigFile{}, err
	}

	configFile, err := i.ConfigFile()
	if err != nil {
		return v1.ConfigFile{}, err
	}

	return *configFile, nil
}

func ImageDelete(reference string) error {
	r, err := name.ParseReference(reference, name.WeakValidation)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	auth, err := authn.DefaultKeychain.Resolve(r.Context().Registry)
	if err != nil {
		return fmt.Errorf("getting creds for %q: %v", r, err)
	}

	if err := remote.Delete(r, auth, http.DefaultTransport, remote.DeleteOptions{}); err != nil {
		return fmt.Errorf("deleting image %q: %v", r, err)
	}

	return nil
}

func ImageDigest(reference string) (string, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return "", err
	}

	digest, err := i.Digest()
	if err != nil {
		return "", err
	}

	return digest.String(), nil
}

func getImage(reference string) (v1.Image, name.Reference, error) {
	ref, err := name.ParseReference(reference, name.WeakValidation)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
	if err != nil {
		return nil, nil, fmt.Errorf("reading image %q: %v", ref, err)
	}

	return img, ref, nil
}
<commit_msg>Fix deleting image from Gitlab's Container Registry<commit_after>package docker_registry

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/remote"
	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
)

func DimgTags(reference string) ([]string, error) {
	return tagsByDappDimgLabel(reference, "true")
}

func DimgstageTags(reference string) ([]string, error) {
	return tagsByDappDimgLabel(reference, "false")
}

func tagsByDappDimgLabel(reference, labelValue string) ([]string, error) {
	var dimgTags []string

	allTags, err := tags(reference)
	if err != nil {
		return nil, err
	}

	for _, tag := range allTags {
		tagReference := strings.Join([]string{reference, tag}, ":")
		i, _, err := getImage(tagReference)
		if err != nil {
			return nil, err
		}

		configFile, err := i.ConfigFile()
		if err != nil {
			return nil, err
		}

		for k, v := range configFile.Config.Labels {
			if k == "dapp-dimg" && v == labelValue {
				dimgTags = append(dimgTags, tag)
				break
			}
		}
	}

	return dimgTags, nil
}

func tags(reference string) ([]string, error) {
	repo, err := name.NewRepository(reference, name.WeakValidation)
	if err != nil {
		return nil, fmt.Errorf("parsing repo %q: %v", reference, err)
	}

	auth, err := authn.DefaultKeychain.Resolve(repo.Registry)
	if err != nil {
		return nil, fmt.Errorf("getting creds for %q: %v", repo, err)
	}

	tags, err := remote.List(repo, auth, http.DefaultTransport)
	if err != nil {
		return nil, fmt.Errorf("reading tags for %q: %v", repo, err)
	}

	return tags, nil
}

func ImageId(reference string) (string, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return "", err
	}

	manifest, err := i.Manifest()
	if err != nil {
		return "", err
	}

	return manifest.Config.Digest.String(), nil
}

func ImageParentId(reference string) (string, error) {
	configFile, err := ImageConfigFile(reference)
	if err != nil {
		return "", err
	}

	return configFile.ContainerConfig.Image, nil
}

func ImageConfigFile(reference string) (v1.ConfigFile, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return v1.ConfigFile{}, err
	}

	configFile, err := i.ConfigFile()
	if err != nil {
		return v1.ConfigFile{}, err
	}

	return *configFile, nil
}

func ImageDelete(reference string) error {
	r, err := name.ParseReference(reference, name.WeakValidation)
	if err != nil {
		return fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	auth, err := authn.DefaultKeychain.Resolve(r.Context().Registry)
	if err != nil {
		return fmt.Errorf("getting creds for %q: %v", r, err)
	}

	if err := remote.Delete(r, auth, http.DefaultTransport, remote.DeleteOptions{}); err != nil {
		if strings.Contains(err.Error(), "UNAUTHORIZED") {
			if gitlabRegistryDeleteErr := GitlabRegistryDelete(r, auth, http.DefaultTransport); gitlabRegistryDeleteErr != nil {
				if strings.Contains(gitlabRegistryDeleteErr.Error(), "UNAUTHORIZED") {
					return fmt.Errorf("deleting image %q: %v", r, err)
				}
				return fmt.Errorf("deleting image %q: %v", r, gitlabRegistryDeleteErr)
			}
		} else {
			return fmt.Errorf("deleting image %q: %v", r, err)
		}
	}

	return nil
}

// TODO https://gitlab.com/gitlab-org/gitlab-ce/issues/48968
func GitlabRegistryDelete(ref name.Reference, auth authn.Authenticator, t http.RoundTripper) error {
	scopes := []string{ref.Scope("*")}
	tr, err := transport.New(ref.Context().Registry, auth, t, scopes)
	if err != nil {
		return err
	}
	c := &http.Client{Transport: tr}

	u := url.URL{
		Scheme: ref.Context().Registry.Scheme(),
		Host:   ref.Context().RegistryStr(),
		Path:   fmt.Sprintf("/v2/%s/manifests/%s", ref.Context().RepositoryStr(), ref.Identifier()),
	}

	req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
	if err != nil {
		return err
	}

	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK, http.StatusAccepted:
		return nil
	default:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("unrecognized status code during DELETE: %v; %v", resp.Status, string(b))
	}
}

func ImageDigest(reference string) (string, error) {
	i, _, err := getImage(reference)
	if err != nil {
		return "", err
	}

	digest, err := i.Digest()
	if err != nil {
		return "", err
	}

	return digest.String(), nil
}

func getImage(reference string) (v1.Image, name.Reference, error) {
	ref, err := name.ParseReference(reference, name.WeakValidation)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing reference %q: %v", reference, err)
	}

	img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
	if err != nil {
		return nil, nil, fmt.Errorf("reading image %q: %v", ref, err)
	}

	return img, ref, nil
}
<|endoftext|>


<commit_before>package dtls

import "fmt"

func clientHandshakeHandler(c *Conn) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	for out, fragEpoch := c.fragmentBuffer.pop(); out != nil; out, fragEpoch = c.fragmentBuffer.pop() {
		rawHandshake := &handshake{}
		if err := rawHandshake.unmarshal(out); err != nil {
			return err
		}
		c.handshakeCache.push(out, fragEpoch, rawHandshake.handshakeHeader.messageSequence /* isLocal */, false, c.currFlight.get())

		switch h := rawHandshake.handshakeMessage.(type) {
		case *handshakeMessageHelloVerifyRequest:
			if c.currFlight.get() == flight1 {
				c.cookie = append([]byte{}, h.cookie...)
				c.localSequenceNumber = 1
				c.currFlight.set(flight3)
			}

		case *handshakeMessageServerHello:
			switch c.currFlight.get() {
			case flight1:
				// HelloVerifyRequest can be skipped by the server
				fallthrough
			case flight3:
				c.cipherSuite = h.cipherSuite
				c.remoteRandom = h.random
			}
		case *handshakeMessageCertificate:
			if c.currFlight.get() == flight3 {
				c.remoteCertificate = h.certificate
			}

		// TODO: CertificateRequest

		case *handshakeMessageServerKeyExchange:
			if c.currFlight.get() == flight3 {
				c.remoteKeypair = &namedCurveKeypair{h.namedCurve, h.publicKey, nil}

				clientRandom, err := c.localRandom.marshal()
				if err != nil {
					return err
				}
				serverRandom, err := c.remoteRandom.marshal()
				if err != nil {
					return err
				}
				preMasterSecret, err := prfPreMasterSecret(c.remoteKeypair.publicKey, c.localKeypair.privateKey, c.localKeypair.curve)
				if err != nil {
					return err
				}

				c.keys = prfEncryptionKeys(prfMasterSecret(preMasterSecret, clientRandom, serverRandom), clientRandom, serverRandom)
				c.localGCM, err = newAESGCM(c.keys.clientWriteKey)
				if err != nil {
					return err
				}

				c.remoteGCM, err = newAESGCM(c.keys.serverWriteKey)
				if err != nil {
					return err
				}
			}

		case *handshakeMessageServerHelloDone:
			if c.currFlight.get() == flight3 {
				c.localSequenceNumber = 2
				c.currFlight.set(flight5)
			}

		case *handshakeMessageFinished:
			c.localEpoch = 1
			c.localSequenceNumber = 1
			fmt.Println("Handshake finished")
			// TODO: verify

		default:
			return fmt.Errorf("Unhandled handshake %d", h.handshakeType())
		}
	}

	return nil
}

func clientTimerThread(c *Conn) {
	for range c.workerTicker.C {
		switch c.currFlight.get() {
		case flight1:
			fallthrough
		case flight3:
			c.lock.RLock()
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber,
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber),
					},
					handshakeMessage: &handshakeMessageClientHello{
						version:            protocolVersion1_2,
						cookie:             c.cookie,
						random:             c.localRandom,
						cipherSuites:       defaultCipherSuites,
						compressionMethods: defaultCompressionMethods,
						extensions: []extension{
							&extensionSupportedGroups{
								supportedGroups: []namedCurve{namedCurveX25519, namedCurveP256},
							},
						},
					}},
			}, false)
			c.lock.RUnlock()
		case flight5:
			c.lock.RLock()
			// TODO: Better way to end handshake
			if c.remoteEpoch != 0 {
				// Handshake is done
				c.lock.RUnlock()
				return
			}
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber,
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber),
					},
					handshakeMessage: &handshakeMessageClientKeyExchange{
						publicKey: c.localKeypair.publicKey,
					}},
			}, false)

			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber + 1,
					protocolVersion: protocolVersion1_2,
				},
				content: &changeCipherSpec{},
			}, false)

			if len(c.localVerifyData) == 0 {
				c.localVerifyData = prfVerifyDataClient(c.keys.masterSecret, c.handshakeCache.combinedHandshake())
			}

			// TODO: Fix hard-coded epoch & sequenceNumber, taking retransmitting into account.
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					epoch:           1,
					sequenceNumber:  0, // sequenceNumber restarts per epoch
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber + 1), // KeyExchange + 1
					},
					handshakeMessage: &handshakeMessageFinished{
						verifyData: c.localVerifyData,
					}},
			}, true)
			c.lock.RUnlock()
		default:
			panic(fmt.Errorf("Unhandled flight %s", c.currFlight.get()))
		}
	}

}
<commit_msg>Advance flight if HelloVerifyRequest skipped<commit_after>package dtls

import "fmt"

func clientHandshakeHandler(c *Conn) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	for out, fragEpoch := c.fragmentBuffer.pop(); out != nil; out, fragEpoch = c.fragmentBuffer.pop() {
		rawHandshake := &handshake{}
		if err := rawHandshake.unmarshal(out); err != nil {
			return err
		}
		c.handshakeCache.push(out, fragEpoch, rawHandshake.handshakeHeader.messageSequence /* isLocal */, false, c.currFlight.get())

		switch h := rawHandshake.handshakeMessage.(type) {
		case *handshakeMessageHelloVerifyRequest:
			if c.currFlight.get() == flight1 {
				c.cookie = append([]byte{}, h.cookie...)
				c.localSequenceNumber = 1
				c.currFlight.set(flight3)
			}

		case *handshakeMessageServerHello:
			switch c.currFlight.get() {
			case flight1:
				// HelloVerifyRequest can be skipped by the server
				c.currFlight.set(flight3)
				fallthrough
			case flight3:
				c.cipherSuite = h.cipherSuite
				c.remoteRandom = h.random
			}

		case *handshakeMessageCertificate:
			if c.currFlight.get() == flight3 {
				c.remoteCertificate = h.certificate
			}

		// TODO: CertificateRequest

		case *handshakeMessageServerKeyExchange:
			if c.currFlight.get() == flight3 {
				c.remoteKeypair = &namedCurveKeypair{h.namedCurve, h.publicKey, nil}

				clientRandom, err := c.localRandom.marshal()
				if err != nil {
					return err
				}
				serverRandom, err := c.remoteRandom.marshal()
				if err != nil {
					return err
				}
				preMasterSecret, err := prfPreMasterSecret(c.remoteKeypair.publicKey, c.localKeypair.privateKey, c.localKeypair.curve)
				if err != nil {
					return err
				}

				c.keys = prfEncryptionKeys(prfMasterSecret(preMasterSecret, clientRandom, serverRandom), clientRandom, serverRandom)
				c.localGCM, err = newAESGCM(c.keys.clientWriteKey)
				if err != nil {
					return err
				}

				c.remoteGCM, err = newAESGCM(c.keys.serverWriteKey)
				if err != nil {
					return err
				}
			}

		case *handshakeMessageServerHelloDone:
			if c.currFlight.get() == flight3 {
				c.localSequenceNumber = 2
				c.currFlight.set(flight5)
			}

		case *handshakeMessageFinished:
			c.localEpoch = 1
			c.localSequenceNumber = 1
			fmt.Println("Handshake finished")
			// TODO: verify

		default:
			return fmt.Errorf("Unhandled handshake %d", h.handshakeType())
		}
	}

	return nil
}

func clientTimerThread(c *Conn) {
	for range c.workerTicker.C {
		switch c.currFlight.get() {
		case flight1:
			fallthrough
		case flight3:
			c.lock.RLock()
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber,
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber),
					},
					handshakeMessage: &handshakeMessageClientHello{
						version:            protocolVersion1_2,
						cookie:             c.cookie,
						random:             c.localRandom,
						cipherSuites:       defaultCipherSuites,
						compressionMethods: defaultCompressionMethods,
						extensions: []extension{
							&extensionSupportedGroups{
								supportedGroups: []namedCurve{namedCurveX25519, namedCurveP256},
							},
						},
					}},
			}, false)
			c.lock.RUnlock()
		case flight5:
			c.lock.RLock()
			// TODO: Better way to end handshake
			if c.remoteEpoch != 0 {
				// Handshake is done
				c.lock.RUnlock()
				return
			}
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber,
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber),
					},
					handshakeMessage: &handshakeMessageClientKeyExchange{
						publicKey: c.localKeypair.publicKey,
					}},
			}, false)

			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					sequenceNumber:  c.localSequenceNumber + 1,
					protocolVersion: protocolVersion1_2,
				},
				content: &changeCipherSpec{},
			}, false)

			if len(c.localVerifyData) == 0 {
				c.localVerifyData = prfVerifyDataClient(c.keys.masterSecret, c.handshakeCache.combinedHandshake())
			}

			// TODO: Fix hard-coded epoch & sequenceNumber, taking retransmitting into account.
			c.internalSend(&recordLayer{
				recordLayerHeader: recordLayerHeader{
					epoch:           1,
					sequenceNumber:  0, // sequenceNumber restarts per epoch
					protocolVersion: protocolVersion1_2,
				},
				content: &handshake{
					// sequenceNumber and messageSequence line up, may need to be re-evaluated
					handshakeHeader: handshakeHeader{
						messageSequence: uint16(c.localSequenceNumber + 1), // KeyExchange + 1
					},
					handshakeMessage: &handshakeMessageFinished{
						verifyData: c.localVerifyData,
					}},
			}, true)
			c.lock.RUnlock()
		default:
			panic(fmt.Errorf("Unhandled flight %s", c.currFlight.get()))
		}
	}

}
<|endoftext|>


<commit_before>// Copyright 2017 CNI 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 hns

import (
	"fmt"
	"net"
	"strings"

	"github.com/Microsoft/hcsshim"
	"github.com/Microsoft/hcsshim/hcn"

	"github.com/containernetworking/cni/pkg/types"
	current "github.com/containernetworking/cni/pkg/types/100"

	"github.com/containernetworking/plugins/pkg/errors"
)

const (
	pauseContainerNetNS = "none"
)

type EndpointInfo struct {
	EndpointName string
	DNS          types.DNS
	NetworkName  string
	NetworkId    string
	Gateway      net.IP
	IpAddress    net.IP
}

// GetSandboxContainerID returns the sandbox ID of this pod.
func GetSandboxContainerID(containerID string, netNs string) string {
	if len(netNs) != 0 && netNs != pauseContainerNetNS {
		splits := strings.SplitN(netNs, ":", 2)
		if len(splits) == 2 {
			containerID = splits[1]
		}
	}

	return containerID
}

// GetIpString returns the given IP in string.
func GetIpString(ip *net.IP) string {
	if len(*ip) == 0 {
		return ""
	} else {
		return ip.String()
	}
}

// GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type.
func GetDefaultDestinationPrefix(ip *net.IP) string {
	destinationPrefix := "0.0.0.0/0"
	if ipv6 := ip.To4(); ipv6 == nil {
		destinationPrefix = "::/0"
	}
	return destinationPrefix
}

// ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN.
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
	return GetSandboxContainerID(containerID, netNs) + "_" + networkName
}

// GenerateHnsEndpoint generates an HNSEndpoint with given info and config.
func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) {
	// run the IPAM plugin and get back the config to apply
	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
	if err != nil && !hcsshim.IsNotExist(err) {
		return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
	}

	if hnsEndpoint != nil {
		if hnsEndpoint.VirtualNetwork != epInfo.NetworkId {
			_, err = hnsEndpoint.Delete()
			if err != nil {
				return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
			}
			hnsEndpoint = nil
		}
	}

	if n.LoopbackDSR {
		n.ApplyLoopbackDSR(&epInfo.IpAddress)
	}
	if hnsEndpoint == nil {
		hnsEndpoint = &hcsshim.HNSEndpoint{
			Name:           epInfo.EndpointName,
			VirtualNetwork: epInfo.NetworkId,
			DNSServerList:  strings.Join(epInfo.DNS.Nameservers, ","),
			DNSSuffix:      strings.Join(epInfo.DNS.Search, ","),
			GatewayAddress: GetIpString(&epInfo.Gateway),
			IPAddress:      epInfo.IpAddress,
			Policies:       n.MarshalPolicies(),
		}
	}
	return hnsEndpoint, nil
}

// RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID,
// or removes the given name endpoint completely.
func RemoveHnsEndpoint(epName string, netns string, containerID string) error {
	if len(netns) == 0 {
		return nil
	}

	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)

	if hcsshim.IsNotExist(err) {
		return nil
	} else if err != nil {
		return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
	}

	// for shared endpoint, detach it from the container.
	if netns != pauseContainerNetNS {
		hnsEndpoint.ContainerDetach(containerID)
		return nil
	}

	// for removing the endpoint completely, hot detach is used at first.
	hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
	hnsEndpoint.Delete()

	return nil
}

type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)

// AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID.
func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
	// for shared endpoint, we expect that the endpoint already exists.
	if netns != pauseContainerNetNS {
		_, err := hcsshim.GetHNSEndpointByName(epName)
		if err != nil {
			return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
		}
	}

	// check if endpoint already exists
	createEndpoint := true
	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
	if hnsEndpoint != nil && strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
		createEndpoint = false
	}

	if createEndpoint {
		if hnsEndpoint != nil {
			if _, err = hnsEndpoint.Delete(); err != nil {
				return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
			}
		}

		if hnsEndpoint, err = makeEndpoint(); err != nil {
			return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
		}

		if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
			return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
		}

	}

	// hot attach
	if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
		if createEndpoint {
			err := RemoveHnsEndpoint(epName, netns, containerID)
			if err != nil {
				return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID)
			}
		}
		if hcsshim.ErrComputeSystemDoesNotExist == err {
			return hnsEndpoint, nil
		}
		return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id)
	}

	return hnsEndpoint, nil
}

// ConstructHnsResult constructs the CNI result for the HNSEndpoint.
func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
	resultInterface := &current.Interface{
		Name: hnsEndpoint.Name,
		Mac:  hnsEndpoint.MacAddress,
	}
	_, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix)
	if err != nil {
		return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
	}

	resultIPConfig := &current.IPConfig{
		Address: net.IPNet{
			IP:   hnsEndpoint.IPAddress,
			Mask: ipSubnet.Mask},
		Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
	}
	result := &current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{resultInterface},
		IPs:        []*current.IPConfig{resultIPConfig},
		DNS: types.DNS{
			Search:      strings.Split(hnsEndpoint.DNSSuffix, ","),
			Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
		},
	}

	return result, nil
}

// GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config.
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
	// run the IPAM plugin and get back the config to apply
	hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
	if err != nil && !hcn.IsNotFoundError(err) {
		return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
	}

	if hcnEndpoint != nil {
		// If the endpont already exists, then we should return error unless
		// the endpoint is based on a different network then delete
		// should that fail return error
		if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
			err = hcnEndpoint.Delete()
			if err != nil {
				return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
			}
		} else {
			return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
		}
	}

	if hcnEndpoint == nil {
		routes := []hcn.Route{
			{
				NextHop:           GetIpString(&epInfo.Gateway),
				DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
			},
		}

		hcnDns := hcn.Dns{
			Search:     epInfo.DNS.Search,
			ServerList: epInfo.DNS.Nameservers,
		}

		hcnIpConfig := hcn.IpConfig{
			IpAddress: GetIpString(&epInfo.IpAddress),
		}
		ipConfigs := []hcn.IpConfig{hcnIpConfig}

		if n.LoopbackDSR {
			n.ApplyLoopbackDSR(&epInfo.IpAddress)
		}
		hcnEndpoint = &hcn.HostComputeEndpoint{
			SchemaVersion:      hcn.Version{Major: 2},
			Name:               epInfo.EndpointName,
			HostComputeNetwork: epInfo.NetworkId,
			Dns:                hcnDns,
			Routes:             routes,
			IpConfigurations:   ipConfigs,
			Policies: func() []hcn.EndpointPolicy {
				if n.HcnPolicyArgs == nil {
					n.HcnPolicyArgs = []hcn.EndpointPolicy{}
				}
				return n.HcnPolicyArgs
			}(),
		}
	}
	return hcnEndpoint, nil
}

// RemoveHcnEndpoint removes the given name endpoint from namespace.
func RemoveHcnEndpoint(epName string) error {
	hcnEndpoint, err := hcn.GetEndpointByName(epName)
	if hcn.IsNotFoundError(err) {
		return nil
	} else if err != nil {
		_ = fmt.Errorf("[win-cni] Failed to find endpoint %v, err:%v", epName, err)
		return err
	}
	if hcnEndpoint != nil {
		err = hcnEndpoint.Delete()
		if err != nil {
			return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err)
		}
	}
	return nil
}

type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)

// AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace.
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
	hcnEndpoint, err := makeEndpoint()
	if err != nil {
		return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint")
	}

	if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
		return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint")
	}

	err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
	if err != nil {
		err := RemoveHcnEndpoint(epName)
		if err != nil {
			return nil, errors.Annotatef(err, "failed to remote the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace)
		}
		return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace)
	}
	return hcnEndpoint, nil
}

// ConstructHcnResult constructs the CNI result for the HostComputeEndpoint.
func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) {
	resultInterface := &current.Interface{
		Name: hcnEndpoint.Name,
		Mac:  hcnEndpoint.MacAddress,
	}
	_, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix)
	if err != nil {
		return nil, err
	}

	ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
	resultIPConfig := &current.IPConfig{
		Address: net.IPNet{
			IP:   ipAddress,
			Mask: ipSubnet.Mask},
		Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
	}
	result := &current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{resultInterface},
		IPs:        []*current.IPConfig{resultIPConfig},
		DNS: types.DNS{
			Search:      hcnEndpoint.Dns.Search,
			Nameservers: hcnEndpoint.Dns.ServerList,
		},
	}

	return result, nil
}
<commit_msg>refactor(win-bridge): hns api processing<commit_after>// Copyright 2017 CNI 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 hns

import (
	"fmt"
	"net"
	"strings"

	"github.com/Microsoft/hcsshim"
	"github.com/Microsoft/hcsshim/hcn"

	"github.com/containernetworking/cni/pkg/types"
	current "github.com/containernetworking/cni/pkg/types/100"

	"github.com/containernetworking/plugins/pkg/errors"
)

const (
	pauseContainerNetNS = "none"
)

type EndpointInfo struct {
	EndpointName string
	DNS          types.DNS
	NetworkName  string
	NetworkId    string
	Gateway      net.IP
	IpAddress    net.IP
}

// GetSandboxContainerID returns the sandbox ID of this pod.
func GetSandboxContainerID(containerID string, netNs string) string {
	if len(netNs) != 0 && netNs != pauseContainerNetNS {
		splits := strings.SplitN(netNs, ":", 2)
		if len(splits) == 2 {
			containerID = splits[1]
		}
	}

	return containerID
}

// GetIpString returns the given IP in string.
func GetIpString(ip *net.IP) string {
	if len(*ip) == 0 {
		return ""
	} else {
		return ip.String()
	}
}

// GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type.
func GetDefaultDestinationPrefix(ip *net.IP) string {
	destinationPrefix := "0.0.0.0/0"
	if ipv6 := ip.To4(); ipv6 == nil {
		destinationPrefix = "::/0"
	}
	return destinationPrefix
}

// ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN.
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
	return GetSandboxContainerID(containerID, netNs) + "_" + networkName
}

// GenerateHnsEndpoint generates an HNSEndpoint with given info and config.
func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) {
	// run the IPAM plugin and get back the config to apply
	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
	if err != nil && !hcsshim.IsNotExist(err) {
		return nil, errors.Annotatef(err, "failed to get HNSEndpoint %s", epInfo.EndpointName)
	}

	if hnsEndpoint != nil {
		if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) {
			return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName)
		}
		// remove endpoint if corrupted
		if _, err = hnsEndpoint.Delete(); err != nil {
			return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epInfo.EndpointName)
		}
	}

	if n.LoopbackDSR {
		n.ApplyLoopbackDSR(&epInfo.IpAddress)
	}
	hnsEndpoint = &hcsshim.HNSEndpoint{
		Name:           epInfo.EndpointName,
		VirtualNetwork: epInfo.NetworkId,
		DNSServerList:  strings.Join(epInfo.DNS.Nameservers, ","),
		DNSSuffix:      strings.Join(epInfo.DNS.Search, ","),
		GatewayAddress: GetIpString(&epInfo.Gateway),
		IPAddress:      epInfo.IpAddress,
		Policies:       n.MarshalPolicies(),
	}
	return hnsEndpoint, nil
}

// RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID,
// or removes the given name endpoint completely.
func RemoveHnsEndpoint(epName string, netns string, containerID string) error {
	if len(netns) == 0 {
		return nil
	}

	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
	if err != nil {
		if hcsshim.IsNotExist(err) {
			return nil
		}
		return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
	}

	// for shared endpoint, detach it from the container
	if netns != pauseContainerNetNS {
		_ = hnsEndpoint.ContainerDetach(containerID)
		return nil
	}

	// for removing the endpoint completely, hot detach is used at first
	_ = hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
	_, _ = hnsEndpoint.Delete()
	return nil
}

type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)

// AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID.
func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
	hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
	if err != nil {
		if !hcsshim.IsNotExist(err) {
			return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
		}
	}

	// for shared endpoint, we expect that the endpoint already exists
	if netns != pauseContainerNetNS {
		if hnsEndpoint == nil {
			return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
		}
	}

	// verify the existing endpoint is corrupted or not
	if hnsEndpoint != nil {
		if !strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
			if _, err := hnsEndpoint.Delete(); err != nil {
				return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epName)
			}
			hnsEndpoint = nil
		}
	}

	// create endpoint if not found
	var isNewEndpoint bool
	if hnsEndpoint == nil {
		if hnsEndpoint, err = makeEndpoint(); err != nil {
			return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
		}
		if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
			return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
		}
		isNewEndpoint = true
	}

	// attach to container
	if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
		if isNewEndpoint {
			if err := RemoveHnsEndpoint(epName, netns, containerID); err != nil {
				return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID)
			}
		} else if hcsshim.ErrComputeSystemDoesNotExist == err {
			return hnsEndpoint, nil
		}
		return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id)
	}
	return hnsEndpoint, nil
}

// ConstructHnsResult constructs the CNI result for the HNSEndpoint.
func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
	resultInterface := &current.Interface{
		Name: hnsEndpoint.Name,
		Mac:  hnsEndpoint.MacAddress,
	}
	_, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix)
	if err != nil {
		return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
	}

	resultIPConfig := &current.IPConfig{
		Address: net.IPNet{
			IP:   hnsEndpoint.IPAddress,
			Mask: ipSubnet.Mask},
		Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
	}
	result := &current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{resultInterface},
		IPs:        []*current.IPConfig{resultIPConfig},
		DNS: types.DNS{
			Search:      strings.Split(hnsEndpoint.DNSSuffix, ","),
			Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
		},
	}

	return result, nil
}

// GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config.
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
	// run the IPAM plugin and get back the config to apply
	hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
	if err != nil && !hcn.IsNotFoundError(err) {
		return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
	}

	if hcnEndpoint != nil {
		// If the endpont already exists, then we should return error unless
		// the endpoint is based on a different network then delete
		// should that fail return error
		if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
			err = hcnEndpoint.Delete()
			if err != nil {
				return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
			}
		} else {
			return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
		}
	}

	if hcnEndpoint == nil {
		routes := []hcn.Route{
			{
				NextHop:           GetIpString(&epInfo.Gateway),
				DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
			},
		}

		hcnDns := hcn.Dns{
			Search:     epInfo.DNS.Search,
			ServerList: epInfo.DNS.Nameservers,
		}

		hcnIpConfig := hcn.IpConfig{
			IpAddress: GetIpString(&epInfo.IpAddress),
		}
		ipConfigs := []hcn.IpConfig{hcnIpConfig}

		if n.LoopbackDSR {
			n.ApplyLoopbackDSR(&epInfo.IpAddress)
		}
		hcnEndpoint = &hcn.HostComputeEndpoint{
			SchemaVersion:      hcn.Version{Major: 2},
			Name:               epInfo.EndpointName,
			HostComputeNetwork: epInfo.NetworkId,
			Dns:                hcnDns,
			Routes:             routes,
			IpConfigurations:   ipConfigs,
			Policies: func() []hcn.EndpointPolicy {
				if n.HcnPolicyArgs == nil {
					n.HcnPolicyArgs = []hcn.EndpointPolicy{}
				}
				return n.HcnPolicyArgs
			}(),
		}
	}
	return hcnEndpoint, nil
}

// RemoveHcnEndpoint removes the given name endpoint from namespace.
func RemoveHcnEndpoint(epName string) error {
	hcnEndpoint, err := hcn.GetEndpointByName(epName)
	if hcn.IsNotFoundError(err) {
		return nil
	} else if err != nil {
		_ = fmt.Errorf("[win-cni] Failed to find endpoint %v, err:%v", epName, err)
		return err
	}
	if hcnEndpoint != nil {
		err = hcnEndpoint.Delete()
		if err != nil {
			return fmt.Errorf("[win-cni] Failed to delete endpoint %v, err:%v", epName, err)
		}
	}
	return nil
}

type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)

// AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace.
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
	hcnEndpoint, err := makeEndpoint()
	if err != nil {
		return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint")
	}

	if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
		return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint")
	}

	err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
	if err != nil {
		err := RemoveHcnEndpoint(epName)
		if err != nil {
			return nil, errors.Annotatef(err, "failed to remote the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace)
		}
		return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace)
	}
	return hcnEndpoint, nil
}

// ConstructHcnResult constructs the CNI result for the HostComputeEndpoint.
func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) {
	resultInterface := &current.Interface{
		Name: hcnEndpoint.Name,
		Mac:  hcnEndpoint.MacAddress,
	}
	_, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix)
	if err != nil {
		return nil, err
	}

	ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
	resultIPConfig := &current.IPConfig{
		Address: net.IPNet{
			IP:   ipAddress,
			Mask: ipSubnet.Mask},
		Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
	}
	result := &current.Result{
		CNIVersion: current.ImplementedSpecVersion,
		Interfaces: []*current.Interface{resultInterface},
		IPs:        []*current.IPConfig{resultIPConfig},
		DNS: types.DNS{
			Search:      hcnEndpoint.Dns.Search,
			Nameservers: hcnEndpoint.Dns.ServerList,
		},
	}

	return result, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 util

import (
	"reflect"
	"sort"
	"testing"

	"github.com/GoogleContainerTools/kaniko/testutil"
)

var testURL = "https://github.com/GoogleContainerTools/runtimes-common/blob/master/LICENSE"

var testEnvReplacement = []struct {
	path         string
	envs         []string
	isFilepath   bool
	expectedPath string
}{
	{
		path: "/simple/path",
		envs: []string{
			"simple=/path/",
		},
		isFilepath:   true,
		expectedPath: "/simple/path",
	},
	{
		path: "/simple/path/",
		envs: []string{
			"simple=/path/",
		},
		isFilepath:   true,
		expectedPath: "/simple/path/",
	},
	{
		path: "${a}/b",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b",
	},
	{
		path: "/$a/b",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b",
	},
	{
		path: "/$a/b/",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b/",
	},
	{
		path: "\\$foo",
		envs: []string{
			"foo=/path/",
		},
		isFilepath:   true,
		expectedPath: "$foo",
	},
	{
		path: "8080/$protocol",
		envs: []string{
			"protocol=udp",
		},
		expectedPath: "8080/udp",
	},
	{
		path: "8080/$protocol",
		envs: []string{
			"protocol=udp",
		},
		expectedPath: "8080/udp",
	},
	{
		path: "$url",
		envs: []string{
			"url=http://example.com",
		},
		isFilepath:   true,
		expectedPath: "http://example.com",
	},
}

func Test_EnvReplacement(t *testing.T) {
	for _, test := range testEnvReplacement {
		actualPath, err := ResolveEnvironmentReplacement(test.path, test.envs, test.isFilepath)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedPath, actualPath)

	}
}

var buildContextPath = "../../integration/"

var destinationFilepathTests = []struct {
	src              string
	dest             string
	cwd              string
	expectedFilepath string
}{
	{
		src:              "context/foo",
		dest:             "/foo",
		cwd:              "/",
		expectedFilepath: "/foo",
	},
	{
		src:              "context/foo",
		dest:             "/foodir/",
		cwd:              "/",
		expectedFilepath: "/foodir/foo",
	},
	{
		src:              "context/foo",
		cwd:              "/",
		dest:             "foo",
		expectedFilepath: "/foo",
	},
	{
		src:              "context/bar/",
		cwd:              "/",
		dest:             "pkg/",
		expectedFilepath: "/pkg/bar",
	},
	{
		src:              "context/bar/",
		cwd:              "/newdir",
		dest:             "pkg/",
		expectedFilepath: "/newdir/pkg/bar",
	},
	{
		src:              "./context/empty",
		cwd:              "/",
		dest:             "/empty",
		expectedFilepath: "/empty",
	},
	{
		src:              "./context/empty",
		cwd:              "/dir",
		dest:             "/empty",
		expectedFilepath: "/empty",
	},
	{
		src:              "./",
		cwd:              "/",
		dest:             "/dir",
		expectedFilepath: "/dir",
	},
	{
		src:              "context/foo",
		cwd:              "/test",
		dest:             ".",
		expectedFilepath: "/test/foo",
	},
}

func Test_DestinationFilepath(t *testing.T) {
	for _, test := range destinationFilepathTests {
		actualFilepath, err := DestinationFilepath(test.src, test.dest, test.cwd)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFilepath, actualFilepath)
	}
}

var urlDestFilepathTests = []struct {
	url          string
	cwd          string
	dest         string
	expectedDest string
	envs         []string
}{
	{
		url:          "https://something/something",
		cwd:          "/test",
		dest:         ".",
		expectedDest: "/test/something",
	},
	{
		url:          "https://something/something",
		cwd:          "/cwd",
		dest:         "/test",
		expectedDest: "/test",
	},
	{
		url:          "https://something/something",
		cwd:          "/test",
		dest:         "/dest/",
		expectedDest: "/dest/something",
	},
	{
		url:          "https://something/$foo.tar.gz",
		cwd:          "/test",
		dest:         "/foo/",
		expectedDest: "/foo/bar.tar.gz",
		envs:         []string{"foo=bar"},
	},
}

func Test_UrlDestFilepath(t *testing.T) {
	for _, test := range urlDestFilepathTests {
		actualDest, err := URLDestinationFilepath(test.url, test.dest, test.cwd, test.envs)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedDest, actualDest)
	}
}

var matchSourcesTests = []struct {
	srcs          []string
	files         []string
	expectedFiles []string
}{
	{
		srcs: []string{
			"pkg/*",
			"/root/dir?",
			testURL,
		},
		files: []string{
			"pkg/a",
			"pkg/b",
			"/pkg/d",
			"pkg/b/d/",
			"dir/",
			"root/dir1",
		},
		expectedFiles: []string{
			"/root/dir1",
			"pkg/a",
			"pkg/b",
			testURL,
		},
	},
}

func Test_MatchSources(t *testing.T) {
	for _, test := range matchSourcesTests {
		actualFiles, err := matchSources(test.srcs, test.files)
		sort.Strings(actualFiles)
		sort.Strings(test.expectedFiles)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles)
	}
}

var isSrcValidTests = []struct {
	name            string
	srcsAndDest     []string
	resolvedSources []string
	shouldErr       bool
}{
	{
		name: "dest isn't directory",
		srcsAndDest: []string{
			"context/foo",
			"context/bar",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: true,
	},
	{
		name: "dest is directory",
		srcsAndDest: []string{
			"context/foo",
			"context/bar",
			"dest/",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy file to file",
		srcsAndDest: []string{
			"context/bar/bam",
			"dest",
		},
		resolvedSources: []string{
			"context/bar/bam",
		},
		shouldErr: false,
	},
	{
		name: "copy files with wildcards to dir",
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			"dest/",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy multilple files with wildcards to file",
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: true,
	},
	{
		name: "copy two files to file, one of which doesn't exist",
		srcsAndDest: []string{
			"context/foo",
			"context/doesntexist*",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
		},
		shouldErr: false,
	},
	{
		name: "copy dir to dest not specified as dir",
		srcsAndDest: []string{
			"context/",
			"dest",
		},
		resolvedSources: []string{
			"context/",
		},
		shouldErr: false,
	},
	{
		name: "copy url to file",
		srcsAndDest: []string{
			testURL,
			"dest",
		},
		resolvedSources: []string{
			testURL,
		},
		shouldErr: false,
	},
	{
		name: "copy two srcs, one excluded, to file",
		srcsAndDest: []string{
			"ignore/foo",
			"ignore/bar",
			"dest",
		},
		resolvedSources: []string{
			"ignore/foo",
			"ignore/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy two srcs, both excluded, to file",
		srcsAndDest: []string{
			"ignore/baz",
			"ignore/bar",
			"dest",
		},
		resolvedSources: []string{
			"ignore/baz",
			"ignore/bar",
		},
		shouldErr: true,
	},
}

func Test_IsSrcsValid(t *testing.T) {
	for _, test := range isSrcValidTests {
		t.Run(test.name, func(t *testing.T) {
			if err := GetExcludedFiles(buildContextPath); err != nil {
				t.Fatalf("error getting excluded files: %v", err)
			}
			err := IsSrcsValid(test.srcsAndDest, test.resolvedSources, buildContextPath)
			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

var testResolveSources = []struct {
	srcsAndDest  []string
	expectedList []string
}{
	{
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			testURL,
		},
		expectedList: []string{
			"context/foo",
			"context/bar",
			testURL,
		},
	},
}

func Test_ResolveSources(t *testing.T) {
	for _, test := range testResolveSources {
		actualList, err := ResolveSources(test.srcsAndDest, buildContextPath)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedList, actualList)
	}
}

var testRemoteUrls = []struct {
	name  string
	url   string
	valid bool
}{
	{
		name:  "Valid URL",
		url:   "https://google.com",
		valid: true,
	},
	{
		name:  "Invalid URL",
		url:   "not/real/",
		valid: false,
	},
	{
		name:  "URL which fails on GET",
		url:   "https://thereisnowaythiswilleverbearealurlrightrightrightcatsarethebest.com/something/not/real",
		valid: false,
	},
}

func Test_RemoteUrls(t *testing.T) {
	for _, test := range testRemoteUrls {
		t.Run(test.name, func(t *testing.T) {
			valid := IsSrcRemoteFileURL(test.url)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.valid, valid)
		})
	}

}

func TestResolveEnvironmentReplacementList(t *testing.T) {
	type args struct {
		values     []string
		envs       []string
		isFilepath bool
	}
	tests := []struct {
		name    string
		args    args
		want    []string
		wantErr bool
	}{
		{
			name: "url",
			args: args{
				values: []string{
					"https://google.com/$foo", "$bar", "$url",
				},
				envs: []string{
					"foo=baz",
					"bar=bat",
					"url=https://google.com",
				},
			},
			want: []string{"https://google.com/baz", "bat", "https://google.com"},
		},
		{
			name: "mixed",
			args: args{
				values: []string{
					"$foo", "$bar$baz", "baz",
				},
				envs: []string{
					"foo=FOO",
					"bar=BAR",
					"baz=BAZ",
				},
			},
			want: []string{"FOO", "BARBAZ", "baz"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ResolveEnvironmentReplacementList(tt.args.values, tt.args.envs, tt.args.isFilepath)
			if (err != nil) != tt.wantErr {
				t.Errorf("ResolveEnvironmentReplacementList() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ResolveEnvironmentReplacementList() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>remove trailing whitespace<commit_after>/*
Copyright 2018 Google LLC

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 util

import (
	"reflect"
	"sort"
	"testing"

	"github.com/GoogleContainerTools/kaniko/testutil"
)

var testURL = "https://github.com/GoogleContainerTools/runtimes-common/blob/master/LICENSE"

var testEnvReplacement = []struct {
	path         string
	envs         []string
	isFilepath   bool
	expectedPath string
}{
	{
		path: "/simple/path",
		envs: []string{
			"simple=/path/",
		},
		isFilepath:   true,
		expectedPath: "/simple/path",
	},
	{
		path: "/simple/path/",
		envs: []string{
			"simple=/path/",
		},
		isFilepath:   true,
		expectedPath: "/simple/path/",
	},
	{
		path: "${a}/b",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b",
	},
	{
		path: "/$a/b",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b",
	},
	{
		path: "/$a/b/",
		envs: []string{
			"a=/path/",
			"b=/path2/",
		},
		isFilepath:   true,
		expectedPath: "/path/b/",
	},
	{
		path: "\\$foo",
		envs: []string{
			"foo=/path/",
		},
		isFilepath:   true,
		expectedPath: "$foo",
	},
	{
		path: "8080/$protocol",
		envs: []string{
			"protocol=udp",
		},
		expectedPath: "8080/udp",
	},
	{
		path: "8080/$protocol",
		envs: []string{
			"protocol=udp",
		},
		expectedPath: "8080/udp",
	},
	{
		path: "$url",
		envs: []string{
			"url=http://example.com",
		},
		isFilepath:   true,
		expectedPath: "http://example.com",
	},
	{
		path: "$url",
		envs: []string{
			"url=http://example.com",
		},
		isFilepath:   false,
		expectedPath: "http://example.com",
	},
}

func Test_EnvReplacement(t *testing.T) {
	for _, test := range testEnvReplacement {
		actualPath, err := ResolveEnvironmentReplacement(test.path, test.envs, test.isFilepath)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedPath, actualPath)

	}
}

var buildContextPath = "../../integration/"

var destinationFilepathTests = []struct {
	src              string
	dest             string
	cwd              string
	expectedFilepath string
}{
	{
		src:              "context/foo",
		dest:             "/foo",
		cwd:              "/",
		expectedFilepath: "/foo",
	},
	{
		src:              "context/foo",
		dest:             "/foodir/",
		cwd:              "/",
		expectedFilepath: "/foodir/foo",
	},
	{
		src:              "context/foo",
		cwd:              "/",
		dest:             "foo",
		expectedFilepath: "/foo",
	},
	{
		src:              "context/bar/",
		cwd:              "/",
		dest:             "pkg/",
		expectedFilepath: "/pkg/bar",
	},
	{
		src:              "context/bar/",
		cwd:              "/newdir",
		dest:             "pkg/",
		expectedFilepath: "/newdir/pkg/bar",
	},
	{
		src:              "./context/empty",
		cwd:              "/",
		dest:             "/empty",
		expectedFilepath: "/empty",
	},
	{
		src:              "./context/empty",
		cwd:              "/dir",
		dest:             "/empty",
		expectedFilepath: "/empty",
	},
	{
		src:              "./",
		cwd:              "/",
		dest:             "/dir",
		expectedFilepath: "/dir",
	},
	{
		src:              "context/foo",
		cwd:              "/test",
		dest:             ".",
		expectedFilepath: "/test/foo",
	},
}

func Test_DestinationFilepath(t *testing.T) {
	for _, test := range destinationFilepathTests {
		actualFilepath, err := DestinationFilepath(test.src, test.dest, test.cwd)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFilepath, actualFilepath)
	}
}

var urlDestFilepathTests = []struct {
	url          string
	cwd          string
	dest         string
	expectedDest string
	envs         []string
}{
	{
		url:          "https://something/something",
		cwd:          "/test",
		dest:         ".",
		expectedDest: "/test/something",
	},
	{
		url:          "https://something/something",
		cwd:          "/cwd",
		dest:         "/test",
		expectedDest: "/test",
	},
	{
		url:          "https://something/something",
		cwd:          "/test",
		dest:         "/dest/",
		expectedDest: "/dest/something",
	},
	{
		url:          "https://something/$foo.tar.gz",
		cwd:          "/test",
		dest:         "/foo/",
		expectedDest: "/foo/bar.tar.gz",
		envs:         []string{"foo=bar"},
	},
}

func Test_UrlDestFilepath(t *testing.T) {
	for _, test := range urlDestFilepathTests {
		actualDest, err := URLDestinationFilepath(test.url, test.dest, test.cwd, test.envs)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedDest, actualDest)
	}
}

var matchSourcesTests = []struct {
	srcs          []string
	files         []string
	expectedFiles []string
}{
	{
		srcs: []string{
			"pkg/*",
			"/root/dir?",
			testURL,
		},
		files: []string{
			"pkg/a",
			"pkg/b",
			"/pkg/d",
			"pkg/b/d/",
			"dir/",
			"root/dir1",
		},
		expectedFiles: []string{
			"/root/dir1",
			"pkg/a",
			"pkg/b",
			testURL,
		},
	},
}

func Test_MatchSources(t *testing.T) {
	for _, test := range matchSourcesTests {
		actualFiles, err := matchSources(test.srcs, test.files)
		sort.Strings(actualFiles)
		sort.Strings(test.expectedFiles)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles)
	}
}

var isSrcValidTests = []struct {
	name            string
	srcsAndDest     []string
	resolvedSources []string
	shouldErr       bool
}{
	{
		name: "dest isn't directory",
		srcsAndDest: []string{
			"context/foo",
			"context/bar",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: true,
	},
	{
		name: "dest is directory",
		srcsAndDest: []string{
			"context/foo",
			"context/bar",
			"dest/",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy file to file",
		srcsAndDest: []string{
			"context/bar/bam",
			"dest",
		},
		resolvedSources: []string{
			"context/bar/bam",
		},
		shouldErr: false,
	},
	{
		name: "copy files with wildcards to dir",
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			"dest/",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy multilple files with wildcards to file",
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
			"context/bar",
		},
		shouldErr: true,
	},
	{
		name: "copy two files to file, one of which doesn't exist",
		srcsAndDest: []string{
			"context/foo",
			"context/doesntexist*",
			"dest",
		},
		resolvedSources: []string{
			"context/foo",
		},
		shouldErr: false,
	},
	{
		name: "copy dir to dest not specified as dir",
		srcsAndDest: []string{
			"context/",
			"dest",
		},
		resolvedSources: []string{
			"context/",
		},
		shouldErr: false,
	},
	{
		name: "copy url to file",
		srcsAndDest: []string{
			testURL,
			"dest",
		},
		resolvedSources: []string{
			testURL,
		},
		shouldErr: false,
	},
	{
		name: "copy two srcs, one excluded, to file",
		srcsAndDest: []string{
			"ignore/foo",
			"ignore/bar",
			"dest",
		},
		resolvedSources: []string{
			"ignore/foo",
			"ignore/bar",
		},
		shouldErr: false,
	},
	{
		name: "copy two srcs, both excluded, to file",
		srcsAndDest: []string{
			"ignore/baz",
			"ignore/bar",
			"dest",
		},
		resolvedSources: []string{
			"ignore/baz",
			"ignore/bar",
		},
		shouldErr: true,
	},
}

func Test_IsSrcsValid(t *testing.T) {
	for _, test := range isSrcValidTests {
		t.Run(test.name, func(t *testing.T) {
			if err := GetExcludedFiles(buildContextPath); err != nil {
				t.Fatalf("error getting excluded files: %v", err)
			}
			err := IsSrcsValid(test.srcsAndDest, test.resolvedSources, buildContextPath)
			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

var testResolveSources = []struct {
	srcsAndDest  []string
	expectedList []string
}{
	{
		srcsAndDest: []string{
			"context/foo",
			"context/b*",
			testURL,
		},
		expectedList: []string{
			"context/foo",
			"context/bar",
			testURL,
		},
	},
}

func Test_ResolveSources(t *testing.T) {
	for _, test := range testResolveSources {
		actualList, err := ResolveSources(test.srcsAndDest, buildContextPath)
		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedList, actualList)
	}
}

var testRemoteUrls = []struct {
	name  string
	url   string
	valid bool
}{
	{
		name:  "Valid URL",
		url:   "https://google.com",
		valid: true,
	},
	{
		name:  "Invalid URL",
		url:   "not/real/",
		valid: false,
	},
	{
		name:  "URL which fails on GET",
		url:   "https://thereisnowaythiswilleverbearealurlrightrightrightcatsarethebest.com/something/not/real",
		valid: false,
	},
}

func Test_RemoteUrls(t *testing.T) {
	for _, test := range testRemoteUrls {
		t.Run(test.name, func(t *testing.T) {
			valid := IsSrcRemoteFileURL(test.url)
			testutil.CheckErrorAndDeepEqual(t, false, nil, test.valid, valid)
		})
	}

}

func TestResolveEnvironmentReplacementList(t *testing.T) {
	type args struct {
		values     []string
		envs       []string
		isFilepath bool
	}
	tests := []struct {
		name    string
		args    args
		want    []string
		wantErr bool
	}{
		{
			name: "url",
			args: args{
				values: []string{
					"https://google.com/$foo", "$bar", "$url",
				},
				envs: []string{
					"foo=baz",
					"bar=bat",
					"url=https://google.com",
				},
			},
			want: []string{"https://google.com/baz", "bat", "https://google.com"},
		},
		{
			name: "mixed",
			args: args{
				values: []string{
					"$foo", "$bar$baz", "baz",
				},
				envs: []string{
					"foo=FOO",
					"bar=BAR",
					"baz=BAZ",
				},
			},
			want: []string{"FOO", "BARBAZ", "baz"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ResolveEnvironmentReplacementList(tt.args.values, tt.args.envs, tt.args.isFilepath)
			if (err != nil) != tt.wantErr {
				t.Errorf("ResolveEnvironmentReplacementList() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("ResolveEnvironmentReplacementList() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"github.com/codegangsta/cli"
	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
	pluginsutils "github.com/jfrog/jfrog-cli/plugins/utils"
	"github.com/jfrog/jfrog-cli/utils/cliutils"
	"github.com/jfrog/jfrog-client-go/httpclient"
	"github.com/jfrog/jfrog-client-go/utils"
	"github.com/jfrog/jfrog-client-go/utils/errorutils"
	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
	"github.com/jfrog/jfrog-client-go/utils/io/httputils"
	"github.com/jfrog/jfrog-client-go/utils/log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
)

const pluginsRegistryUrl = "https://releases.jfrog.io/artifactory"
const pluginsRegistryRepo = "jfrog-cli-plugins"
const latestVersionName = "latest"

func InstallCmd(c *cli.Context) error {
	if c.NArg() != 1 {
		return cliutils.PrintHelpAndReturnError("Wrong number of arguments.", c)
	}
	return runInstallCmd(c.Args().Get(0))
}

func runInstallCmd(requestedPlugin string) error {
	pluginName, version, err := getNameAndVersion(requestedPlugin)
	if err != nil {
		return err
	}
	srcPath, err := buildSrcPath(pluginName, version)
	if err != nil {
		return err
	}
	downloadUrl := utils.AddTrailingSlashIfNeeded(pluginsRegistryUrl) + srcPath

	pluginsDir, err := coreutils.GetJfrogPluginsDir()
	if err != nil {
		return err
	}

	exists, err := fileutils.IsDirExists(pluginsDir, false)
	if err != nil {
		return err
	}
	if exists {
		should, err := shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl)
		if err != nil {
			return err
		}
		if !should {
			return errors.New("requested plugin already exists locally")
		}
	} else {
		err = createPluginsDir(pluginsDir)
		if err != nil {
			return err
		}
	}

	return downloadPlugin(pluginsDir, pluginName, downloadUrl)
}

func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string) (bool, error) {
	log.Debug("Verifying plugin download is needed...")
	client, err := httpclient.ClientBuilder().Build()
	if err != nil {
		return false, err
	}
	log.Debug("Fetching plugin details from: ", downloadUrl)

	details, resp, err := client.GetRemoteFileDetails(downloadUrl, httputils.HttpClientDetails{})
	if err != nil {
		return false, err
	}
	log.Debug("Artifactory response: ", resp.Status)
	err = errorutils.CheckResponseStatus(resp, http.StatusOK)
	if err != nil {
		return false, err
	}
	isEqual, err := fileutils.IsEqualToLocalFile(filepath.Join(pluginsDir, pluginName), details.Checksum.Md5, details.Checksum.Sha1)
	return !isEqual, err
}

func buildSrcPath(pluginName, version string) (string, error) {
	arc, err := getArchitecture()
	if err != nil {
		return "", err
	}
	return path.Join(pluginsRegistryRepo, pluginName, version, arc, pluginsutils.GetPluginExecutableName(pluginName)), nil
}

func createPluginsDir(pluginsDir string) error {
	return os.MkdirAll(pluginsDir, 0777)
}

func downloadPlugin(pluginsDir, pluginName, downloadUrl string) error {
	exeName := pluginsutils.GetPluginExecutableName(pluginName)
	log.Debug("Downloading plugin from: ", downloadUrl)
	downloadDetails := &httpclient.DownloadFileDetails{
		FileName:      pluginName,
		DownloadPath:  downloadUrl,
		LocalPath:     pluginsDir,
		LocalFileName: exeName,
	}

	client, err := httpclient.ClientBuilder().Build()
	if err != nil {
		return err
	}

	resp, err := client.DownloadFile(downloadDetails, "", httputils.HttpClientDetails{}, 3, false)
	if err != nil {
		return err
	}
	log.Debug("Artifactory response: ", resp.Status)
	err = errorutils.CheckResponseStatus(resp, http.StatusOK)
	if err != nil {
		return err
	}
	log.Debug("Plugin downloaded successfully.")
	return os.Chmod(filepath.Join(pluginsDir, exeName), 0777)
}

func getNameAndVersion(requested string) (name, version string, err error) {
	split := strings.Split(requested, "@")
	if len(split) == 1 || (len(split) == 2 && split[1] == "") {
		return split[0], latestVersionName, nil
	}
	if len(split) > 2 {
		return "", "", errors.New("unexpected number of '@' separators in provided argument")
	}
	return split[0], split[1], nil
}

// Get the architecture name corresponding to the architectures that exist in registry.
func getArchitecture() (string, error) {
	switch runtime.GOOS {
	case "windows":
		return "windows-amd64", nil
	case "darwin":
		return "mac-386", nil
	}
	// Assuming linux.
	switch runtime.GOARCH {
	case "amd64":
		return "linux-amd64", nil
	case "arm64":
		return "linux-arm64", nil
	case "arm":
		return "linux-arm", nil
	case "386":
		return "linux-386", nil
	}
	return "", errors.New("no compatible plugin architecture was found for the architecture of this machine")
}
<commit_msg>Added Progress Bar for plugin install (#915)<commit_after>package commands

import (
	"errors"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
	pluginsutils "github.com/jfrog/jfrog-cli/plugins/utils"
	"github.com/jfrog/jfrog-cli/utils/cliutils"
	logUtils "github.com/jfrog/jfrog-cli/utils/log"
	"github.com/jfrog/jfrog-cli/utils/progressbar"
	"github.com/jfrog/jfrog-client-go/httpclient"
	"github.com/jfrog/jfrog-client-go/utils"
	"github.com/jfrog/jfrog-client-go/utils/errorutils"
	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
	"github.com/jfrog/jfrog-client-go/utils/io/httputils"
	"github.com/jfrog/jfrog-client-go/utils/log"
)

const pluginsRegistryUrl = "https://releases.jfrog.io/artifactory"
const pluginsRegistryRepo = "jfrog-cli-plugins"
const latestVersionName = "latest"

func InstallCmd(c *cli.Context) error {
	if c.NArg() != 1 {
		return cliutils.PrintHelpAndReturnError("Wrong number of arguments.", c)
	}
	return runInstallCmd(c.Args().Get(0))
}

func runInstallCmd(requestedPlugin string) error {
	pluginName, version, err := getNameAndVersion(requestedPlugin)
	if err != nil {
		return err
	}
	srcPath, err := buildSrcPath(pluginName, version)
	if err != nil {
		return err
	}
	downloadUrl := utils.AddTrailingSlashIfNeeded(pluginsRegistryUrl) + srcPath

	pluginsDir, err := coreutils.GetJfrogPluginsDir()
	if err != nil {
		return err
	}

	exists, err := fileutils.IsDirExists(pluginsDir, false)
	if err != nil {
		return err
	}
	if exists {
		should, err := shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl)
		if err != nil {
			return err
		}
		if !should {
			return errors.New("requested plugin already exists locally")
		}
	} else {
		err = createPluginsDir(pluginsDir)
		if err != nil {
			return err
		}
	}

	return downloadPlugin(pluginsDir, pluginName, downloadUrl)
}

func shouldDownloadPlugin(pluginsDir, pluginName, downloadUrl string) (bool, error) {
	log.Debug("Verifying plugin download is needed...")
	client, err := httpclient.ClientBuilder().Build()
	if err != nil {
		return false, err
	}
	log.Debug("Fetching plugin details from: ", downloadUrl)

	details, resp, err := client.GetRemoteFileDetails(downloadUrl, httputils.HttpClientDetails{})
	if err != nil {
		return false, err
	}
	log.Debug("Artifactory response: ", resp.Status)
	err = errorutils.CheckResponseStatus(resp, http.StatusOK)
	if err != nil {
		return false, err
	}
	isEqual, err := fileutils.IsEqualToLocalFile(filepath.Join(pluginsDir, pluginName), details.Checksum.Md5, details.Checksum.Sha1)
	return !isEqual, err
}

func buildSrcPath(pluginName, version string) (string, error) {
	arc, err := getArchitecture()
	if err != nil {
		return "", err
	}
	return path.Join(pluginsRegistryRepo, pluginName, version, arc, pluginsutils.GetPluginExecutableName(pluginName)), nil
}

func createPluginsDir(pluginsDir string) error {
	return os.MkdirAll(pluginsDir, 0777)
}

func downloadPlugin(pluginsDir, pluginName, downloadUrl string) error {
	exeName := pluginsutils.GetPluginExecutableName(pluginName)
	log.Debug("Downloading plugin from: ", downloadUrl)
	downloadDetails := &httpclient.DownloadFileDetails{
		FileName:      pluginName,
		DownloadPath:  downloadUrl,
		LocalPath:     pluginsDir,
		LocalFileName: exeName,
		RelativePath:  exeName,
	}

	client, err := httpclient.ClientBuilder().Build()
	if err != nil {
		return err
	}
	// Init progress bar.
	progressMgr, logFile, err := progressbar.InitProgressBarIfPossible()
	if err != nil {
		return err
	}
	if progressMgr != nil {
		progressMgr.IncGeneralProgressTotalBy(1)
		defer logUtils.CloseLogFile(logFile)
		defer progressMgr.Quit()
	}
	log.Info("Downloading plugin: " + pluginName)

	resp, err := client.DownloadFileWithProgress(downloadDetails, "", httputils.HttpClientDetails{}, 3, false, progressMgr)
	if err != nil {
		return err
	}
	log.Debug("Artifactory response: ", resp.Status)
	err = errorutils.CheckResponseStatus(resp, http.StatusOK)
	if err != nil {
		return err
	}
	log.Debug("Plugin downloaded successfully.")
	return os.Chmod(filepath.Join(pluginsDir, exeName), 0777)
}

func getNameAndVersion(requested string) (name, version string, err error) {
	split := strings.Split(requested, "@")
	if len(split) == 1 || (len(split) == 2 && split[1] == "") {
		return split[0], latestVersionName, nil
	}
	if len(split) > 2 {
		return "", "", errors.New("unexpected number of '@' separators in provided argument")
	}
	return split[0], split[1], nil
}

// Get the architecture name corresponding to the architectures that exist in registry.
func getArchitecture() (string, error) {
	switch runtime.GOOS {
	case "windows":
		return "windows-amd64", nil
	case "darwin":
		return "mac-386", nil
	}
	// Assuming linux.
	switch runtime.GOARCH {
	case "amd64":
		return "linux-amd64", nil
	case "arm64":
		return "linux-arm64", nil
	case "arm":
		return "linux-arm", nil
	case "386":
		return "linux-386", nil
	}
	return "", errors.New("no compatible plugin architecture was found for the architecture of this machine")
}
<|endoftext|>


<commit_before>package logrus

import (
	"bytes"
	"fmt"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	red    = 31
	yellow = 33
	blue   = 36
	gray   = 37
)

var baseTimestamp time.Time

func init() {
	baseTimestamp = time.Now()
}

// TextFormatter formats logs into text
type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
	EnvironmentOverrideColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool

	// The keys sorting function, when uninitialized it uses sort.Strings.
	SortingFunc func([]string)

	// Disables the truncation of the level text to 4 characters.
	DisableLevelTruncation bool

	// PadLevelText Adds padding the level text so that all the levels output at the same length
	// PadLevelText is a superset of the DisableLevelTruncation option
	PadLevelText bool

	// QuoteEmptyFields will wrap empty fields in quotes if true
	QuoteEmptyFields bool

	// Whether the logger's out is to a terminal
	isTerminal bool

	// FieldMap allows users to customize the names of keys for default fields.
	// As an example:
	// formatter := &TextFormatter{
	//     FieldMap: FieldMap{
	//         FieldKeyTime:  "@timestamp",
	//         FieldKeyLevel: "@level",
	//         FieldKeyMsg:   "@message"}}
	FieldMap FieldMap

	// CallerPrettyfier can be set by the user to modify the content
	// of the function and file keys in the data when ReportCaller is
	// activated. If any of the returned value is the empty string the
	// corresponding key will be removed from fields.
	CallerPrettyfier func(*runtime.Frame) (function string, file string)

	terminalInitOnce sync.Once

	// The max length of the level text, generated dynamically on init
	levelTextMaxLength int
}

func (f *TextFormatter) init(entry *Entry) {
	if entry.Logger != nil {
		f.isTerminal = checkIfTerminal(entry.Logger.Out)
	}
	// Get the max length of the level text
	for _, level := range AllLevels {
		levelTextLength := len(level.String())
		if levelTextLength > f.levelTextMaxLength {
			f.levelTextMaxLength = levelTextLength
		}
	}
}

func (f *TextFormatter) isColored() bool {
	isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))

	if f.EnvironmentOverrideColors {
		if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
			isColored = true
		} else if ok && force == "0" {
			isColored = false
		} else if os.Getenv("CLICOLOR") == "0" {
			isColored = false
		}
	}

	return isColored && !f.DisableColors
}

// Format renders a single log entry
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
	data := make(Fields)
	for k, v := range entry.Data {
		data[k] = v
	}
	prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
	keys := make([]string, 0, len(data))
	for k := range data {
		keys = append(keys, k)
	}

	var funcVal, fileVal string

	fixedKeys := make([]string, 0, 4+len(data))
	if !f.DisableTimestamp {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
	}
	fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
	if entry.Message != "" {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
	}
	if entry.err != "" {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
	}
	if entry.HasCaller() {
		if f.CallerPrettyfier != nil {
			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
		} else {
			funcVal = entry.Caller.Function
			fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
		}

		if funcVal != "" {
			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
		}
		if fileVal != "" {
			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
		}
	}

	if !f.DisableSorting {
		if f.SortingFunc == nil {
			sort.Strings(keys)
			fixedKeys = append(fixedKeys, keys...)
		} else {
			if !f.isColored() {
				fixedKeys = append(fixedKeys, keys...)
				f.SortingFunc(fixedKeys)
			} else {
				f.SortingFunc(keys)
			}
		}
	} else {
		fixedKeys = append(fixedKeys, keys...)
	}

	var b *bytes.Buffer
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	f.terminalInitOnce.Do(func() { f.init(entry) })

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = defaultTimestampFormat
	}
	if f.isColored() {
		f.printColored(b, entry, keys, data, timestampFormat)
	} else {

		for _, key := range fixedKeys {
			var value interface{}
			switch {
			case key == f.FieldMap.resolve(FieldKeyTime):
				value = entry.Time.Format(timestampFormat)
			case key == f.FieldMap.resolve(FieldKeyLevel):
				value = entry.Level.String()
			case key == f.FieldMap.resolve(FieldKeyMsg):
				value = entry.Message
			case key == f.FieldMap.resolve(FieldKeyLogrusError):
				value = entry.err
			case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
				value = funcVal
			case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
				value = fileVal
			default:
				value = data[key]
			}
			f.appendKeyValue(b, key, value)
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
	var levelColor int
	switch entry.Level {
	case DebugLevel, TraceLevel:
		levelColor = gray
	case WarnLevel:
		levelColor = yellow
	case ErrorLevel, FatalLevel, PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())
	if !f.DisableLevelTruncation && !f.PadLevelText {
		levelText = levelText[0:4]
	}
	if f.PadLevelText {
		// Generates the format string used in the next line, for example "%-6s" or "%-7s".
		// Based on the max level text length.
		formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
		// Formats the level text by appending spaces up to the max length, for example:
		// 	- "INFO   "
		//	- "WARNING"
		levelText = fmt.Sprintf(formatString, levelText)
	}

	// Remove a single newline if it already exists in the message to keep
	// the behavior of logrus text_formatter the same as the stdlib log package
	entry.Message = strings.TrimSuffix(entry.Message, "\n")

	caller := ""
	if entry.HasCaller() {
		funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)

		if f.CallerPrettyfier != nil {
			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
		}

		if fileVal == "" {
			caller = funcVal
		} else if funcVal == "" {
			caller = fileVal
		} else {
			caller = fileVal + " " + funcVal
		}
	}

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
	}
	for _, k := range keys {
		v := data[k]
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
		f.appendValue(b, v)
	}
}

func (f *TextFormatter) needsQuoting(text string) bool {
	if f.QuoteEmptyFields && len(text) == 0 {
		return true
	}
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
			return true
		}
	}
	return false
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
	if b.Len() > 0 {
		b.WriteByte(' ')
	}
	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	stringVal, ok := value.(string)
	if !ok {
		stringVal = fmt.Sprint(value)
	}

	if !f.needsQuoting(stringVal) {
		b.WriteString(stringVal)
	} else {
		b.WriteString(fmt.Sprintf("%q", stringVal))
	}
}
<commit_msg>len => RuneCount<commit_after>package logrus

import (
	"bytes"
	"fmt"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode/utf8"
)

const (
	red    = 31
	yellow = 33
	blue   = 36
	gray   = 37
)

var baseTimestamp time.Time

func init() {
	baseTimestamp = time.Now()
}

// TextFormatter formats logs into text
type TextFormatter struct {
	// Set to true to bypass checking for a TTY before outputting colors.
	ForceColors bool

	// Force disabling colors.
	DisableColors bool

	// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
	EnvironmentOverrideColors bool

	// Disable timestamp logging. useful when output is redirected to logging
	// system that already adds timestamps.
	DisableTimestamp bool

	// Enable logging the full timestamp when a TTY is attached instead of just
	// the time passed since beginning of execution.
	FullTimestamp bool

	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool

	// The keys sorting function, when uninitialized it uses sort.Strings.
	SortingFunc func([]string)

	// Disables the truncation of the level text to 4 characters.
	DisableLevelTruncation bool

	// PadLevelText Adds padding the level text so that all the levels output at the same length
	// PadLevelText is a superset of the DisableLevelTruncation option
	PadLevelText bool

	// QuoteEmptyFields will wrap empty fields in quotes if true
	QuoteEmptyFields bool

	// Whether the logger's out is to a terminal
	isTerminal bool

	// FieldMap allows users to customize the names of keys for default fields.
	// As an example:
	// formatter := &TextFormatter{
	//     FieldMap: FieldMap{
	//         FieldKeyTime:  "@timestamp",
	//         FieldKeyLevel: "@level",
	//         FieldKeyMsg:   "@message"}}
	FieldMap FieldMap

	// CallerPrettyfier can be set by the user to modify the content
	// of the function and file keys in the data when ReportCaller is
	// activated. If any of the returned value is the empty string the
	// corresponding key will be removed from fields.
	CallerPrettyfier func(*runtime.Frame) (function string, file string)

	terminalInitOnce sync.Once

	// The max length of the level text, generated dynamically on init
	levelTextMaxLength int
}

func (f *TextFormatter) init(entry *Entry) {
	if entry.Logger != nil {
		f.isTerminal = checkIfTerminal(entry.Logger.Out)
	}
	// Get the max length of the level text
	for _, level := range AllLevels {
		levelTextLength := utf8.RuneCount([]byte(level.String()))
		if levelTextLength > f.levelTextMaxLength {
			f.levelTextMaxLength = levelTextLength
		}
	}
}

func (f *TextFormatter) isColored() bool {
	isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))

	if f.EnvironmentOverrideColors {
		if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
			isColored = true
		} else if ok && force == "0" {
			isColored = false
		} else if os.Getenv("CLICOLOR") == "0" {
			isColored = false
		}
	}

	return isColored && !f.DisableColors
}

// Format renders a single log entry
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
	data := make(Fields)
	for k, v := range entry.Data {
		data[k] = v
	}
	prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
	keys := make([]string, 0, len(data))
	for k := range data {
		keys = append(keys, k)
	}

	var funcVal, fileVal string

	fixedKeys := make([]string, 0, 4+len(data))
	if !f.DisableTimestamp {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
	}
	fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
	if entry.Message != "" {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
	}
	if entry.err != "" {
		fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
	}
	if entry.HasCaller() {
		if f.CallerPrettyfier != nil {
			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
		} else {
			funcVal = entry.Caller.Function
			fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
		}

		if funcVal != "" {
			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
		}
		if fileVal != "" {
			fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
		}
	}

	if !f.DisableSorting {
		if f.SortingFunc == nil {
			sort.Strings(keys)
			fixedKeys = append(fixedKeys, keys...)
		} else {
			if !f.isColored() {
				fixedKeys = append(fixedKeys, keys...)
				f.SortingFunc(fixedKeys)
			} else {
				f.SortingFunc(keys)
			}
		}
	} else {
		fixedKeys = append(fixedKeys, keys...)
	}

	var b *bytes.Buffer
	if entry.Buffer != nil {
		b = entry.Buffer
	} else {
		b = &bytes.Buffer{}
	}

	f.terminalInitOnce.Do(func() { f.init(entry) })

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = defaultTimestampFormat
	}
	if f.isColored() {
		f.printColored(b, entry, keys, data, timestampFormat)
	} else {

		for _, key := range fixedKeys {
			var value interface{}
			switch {
			case key == f.FieldMap.resolve(FieldKeyTime):
				value = entry.Time.Format(timestampFormat)
			case key == f.FieldMap.resolve(FieldKeyLevel):
				value = entry.Level.String()
			case key == f.FieldMap.resolve(FieldKeyMsg):
				value = entry.Message
			case key == f.FieldMap.resolve(FieldKeyLogrusError):
				value = entry.err
			case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
				value = funcVal
			case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
				value = fileVal
			default:
				value = data[key]
			}
			f.appendKeyValue(b, key, value)
		}
	}

	b.WriteByte('\n')
	return b.Bytes(), nil
}

func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
	var levelColor int
	switch entry.Level {
	case DebugLevel, TraceLevel:
		levelColor = gray
	case WarnLevel:
		levelColor = yellow
	case ErrorLevel, FatalLevel, PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}

	levelText := strings.ToUpper(entry.Level.String())
	if !f.DisableLevelTruncation && !f.PadLevelText {
		levelText = levelText[0:4]
	}
	if f.PadLevelText {
		// Generates the format string used in the next line, for example "%-6s" or "%-7s".
		// Based on the max level text length.
		formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
		// Formats the level text by appending spaces up to the max length, for example:
		// 	- "INFO   "
		//	- "WARNING"
		levelText = fmt.Sprintf(formatString, levelText)
	}

	// Remove a single newline if it already exists in the message to keep
	// the behavior of logrus text_formatter the same as the stdlib log package
	entry.Message = strings.TrimSuffix(entry.Message, "\n")

	caller := ""
	if entry.HasCaller() {
		funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)

		if f.CallerPrettyfier != nil {
			funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
		}

		if fileVal == "" {
			caller = funcVal
		} else if funcVal == "" {
			caller = fileVal
		} else {
			caller = fileVal + " " + funcVal
		}
	}

	if f.DisableTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
	} else if !f.FullTimestamp {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
	} else {
		fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
	}
	for _, k := range keys {
		v := data[k]
		fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
		f.appendValue(b, v)
	}
}

func (f *TextFormatter) needsQuoting(text string) bool {
	if f.QuoteEmptyFields && len(text) == 0 {
		return true
	}
	for _, ch := range text {
		if !((ch >= 'a' && ch <= 'z') ||
			(ch >= 'A' && ch <= 'Z') ||
			(ch >= '0' && ch <= '9') ||
			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
			return true
		}
	}
	return false
}

func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
	if b.Len() > 0 {
		b.WriteByte(' ')
	}
	b.WriteString(key)
	b.WriteByte('=')
	f.appendValue(b, value)
}

func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
	stringVal, ok := value.(string)
	if !ok {
		stringVal = fmt.Sprint(value)
	}

	if !f.needsQuoting(stringVal) {
		b.WriteString(stringVal)
	} else {
		b.WriteString(fmt.Sprintf("%q", stringVal))
	}
}
<|endoftext|>