<commit_before>// Copyright 2017 Google Inc.
//
// 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 apiGatewayConfDeploy

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"time"

	"github.com/30x/apid-core"
	"sync"
)

const (
	configProtocol                   = "protocol_type"
	configAPIListen                  = "api_listen"
	configBundleBlobDownloadEndpoint = "gatewaydeploy_bundle_download_endpoint"
	configBundleDirKey               = "gatewaydeploy_bundle_dir"
	configDebounceDuration           = "gatewaydeploy_debounce_duration"
	configBundleCleanupDelay         = "gatewaydeploy_bundle_cleanup_delay"
	configMarkDeployFailedAfter      = "gatewaydeploy_deployment_timeout"
	configDownloadConnTimeout        = "gatewaydeploy_download_connection_timeout"
	configApiServerBaseURI           = "apigeesync_proxy_server_base"
	configApidInstanceID             = "apigeesync_apid_instance_id"
	configApidClusterID              = "apigeesync_cluster_id"
	configConcurrentDownloads        = "apigeesync_concurrent_downloads"
	configDownloadQueueSize          = "apigeesync_download_queue_size"
	configBlobServerBaseURI          = "apigeesync_blob_server_base"
	configStoragePath                = "local_storage_path"
)

var (
	services         apid.Services
	log              apid.LogService
	config           apid.ConfigService
	bundlePath       string
	debounceDuration time.Duration
	apiServerBaseURI *url.URL
	eventHandler     *apigeeSyncHandler
)

func init() {
	apid.RegisterPlugin(initPlugin)
}

func initPlugin(s apid.Services) (apid.PluginData, error) {
	services = s
	log = services.Log().ForModule("apiGatewayConfDeploy")
	log.Debug("start init")

	config = services.Config()

	if !config.IsSet(configApiServerBaseURI) {
		return pluginData, fmt.Errorf("Missing required config value: %s", configApiServerBaseURI)
	}

	if !config.IsSet(configBlobServerBaseURI) {
		return pluginData, fmt.Errorf("Missing required config value: %s", configBlobServerBaseURI)
	}

	var err error
	apiServerBaseURI, err = url.Parse(config.GetString(configApiServerBaseURI))
	if err != nil {
		return pluginData, fmt.Errorf("%s value %s parse err: %v", configApiServerBaseURI, apiServerBaseURI, err)
	}

	config.SetDefault(configBundleDirKey, "bundles")
	config.SetDefault(configDebounceDuration, time.Second)
	config.SetDefault(configBundleCleanupDelay, time.Minute)
	config.SetDefault(configMarkDeployFailedAfter, 5*time.Minute)
	config.SetDefault(configDownloadConnTimeout, 5*time.Minute)
	config.SetDefault(configConcurrentDownloads, 15)
	config.SetDefault(configDownloadQueueSize, 2000)

	debounceDuration = config.GetDuration(configDebounceDuration)
	if debounceDuration < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configDebounceDuration)
	}

	bundleCleanupDelay := config.GetDuration(configBundleCleanupDelay)
	if bundleCleanupDelay < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configBundleCleanupDelay)
	}

	markDeploymentFailedAfter := config.GetDuration(configMarkDeployFailedAfter)
	if markDeploymentFailedAfter < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configMarkDeployFailedAfter)
	}

	bundleDownloadConnTimeout := config.GetDuration(configDownloadConnTimeout)
	if bundleDownloadConnTimeout < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configDownloadConnTimeout)
	}

	log.Debug("apiServerBaseURI = " + apiServerBaseURI.String())

	// initialize db manager

	dbMan := &dbManager{
		data:  services.Data(),
		dbMux: sync.RWMutex{},
	}

	// initialize api manager

	apiMan := &apiManager{
		dbMan:               dbMan,
		deploymentsEndpoint: deploymentsEndpoint,
		blobEndpoint:        blobEndpoint,
		eTag:                0,
		deploymentsChanged:  make(chan interface{}, 5),
		addSubscriber:       make(chan chan deploymentsResult),
		removeSubscriber:    make(chan chan deploymentsResult),
		apiInitialized:      false,
	}

	// initialize bundle manager

	blobServerURL := config.GetString(configBlobServerBaseURI)
	relativeBundlePath := config.GetString(configBundleDirKey)
	storagePath := config.GetString(configStoragePath)
	bundlePath = path.Join(storagePath, relativeBundlePath)
	if err := os.MkdirAll(bundlePath, 0700); err != nil {
		return pluginData, fmt.Errorf("Failed bundle directory creation: %v", err)
	}
	log.Infof("Bundle directory path is %s", bundlePath)
	concurrentDownloads := config.GetInt(configConcurrentDownloads)
	downloadQueueSize := config.GetInt(configDownloadQueueSize)
	bundleMan := &bundleManager{
		blobServerUrl:             blobServerURL,
		dbMan:                     dbMan,
		apiMan:                    apiMan,
		concurrentDownloads:       concurrentDownloads,
		markDeploymentFailedAfter: markDeploymentFailedAfter,
		bundleRetryDelay:          time.Second,
		bundleCleanupDelay:        bundleCleanupDelay,
		downloadQueue:             make(chan *DownloadRequest, downloadQueueSize),
		isClosed:                  new(int32),
		client: &http.Client{
			Timeout: bundleDownloadConnTimeout,
		},
	}

	bundleMan.initializeBundleDownloading()

	//TODO initialize apiMan.distributeEvents() for api call with "block"
	//go apiMan.distributeEvents()

	// initialize event handler
	eventHandler = &apigeeSyncHandler{
		dbMan:     dbMan,
		apiMan:    apiMan,
		bundleMan: bundleMan,
		closed:    false,
	}

	eventHandler.initListener(services)

	log.Debug("end init")

	return pluginData, nil
}
<commit_msg>[XAPID-1070] reuse the same http client, change maxIdleConnsPerHost<commit_after>// Copyright 2017 Google Inc.
//
// 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 apiGatewayConfDeploy

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"time"

	"github.com/30x/apid-core"
	"net/http"
	"sync"
)

const (
	configProtocol                   = "protocol_type"
	configAPIListen                  = "api_listen"
	configBundleBlobDownloadEndpoint = "gatewaydeploy_bundle_download_endpoint"
	configBundleDirKey               = "gatewaydeploy_bundle_dir"
	configDebounceDuration           = "gatewaydeploy_debounce_duration"
	configBundleCleanupDelay         = "gatewaydeploy_bundle_cleanup_delay"
	configMarkDeployFailedAfter      = "gatewaydeploy_deployment_timeout"
	configDownloadConnTimeout        = "gatewaydeploy_download_connection_timeout"
	configApiServerBaseURI           = "apigeesync_proxy_server_base"
	configApidInstanceID             = "apigeesync_apid_instance_id"
	configApidClusterID              = "apigeesync_cluster_id"
	configConcurrentDownloads        = "apigeesync_concurrent_downloads"
	configDownloadQueueSize          = "apigeesync_download_queue_size"
	configBlobServerBaseURI          = "apigeesync_blob_server_base"
	configStoragePath                = "local_storage_path"
	maxIdleConnsPerHost              = 50
	httpTimeout                      = time.Minute
	configBearerToken                = "apigeesync_bearer_token"
)

var (
	services         apid.Services
	log              apid.LogService
	config           apid.ConfigService
	bundlePath       string
	debounceDuration time.Duration
	apiServerBaseURI *url.URL
	eventHandler     *apigeeSyncHandler
)

func init() {
	apid.RegisterPlugin(initPlugin)
}

func initPlugin(s apid.Services) (apid.PluginData, error) {
	services = s
	log = services.Log().ForModule("apiGatewayConfDeploy")
	log.Debug("start init")

	config = services.Config()

	if !config.IsSet(configApiServerBaseURI) {
		return pluginData, fmt.Errorf("Missing required config value: %s", configApiServerBaseURI)
	}

	if !config.IsSet(configBlobServerBaseURI) {
		return pluginData, fmt.Errorf("Missing required config value: %s", configBlobServerBaseURI)
	}

	var err error
	apiServerBaseURI, err = url.Parse(config.GetString(configApiServerBaseURI))
	if err != nil {
		return pluginData, fmt.Errorf("%s value %s parse err: %v", configApiServerBaseURI, apiServerBaseURI, err)
	}

	config.SetDefault(configBundleDirKey, "bundles")
	config.SetDefault(configDebounceDuration, time.Second)
	config.SetDefault(configBundleCleanupDelay, time.Minute)
	config.SetDefault(configMarkDeployFailedAfter, 5*time.Minute)
	config.SetDefault(configDownloadConnTimeout, 5*time.Minute)
	config.SetDefault(configConcurrentDownloads, 15)
	config.SetDefault(configDownloadQueueSize, 2000)

	debounceDuration = config.GetDuration(configDebounceDuration)
	if debounceDuration < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configDebounceDuration)
	}

	bundleCleanupDelay := config.GetDuration(configBundleCleanupDelay)
	if bundleCleanupDelay < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configBundleCleanupDelay)
	}

	markDeploymentFailedAfter := config.GetDuration(configMarkDeployFailedAfter)
	if markDeploymentFailedAfter < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configMarkDeployFailedAfter)
	}

	bundleDownloadConnTimeout := config.GetDuration(configDownloadConnTimeout)
	if bundleDownloadConnTimeout < time.Millisecond {
		return pluginData, fmt.Errorf("%s must be a positive duration", configDownloadConnTimeout)
	}

	log.Debug("apiServerBaseURI = " + apiServerBaseURI.String())

	// initialize tracker client
	httpClient := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnsPerHost,
		},
		Timeout: httpTimeout,
		CheckRedirect: func(req *http.Request, _ []*http.Request) error {
			req.Header.Set("Authorization", getBearerToken())
			return nil
		},
	}

	// initialize db manager

	dbMan := &dbManager{
		data:  services.Data(),
		dbMux: sync.RWMutex{},
	}

	// initialize api manager

	apiMan := &apiManager{
		dbMan:               dbMan,
		deploymentsEndpoint: deploymentsEndpoint,
		blobEndpoint:        blobEndpoint,
		eTag:                0,
		deploymentsChanged:  make(chan interface{}, 5),
		addSubscriber:       make(chan chan deploymentsResult),
		removeSubscriber:    make(chan chan deploymentsResult),
		apiInitialized:      false,
	}

	// initialize bundle manager

	blobServerURL := config.GetString(configBlobServerBaseURI)
	relativeBundlePath := config.GetString(configBundleDirKey)
	storagePath := config.GetString(configStoragePath)
	bundlePath = path.Join(storagePath, relativeBundlePath)
	if err := os.MkdirAll(bundlePath, 0700); err != nil {
		return pluginData, fmt.Errorf("Failed bundle directory creation: %v", err)
	}
	log.Infof("Bundle directory path is %s", bundlePath)
	concurrentDownloads := config.GetInt(configConcurrentDownloads)
	downloadQueueSize := config.GetInt(configDownloadQueueSize)
	bundleMan := &bundleManager{
		blobServerUrl:             blobServerURL,
		dbMan:                     dbMan,
		apiMan:                    apiMan,
		concurrentDownloads:       concurrentDownloads,
		markDeploymentFailedAfter: markDeploymentFailedAfter,
		bundleRetryDelay:          time.Second,
		bundleCleanupDelay:        bundleCleanupDelay,
		downloadQueue:             make(chan *DownloadRequest, downloadQueueSize),
		isClosed:                  new(int32),
		client:                    httpClient,
	}

	bundleMan.initializeBundleDownloading()

	//TODO initialize apiMan.distributeEvents() for api call with "block"
	//go apiMan.distributeEvents()

	// initialize event handler
	eventHandler = &apigeeSyncHandler{
		dbMan:     dbMan,
		apiMan:    apiMan,
		bundleMan: bundleMan,
		closed:    false,
	}

	eventHandler.initListener(services)

	log.Debug("end init")

	return pluginData, nil
}

func getBearerToken() string {
	return "Bearer " + config.GetString(configBearerToken)
}
<|endoftext|>


<commit_before>package main

import (
    "net"
//    "golang.org/x/net/ipv4"
    "fmt"
)

type IP_Conn struct {
    pc        *net.IPConn
    version   uint8
    dst, src  string
    headerLen uint16
    //len uint16
    //id uint16
    ttl      uint8
    protocol uint8
    //checksum int
}

func NewIP_Conn(dst string) (*IP_Conn, error) {
    pc, err := net.ListenIP("ip4:17", &net.IPAddr{IP: net.ParseIP(dst)})
    if err != nil {
        fmt.Println("Failed to ListenIP")
        return nil, err
    }

    return &IP_Conn{
        pc:        pc,
        version:   4,
        headerLen: 20,
        dst:       dst,
        src:       "127.0.0.1",
        ttl:       8,
        protocol:  17,
    }, nil
}

func calcChecksum(head []byte, excludeChecksum bool) uint16 {
    totalSum := uint64(0)
    for ind, elem := range head {
        if (ind == 10 || ind == 11) && excludeChecksum { // Ignore the checksum in some situations
            continue
        }

        if ind%2 == 0 {
            totalSum += (uint64(elem) << 8)
        } else {
            totalSum += uint64(elem)
        }
    }
    fmt.Println(totalSum)

    for prefix := (totalSum >> 16); prefix != 0; prefix = (totalSum >> 16) {
//        fmt.Println(prefix)
//        fmt.Println(totalSum)
//        fmt.Println(totalSum & 0xffff)
        totalSum = uint64(totalSum & 0xffff) + prefix
    }
    fmt.Println(totalSum)

    carried := uint16(totalSum)

    return ^carried
}

func slicePacket(b []byte) (payload []byte) {
    hdrLen := int(b[0] & 0x0f) * 4
    fmt.Println("HdrLen: ", hdrLen)
    return payload[hdrLen:]
}

func (ipc *IP_Conn) ReadFrom(b []byte) (payload []byte, e error) {
    n, _, err := ipc.pc.ReadFrom(b)
    b = b[:n]
    fmt.Println("Read Length: ", n)
    p := slicePacket(b)

    return p, err
}

func (ipc *IP_Conn) WriteTo(p []byte) error {
    totalLen := uint16(ipc.headerLen) + uint16(len(p))
    fmt.Println(totalLen)
    packet := make([]byte, ipc.headerLen)
    packet[0] = (byte)((ipc.version << 4) + (uint8)(ipc.headerLen / 4)) // Version, IHL
    packet[1] = 0
    packet[2] = (byte)(totalLen >> 8) // Total Len
    packet[3] = (byte)(totalLen)
    packet[4] = 0 // Identification (for now)
    packet[5] = 0
    packet[6] = byte(1 << 6)         // Flags: Don't fragment
    packet[7] = 0                    // Fragment Offset
    packet[8] = (byte)(ipc.ttl)      // Time to Live
    packet[9] = (byte)(ipc.protocol) // Protocol

    // Src and Dst IPs
    srcIP := net.ParseIP(ipc.src)
    fmt.Println(srcIP)
//    fmt.Println(srcIP[12])
//    fmt.Println(srcIP[13])
//    fmt.Println(srcIP[14])
//    fmt.Println(srcIP[15])
    dstIP := net.ParseIP(ipc.dst)
    fmt.Println(dstIP)
    packet[12] = srcIP[12]
    packet[13] = srcIP[13]
    packet[14] = srcIP[14]
    packet[15] = srcIP[15]
    packet[16] = dstIP[12]
    packet[17] = dstIP[13]
    packet[18] = dstIP[14]
    packet[19] = dstIP[15]

    // IPv4 header test (before checksum)
    fmt.Println(packet)

    // Checksum
    checksum := calcChecksum(packet[:20], true)
    packet[10] = byte(checksum >> 8)
    packet[11] = byte(checksum)

    // Payload
    packet = append(packet, p...)
    fmt.Println("Full Packet:  ", packet)

    dstIPAddr, err := net.ResolveIPAddr("ip", ipc.dst)
    if err != nil {
//        fmt.Println(err)
        return err
    }
    fmt.Println("Full Address: ", dstIPAddr)

    ipc.pc.WriteMsgIP(packet, nil, dstIPAddr)
    return err
}

func (ipc *IP_Conn) Close() error {
    return ipc.pc.Close()
}

/* h := &ipv4.Header{
	Version:  ipv4.Version,      // protocol version
	Len:      20,                // header length
	TOS:      0,                 // type-of-service (0 is everything normal)
	TotalLen: len(x) + 20,       // packet total length (octets)
	ID:       0,                 // identification
	Flags:    ipv4.DontFragment, // flags
	FragOff:  0,                 // fragment offset
	TTL:      8,                 // time-to-live (maximum lifespan in seconds)
	Protocol: 17,                // next protocol (17 is UDP)
	Checksum: 0,                 // checksum (apparently autocomputed)
	//Src:    net.IPv4(127, 0, 0, 1), // source address, apparently done automatically
	Dst: net.ParseIP(c.manager.ipAddress), // destination address
	//Options                         // options, extension headers
}
*/
<commit_msg>Error messages<commit_after>package main

import (
    "net"
//    "golang.org/x/net/ipv4"
    "fmt"
)

type IP_Conn struct {
    pc        *net.IPConn
    version   uint8
    dst, src  string
    headerLen uint16
    //len uint16
    //id uint16
    ttl      uint8
    protocol uint8
    //checksum int
}

func NewIP_Conn(dst string) (*IP_Conn, error) {
    pc, err := net.ListenIP("ip4:17", &net.IPAddr{IP: net.ParseIP(dst)})
    if err != nil {
        fmt.Println("Failed to ListenIP")
        return nil, err
    }

    return &IP_Conn{
        pc:        pc,
        version:   4,
        headerLen: 20,
        dst:       dst,
        src:       "127.0.0.1",
        ttl:       8,
        protocol:  17,
    }, nil
}

func calcChecksum(head []byte, excludeChecksum bool) uint16 {
    totalSum := uint64(0)
    for ind, elem := range head {
        if (ind == 10 || ind == 11) && excludeChecksum { // Ignore the checksum in some situations
            continue
        }

        if ind%2 == 0 {
            totalSum += (uint64(elem) << 8)
        } else {
            totalSum += uint64(elem)
        }
    }
    fmt.Println(totalSum)

    for prefix := (totalSum >> 16); prefix != 0; prefix = (totalSum >> 16) {
//        fmt.Println(prefix)
//        fmt.Println(totalSum)
//        fmt.Println(totalSum & 0xffff)
        totalSum = uint64(totalSum & 0xffff) + prefix
    }
    fmt.Println(totalSum)

    carried := uint16(totalSum)

    return ^carried
}

func slicePacket(b []byte) (payload []byte) {
    hdrLen := int(b[0] & 0x0f) * 4
    fmt.Println("HdrLen: ", hdrLen)
    return payload[hdrLen:]
}

func (ipc *IP_Conn) ReadFrom(b []byte) (payload []byte, e error) {
    n, _, err := ipc.pc.ReadFrom(b)
    b = b[:n]
    fmt.Println("Read Length: ", n)
    fmt.Println("Full Read Data (after trim): ", b)
    p := slicePacket(b)

    return p, err
}

func (ipc *IP_Conn) WriteTo(p []byte) error {
    totalLen := uint16(ipc.headerLen) + uint16(len(p))
    fmt.Println(totalLen)
    packet := make([]byte, ipc.headerLen)
    packet[0] = (byte)((ipc.version << 4) + (uint8)(ipc.headerLen / 4)) // Version, IHL
    packet[1] = 0
    packet[2] = (byte)(totalLen >> 8) // Total Len
    packet[3] = (byte)(totalLen)
    packet[4] = 0 // Identification (for now)
    packet[5] = 0
    packet[6] = byte(1 << 6)         // Flags: Don't fragment
    packet[7] = 0                    // Fragment Offset
    packet[8] = (byte)(ipc.ttl)      // Time to Live
    packet[9] = (byte)(ipc.protocol) // Protocol

    // Src and Dst IPs
    srcIP := net.ParseIP(ipc.src)
    fmt.Println(srcIP)
//    fmt.Println(srcIP[12])
//    fmt.Println(srcIP[13])
//    fmt.Println(srcIP[14])
//    fmt.Println(srcIP[15])
    dstIP := net.ParseIP(ipc.dst)
    fmt.Println(dstIP)
    packet[12] = srcIP[12]
    packet[13] = srcIP[13]
    packet[14] = srcIP[14]
    packet[15] = srcIP[15]
    packet[16] = dstIP[12]
    packet[17] = dstIP[13]
    packet[18] = dstIP[14]
    packet[19] = dstIP[15]

    // IPv4 header test (before checksum)
    fmt.Println(packet)

    // Checksum
    checksum := calcChecksum(packet[:20], true)
    packet[10] = byte(checksum >> 8)
    packet[11] = byte(checksum)

    // Payload
    packet = append(packet, p...)
    fmt.Println("Full Packet:  ", packet)

    dstIPAddr, err := net.ResolveIPAddr("ip", ipc.dst)
    if err != nil {
//        fmt.Println(err)
        return err
    }
    fmt.Println("Full Address: ", dstIPAddr)

    ipc.pc.WriteMsgIP(packet, nil, dstIPAddr)
    return err
}

func (ipc *IP_Conn) Close() error {
    return ipc.pc.Close()
}

/* h := &ipv4.Header{
	Version:  ipv4.Version,      // protocol version
	Len:      20,                // header length
	TOS:      0,                 // type-of-service (0 is everything normal)
	TotalLen: len(x) + 20,       // packet total length (octets)
	ID:       0,                 // identification
	Flags:    ipv4.DontFragment, // flags
	FragOff:  0,                 // fragment offset
	TTL:      8,                 // time-to-live (maximum lifespan in seconds)
	Protocol: 17,                // next protocol (17 is UDP)
	Checksum: 0,                 // checksum (apparently autocomputed)
	//Src:    net.IPv4(127, 0, 0, 1), // source address, apparently done automatically
	Dst: net.ParseIP(c.manager.ipAddress), // destination address
	//Options                         // options, extension headers
}
*/
<|endoftext|>


<commit_before>package libcarina

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"path"
	"regexp"
	"strings"

	"github.com/pkg/errors"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/rackspace"
)

// UserAgentPrefix is the default user agent string, consumers should append their application version to CarinaClient.UserAgent
const UserAgentPrefix = "getcarina/libcarina"

// CarinaClient accesses Carina directly
type CarinaClient struct {
	Client    *http.Client
	Username  string
	Token     string
	Endpoint  string
	UserAgent string
}

// HTTPErr is returned when API requests are not successful
type HTTPErr struct {
	Method     string
	URL        string
	StatusCode int
	Status     string
	Body       string
}

// CarinaGenericErrorResponse represents the response returned by Carina when a request fails
type CarinaGenericErrorResponse struct {
	Errors []CarinaError `json:"errors"`
}

// CarinaError represents an error message from the Carina API
type CarinaError struct {
	Code      string `json:"code"`
	Detail    string `json:"detail"`
	RequestID string `json:"request_id"`
	Status    int    `json:"status"`
	Title     string `json:"title"`
}

// CarinaUnacceptableErrorResonse represents the response returned by Carina when the StatusCode is 406
type CarinaUnacceptableErrorResonse struct {
	Errors []CarinaUnacceptableError `json:"errors"`
}

// CarinaUnacceptableError represents a 406 response from the Carina API
type CarinaUnacceptableError struct {
	CarinaError
	MaxVersion string `json:"max_version"`
	MinVersion string `json:"min_version"`
}

// genericError is a multi-purpose error formatter for generic errors from the Carina API
func (err HTTPErr) genericError() string {
	var carinaResp CarinaGenericErrorResponse

	jsonErr := json.Unmarshal([]byte(err.Body), &carinaResp)
	if jsonErr != nil {
		return fmt.Sprintf("%s %s (%d)", err.Method, err.URL, err.StatusCode)
	}

	var errorMessages bytes.Buffer
	for _, carinaErr := range carinaResp.Errors {
		errorMessages.WriteString("\nMessage: ")
		errorMessages.WriteString(carinaErr.Title)
		errorMessages.WriteString(" - ")
		errorMessages.WriteString(carinaErr.Detail)
	}
	return fmt.Sprintf("%s %s (%d)%s", err.Method, err.URL, err.StatusCode, errorMessages.String())
}

// unacceptableError is a error formatter for parsing a 406 response from the Carina API
func (err HTTPErr) unacceptableError() string {
	var carinaResp CarinaUnacceptableErrorResonse

	jsonErr := json.Unmarshal([]byte(err.Body), &carinaResp)
	if jsonErr != nil {
		return err.genericError()
	}

	var errorMessages bytes.Buffer
	for _, carinaErr := range carinaResp.Errors {
		errorMessages.WriteString("\nMessage: ")
		errorMessages.WriteString(carinaErr.Title)
		errorMessages.WriteString(" - The client supports ")
		errorMessages.WriteString(SupportedAPIVersion)
		errorMessages.WriteString(" while the server supports ")
		errorMessages.WriteString(carinaErr.MinVersion)
		errorMessages.WriteString(" - ")
		errorMessages.WriteString(carinaErr.MaxVersion)
		errorMessages.WriteString(".")
	}
	return fmt.Sprintf("%s %s (%d)%s", err.Method, err.URL, err.StatusCode, errorMessages.String())
}

// Error routes to either genericError or other, more-specific, response formatters to give provide a user-friendly error
func (err HTTPErr) Error() string {
	switch err.StatusCode {
	default:
		return err.genericError()
	case 406:
		return err.unacceptableError()
	}
}

// NewClient create an authenticated CarinaClient
func NewClient(username string, apikey string, region string, authEndpointOverride string, cachedToken string, cachedEndpoint string) (*CarinaClient, error) {
	authEndpoint := rackspace.RackspaceUSIdentity
	if authEndpointOverride != "" {
		authEndpoint = authEndpointOverride
	}

	verifyToken := func() error {
		req, err := http.NewRequest("HEAD", authEndpoint+"tokens/"+cachedToken, nil)
		if err != nil {
			return errors.WithStack(err)
		}

		req.Header.Add("Accept", "application/json")
		req.Header.Add("X-Auth-Token", cachedToken)
		req.Header.Add("User-Agent", UserAgentPrefix)

		httpClient := &http.Client{}
		resp, err := httpClient.Do(req)
		if err != nil {
			return errors.WithStack(err)
		}

		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			return fmt.Errorf("Cached token is invalid")
		}

		return nil
	}

	// Attempt to authenticate with the cached token first, falling back on the apikey
	if cachedToken == "" || verifyToken() != nil {
		ao := &gophercloud.AuthOptions{
			Username:         username,
			APIKey:           apikey,
			IdentityEndpoint: authEndpoint,
		}

		provider, err := rackspace.AuthenticatedClient(*ao)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		cachedToken = provider.TokenID

		eo := gophercloud.EndpointOpts{Region: region}
		eo.ApplyDefaults(CarinaEndpointType)
		url, err := provider.EndpointLocator(eo)
		if err != nil {
			return nil, errors.WithStack(err)
		}

		cachedEndpoint = strings.TrimRight(url, "/")
	}

	return &CarinaClient{
		Client:    &http.Client{},
		Username:  username,
		Token:     cachedToken,
		Endpoint:  cachedEndpoint,
		UserAgent: UserAgentPrefix,
	}, nil
}

// NewRequest handles a request using auth used by Carina
func (c *CarinaClient) NewRequest(method string, uri string, body io.Reader) (*http.Response, error) {
	req, err := http.NewRequest(method, c.Endpoint+uri, body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-Auth-Token", c.Token)
	req.Header.Set("User-Agent", c.UserAgent)
	req.Header.Add("API-Version", CarinaEndpointType+" "+SupportedAPIVersion)

	resp, err := c.Client.Do(req)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if resp.StatusCode >= 400 {
		err := HTTPErr{
			Method:     req.Method,
			URL:        req.URL.String(),
			StatusCode: resp.StatusCode,
			Status:     resp.Status,
		}
		defer resp.Body.Close()
		b, _ := ioutil.ReadAll(resp.Body)
		err.Body = string(b)
		return nil, errors.WithStack(err)
	}

	return resp, nil
}

// List the current clusters
func (c *CarinaClient) List() ([]*Cluster, error) {
	resp, err := c.NewRequest("GET", "/clusters", nil)
	if err != nil {
		return nil, err
	}

	var result struct {
		Clusters []*Cluster `json:"clusters"`
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&result)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return result.Clusters, nil
}

func clusterFromResponse(resp *http.Response, err error) (*Cluster, error) {
	if err != nil {
		return nil, errors.WithStack(err)
	}

	cluster := &Cluster{}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&cluster)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return cluster, nil
}

func isClusterID(token string) bool {
	r := regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
	return r.MatchString(token)
}

func (c *CarinaClient) lookupClusterName(token string) (string, error) {
	if !isClusterID(token) {
		return token, nil
	}

	clusters, err := c.List()
	if err != nil {
		return "", err
	}

	var name string
	for _, cluster := range clusters {
		if strings.ToLower(cluster.ID) == strings.ToLower(token) {
			name = cluster.Name
			break
		}
	}

	if name == "" {
		return "", HTTPErr{
			StatusCode: http.StatusNotFound,
			Status:     "404 NOT FOUND",
			Body:       `{"message": "Cluster "` + token + ` not found"}`}
	}

	return name, nil
}

func (c *CarinaClient) lookupClusterID(token string) (string, error) {
	if isClusterID(token) {
		return token, nil
	}

	clusters, err := c.List()
	if err != nil {
		return "", err
	}

	var id string
	for _, cluster := range clusters {
		if strings.ToLower(cluster.Name) == strings.ToLower(token) {
			if id != "" {
				return "", fmt.Errorf("The cluster (%s) is not unique. Retry the request using the cluster id", token)
			}
			id = cluster.ID
		}
	}

	if id == "" {
		return "", HTTPErr{
			StatusCode: http.StatusNotFound,
			Status:     "404 NOT FOUND",
			Body:       `{"message": "Cluster "` + token + ` not found"}`}
	}

	return id, nil
}

// ListClusterTypes returns a list of cluster types
func (c *CarinaClient) ListClusterTypes() ([]*ClusterType, error) {
	resp, err := c.NewRequest("GET", "/cluster_types", nil)
	if err != nil {
		return nil, err
	}

	var result struct {
		Types []*ClusterType `json:"cluster_types"`
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&result)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return result.Types, nil
}

// Get a cluster by cluster by its name or id
func (c *CarinaClient) Get(token string) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id)
	resp, err := c.NewRequest("GET", uri, nil)
	return clusterFromResponse(resp, err)
}

// Create a new cluster with cluster options
func (c *CarinaClient) Create(clusterOpts *CreateClusterOpts) (*Cluster, error) {
	clusterOptsJSON, err := json.Marshal(clusterOpts)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	body := bytes.NewReader(clusterOptsJSON)
	resp, err := c.NewRequest("POST", "/clusters", body)
	return clusterFromResponse(resp, err)
}

// Resize a cluster with resize task options
func (c *CarinaClient) Resize(token string, nodes int) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	resizeOpts := newResizeOpts(nodes)
	resizeOptsJSON, err := json.Marshal(resizeOpts)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	body := bytes.NewReader(resizeOptsJSON)
	uri := path.Join("/clusters", id, "tasks")
	resp, err := c.NewRequest("POST", uri, body)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return c.Get(token)
}

// GetCredentials returns a Credentials struct for the given cluster name
func (c *CarinaClient) GetCredentials(token string) (*CredentialsBundle, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	name, err := c.lookupClusterName(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id, "credentials/zip")
	resp, err := c.NewRequest("GET", uri, nil)
	if err != nil {
		return nil, err
	}

	// Read the body as a zip file
	buf := &bytes.Buffer{}
	_, err = io.Copy(buf, resp.Body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	b := bytes.NewReader(buf.Bytes())
	zipr, err := zip.NewReader(b, int64(b.Len()))
	if err != nil {
		return nil, errors.WithStack(err)
	}

	// Fetch the contents for each file in the zipfile
	creds := NewCredentialsBundle()
	for _, zf := range zipr.File {
		_, fname := path.Split(zf.Name)
		fi := zf.FileInfo()

		if fi.IsDir() {
			// Explicitly skip past directories (the UUID directory from a previous release)
			continue
		}

		rc, err := zf.Open()
		if err != nil {
			return nil, errors.WithStack(err)
		}

		b, err := ioutil.ReadAll(rc)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		creds.Files[fname] = b
	}

	appendClusterName(name, creds)

	return creds, nil
}

// Set the CLUSTER_NAME environment variable in the scripts
func appendClusterName(name string, creds *CredentialsBundle) {
	addStmt := func(fileName string, stmt string) {
		script := creds.Files[fileName]
		script = append(script, []byte(stmt)...)
		creds.Files[fileName] = script
	}

	for fileName := range creds.Files {
		switch fileName {
		case "docker.env", "kubectl.env":
			addStmt(fileName, fmt.Sprintf("export CARINA_CLUSTER_NAME=%s\n", name))
		case "docker.fish", "kubectl.fish":
			addStmt(fileName, fmt.Sprintf("set -x CARINA_CLUSTER_NAME %s\n", name))
		case "docker.ps1", "kubectl.ps1":
			addStmt(fileName, fmt.Sprintf("$env:CARINA_CLUSTER_NAME=\"%s\"\n", name))
		case "docker.cmd", "kubectl.cmd":
			addStmt(fileName, fmt.Sprintf("set CARINA_CLUSTER_NAME=%s\n", name))
		}
	}
}

// Delete nukes a cluster out of existence
func (c *CarinaClient) Delete(token string) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id)
	resp, err := c.NewRequest("DELETE", uri, nil)
	return clusterFromResponse(resp, err)
}
<commit_msg>Safely append CLUSTER_NAME to the creds<commit_after>package libcarina

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"path"
	"regexp"
	"strings"

	"github.com/pkg/errors"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/rackspace"
)

// UserAgentPrefix is the default user agent string, consumers should append their application version to CarinaClient.UserAgent
const UserAgentPrefix = "getcarina/libcarina"

// CarinaClient accesses Carina directly
type CarinaClient struct {
	Client    *http.Client
	Username  string
	Token     string
	Endpoint  string
	UserAgent string
}

// HTTPErr is returned when API requests are not successful
type HTTPErr struct {
	Method     string
	URL        string
	StatusCode int
	Status     string
	Body       string
}

// CarinaGenericErrorResponse represents the response returned by Carina when a request fails
type CarinaGenericErrorResponse struct {
	Errors []CarinaError `json:"errors"`
}

// CarinaError represents an error message from the Carina API
type CarinaError struct {
	Code      string `json:"code"`
	Detail    string `json:"detail"`
	RequestID string `json:"request_id"`
	Status    int    `json:"status"`
	Title     string `json:"title"`
}

// CarinaUnacceptableErrorResonse represents the response returned by Carina when the StatusCode is 406
type CarinaUnacceptableErrorResonse struct {
	Errors []CarinaUnacceptableError `json:"errors"`
}

// CarinaUnacceptableError represents a 406 response from the Carina API
type CarinaUnacceptableError struct {
	CarinaError
	MaxVersion string `json:"max_version"`
	MinVersion string `json:"min_version"`
}

// genericError is a multi-purpose error formatter for generic errors from the Carina API
func (err HTTPErr) genericError() string {
	var carinaResp CarinaGenericErrorResponse

	jsonErr := json.Unmarshal([]byte(err.Body), &carinaResp)
	if jsonErr != nil {
		return fmt.Sprintf("%s %s (%d)", err.Method, err.URL, err.StatusCode)
	}

	var errorMessages bytes.Buffer
	for _, carinaErr := range carinaResp.Errors {
		errorMessages.WriteString("\nMessage: ")
		errorMessages.WriteString(carinaErr.Title)
		errorMessages.WriteString(" - ")
		errorMessages.WriteString(carinaErr.Detail)
	}
	return fmt.Sprintf("%s %s (%d)%s", err.Method, err.URL, err.StatusCode, errorMessages.String())
}

// unacceptableError is a error formatter for parsing a 406 response from the Carina API
func (err HTTPErr) unacceptableError() string {
	var carinaResp CarinaUnacceptableErrorResonse

	jsonErr := json.Unmarshal([]byte(err.Body), &carinaResp)
	if jsonErr != nil {
		return err.genericError()
	}

	var errorMessages bytes.Buffer
	for _, carinaErr := range carinaResp.Errors {
		errorMessages.WriteString("\nMessage: ")
		errorMessages.WriteString(carinaErr.Title)
		errorMessages.WriteString(" - The client supports ")
		errorMessages.WriteString(SupportedAPIVersion)
		errorMessages.WriteString(" while the server supports ")
		errorMessages.WriteString(carinaErr.MinVersion)
		errorMessages.WriteString(" - ")
		errorMessages.WriteString(carinaErr.MaxVersion)
		errorMessages.WriteString(".")
	}
	return fmt.Sprintf("%s %s (%d)%s", err.Method, err.URL, err.StatusCode, errorMessages.String())
}

// Error routes to either genericError or other, more-specific, response formatters to give provide a user-friendly error
func (err HTTPErr) Error() string {
	switch err.StatusCode {
	default:
		return err.genericError()
	case 406:
		return err.unacceptableError()
	}
}

// NewClient create an authenticated CarinaClient
func NewClient(username string, apikey string, region string, authEndpointOverride string, cachedToken string, cachedEndpoint string) (*CarinaClient, error) {
	authEndpoint := rackspace.RackspaceUSIdentity
	if authEndpointOverride != "" {
		authEndpoint = authEndpointOverride
	}

	verifyToken := func() error {
		req, err := http.NewRequest("HEAD", authEndpoint+"tokens/"+cachedToken, nil)
		if err != nil {
			return errors.WithStack(err)
		}

		req.Header.Add("Accept", "application/json")
		req.Header.Add("X-Auth-Token", cachedToken)
		req.Header.Add("User-Agent", UserAgentPrefix)

		httpClient := &http.Client{}
		resp, err := httpClient.Do(req)
		if err != nil {
			return errors.WithStack(err)
		}

		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			return fmt.Errorf("Cached token is invalid")
		}

		return nil
	}

	// Attempt to authenticate with the cached token first, falling back on the apikey
	if cachedToken == "" || verifyToken() != nil {
		ao := &gophercloud.AuthOptions{
			Username:         username,
			APIKey:           apikey,
			IdentityEndpoint: authEndpoint,
		}

		provider, err := rackspace.AuthenticatedClient(*ao)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		cachedToken = provider.TokenID

		eo := gophercloud.EndpointOpts{Region: region}
		eo.ApplyDefaults(CarinaEndpointType)
		url, err := provider.EndpointLocator(eo)
		if err != nil {
			return nil, errors.WithStack(err)
		}

		cachedEndpoint = strings.TrimRight(url, "/")
	}

	return &CarinaClient{
		Client:    &http.Client{},
		Username:  username,
		Token:     cachedToken,
		Endpoint:  cachedEndpoint,
		UserAgent: UserAgentPrefix,
	}, nil
}

// NewRequest handles a request using auth used by Carina
func (c *CarinaClient) NewRequest(method string, uri string, body io.Reader) (*http.Response, error) {
	req, err := http.NewRequest(method, c.Endpoint+uri, body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-Auth-Token", c.Token)
	req.Header.Set("User-Agent", c.UserAgent)
	req.Header.Add("API-Version", CarinaEndpointType+" "+SupportedAPIVersion)

	resp, err := c.Client.Do(req)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	if resp.StatusCode >= 400 {
		err := HTTPErr{
			Method:     req.Method,
			URL:        req.URL.String(),
			StatusCode: resp.StatusCode,
			Status:     resp.Status,
		}
		defer resp.Body.Close()
		b, _ := ioutil.ReadAll(resp.Body)
		err.Body = string(b)
		return nil, errors.WithStack(err)
	}

	return resp, nil
}

// List the current clusters
func (c *CarinaClient) List() ([]*Cluster, error) {
	resp, err := c.NewRequest("GET", "/clusters", nil)
	if err != nil {
		return nil, err
	}

	var result struct {
		Clusters []*Cluster `json:"clusters"`
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&result)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return result.Clusters, nil
}

func clusterFromResponse(resp *http.Response, err error) (*Cluster, error) {
	if err != nil {
		return nil, errors.WithStack(err)
	}

	cluster := &Cluster{}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&cluster)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return cluster, nil
}

func isClusterID(token string) bool {
	r := regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
	return r.MatchString(token)
}

func (c *CarinaClient) lookupClusterName(token string) (string, error) {
	if !isClusterID(token) {
		return token, nil
	}

	clusters, err := c.List()
	if err != nil {
		return "", err
	}

	var name string
	for _, cluster := range clusters {
		if strings.ToLower(cluster.ID) == strings.ToLower(token) {
			name = cluster.Name
			break
		}
	}

	if name == "" {
		return "", HTTPErr{
			StatusCode: http.StatusNotFound,
			Status:     "404 NOT FOUND",
			Body:       `{"message": "Cluster "` + token + ` not found"}`}
	}

	return name, nil
}

func (c *CarinaClient) lookupClusterID(token string) (string, error) {
	if isClusterID(token) {
		return token, nil
	}

	clusters, err := c.List()
	if err != nil {
		return "", err
	}

	var id string
	for _, cluster := range clusters {
		if strings.ToLower(cluster.Name) == strings.ToLower(token) {
			if id != "" {
				return "", fmt.Errorf("The cluster (%s) is not unique. Retry the request using the cluster id", token)
			}
			id = cluster.ID
		}
	}

	if id == "" {
		return "", HTTPErr{
			StatusCode: http.StatusNotFound,
			Status:     "404 NOT FOUND",
			Body:       `{"message": "Cluster "` + token + ` not found"}`}
	}

	return id, nil
}

// ListClusterTypes returns a list of cluster types
func (c *CarinaClient) ListClusterTypes() ([]*ClusterType, error) {
	resp, err := c.NewRequest("GET", "/cluster_types", nil)
	if err != nil {
		return nil, err
	}

	var result struct {
		Types []*ClusterType `json:"cluster_types"`
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&result)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return result.Types, nil
}

// Get a cluster by cluster by its name or id
func (c *CarinaClient) Get(token string) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id)
	resp, err := c.NewRequest("GET", uri, nil)
	return clusterFromResponse(resp, err)
}

// Create a new cluster with cluster options
func (c *CarinaClient) Create(clusterOpts *CreateClusterOpts) (*Cluster, error) {
	clusterOptsJSON, err := json.Marshal(clusterOpts)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	body := bytes.NewReader(clusterOptsJSON)
	resp, err := c.NewRequest("POST", "/clusters", body)
	return clusterFromResponse(resp, err)
}

// Resize a cluster with resize task options
func (c *CarinaClient) Resize(token string, nodes int) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	resizeOpts := newResizeOpts(nodes)
	resizeOptsJSON, err := json.Marshal(resizeOpts)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	body := bytes.NewReader(resizeOptsJSON)
	uri := path.Join("/clusters", id, "tasks")
	resp, err := c.NewRequest("POST", uri, body)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return c.Get(token)
}

// GetCredentials returns a Credentials struct for the given cluster name
func (c *CarinaClient) GetCredentials(token string) (*CredentialsBundle, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	name, err := c.lookupClusterName(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id, "credentials/zip")
	resp, err := c.NewRequest("GET", uri, nil)
	if err != nil {
		return nil, err
	}

	// Read the body as a zip file
	buf := &bytes.Buffer{}
	_, err = io.Copy(buf, resp.Body)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	b := bytes.NewReader(buf.Bytes())
	zipr, err := zip.NewReader(b, int64(b.Len()))
	if err != nil {
		return nil, errors.WithStack(err)
	}

	// Fetch the contents for each file in the zipfile
	creds := NewCredentialsBundle()
	for _, zf := range zipr.File {
		_, fname := path.Split(zf.Name)
		fi := zf.FileInfo()

		if fi.IsDir() {
			// Explicitly skip past directories (the UUID directory from a previous release)
			continue
		}

		rc, err := zf.Open()
		if err != nil {
			return nil, errors.WithStack(err)
		}

		b, err := ioutil.ReadAll(rc)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		creds.Files[fname] = b
	}

	appendClusterName(name, creds)

	return creds, nil
}

// Set the CLUSTER_NAME environment variable in the scripts
func appendClusterName(name string, creds *CredentialsBundle) {
	addStmt := func(fileName string, stmt string) {
		script := creds.Files[fileName]
		script = append(script, []byte(stmt)...)
		creds.Files[fileName] = script
	}

	for fileName := range creds.Files {
		switch fileName {
		case "docker.env", "kubectl.env":
			addStmt(fileName, fmt.Sprintf("\nexport CARINA_CLUSTER_NAME=%s\n", name))
		case "docker.fish", "kubectl.fish":
			addStmt(fileName, fmt.Sprintf("\nset -x CARINA_CLUSTER_NAME %s\n", name))
		case "docker.ps1", "kubectl.ps1":
			addStmt(fileName, fmt.Sprintf("\n$env:CARINA_CLUSTER_NAME=\"%s\"\n", name))
		case "docker.cmd", "kubectl.cmd":
			addStmt(fileName, fmt.Sprintf("\nset CARINA_CLUSTER_NAME=%s\n", name))
		}
	}
}

// Delete nukes a cluster out of existence
func (c *CarinaClient) Delete(token string) (*Cluster, error) {
	id, err := c.lookupClusterID(token)
	if err != nil {
		return nil, err
	}

	uri := path.Join("/clusters", id)
	resp, err := c.NewRequest("DELETE", uri, nil)
	return clusterFromResponse(resp, err)
}
<|endoftext|>


<commit_before>package readline

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/zetamatta/go-box"
	"github.com/zetamatta/go-getch"
)

var FlushBeforeReadline = false

type Result int

const (
	CONTINUE Result = iota
	ENTER    Result = iota
	ABORT    Result = iota
	INTR     Result = iota
)

func (this Result) String() string {
	switch this {
	case CONTINUE:
		return "CONTINUE"
	case ENTER:
		return "ENTER"
	case ABORT:
		return "ABORT"
	case INTR:
		return "INTR"
	default:
		return "ERROR"
	}
}

type KeyFuncT interface {
	Call(ctx context.Context, buffer *Buffer) Result
}

type KeyGoFuncT struct {
	Func func(ctx context.Context, buffer *Buffer) Result
	Name string
}

func (this *KeyGoFuncT) Call(ctx context.Context, buffer *Buffer) Result {
	if this.Func == nil {
		return CONTINUE
	}
	return this.Func(ctx, buffer)
}

func (this KeyGoFuncT) String() string {
	return this.Name
}

var keyMap = map[rune]KeyFuncT{
	name2char[K_CTRL_A]: name2func(F_BEGINNING_OF_LINE),
	name2char[K_CTRL_B]: name2func(F_BACKWARD_CHAR),
	name2char[K_CTRL_C]: name2func(F_INTR),
	name2char[K_CTRL_D]: name2func(F_DELETE_OR_ABORT),
	name2char[K_CTRL_E]: name2func(F_END_OF_LINE),
	name2char[K_CTRL_F]: name2func(F_FORWARD_CHAR),
	name2char[K_CTRL_H]: name2func(F_BACKWARD_DELETE_CHAR),
	name2char[K_CTRL_K]: name2func(F_KILL_LINE),
	name2char[K_CTRL_L]: name2func(F_CLEAR_SCREEN),
	name2char[K_CTRL_M]: name2func(F_ACCEPT_LINE),
	name2char[K_CTRL_R]: name2func(F_ISEARCH_BACKWARD),
	name2char[K_CTRL_U]: name2func(F_UNIX_LINE_DISCARD),
	name2char[K_CTRL_Y]: name2func(F_YANK),
	name2char[K_DELETE]: name2func(F_DELETE_CHAR),
	name2char[K_ENTER]:  name2func(F_ACCEPT_LINE),
	name2char[K_ESCAPE]: name2func(F_KILL_WHOLE_LINE),
	name2char[K_CTRL_N]: name2func(F_HISTORY_DOWN),
	name2char[K_CTRL_P]: name2func(F_HISTORY_UP),
	name2char[K_CTRL_Q]: name2func(F_QUOTED_INSERT),
	name2char[K_CTRL_T]: name2func(F_SWAPCHAR),
	name2char[K_CTRL_V]: name2func(F_QUOTED_INSERT),
	name2char[K_CTRL_W]: name2func(F_UNIX_WORD_RUBOUT),
}

var scanMap = map[uint16]KeyFuncT{
	name2scan[K_CTRL]:   name2func(F_PASS),
	name2scan[K_DELETE]: name2func(F_DELETE_CHAR),
	name2scan[K_END]:    name2func(F_END_OF_LINE),
	name2scan[K_HOME]:   name2func(F_BEGINNING_OF_LINE),
	name2scan[K_LEFT]:   name2func(F_BACKWARD_CHAR),
	name2scan[K_RIGHT]:  name2func(F_FORWARD_CHAR),
	name2scan[K_SHIFT]:  name2func(F_PASS),
	name2scan[K_DOWN]:   name2func(F_HISTORY_DOWN),
	name2scan[K_UP]:     name2func(F_HISTORY_UP),
}

var altMap = map[uint16]KeyFuncT{
	name2alt[K_ALT_V]: name2func(F_YANK),
	name2alt[K_ALT_Y]: name2func(F_YANK_WITH_QUOTE),
}

func normWord(src string) string {
	return strings.Replace(strings.ToUpper(src), "-", "_", -1)
}

func BindKeyFunc(keyName string, funcValue KeyFuncT) error {
	keyName_ := normWord(keyName)
	if altValue, altOk := name2alt[keyName_]; altOk {
		altMap[altValue] = funcValue
		return nil
	} else if charValue, charOk := name2char[keyName_]; charOk {
		keyMap[charValue] = funcValue
		return nil
	} else if scanValue, scanOk := name2scan[keyName_]; scanOk {
		scanMap[scanValue] = funcValue
		return nil
	} else {
		return fmt.Errorf("%s: no such keyname", keyName)
	}
}

func BindKeyClosure(name string, f func(context.Context, *Buffer) Result) error {
	return BindKeyFunc(name, &KeyGoFuncT{Func: f, Name: "annonymous"})
}

func GetBindKey(keyName string) KeyFuncT {
	keyName_ := normWord(keyName)
	if altValue, altOk := name2alt[keyName_]; altOk {
		return altMap[altValue]
	} else if charValue, charOk := name2char[keyName_]; charOk {
		return keyMap[charValue]
	} else if scanValue, scanOk := name2scan[keyName_]; scanOk {
		return scanMap[scanValue]
	} else {
		return nil
	}
}

func GetFunc(funcName string) (KeyFuncT, error) {
	rc := name2func(normWord(funcName))
	if rc != nil {
		return rc, nil
	} else {
		return nil, fmt.Errorf("%s: not found in the function-list", funcName)
	}
}

func BindKeySymbol(keyName, funcName string) error {
	funcValue := name2func(normWord(funcName))
	if funcValue == nil {
		return fmt.Errorf("%s: no such function.", funcName)
	}
	return BindKeyFunc(keyName, funcValue)
}

type EmptyHistory struct{}

func (this *EmptyHistory) Len() int      { return 0 }
func (this *EmptyHistory) At(int) string { return "" }

const (
	CURSOR_OFF = "\x1B[?25l"
	CURSOR_ON  = "\x1B[?25h\x1B[s\x1B[u"
)

var CtrlC = errors.New("^C")

// Call LineEditor
// - ENTER typed -> returns TEXT and nil
// - CTRL-C typed -> returns "" and readline.CtrlC
// - CTRL-D typed -> returns "" and io.EOF
func (session *Editor) ReadLine(ctx context.Context) (string, error) {
	if session.Prompt == nil {
		session.Prompt = func() (int, error) {
			fmt.Fprint(Console, "\n> ")
			return 2, nil
		}
	}
	if session.History == nil {
		session.History = new(EmptyHistory)
	}
	this := Buffer{
		Editor:         session,
		Buffer:         make([]rune, 20),
		HistoryPointer: session.History.Len(),
	}
	this.TermWidth, _ = box.GetScreenBufferInfo().ViewSize()

	var err1 error
	this.TopColumn, err1 = session.Prompt()
	if err1 != nil {
		// unable to get prompt-string.
		fmt.Fprintf(Console, "%s\n$ ", err1.Error())
		this.TopColumn = 2
	} else if this.TopColumn >= this.TermWidth-3 {
		// ViewWidth is too narrow to edit.
		fmt.Fprint(Console, "\n")
		this.TopColumn = 0
	}
	defer fmt.Fprint(Console, CURSOR_ON)

	this.InsertString(0, session.Default)
	if this.Cursor > this.Length {
		this.Cursor = this.Length
	}
	this.RepaintAfterPrompt()

	if FlushBeforeReadline {
		getch.Flush()
	}

	cursorOnSwitch := false
	for {
		var e getch.Event
		if !cursorOnSwitch {
			fmt.Fprint(Console, CURSOR_ON)
			cursorOnSwitch = true
		}
		Console.Flush()
		for e.Key == nil {
			e = getch.All()
			if e.Resize != nil {
				w := int(e.Resize.Width)
				if this.TermWidth != w {
					this.TermWidth = w
					fmt.Fprintf(Console, "\x1B[%dG", this.TopColumn+1)
					this.RepaintAfterPrompt()
				}
			}
		}
		this.Unicode = e.Key.Rune
		this.Keycode = e.Key.Scan
		this.ShiftState = e.Key.Shift
		var f KeyFuncT
		var ok bool
		if (this.ShiftState&getch.ALT_PRESSED) != 0 &&
			(this.ShiftState&getch.CTRL_PRESSED) == 0 {
			f, ok = altMap[this.Keycode]
			if !ok {
				continue
			}
		} else if this.Unicode != 0 {
			f, ok = keyMap[this.Unicode]
			if !ok {
				//f = KeyFuncInsertReport
				f = &KeyGoFuncT{Func: KeyFuncInsertSelf, Name: fmt.Sprintf("%v", this.Unicode)}
			}
		} else {
			f, ok = scanMap[this.Keycode]
			if !ok {
				f = &KeyGoFuncT{Func: nil, Name: ""}
			}
		}
		if fg, ok := f.(*KeyGoFuncT); !ok || fg.Func != nil {
			fmt.Fprint(Console, CURSOR_OFF)
			cursorOnSwitch = false
		}
		rc := f.Call(ctx, &this)
		if rc != CONTINUE {
			fmt.Fprint(Console, "\n")
			Console.Flush()
			result := this.String()
			if rc == ENTER {
				return result, nil
			} else if rc == INTR {
				return result, CtrlC
			} else {
				return result, io.EOF
			}
		}
	}
}
<commit_msg>Fix: the cursor blink was switched to off on the child process.<commit_after>package readline

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"

	"github.com/zetamatta/go-box"
	"github.com/zetamatta/go-getch"
)

var FlushBeforeReadline = false

type Result int

const (
	CONTINUE Result = iota
	ENTER    Result = iota
	ABORT    Result = iota
	INTR     Result = iota
)

func (this Result) String() string {
	switch this {
	case CONTINUE:
		return "CONTINUE"
	case ENTER:
		return "ENTER"
	case ABORT:
		return "ABORT"
	case INTR:
		return "INTR"
	default:
		return "ERROR"
	}
}

type KeyFuncT interface {
	Call(ctx context.Context, buffer *Buffer) Result
}

type KeyGoFuncT struct {
	Func func(ctx context.Context, buffer *Buffer) Result
	Name string
}

func (this *KeyGoFuncT) Call(ctx context.Context, buffer *Buffer) Result {
	if this.Func == nil {
		return CONTINUE
	}
	return this.Func(ctx, buffer)
}

func (this KeyGoFuncT) String() string {
	return this.Name
}

var keyMap = map[rune]KeyFuncT{
	name2char[K_CTRL_A]: name2func(F_BEGINNING_OF_LINE),
	name2char[K_CTRL_B]: name2func(F_BACKWARD_CHAR),
	name2char[K_CTRL_C]: name2func(F_INTR),
	name2char[K_CTRL_D]: name2func(F_DELETE_OR_ABORT),
	name2char[K_CTRL_E]: name2func(F_END_OF_LINE),
	name2char[K_CTRL_F]: name2func(F_FORWARD_CHAR),
	name2char[K_CTRL_H]: name2func(F_BACKWARD_DELETE_CHAR),
	name2char[K_CTRL_K]: name2func(F_KILL_LINE),
	name2char[K_CTRL_L]: name2func(F_CLEAR_SCREEN),
	name2char[K_CTRL_M]: name2func(F_ACCEPT_LINE),
	name2char[K_CTRL_R]: name2func(F_ISEARCH_BACKWARD),
	name2char[K_CTRL_U]: name2func(F_UNIX_LINE_DISCARD),
	name2char[K_CTRL_Y]: name2func(F_YANK),
	name2char[K_DELETE]: name2func(F_DELETE_CHAR),
	name2char[K_ENTER]:  name2func(F_ACCEPT_LINE),
	name2char[K_ESCAPE]: name2func(F_KILL_WHOLE_LINE),
	name2char[K_CTRL_N]: name2func(F_HISTORY_DOWN),
	name2char[K_CTRL_P]: name2func(F_HISTORY_UP),
	name2char[K_CTRL_Q]: name2func(F_QUOTED_INSERT),
	name2char[K_CTRL_T]: name2func(F_SWAPCHAR),
	name2char[K_CTRL_V]: name2func(F_QUOTED_INSERT),
	name2char[K_CTRL_W]: name2func(F_UNIX_WORD_RUBOUT),
}

var scanMap = map[uint16]KeyFuncT{
	name2scan[K_CTRL]:   name2func(F_PASS),
	name2scan[K_DELETE]: name2func(F_DELETE_CHAR),
	name2scan[K_END]:    name2func(F_END_OF_LINE),
	name2scan[K_HOME]:   name2func(F_BEGINNING_OF_LINE),
	name2scan[K_LEFT]:   name2func(F_BACKWARD_CHAR),
	name2scan[K_RIGHT]:  name2func(F_FORWARD_CHAR),
	name2scan[K_SHIFT]:  name2func(F_PASS),
	name2scan[K_DOWN]:   name2func(F_HISTORY_DOWN),
	name2scan[K_UP]:     name2func(F_HISTORY_UP),
}

var altMap = map[uint16]KeyFuncT{
	name2alt[K_ALT_V]: name2func(F_YANK),
	name2alt[K_ALT_Y]: name2func(F_YANK_WITH_QUOTE),
}

func normWord(src string) string {
	return strings.Replace(strings.ToUpper(src), "-", "_", -1)
}

func BindKeyFunc(keyName string, funcValue KeyFuncT) error {
	keyName_ := normWord(keyName)
	if altValue, altOk := name2alt[keyName_]; altOk {
		altMap[altValue] = funcValue
		return nil
	} else if charValue, charOk := name2char[keyName_]; charOk {
		keyMap[charValue] = funcValue
		return nil
	} else if scanValue, scanOk := name2scan[keyName_]; scanOk {
		scanMap[scanValue] = funcValue
		return nil
	} else {
		return fmt.Errorf("%s: no such keyname", keyName)
	}
}

func BindKeyClosure(name string, f func(context.Context, *Buffer) Result) error {
	return BindKeyFunc(name, &KeyGoFuncT{Func: f, Name: "annonymous"})
}

func GetBindKey(keyName string) KeyFuncT {
	keyName_ := normWord(keyName)
	if altValue, altOk := name2alt[keyName_]; altOk {
		return altMap[altValue]
	} else if charValue, charOk := name2char[keyName_]; charOk {
		return keyMap[charValue]
	} else if scanValue, scanOk := name2scan[keyName_]; scanOk {
		return scanMap[scanValue]
	} else {
		return nil
	}
}

func GetFunc(funcName string) (KeyFuncT, error) {
	rc := name2func(normWord(funcName))
	if rc != nil {
		return rc, nil
	} else {
		return nil, fmt.Errorf("%s: not found in the function-list", funcName)
	}
}

func BindKeySymbol(keyName, funcName string) error {
	funcValue := name2func(normWord(funcName))
	if funcValue == nil {
		return fmt.Errorf("%s: no such function.", funcName)
	}
	return BindKeyFunc(keyName, funcValue)
}

type EmptyHistory struct{}

func (this *EmptyHistory) Len() int      { return 0 }
func (this *EmptyHistory) At(int) string { return "" }

const (
	CURSOR_OFF = "\x1B[?25l"
	CURSOR_ON  = "\x1B[?25h\x1B[s\x1B[u"
)

var CtrlC = errors.New("^C")

// Call LineEditor
// - ENTER typed -> returns TEXT and nil
// - CTRL-C typed -> returns "" and readline.CtrlC
// - CTRL-D typed -> returns "" and io.EOF
func (session *Editor) ReadLine(ctx context.Context) (string, error) {
	if session.Prompt == nil {
		session.Prompt = func() (int, error) {
			fmt.Fprint(Console, "\n> ")
			return 2, nil
		}
	}
	if session.History == nil {
		session.History = new(EmptyHistory)
	}
	this := Buffer{
		Editor:         session,
		Buffer:         make([]rune, 20),
		HistoryPointer: session.History.Len(),
	}
	this.TermWidth, _ = box.GetScreenBufferInfo().ViewSize()

	var err1 error
	this.TopColumn, err1 = session.Prompt()
	if err1 != nil {
		// unable to get prompt-string.
		fmt.Fprintf(Console, "%s\n$ ", err1.Error())
		this.TopColumn = 2
	} else if this.TopColumn >= this.TermWidth-3 {
		// ViewWidth is too narrow to edit.
		fmt.Fprint(Console, "\n")
		this.TopColumn = 0
	}
	defer fmt.Fprint(Console, CURSOR_ON)

	this.InsertString(0, session.Default)
	if this.Cursor > this.Length {
		this.Cursor = this.Length
	}
	this.RepaintAfterPrompt()

	if FlushBeforeReadline {
		getch.Flush()
	}

	cursorOnSwitch := false
	for {
		var e getch.Event
		if !cursorOnSwitch {
			fmt.Fprint(Console, CURSOR_ON)
			cursorOnSwitch = true
		}
		Console.Flush()
		for e.Key == nil {
			e = getch.All()
			if e.Resize != nil {
				w := int(e.Resize.Width)
				if this.TermWidth != w {
					this.TermWidth = w
					fmt.Fprintf(Console, "\x1B[%dG", this.TopColumn+1)
					this.RepaintAfterPrompt()
				}
			}
		}
		this.Unicode = e.Key.Rune
		this.Keycode = e.Key.Scan
		this.ShiftState = e.Key.Shift
		var f KeyFuncT
		var ok bool
		if (this.ShiftState&getch.ALT_PRESSED) != 0 &&
			(this.ShiftState&getch.CTRL_PRESSED) == 0 {
			f, ok = altMap[this.Keycode]
			if !ok {
				continue
			}
		} else if this.Unicode != 0 {
			f, ok = keyMap[this.Unicode]
			if !ok {
				//f = KeyFuncInsertReport
				f = &KeyGoFuncT{Func: KeyFuncInsertSelf, Name: fmt.Sprintf("%v", this.Unicode)}
			}
		} else {
			f, ok = scanMap[this.Keycode]
			if !ok {
				f = &KeyGoFuncT{Func: nil, Name: ""}
			}
		}
		if fg, ok := f.(*KeyGoFuncT); !ok || fg.Func != nil {
			fmt.Fprint(Console, CURSOR_OFF)
			cursorOnSwitch = false
		}
		rc := f.Call(ctx, &this)
		if rc != CONTINUE {
			fmt.Fprint(Console, "\n")
			if !cursorOnSwitch {
				io.WriteString(Console, CURSOR_ON)
			}
			Console.Flush()
			result := this.String()
			if rc == ENTER {
				return result, nil
			} else if rc == INTR {
				return result, CtrlC
			} else {
				return result, io.EOF
			}
		}
	}
}
<|endoftext|>


<commit_before>package receiver

import (
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	log "github.com/Sirupsen/logrus"
	"github.com/relistan/go-director"
	"github.com/mohae/deepcopy"
)

const (
	RELOAD_HOLD_DOWN = 5 * time.Second // Reload at worst every 5 seconds
)

type Receiver struct {
	StateLock      sync.Mutex
	ReloadChan     chan time.Time
	CurrentState   *catalog.ServicesState
	LastSvcChanged *service.Service
	OnUpdate       func(state *catalog.ServicesState)
	Looper         director.Looper
}

func NewReceiver(capacity int, onUpdate func(state *catalog.ServicesState)) *Receiver {
	return &Receiver{
		ReloadChan: make(chan time.Time, capacity),
		OnUpdate:   onUpdate,
		Looper:     director.NewImmediateTimedLooper(director.FOREVER, RELOAD_HOLD_DOWN, make(chan error)),
	}
}

// Check all the state transitions and only update HAproxy when a change
// will affect service availability.
func ShouldNotify(oldStatus int, newStatus int) bool {
	log.Debugf("Checking event. OldStatus: %s NewStatus: %s",
		service.StatusString(oldStatus), service.StatusString(newStatus),
	)

	// Compare old and new states to find significant changes only
	switch newStatus {
	case service.ALIVE:
		return true
	case service.TOMBSTONE:
		return true
	case service.UNKNOWN:
		if oldStatus == service.ALIVE {
			return true
		}
	case service.UNHEALTHY:
		if oldStatus == service.ALIVE {
			return true
		}
	default:
		log.Errorf("Got unknown service change status: %d", newStatus)
		return false
	}

	log.Debugf("Skipped HAproxy update due to state machine check")
	return false
}

// Used to fetch the current state from a Sidecar endpoint, usually
// on startup of this process, when the currentState is empty.
func FetchState(url string) (*catalog.ServicesState, error) {
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}

	return state, nil
}

// ProcessUpdates loops forever, processing updates to the state.
// By the time we get here, the HTTP UpdateHandler has already set the
// CurrentState to the newest state we know about. Here we'll try to group
// updates together to prevent repeatedly updating on a series of events that
// arrive in a row.
func (rcvr *Receiver) ProcessUpdates() {
	rcvr.Looper.Loop(func() error {
		// Batch up to RELOAD_BUFFER number updates into a
		// single update.
		first := <-rcvr.ReloadChan
		pending := len(rcvr.ReloadChan)

		// Call the callback
		if rcvr.OnUpdate == nil {
			log.Error("OnUpdate() callback not defined!")
		} else {
			rcvr.StateLock.Lock()
			// Copy the state while locked so we don't have it change
			// under us while writing and we don't hold onto the lock the
			// whole time we're writing to disk (e.g. in haproxy-api).
			var tmpState *catalog.ServicesState
			tmpState = deepcopy.Copy(rcvr.CurrentState).(*catalog.ServicesState)
			rcvr.StateLock.Unlock()

			rcvr.OnUpdate(tmpState)
		}

		// We just flushed the most recent state, dump all the
		// pending items up to that point.
		var reload time.Time
		for i := 0; i < pending; i++ {
			reload = <-rcvr.ReloadChan
		}

		if pending > 0 {
			log.Infof("Skipped %d messages between %s and %s", pending, first, reload)
		}

		// Don't notify more frequently than every RELOAD_HOLD_DOWN period. When a
		// deployment rolls across the cluster it can trigger a bunch of groupable
		// updates. The Looper handles the sleep after the return nil.
		log.Debug("Holding down...")

		return nil
	})
}

// EnqueueUpdate puts a new timestamp on the update channel, to be processed in a
// goroutine that runs the ProcessUpdates function.
func (rcvr *Receiver) EnqueueUpdate() {
	rcvr.ReloadChan <- time.Now().UTC()
}

// FetchInitialState is used at startup to bootstrap initial state from Sidecar.
func (rcvr *Receiver) FetchInitialState(stateUrl string) error {
	rcvr.StateLock.Lock()
	defer rcvr.StateLock.Unlock()

	log.Info("Fetching initial state on startup...")
	state, err := FetchState(stateUrl)
	if err != nil {
		return err
	} else {
		log.Info("Successfully retrieved state")
		rcvr.CurrentState = state
		if rcvr.OnUpdate == nil {
			log.Error("OnUpdate() callback not defined!")
		} else {
			rcvr.OnUpdate(state)
		}
	}

	return nil
}
<commit_msg>Protect receiver from nil Looper<commit_after>package receiver

import (
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	log "github.com/Sirupsen/logrus"
	"github.com/relistan/go-director"
	"github.com/mohae/deepcopy"
)

const (
	RELOAD_HOLD_DOWN = 5 * time.Second // Reload at worst every 5 seconds
)

type Receiver struct {
	StateLock      sync.Mutex
	ReloadChan     chan time.Time
	CurrentState   *catalog.ServicesState
	LastSvcChanged *service.Service
	OnUpdate       func(state *catalog.ServicesState)
	Looper         director.Looper
}

func NewReceiver(capacity int, onUpdate func(state *catalog.ServicesState)) *Receiver {
	return &Receiver{
		ReloadChan: make(chan time.Time, capacity),
		OnUpdate:   onUpdate,
		Looper:     director.NewImmediateTimedLooper(director.FOREVER, RELOAD_HOLD_DOWN, make(chan error)),
	}
}

// Check all the state transitions and only update HAproxy when a change
// will affect service availability.
func ShouldNotify(oldStatus int, newStatus int) bool {
	log.Debugf("Checking event. OldStatus: %s NewStatus: %s",
		service.StatusString(oldStatus), service.StatusString(newStatus),
	)

	// Compare old and new states to find significant changes only
	switch newStatus {
	case service.ALIVE:
		return true
	case service.TOMBSTONE:
		return true
	case service.UNKNOWN:
		if oldStatus == service.ALIVE {
			return true
		}
	case service.UNHEALTHY:
		if oldStatus == service.ALIVE {
			return true
		}
	default:
		log.Errorf("Got unknown service change status: %d", newStatus)
		return false
	}

	log.Debugf("Skipped HAproxy update due to state machine check")
	return false
}

// Used to fetch the current state from a Sidecar endpoint, usually
// on startup of this process, when the currentState is empty.
func FetchState(url string) (*catalog.ServicesState, error) {
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}

	return state, nil
}

// ProcessUpdates loops forever, processing updates to the state.
// By the time we get here, the HTTP UpdateHandler has already set the
// CurrentState to the newest state we know about. Here we'll try to group
// updates together to prevent repeatedly updating on a series of events that
// arrive in a row.
func (rcvr *Receiver) ProcessUpdates() {
	if rcvr.Looper == nil {
		log.Error("Unable to ProcessUpdates(), Looper is nil in receiver!")
		return
	}

	rcvr.Looper.Loop(func() error {
		// Batch up to RELOAD_BUFFER number updates into a
		// single update.
		first := <-rcvr.ReloadChan
		pending := len(rcvr.ReloadChan)

		// Call the callback
		if rcvr.OnUpdate == nil {
			log.Error("OnUpdate() callback not defined!")
		} else {
			rcvr.StateLock.Lock()
			// Copy the state while locked so we don't have it change
			// under us while writing and we don't hold onto the lock the
			// whole time we're writing to disk (e.g. in haproxy-api).
			var tmpState *catalog.ServicesState
			tmpState = deepcopy.Copy(rcvr.CurrentState).(*catalog.ServicesState)
			rcvr.StateLock.Unlock()

			rcvr.OnUpdate(tmpState)
		}

		// We just flushed the most recent state, dump all the
		// pending items up to that point.
		var reload time.Time
		for i := 0; i < pending; i++ {
			reload = <-rcvr.ReloadChan
		}

		if pending > 0 {
			log.Infof("Skipped %d messages between %s and %s", pending, first, reload)
		}

		// Don't notify more frequently than every RELOAD_HOLD_DOWN period. When a
		// deployment rolls across the cluster it can trigger a bunch of groupable
		// updates. The Looper handles the sleep after the return nil.
		log.Debug("Holding down...")

		return nil
	})
}

// EnqueueUpdate puts a new timestamp on the update channel, to be processed in a
// goroutine that runs the ProcessUpdates function.
func (rcvr *Receiver) EnqueueUpdate() {
	rcvr.ReloadChan <- time.Now().UTC()
}

// FetchInitialState is used at startup to bootstrap initial state from Sidecar.
func (rcvr *Receiver) FetchInitialState(stateUrl string) error {
	rcvr.StateLock.Lock()
	defer rcvr.StateLock.Unlock()

	log.Info("Fetching initial state on startup...")
	state, err := FetchState(stateUrl)
	if err != nil {
		return err
	} else {
		log.Info("Successfully retrieved state")
		rcvr.CurrentState = state
		if rcvr.OnUpdate == nil {
			log.Error("OnUpdate() callback not defined!")
		} else {
			rcvr.OnUpdate(state)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// Build this command explicitly: go build gotype.go

/*
The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).

Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.

With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.

Otherwise, each path must be the filename of a Go file belonging
to the same package.

Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).

The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.

Usage:
	gotype [flags] [path...]

The flags are:
	-t
		include local test files in a directory (ignored if -x is provided)
	-x
		consider only external test files in a directory
	-e
		report all errors (not just the first 10)
	-v
		verbose mode
	-c
		compiler used for installed packages (gc, gccgo, or source); default: source

Flags controlling additional output:
	-ast
		print AST (forces -seq)
	-trace
		print parse trace (forces -seq)
	-comments
		parse comments (ignored unless -ast or -trace is provided)
	-panic
		panic on first error

Examples:

To check the files a.go, b.go, and c.go:

	gotype a.go b.go c.go

To check an entire package including (in-package) tests in the directory dir and print the processed files:

	gotype -t -v dir

To check the external test package (if any) in the current directory, based on installed packages compiled with
cmd/compile:

	gotype -c=gc -x .

To verify the output of a pipe:

	echo "package foo" | gotype

*/
package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/scanner"
	"go/token"
	"go/types"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

var (
	// main operation modes
	testFiles  = flag.Bool("t", false, "include in-package test files in a directory")
	xtestFiles = flag.Bool("x", false, "consider only external test files in a directory")
	allErrors  = flag.Bool("e", false, "report all errors, not just the first 10")
	verbose    = flag.Bool("v", false, "verbose mode")
	compiler   = flag.String("c", "source", "compiler used for installed packages (gc, gccgo, or source)")

	// additional output control
	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
	panicOnError  = flag.Bool("panic", false, "panic on first error")
)

var (
	fset       = token.NewFileSet()
	errorCount = 0
	sequential = false
	parserMode parser.Mode
)

func initParserMode() {
	if *allErrors {
		parserMode |= parser.AllErrors
	}
	if *printAST {
		sequential = true
	}
	if *printTrace {
		parserMode |= parser.Trace
		sequential = true
	}
	if *parseComments && (*printAST || *printTrace) {
		parserMode |= parser.ParseComments
	}
}

const usageString = `usage: gotype [flags] [path ...]

The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).

Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.

With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.

Otherwise, each path must be the filename of a Go file belonging
to the same package.

Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).

The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
`

func usage() {
	fmt.Fprintln(os.Stderr, usageString)
	flag.PrintDefaults()
	os.Exit(2)
}

func report(err error) {
	if *panicOnError {
		panic(err)
	}
	scanner.PrintError(os.Stderr, err)
	if list, ok := err.(scanner.ErrorList); ok {
		errorCount += len(list)
		return
	}
	errorCount++
}

// parse may be called concurrently
func parse(filename string, src interface{}) (*ast.File, error) {
	if *verbose {
		fmt.Println(filename)
	}
	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
	if *printAST {
		ast.Print(fset, file)
	}
	return file, err
}

func parseStdin() (*ast.File, error) {
	src, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return nil, err
	}
	return parse("<standard input>", src)
}

func parseFiles(dir string, filenames []string) ([]*ast.File, error) {
	files := make([]*ast.File, len(filenames))
	errors := make([]error, len(filenames))

	var wg sync.WaitGroup
	for i, filename := range filenames {
		wg.Add(1)
		go func(i int, filepath string) {
			defer wg.Done()
			files[i], errors[i] = parse(filepath, nil)
		}(i, filepath.Join(dir, filename))
		if sequential {
			wg.Wait()
		}
	}
	wg.Wait()

	// If there are errors, return the first one for deterministic results.
	var first error
	for _, err := range errors {
		if err != nil {
			first = err
			// If we have an error, some files may be nil.
			// Remove them. (The go/parser always returns
			// a possibly partial AST even in the presence
			// of errors, except if the file doesn't exist
			// in the first place, in which case it cannot
			// matter.)
			i := 0
			for _, f := range files {
				if f != nil {
					files[i] = f
					i++
				}
			}
			files = files[:i]
			break
		}
	}

	return files, first
}

func parseDir(dir string) ([]*ast.File, error) {
	ctxt := build.Default
	pkginfo, err := ctxt.ImportDir(dir, 0)
	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
		return nil, err
	}

	if *xtestFiles {
		return parseFiles(dir, pkginfo.XTestGoFiles)
	}

	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
	if *testFiles {
		filenames = append(filenames, pkginfo.TestGoFiles...)
	}
	return parseFiles(dir, filenames)
}

func getPkgFiles(args []string) ([]*ast.File, error) {
	if len(args) == 0 {
		// stdin
		file, err := parseStdin()
		if err != nil {
			return nil, err
		}
		return []*ast.File{file}, nil
	}

	if len(args) == 1 {
		// possibly a directory
		path := args[0]
		info, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if info.IsDir() {
			return parseDir(path)
		}
	}

	// list of files
	return parseFiles("", args)
}

func checkPkgFiles(files []*ast.File) {
	type bailout struct{}

	// if checkPkgFiles is called multiple times, set up conf only once
	conf := types.Config{
		FakeImportC: true,
		Error: func(err error) {
			if !*allErrors && errorCount >= 10 {
				panic(bailout{})
			}
			report(err)
		},
		Importer: importer.For(*compiler, nil),
		Sizes:    types.SizesFor(build.Default.Compiler, build.Default.GOARCH),
	}

	defer func() {
		switch p := recover().(type) {
		case nil, bailout:
			// normal return or early exit
		default:
			// re-panic
			panic(p)
		}
	}()

	const path = "pkg" // any non-empty string will do for now
	conf.Check(path, fset, files, nil)
}

func printStats(d time.Duration) {
	fileCount := 0
	lineCount := 0
	fset.Iterate(func(f *token.File) bool {
		fileCount++
		lineCount += f.LineCount()
		return true
	})

	fmt.Printf(
		"%s (%d files, %d lines, %d lines/s)\n",
		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
	)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	initParserMode()

	start := time.Now()

	files, err := getPkgFiles(flag.Args())
	if err != nil {
		report(err)
		// ok to continue (files may be empty, but not nil)
	}

	checkPkgFiles(files)
	if errorCount > 0 {
		os.Exit(2)
	}

	if *verbose {
		printStats(time.Since(start))
	}
}
<commit_msg>go/types: use new importer.ForCompiler for gotype command<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ignore

// Build this command explicitly: go build gotype.go

/*
The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).

Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.

With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.

Otherwise, each path must be the filename of a Go file belonging
to the same package.

Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).

The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.

Usage:
	gotype [flags] [path...]

The flags are:
	-t
		include local test files in a directory (ignored if -x is provided)
	-x
		consider only external test files in a directory
	-e
		report all errors (not just the first 10)
	-v
		verbose mode
	-c
		compiler used for installed packages (gc, gccgo, or source); default: source

Flags controlling additional output:
	-ast
		print AST (forces -seq)
	-trace
		print parse trace (forces -seq)
	-comments
		parse comments (ignored unless -ast or -trace is provided)
	-panic
		panic on first error

Examples:

To check the files a.go, b.go, and c.go:

	gotype a.go b.go c.go

To check an entire package including (in-package) tests in the directory dir and print the processed files:

	gotype -t -v dir

To check the external test package (if any) in the current directory, based on installed packages compiled with
cmd/compile:

	gotype -c=gc -x .

To verify the output of a pipe:

	echo "package foo" | gotype

*/
package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/scanner"
	"go/token"
	"go/types"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

var (
	// main operation modes
	testFiles  = flag.Bool("t", false, "include in-package test files in a directory")
	xtestFiles = flag.Bool("x", false, "consider only external test files in a directory")
	allErrors  = flag.Bool("e", false, "report all errors, not just the first 10")
	verbose    = flag.Bool("v", false, "verbose mode")
	compiler   = flag.String("c", "source", "compiler used for installed packages (gc, gccgo, or source)")

	// additional output control
	printAST      = flag.Bool("ast", false, "print AST (forces -seq)")
	printTrace    = flag.Bool("trace", false, "print parse trace (forces -seq)")
	parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
	panicOnError  = flag.Bool("panic", false, "panic on first error")
)

var (
	fset       = token.NewFileSet()
	errorCount = 0
	sequential = false
	parserMode parser.Mode
)

func initParserMode() {
	if *allErrors {
		parserMode |= parser.AllErrors
	}
	if *printAST {
		sequential = true
	}
	if *printTrace {
		parserMode |= parser.Trace
		sequential = true
	}
	if *parseComments && (*printAST || *printTrace) {
		parserMode |= parser.ParseComments
	}
}

const usageString = `usage: gotype [flags] [path ...]

The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).

Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.

With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.

Otherwise, each path must be the filename of a Go file belonging
to the same package.

Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).

The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
`

func usage() {
	fmt.Fprintln(os.Stderr, usageString)
	flag.PrintDefaults()
	os.Exit(2)
}

func report(err error) {
	if *panicOnError {
		panic(err)
	}
	scanner.PrintError(os.Stderr, err)
	if list, ok := err.(scanner.ErrorList); ok {
		errorCount += len(list)
		return
	}
	errorCount++
}

// parse may be called concurrently
func parse(filename string, src interface{}) (*ast.File, error) {
	if *verbose {
		fmt.Println(filename)
	}
	file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
	if *printAST {
		ast.Print(fset, file)
	}
	return file, err
}

func parseStdin() (*ast.File, error) {
	src, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		return nil, err
	}
	return parse("<standard input>", src)
}

func parseFiles(dir string, filenames []string) ([]*ast.File, error) {
	files := make([]*ast.File, len(filenames))
	errors := make([]error, len(filenames))

	var wg sync.WaitGroup
	for i, filename := range filenames {
		wg.Add(1)
		go func(i int, filepath string) {
			defer wg.Done()
			files[i], errors[i] = parse(filepath, nil)
		}(i, filepath.Join(dir, filename))
		if sequential {
			wg.Wait()
		}
	}
	wg.Wait()

	// If there are errors, return the first one for deterministic results.
	var first error
	for _, err := range errors {
		if err != nil {
			first = err
			// If we have an error, some files may be nil.
			// Remove them. (The go/parser always returns
			// a possibly partial AST even in the presence
			// of errors, except if the file doesn't exist
			// in the first place, in which case it cannot
			// matter.)
			i := 0
			for _, f := range files {
				if f != nil {
					files[i] = f
					i++
				}
			}
			files = files[:i]
			break
		}
	}

	return files, first
}

func parseDir(dir string) ([]*ast.File, error) {
	ctxt := build.Default
	pkginfo, err := ctxt.ImportDir(dir, 0)
	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
		return nil, err
	}

	if *xtestFiles {
		return parseFiles(dir, pkginfo.XTestGoFiles)
	}

	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
	if *testFiles {
		filenames = append(filenames, pkginfo.TestGoFiles...)
	}
	return parseFiles(dir, filenames)
}

func getPkgFiles(args []string) ([]*ast.File, error) {
	if len(args) == 0 {
		// stdin
		file, err := parseStdin()
		if err != nil {
			return nil, err
		}
		return []*ast.File{file}, nil
	}

	if len(args) == 1 {
		// possibly a directory
		path := args[0]
		info, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if info.IsDir() {
			return parseDir(path)
		}
	}

	// list of files
	return parseFiles("", args)
}

func checkPkgFiles(files []*ast.File) {
	type bailout struct{}

	// if checkPkgFiles is called multiple times, set up conf only once
	conf := types.Config{
		FakeImportC: true,
		Error: func(err error) {
			if !*allErrors && errorCount >= 10 {
				panic(bailout{})
			}
			report(err)
		},
		Importer: importer.ForCompiler(fset, *compiler, nil),
		Sizes:    types.SizesFor(build.Default.Compiler, build.Default.GOARCH),
	}

	defer func() {
		switch p := recover().(type) {
		case nil, bailout:
			// normal return or early exit
		default:
			// re-panic
			panic(p)
		}
	}()

	const path = "pkg" // any non-empty string will do for now
	conf.Check(path, fset, files, nil)
}

func printStats(d time.Duration) {
	fileCount := 0
	lineCount := 0
	fset.Iterate(func(f *token.File) bool {
		fileCount++
		lineCount += f.LineCount()
		return true
	})

	fmt.Printf(
		"%s (%d files, %d lines, %d lines/s)\n",
		d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
	)
}

func main() {
	flag.Usage = usage
	flag.Parse()
	initParserMode()

	start := time.Now()

	files, err := getPkgFiles(flag.Args())
	if err != nil {
		report(err)
		// ok to continue (files may be empty, but not nil)
	}

	checkPkgFiles(files)
	if errorCount > 0 {
		os.Exit(2)
	}

	if *verbose {
		printStats(time.Since(start))
	}
}
<|endoftext|>


<commit_before>// Package registry contains client primitives to interact with a remote Docker registry.
package registry // import "github.com/docker/docker/registry"

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/distribution/registry/client/transport"
	"github.com/docker/docker/pkg/homedir"
	"github.com/docker/docker/rootless"
	"github.com/docker/go-connections/tlsconfig"
	"github.com/sirupsen/logrus"
)

var (
	// ErrAlreadyExists is an error returned if an image being pushed
	// already exists on the remote side
	ErrAlreadyExists = errors.New("Image already exists")
)

func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
	// PreferredServerCipherSuites should have no effect
	tlsConfig := tlsconfig.ServerDefault()

	tlsConfig.InsecureSkipVerify = !isSecure

	if isSecure && CertsDir != "" {
		certsDir := CertsDir

		if rootless.RunningWithRootlessKit() {
			configHome, err := homedir.GetConfigHome()
			if err != nil {
				return nil, err
			}

			certsDir = filepath.Join(configHome, "docker/certs.d")
		}

		hostDir := filepath.Join(certsDir, cleanPath(hostname))

		logrus.Debugf("hostDir: %s", hostDir)
		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
			return nil, err
		}
	}

	return tlsConfig, nil
}

func hasFile(files []os.FileInfo, name string) bool {
	for _, f := range files {
		if f.Name() == name {
			return true
		}
	}
	return false
}

// ReadCertsDirectory reads the directory for TLS certificates
// including roots and certificate pairs and updates the
// provided TLS configuration.
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
	fs, err := ioutil.ReadDir(directory)
	if err != nil && !os.IsNotExist(err) && !os.IsPermission(err) {
		return err
	}

	for _, f := range fs {
		if strings.HasSuffix(f.Name(), ".crt") {
			if tlsConfig.RootCAs == nil {
				systemPool, err := tlsconfig.SystemCertPool()
				if err != nil {
					return fmt.Errorf("unable to get system cert pool: %v", err)
				}
				tlsConfig.RootCAs = systemPool
			}
			logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
			data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
			if err != nil {
				return err
			}
			tlsConfig.RootCAs.AppendCertsFromPEM(data)
		}
		if strings.HasSuffix(f.Name(), ".cert") {
			certName := f.Name()
			keyName := certName[:len(certName)-5] + ".key"
			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
			if !hasFile(fs, keyName) {
				return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
			}
			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
			if err != nil {
				return err
			}
			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
		}
		if strings.HasSuffix(f.Name(), ".key") {
			keyName := f.Name()
			certName := keyName[:len(keyName)-4] + ".cert"
			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
			if !hasFile(fs, certName) {
				return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
			}
		}
	}

	return nil
}

// Headers returns request modifiers with a User-Agent and metaHeaders
func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
	modifiers := []transport.RequestModifier{}
	if userAgent != "" {
		modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
			"User-Agent": []string{userAgent},
		}))
	}
	if metaHeaders != nil {
		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
	}
	return modifiers
}

// HTTPClient returns an HTTP client structure which uses the given transport
// and contains the necessary headers for redirected requests
func HTTPClient(transport http.RoundTripper) *http.Client {
	return &http.Client{
		Transport:     transport,
		CheckRedirect: addRequiredHeadersToRedirectedRequests,
	}
}

func trustedLocation(req *http.Request) bool {
	var (
		trusteds = []string{"docker.com", "docker.io"}
		hostname = strings.SplitN(req.Host, ":", 2)[0]
	)
	if req.URL.Scheme != "https" {
		return false
	}

	for _, trusted := range trusteds {
		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
			return true
		}
	}
	return false
}

// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
// for redirected requests
func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
	if len(via) != 0 && via[0] != nil {
		if trustedLocation(req) && trustedLocation(via[0]) {
			req.Header = via[0].Header
			return nil
		}
		for k, v := range via[0].Header {
			if k != "Authorization" {
				for _, vv := range v {
					req.Header.Add(k, vv)
				}
			}
		}
	}
	return nil
}

// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
// default TLS configuration.
func NewTransport(tlsConfig *tls.Config) *http.Transport {
	if tlsConfig == nil {
		tlsConfig = tlsconfig.ServerDefault()
	}

	direct := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}

	base := &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		DialContext:         direct.DialContext,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     tlsConfig,
		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
		DisableKeepAlives: true,
	}

	return base
}
<commit_msg>Revert "Don't die when daemon cannot read certs.d"<commit_after>// Package registry contains client primitives to interact with a remote Docker registry.
package registry // import "github.com/docker/docker/registry"

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/distribution/registry/client/transport"
	"github.com/docker/docker/pkg/homedir"
	"github.com/docker/docker/rootless"
	"github.com/docker/go-connections/tlsconfig"
	"github.com/sirupsen/logrus"
)

var (
	// ErrAlreadyExists is an error returned if an image being pushed
	// already exists on the remote side
	ErrAlreadyExists = errors.New("Image already exists")
)

func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
	// PreferredServerCipherSuites should have no effect
	tlsConfig := tlsconfig.ServerDefault()

	tlsConfig.InsecureSkipVerify = !isSecure

	if isSecure && CertsDir != "" {
		certsDir := CertsDir

		if rootless.RunningWithRootlessKit() {
			configHome, err := homedir.GetConfigHome()
			if err != nil {
				return nil, err
			}

			certsDir = filepath.Join(configHome, "docker/certs.d")
		}

		hostDir := filepath.Join(certsDir, cleanPath(hostname))

		logrus.Debugf("hostDir: %s", hostDir)
		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
			return nil, err
		}
	}

	return tlsConfig, nil
}

func hasFile(files []os.FileInfo, name string) bool {
	for _, f := range files {
		if f.Name() == name {
			return true
		}
	}
	return false
}

// ReadCertsDirectory reads the directory for TLS certificates
// including roots and certificate pairs and updates the
// provided TLS configuration.
func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
	fs, err := ioutil.ReadDir(directory)
	if err != nil && !os.IsNotExist(err) {
		return err
	}

	for _, f := range fs {
		if strings.HasSuffix(f.Name(), ".crt") {
			if tlsConfig.RootCAs == nil {
				systemPool, err := tlsconfig.SystemCertPool()
				if err != nil {
					return fmt.Errorf("unable to get system cert pool: %v", err)
				}
				tlsConfig.RootCAs = systemPool
			}
			logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
			data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
			if err != nil {
				return err
			}
			tlsConfig.RootCAs.AppendCertsFromPEM(data)
		}
		if strings.HasSuffix(f.Name(), ".cert") {
			certName := f.Name()
			keyName := certName[:len(certName)-5] + ".key"
			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
			if !hasFile(fs, keyName) {
				return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
			}
			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
			if err != nil {
				return err
			}
			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
		}
		if strings.HasSuffix(f.Name(), ".key") {
			keyName := f.Name()
			certName := keyName[:len(keyName)-4] + ".cert"
			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
			if !hasFile(fs, certName) {
				return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
			}
		}
	}

	return nil
}

// Headers returns request modifiers with a User-Agent and metaHeaders
func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
	modifiers := []transport.RequestModifier{}
	if userAgent != "" {
		modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
			"User-Agent": []string{userAgent},
		}))
	}
	if metaHeaders != nil {
		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
	}
	return modifiers
}

// HTTPClient returns an HTTP client structure which uses the given transport
// and contains the necessary headers for redirected requests
func HTTPClient(transport http.RoundTripper) *http.Client {
	return &http.Client{
		Transport:     transport,
		CheckRedirect: addRequiredHeadersToRedirectedRequests,
	}
}

func trustedLocation(req *http.Request) bool {
	var (
		trusteds = []string{"docker.com", "docker.io"}
		hostname = strings.SplitN(req.Host, ":", 2)[0]
	)
	if req.URL.Scheme != "https" {
		return false
	}

	for _, trusted := range trusteds {
		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
			return true
		}
	}
	return false
}

// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
// for redirected requests
func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
	if len(via) != 0 && via[0] != nil {
		if trustedLocation(req) && trustedLocation(via[0]) {
			req.Header = via[0].Header
			return nil
		}
		for k, v := range via[0].Header {
			if k != "Authorization" {
				for _, vv := range v {
					req.Header.Add(k, vv)
				}
			}
		}
	}
	return nil
}

// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
// default TLS configuration.
func NewTransport(tlsConfig *tls.Config) *http.Transport {
	if tlsConfig == nil {
		tlsConfig = tlsconfig.ServerDefault()
	}

	direct := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}

	base := &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		DialContext:         direct.DialContext,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     tlsConfig,
		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
		DisableKeepAlives: true,
	}

	return base
}
<|endoftext|>