<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 utils

import (
	"os"
	"strings"

	"github.com/gogo/protobuf/proto"

	"fmt"

	"github.com/ligato/cn-infra/db/keyval"
	"github.com/ligato/cn-infra/db/keyval/etcd"
	"github.com/ligato/cn-infra/db/keyval/kvproto"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/cn-infra/servicelabel"
)

const (
	ACLPath          = "config/vpp/acls/v2/acl/"
	InterfacePath    = "config/vpp/v2/interfaces/"
	BridgeDomainPath = "config/vpp/l2/v2/bridge-domain/"
	FibTablePath     = "config/vpp/l2/v2/fib/"
	XConnectPath     = "config/vpp/l2/v2/xconnect/"
	ARPPath          = "config/vpp/v2/arp/"
	RoutePath        = "config/vpp/v2/route/"
	ProxyARPPath     = "config/vpp/v2/proxyarp-global"
	IPScanneightPath = "config/vpp/v2/ipscanneigh-global"
	NATPath          = "config/vpp/nat/v2/nat44-global"
	DNATPath         = "config/vpp/nat/v2/dnat44/"
	IPSecPolicyPath  = "config/vpp/ipsec/v2/spd/"
	IPSecAssociate   = "config/vpp/ipsec/v2/sa/"
)

const (
	lInterfacePath = "config/linux/interfaces/v2/interface/"
	lARPPath       = "config/linux/l4/v2/arp/"
	lRoutePath     = "config/linux/l3/v2/route/"
)

// Common exit flags
const (
	ExitSuccess = iota
	ExitError
	ExitBadConnection
	ExitInvalidInput
	ExitBadFeature
	ExitInterrupted
	ExitIO
	ExitBadArgs = 128
)

// ParseKey parses the etcd Key for the microservice label and the
// data type encoded in the Key. The function returns the microservice
// label, the data type and the list of parameters, that contains path
// segments that follow the data path segment in the Key URL. The
// parameter list is empty if data path is the Last segment in the
// Key.
//

func ParseKey(key string) (label string, dataType string, name string) {
	ps := strings.Split(strings.TrimPrefix(key, servicelabel.GetAllAgentsPrefix()), "/")
	var plugin, statusConfig, version string
	var params []string
	if len(ps) > 0 {
		label = ps[0]
	}
	if len(ps) > 1 {
		plugin = ps[1]
		dataType = plugin
	}
	if len(ps) > 2 {
		statusConfig = ps[2]
		dataType += "/" + statusConfig

		if "vpp" == ps[2] {
			if len(ps) > 3 {
				version = ps[3]
				dataType += "/" + version
			}

			// Recognize key type
			if "v2" == ps[3] {
				if len(ps) > 4 {
					tp := ps[4]
					dataType += "/" + tp
				}

				if len(ps) > 5 {
					dataType += "/"
					params = ps[5:]
				} else {
					params = []string{}
				}
			} else {
				if len(ps) > 4 {
					version := ps[4]
					dataType += "/" + version
				}

				if len(ps) > 5 {
					tp := ps[5]
					dataType += "/" + tp
				}

				if len(ps) > 6 {
					dataType += "/"
					params = ps[6:]
				} else {
					params = []string{}
				}
			}
		} else if "linux" == ps[2] {
			if len(ps) > 3 {
				version = ps[3]
				dataType += "/" + version
			}

			if len(ps) > 4 {
				version := ps[4]
				dataType += "/" + version
			}

			if len(ps) > 5 {
				tp := ps[5]
				dataType += "/" + tp
			}

			if len(ps) > 6 {
				dataType += "/"
				params = ps[6:]
			} else {
				params = []string{}
			}
		} else {
			params = []string{}
		}
	} else {
		params = []string{}
	}

	return label, dataType, rebuildName(params)
}

// Reconstruct item name in case it contains slashes.
func rebuildName(params []string) string {
	var itemName string
	if len(params) > 1 {
		for _, param := range params {
			itemName = itemName + "/" + param
		}
		// Remove the first slash.
		return itemName[1:]
	} else if len(params) == 1 {
		itemName = params[0]
		return itemName
	}
	return itemName
}

// GetDbForAllAgents opens a connection to etcd, specified in the command line
// or the "ETCD_ENDPOINTS" environment variable.
func GetDbForAllAgents(endpoints []string) (keyval.ProtoBroker, error) {
	if len(endpoints) > 0 {
		ep := strings.Join(endpoints, ",")
		os.Setenv("ETCD_ENDPOINTS", ep)
	}

	cfg := &etcd.Config{}
	etcdConfig, err := etcd.ConfigToClient(cfg)

	// Log warnings and errors only.
	log := logrus.DefaultLogger()
	log.SetLevel(logging.WarnLevel)
	etcdBroker, err := etcd.NewEtcdConnectionWithBytes(*etcdConfig, log)
	if err != nil {
		return nil, err
	}

	return kvproto.NewProtoWrapperWithSerializer(etcdBroker, &keyval.SerializerJSON{}), nil

}

func GetModuleName(module proto.Message) string {
	str := proto.MessageName(module)

	tmp := strings.Split(str, ".")

	outstr := tmp[len(tmp)-1]

	if "linux" == tmp[0] {
		outstr = "Linux" + outstr
	}

	return outstr
}

// ExitWithError is used by all commands to print out an error
// and exit.
func ExitWithError(code int, err error) {
	fmt.Fprintln(os.Stderr, "Error: ", err)
	os.Exit(code)
}
<commit_msg>Remove not used definition<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 utils

import (
	"os"
	"strings"

	"github.com/gogo/protobuf/proto"

	"fmt"

	"github.com/ligato/cn-infra/db/keyval"
	"github.com/ligato/cn-infra/db/keyval/etcd"
	"github.com/ligato/cn-infra/db/keyval/kvproto"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/cn-infra/servicelabel"
)

// Common exit flags
const (
	ExitSuccess = iota
	ExitError
	ExitBadConnection
	ExitInvalidInput
	ExitBadFeature
	ExitInterrupted
	ExitIO
	ExitBadArgs = 128
)

// ParseKey parses the etcd Key for the microservice label and the
// data type encoded in the Key. The function returns the microservice
// label, the data type and the list of parameters, that contains path
// segments that follow the data path segment in the Key URL. The
// parameter list is empty if data path is the Last segment in the
// Key.
//

func ParseKey(key string) (label string, dataType string, name string) {
	ps := strings.Split(strings.TrimPrefix(key, servicelabel.GetAllAgentsPrefix()), "/")
	var plugin, statusConfig, version string
	var params []string
	if len(ps) > 0 {
		label = ps[0]
	}
	if len(ps) > 1 {
		plugin = ps[1]
		dataType = plugin
	}
	if len(ps) > 2 {
		statusConfig = ps[2]
		dataType += "/" + statusConfig

		if "vpp" == ps[2] {
			if len(ps) > 3 {
				version = ps[3]
				dataType += "/" + version
			}

			// Recognize key type
			if "v2" == ps[3] {
				if len(ps) > 4 {
					tp := ps[4]
					dataType += "/" + tp
				}

				if len(ps) > 5 {
					dataType += "/"
					params = ps[5:]
				} else {
					params = []string{}
				}
			} else {
				if len(ps) > 4 {
					version := ps[4]
					dataType += "/" + version
				}

				if len(ps) > 5 {
					tp := ps[5]
					dataType += "/" + tp
				}

				if len(ps) > 6 {
					dataType += "/"
					params = ps[6:]
				} else {
					params = []string{}
				}
			}
		} else if "linux" == ps[2] {
			if len(ps) > 3 {
				version = ps[3]
				dataType += "/" + version
			}

			if len(ps) > 4 {
				version := ps[4]
				dataType += "/" + version
			}

			if len(ps) > 5 {
				tp := ps[5]
				dataType += "/" + tp
			}

			if len(ps) > 6 {
				dataType += "/"
				params = ps[6:]
			} else {
				params = []string{}
			}
		} else {
			params = []string{}
		}
	} else {
		params = []string{}
	}

	return label, dataType, rebuildName(params)
}

// Reconstruct item name in case it contains slashes.
func rebuildName(params []string) string {
	var itemName string
	if len(params) > 1 {
		for _, param := range params {
			itemName = itemName + "/" + param
		}
		// Remove the first slash.
		return itemName[1:]
	} else if len(params) == 1 {
		itemName = params[0]
		return itemName
	}
	return itemName
}

// GetDbForAllAgents opens a connection to etcd, specified in the command line
// or the "ETCD_ENDPOINTS" environment variable.
func GetDbForAllAgents(endpoints []string) (keyval.ProtoBroker, error) {
	if len(endpoints) > 0 {
		ep := strings.Join(endpoints, ",")
		os.Setenv("ETCD_ENDPOINTS", ep)
	}

	cfg := &etcd.Config{}
	etcdConfig, err := etcd.ConfigToClient(cfg)

	// Log warnings and errors only.
	log := logrus.DefaultLogger()
	log.SetLevel(logging.WarnLevel)
	etcdBroker, err := etcd.NewEtcdConnectionWithBytes(*etcdConfig, log)
	if err != nil {
		return nil, err
	}

	return kvproto.NewProtoWrapperWithSerializer(etcdBroker, &keyval.SerializerJSON{}), nil

}

func GetModuleName(module proto.Message) string {
	str := proto.MessageName(module)

	tmp := strings.Split(str, ".")

	outstr := tmp[len(tmp)-1]

	if "linux" == tmp[0] {
		outstr = "Linux" + outstr
	}

	return outstr
}

// ExitWithError is used by all commands to print out an error
// and exit.
func ExitWithError(code int, err error) {
	fmt.Fprintln(os.Stderr, "Error: ", err)
	os.Exit(code)
}
<|endoftext|>


<commit_before>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"bytes"
	"code.google.com/p/goprotobuf/proto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"errors"
	"github.com/tonnerre/blubberstore"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"time"
)

// Special "writer" which just counts the number of bytes passed through.
type CountingWriter struct {
	n uint64
}

// Add the length of p to the counter.
func (self *CountingWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	self.n += uint64(n)
	return
}

// Return the full number of bytes passed through Write().
func (self *CountingWriter) BytesWritten() uint64 {
	return self.n
}

type blubberStore struct {
	bindHostPort    string
	blobPath        string
	directoryClient *blubberstore.BlubberDirectoryClient
	insecure        bool
	priv            *rsa.PrivateKey
	tlsConfig       *tls.Config
}

// Construct the file name prefix for the blob with the given blob ID.
// Returns the full name prefix and the parent directory.
func (self *blubberStore) blobId2FileName(blobId []byte) (
	string, string) {
	var parent_dir, first_dir, second_dir string
	var path_name string = hex.EncodeToString(blobId)

	// Extract the first and second directory part piece.
	if len(blobId) > 0 {
		first_dir = strconv.FormatUint(uint64(blobId[0]), 16)
	} else {
		path_name = "zz"
		first_dir = "00"
	}
	if len(blobId) > 1 {
		second_dir = first_dir + strconv.FormatUint(uint64(blobId[1]), 16)
	} else {
		second_dir = first_dir + "00"
	}

	parent_dir = self.blobPath + "/" + first_dir + "/" + second_dir
	return parent_dir + "/" + path_name, parent_dir
}

// Create a new blob with the given blobId, or overwrite an existing one.
// The contents of the blob will be read from input.
func (self *blubberStore) StoreBlob(blobId []byte, input io.Reader,
	overwrite bool) error {
	var outstream cipher.StreamWriter
	var bh blubberstore.BlubberBlockHeader
	var report blubberstore.BlockReport
	var aescipher cipher.Block
	var cksum hash.Hash = sha256.New()
	var parent_dir, file_prefix string
	var counter CountingWriter
	var outfile *os.File
	var buf []byte
	var flag int
	var n int
	var err error

	// Create a block key and IV for the blob data.
	bh.BlockKey = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.BlockKey)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	bh.Iv = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.Iv)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	file_prefix, parent_dir = self.blobId2FileName(blobId)

	// Ensure we have the full directory path in place.
	err = os.MkdirAll(parent_dir, 0700)
	if err != nil {
		return err
	}

	// Now, write the actual data.
	flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
	if !overwrite {
		flag |= os.O_EXCL
	}
	outfile, err = os.OpenFile(file_prefix+".data", flag, 0600)
	if err != nil {
		return err
	}
	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		return err
	}
	outstream = cipher.StreamWriter{
		S: cipher.NewCTR(aescipher, bh.Iv),
		W: outfile,
	}
	_, err = io.Copy(io.MultiWriter(outstream, cksum, &counter), input)
	if err != nil {
		return err
	}
	err = outstream.Close()
	if err != nil {
		return err
	}

	// Fill in the last bits of the blob header.
	bh.Checksum = cksum.Sum(bh.Checksum)
	bh.Size = proto.Uint64(counter.BytesWritten())
	bh.Timestamp = proto.Uint64(uint64(time.Now().Unix()))
	buf, err = proto.Marshal(&bh)
	if err != nil {
		return err
	}
	if !self.insecure {
		// Encrypt the AES key and IV with the RSA key.
		buf, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, buf)
		if err != nil {
			return err
		}
	}

	// Write out the crypto head with the IV and the blob key.
	outfile, err = os.Create(file_prefix + ".crypthead")
	if err != nil {
		return err
	}
	n, err = outfile.Write(buf)
	if err != nil {
		return err
	}
	if n < len(buf) {
		return errors.New("Short write to file")
	}

	err = outfile.Close()
	if err != nil {
		return err
	}

	// If we don't run under a blob directory, this is the end of it.
	if self.directoryClient == nil {
		return nil
	}

	// Now, report the new blob to the directory service.
	report.Server = append(report.Server, self.bindHostPort)
	report.Status = new(blubberstore.BlubberStat)
	report.Status.BlockId = make([]byte, len(blobId))
	report.Status.Checksum = make([]byte, len(bh.Checksum))
	report.Status.Size = bh.Size
	report.Status.Timestamp = bh.Timestamp

	copy(report.Status.BlockId, blobId)
	copy(report.Status.Checksum, bh.Checksum)

	// Since people need to be able to do e.g. quorum writes, we need to
	// wait here synchronously for the directory service.
	return self.directoryClient.ReportBlob(report)
}

// Extract the blubber block head for the given blob ID and return it.
func (self *blubberStore) extractBlockHead(blobId []byte) (
	bh *blubberstore.BlubberBlockHeader, err error) {
	var file_prefix string
	var data []byte

	file_prefix, _ = self.blobId2FileName(blobId)
	data, err = ioutil.ReadFile(file_prefix + ".crypthead")

	if !self.insecure {
		// Decrypt the AES key and IV with the RSA key.
		data, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, data)
		if err != nil {
			return
		}
	}

	bh = new(blubberstore.BlubberBlockHeader)
	err = proto.Unmarshal(data, bh)
	return
}

// Read the blob with the specified blob ID and return it to the caller.
// The contents will be sent as a regular HTTP response.
func (self *blubberStore) RetrieveBlob(blobId []byte, rw io.Writer) error {
	var file_prefix string
	var infile *os.File
	var bh *blubberstore.BlubberBlockHeader
	var instream cipher.StreamReader
	var aescipher cipher.Block
	var err error

	// Get the metadata.
	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return err
	}

	file_prefix, _ = self.blobId2FileName(blobId)
	infile, err = os.Open(file_prefix + ".data")
	if err != nil {
		return err
	}

	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		infile.Close()
		return err
	}
	instream = cipher.StreamReader{
		S: cipher.NewCTR(aescipher, bh.Iv),
		R: infile,
	}

	_, err = io.Copy(rw, instream)
	if err != nil {
		infile.Close()
		return err
	}

	return infile.Close()
}

// Delete the blob with the given blob ID.
func (self *blubberStore) DeleteBlob(blobId []byte) error {
	var report blubberstore.BlockRemovalReport
	var prefix string
	var err error

	prefix, _ = self.blobId2FileName(blobId)
	err = os.Remove(prefix + ".data")
	if err != nil {
		return err
	}
	err = os.Remove(prefix + ".crypthead")
	if err != nil {
		return err
	}

	// If we don't run under a blob directory, this is the end of it.
	if self.directoryClient == nil {
		return nil
	}

	// Now report the deletion of the blob to the directory server.
	report.BlockId = make([]byte, len(blobId))
	report.Server = append(report.Server, self.bindHostPort)

	copy(report.BlockId, blobId)
	return self.directoryClient.RemoveBlobHolder(report)
}

// Get some details about the specified blob.
func (self *blubberStore) StatBlob(blobId []byte) (
	ret blubberstore.BlubberStat, err error) {
	var bh *blubberstore.BlubberBlockHeader

	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return
	}

	ret.BlockId = make([]byte, len(blobId))
	copy(ret.BlockId, blobId)

	ret.Checksum = make([]byte, len(bh.Checksum))
	copy(ret.Checksum, bh.Checksum)

	ret.Size = bh.Size
	ret.Timestamp = bh.Timestamp
	return
}

// Copy a block from the given host.
func (self *blubberStore) CopyBlob(blobId []byte, source string) error {
	var cli http.Client = http.Client{
		Transport: &http.Transport{
			TLSClientConfig: self.tlsConfig,
		},
	}
	var proto string
	var stat, remote_stat blubberstore.BlubberStat
	var resp *http.Response
	var err error

	if self.insecure {
		proto = "http"
	} else {
		proto = "https"
	}

	// Request blob from remote blubber server.
	resp, err = cli.Get(proto + "://" + source + "/" +
		hex.EncodeToString(blobId))
	if err != nil {
		return err
	}

	// Copy the remote contents into a local blob.
	err = self.StoreBlob(blobId, resp.Body, true)
	if err != nil {
		return err
	}

	// Now, let's do an integrity check.
	stat, err = self.StatBlob(blobId)
	if err != nil {
		return err
	}

	// Check the block length and signature against what the server
	// spefified in the HTTP response.
	remote_stat.BlockId, err = hex.DecodeString(
		resp.Header.Get("Content-Id"))
	remote_stat.Checksum, err = hex.DecodeString(
		resp.Header.Get("Content-Checksum"))
	remote_stat.Size = new(uint64)
	*remote_stat.Size, err = strconv.ParseUint(
		resp.Header.Get("Content-Length"), 10, 64)

	if *remote_stat.Size != *stat.Size {
		return errors.New("Size mismatch after copying block (orig: " +
			strconv.FormatUint(*remote_stat.Size, 10) + " (" +
			resp.Header.Get("Content-Length") + "), new: " +
			strconv.FormatUint(*stat.Size, 10) + ")")
	}

	if bytes.Compare(remote_stat.BlockId, stat.BlockId) != 0 {
		return errors.New("BlockId different than requested")
	}

	if bytes.Compare(remote_stat.Checksum, stat.Checksum) != 0 {
		return errors.New("Checksum mismatch (orig: " +
			hex.EncodeToString(remote_stat.Checksum) + ", new: " +
			hex.EncodeToString(stat.Checksum) + ")")
	}

	// If we reached this point we apparently copied the block successfully.
	return nil
}
<commit_msg>Copy size and timestamp rather than just referencing.<commit_after>/**
 * (c) 2014, Tonnerre Lombard <tonnerre@ancient-solutions.com>,
 *	     Ancient Solutions. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the  name  of  Ancient Solutions  nor  the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"bytes"
	"code.google.com/p/goprotobuf/proto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"errors"
	"github.com/tonnerre/blubberstore"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"time"
)

// Special "writer" which just counts the number of bytes passed through.
type CountingWriter struct {
	n uint64
}

// Add the length of p to the counter.
func (self *CountingWriter) Write(p []byte) (n int, err error) {
	n = len(p)
	self.n += uint64(n)
	return
}

// Return the full number of bytes passed through Write().
func (self *CountingWriter) BytesWritten() uint64 {
	return self.n
}

type blubberStore struct {
	bindHostPort    string
	blobPath        string
	directoryClient *blubberstore.BlubberDirectoryClient
	insecure        bool
	priv            *rsa.PrivateKey
	tlsConfig       *tls.Config
}

// Construct the file name prefix for the blob with the given blob ID.
// Returns the full name prefix and the parent directory.
func (self *blubberStore) blobId2FileName(blobId []byte) (
	string, string) {
	var parent_dir, first_dir, second_dir string
	var path_name string = hex.EncodeToString(blobId)

	// Extract the first and second directory part piece.
	if len(blobId) > 0 {
		first_dir = strconv.FormatUint(uint64(blobId[0]), 16)
	} else {
		path_name = "zz"
		first_dir = "00"
	}
	if len(blobId) > 1 {
		second_dir = first_dir + strconv.FormatUint(uint64(blobId[1]), 16)
	} else {
		second_dir = first_dir + "00"
	}

	parent_dir = self.blobPath + "/" + first_dir + "/" + second_dir
	return parent_dir + "/" + path_name, parent_dir
}

// Create a new blob with the given blobId, or overwrite an existing one.
// The contents of the blob will be read from input.
func (self *blubberStore) StoreBlob(blobId []byte, input io.Reader,
	overwrite bool) error {
	var outstream cipher.StreamWriter
	var bh blubberstore.BlubberBlockHeader
	var report blubberstore.BlockReport
	var aescipher cipher.Block
	var cksum hash.Hash = sha256.New()
	var parent_dir, file_prefix string
	var counter CountingWriter
	var outfile *os.File
	var buf []byte
	var flag int
	var n int
	var err error

	// Create a block key and IV for the blob data.
	bh.BlockKey = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.BlockKey)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	bh.Iv = make([]byte, aes.BlockSize)
	n, err = rand.Read(bh.Iv)
	if err != nil {
		return err
	}
	if n != aes.BlockSize {
		return errors.New("Unexpected length of random data")
	}

	file_prefix, parent_dir = self.blobId2FileName(blobId)

	// Ensure we have the full directory path in place.
	err = os.MkdirAll(parent_dir, 0700)
	if err != nil {
		return err
	}

	// Now, write the actual data.
	flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
	if !overwrite {
		flag |= os.O_EXCL
	}
	outfile, err = os.OpenFile(file_prefix+".data", flag, 0600)
	if err != nil {
		return err
	}
	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		return err
	}
	outstream = cipher.StreamWriter{
		S: cipher.NewCTR(aescipher, bh.Iv),
		W: outfile,
	}
	_, err = io.Copy(io.MultiWriter(outstream, cksum, &counter), input)
	if err != nil {
		return err
	}
	err = outstream.Close()
	if err != nil {
		return err
	}

	// Fill in the last bits of the blob header.
	bh.Checksum = cksum.Sum(bh.Checksum)
	bh.Size = proto.Uint64(counter.BytesWritten())
	bh.Timestamp = proto.Uint64(uint64(time.Now().Unix()))
	buf, err = proto.Marshal(&bh)
	if err != nil {
		return err
	}
	if !self.insecure {
		// Encrypt the AES key and IV with the RSA key.
		buf, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, buf)
		if err != nil {
			return err
		}
	}

	// Write out the crypto head with the IV and the blob key.
	outfile, err = os.Create(file_prefix + ".crypthead")
	if err != nil {
		return err
	}
	n, err = outfile.Write(buf)
	if err != nil {
		return err
	}
	if n < len(buf) {
		return errors.New("Short write to file")
	}

	err = outfile.Close()
	if err != nil {
		return err
	}

	// If we don't run under a blob directory, this is the end of it.
	if self.directoryClient == nil {
		return nil
	}

	// Now, report the new blob to the directory service.
	report.Server = append(report.Server, self.bindHostPort)
	report.Status = new(blubberstore.BlubberStat)
	report.Status.BlockId = make([]byte, len(blobId))
	report.Status.Checksum = make([]byte, len(bh.Checksum))
	report.Status.Size = proto.Uint64(*bh.Size)
	report.Status.Timestamp = proto.Uint64(*bh.Timestamp)

	copy(report.Status.BlockId, blobId)
	copy(report.Status.Checksum, bh.Checksum)

	// Since people need to be able to do e.g. quorum writes, we need to
	// wait here synchronously for the directory service.
	return self.directoryClient.ReportBlob(report)
}

// Extract the blubber block head for the given blob ID and return it.
func (self *blubberStore) extractBlockHead(blobId []byte) (
	bh *blubberstore.BlubberBlockHeader, err error) {
	var file_prefix string
	var data []byte

	file_prefix, _ = self.blobId2FileName(blobId)
	data, err = ioutil.ReadFile(file_prefix + ".crypthead")

	if !self.insecure {
		// Decrypt the AES key and IV with the RSA key.
		data, err = rsa.DecryptPKCS1v15(rand.Reader, self.priv, data)
		if err != nil {
			return
		}
	}

	bh = new(blubberstore.BlubberBlockHeader)
	err = proto.Unmarshal(data, bh)
	return
}

// Read the blob with the specified blob ID and return it to the caller.
// The contents will be sent as a regular HTTP response.
func (self *blubberStore) RetrieveBlob(blobId []byte, rw io.Writer) error {
	var file_prefix string
	var infile *os.File
	var bh *blubberstore.BlubberBlockHeader
	var instream cipher.StreamReader
	var aescipher cipher.Block
	var err error

	// Get the metadata.
	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return err
	}

	file_prefix, _ = self.blobId2FileName(blobId)
	infile, err = os.Open(file_prefix + ".data")
	if err != nil {
		return err
	}

	aescipher, err = aes.NewCipher(bh.BlockKey)
	if err != nil {
		infile.Close()
		return err
	}
	instream = cipher.StreamReader{
		S: cipher.NewCTR(aescipher, bh.Iv),
		R: infile,
	}

	_, err = io.Copy(rw, instream)
	if err != nil {
		infile.Close()
		return err
	}

	return infile.Close()
}

// Delete the blob with the given blob ID.
func (self *blubberStore) DeleteBlob(blobId []byte) error {
	var report blubberstore.BlockRemovalReport
	var prefix string
	var err error

	prefix, _ = self.blobId2FileName(blobId)
	err = os.Remove(prefix + ".data")
	if err != nil {
		return err
	}
	err = os.Remove(prefix + ".crypthead")
	if err != nil {
		return err
	}

	// If we don't run under a blob directory, this is the end of it.
	if self.directoryClient == nil {
		return nil
	}

	// Now report the deletion of the blob to the directory server.
	report.BlockId = make([]byte, len(blobId))
	report.Server = append(report.Server, self.bindHostPort)

	copy(report.BlockId, blobId)
	return self.directoryClient.RemoveBlobHolder(report)
}

// Get some details about the specified blob.
func (self *blubberStore) StatBlob(blobId []byte) (
	ret blubberstore.BlubberStat, err error) {
	var bh *blubberstore.BlubberBlockHeader

	bh, err = self.extractBlockHead(blobId)
	if err != nil {
		return
	}

	ret.BlockId = make([]byte, len(blobId))
	copy(ret.BlockId, blobId)

	ret.Checksum = make([]byte, len(bh.Checksum))
	copy(ret.Checksum, bh.Checksum)

	ret.Size = bh.Size
	ret.Timestamp = bh.Timestamp
	return
}

// Copy a block from the given host.
func (self *blubberStore) CopyBlob(blobId []byte, source string) error {
	var cli http.Client = http.Client{
		Transport: &http.Transport{
			TLSClientConfig: self.tlsConfig,
		},
	}
	var proto string
	var stat, remote_stat blubberstore.BlubberStat
	var resp *http.Response
	var err error

	if self.insecure {
		proto = "http"
	} else {
		proto = "https"
	}

	// Request blob from remote blubber server.
	resp, err = cli.Get(proto + "://" + source + "/" +
		hex.EncodeToString(blobId))
	if err != nil {
		return err
	}

	// Copy the remote contents into a local blob.
	err = self.StoreBlob(blobId, resp.Body, true)
	if err != nil {
		return err
	}

	// Now, let's do an integrity check.
	stat, err = self.StatBlob(blobId)
	if err != nil {
		return err
	}

	// Check the block length and signature against what the server
	// spefified in the HTTP response.
	remote_stat.BlockId, err = hex.DecodeString(
		resp.Header.Get("Content-Id"))
	remote_stat.Checksum, err = hex.DecodeString(
		resp.Header.Get("Content-Checksum"))
	remote_stat.Size = new(uint64)
	*remote_stat.Size, err = strconv.ParseUint(
		resp.Header.Get("Content-Length"), 10, 64)

	if *remote_stat.Size != *stat.Size {
		return errors.New("Size mismatch after copying block (orig: " +
			strconv.FormatUint(*remote_stat.Size, 10) + " (" +
			resp.Header.Get("Content-Length") + "), new: " +
			strconv.FormatUint(*stat.Size, 10) + ")")
	}

	if bytes.Compare(remote_stat.BlockId, stat.BlockId) != 0 {
		return errors.New("BlockId different than requested")
	}

	if bytes.Compare(remote_stat.Checksum, stat.Checksum) != 0 {
		return errors.New("Checksum mismatch (orig: " +
			hex.EncodeToString(remote_stat.Checksum) + ", new: " +
			hex.EncodeToString(stat.Checksum) + ")")
	}

	// If we reached this point we apparently copied the block successfully.
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 addons

import (
	"fmt"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/pkg/errors"
	"github.com/spf13/viper"

	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/command"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/storageclass"
	"k8s.io/minikube/pkg/util/retry"
)

// defaultStorageClassProvisioner is the name of the default storage class provisioner
const defaultStorageClassProvisioner = "standard"

// RunCallbacks runs all actions associated to an addon, but does not set it (thread-safe)
func RunCallbacks(cc *config.ClusterConfig, name string, value string) error {
	glog.Infof("Setting %s=%s in profile %q", name, value, cc.Name)
	a, valid := isAddonValid(name)
	if !valid {
		return errors.Errorf("%s is not a valid addon", name)
	}

	// Run any additional validations for this property
	if err := run(cc, name, value, a.validations); err != nil {
		return errors.Wrap(err, "running validations")
	}

	// Run any callbacks for this property
	if err := run(cc, name, value, a.callbacks); err != nil {
		return errors.Wrap(err, "running callbacks")
	}
	return nil
}

// Set sets a value in the config (not threadsafe)
func Set(cc *config.ClusterConfig, name string, value string) error {
	a, valid := isAddonValid(name)
	if !valid {
		return errors.Errorf("%s is not a valid addon", name)
	}
	return a.set(cc, name, value)
}

// SetAndSave sets a value and saves the config
func SetAndSave(profile string, name string, value string) error {
	cc, err := config.Load(profile)
	if err != nil {
		return errors.Wrap(err, "loading profile")
	}

	if err := RunCallbacks(cc, name, value); err != nil {
		return errors.Wrap(err, "run callbacks")
	}

	if err := Set(cc, name, value); err != nil {
		return errors.Wrap(err, "set")
	}

	glog.Infof("Writing out %q config to set %s=%v...", profile, name, value)
	return config.Write(profile, cc)
}

// Runs all the validation or callback functions and collects errors
func run(cc *config.ClusterConfig, name string, value string, fns []setFn) error {
	var errors []error
	for _, fn := range fns {
		err := fn(cc, name, value)
		if err != nil {
			errors = append(errors, err)
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("%v", errors)
	}
	return nil
}

// SetBool sets a bool value in the config (not threadsafe)
func SetBool(cc *config.ClusterConfig, name string, val string) error {
	b, err := strconv.ParseBool(val)
	if err != nil {
		return err
	}
	if cc.Addons == nil {
		cc.Addons = map[string]bool{}
	}
	cc.Addons[name] = b
	return nil
}

// enableOrDisableAddon updates addon status executing any commands necessary
func enableOrDisableAddon(cc *config.ClusterConfig, name string, val string) error {
	glog.Infof("Setting addon %s=%s in %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrapf(err, "parsing bool: %s", name)
	}
	addon := assets.Addons[name]

	// check addon status before enabling/disabling it
	if isAddonAlreadySet(cc, addon, enable) {
		glog.Warningf("addon %s should already be in state %v", name, val)
		if !enable {
			return nil
		}
	}

	// to match both ingress and ingress-dns adons
	// 	if strings.HasPrefix(name, "ingress") && enable && driver.IsKIC(cc.Driver) && runtime.GOOS != "linux" {
	// 		exit.UsageT(`Due to {{.driver_name}} networking limitations on {{.os_name}}, {{.addon_name}} addon is not supported for this driver.
	// Alternatively to use this addon you can use a vm-based driver:

	// 	'minikube start --vm=true'

	// To track the update on this work in progress feature please check:
	// https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Driver, "os_name": runtime.GOOS, "addon_name": name})
	// 	}

	if strings.HasPrefix(name, "istio") && enable {
		minMem := 8192
		minCPUs := 4
		if cc.Memory < minMem {
			out.WarningT("Istio needs {{.minMem}}MB of memory -- your configuration only allocates {{.memory}}MB", out.V{"minMem": minMem, "memory": cc.Memory})
		}
		if cc.CPUs < minCPUs {
			out.WarningT("Istio needs {{.minCPUs}} CPUs -- your configuration only allocates {{.cpus}} CPUs", out.V{"minCPUs": minCPUs, "cpus": cc.CPUs})
		}
	}

	// TODO(r2d4): config package should not reference API, pull this out
	api, err := machine.NewAPIClient()
	if err != nil {
		return errors.Wrap(err, "machine client")
	}
	defer api.Close()

	cp, err := config.PrimaryControlPlane(cc)
	if err != nil {
		exit.WithError("Error getting primary control plane", err)
	}

	mName := driver.MachineName(*cc, cp)
	host, err := machine.LoadHost(api, mName)
	if err != nil || !machine.IsRunning(api, mName) {
		glog.Warningf("%q is not running, setting %s=%v and skipping enablement (err=%v)", mName, addon.Name(), enable, err)
		return nil
	}

	if name == "registry" {
		if driver.NeedsPortForward(cc.Driver) {
			port, err := oci.ForwardedPort(cc.Driver, cc.Name, constants.RegistryAddonPort)
			if err != nil {
				return errors.Wrap(err, "registry port")
			}
			out.T(out.Tip, `Registry addon on with {{.driver}} uses {{.port}} please use that instead of default 5000`, out.V{"driver": cc.Driver, "port": port})
			out.T(out.Documentation, `For more information see: https://minikube.sigs.k8s.io/docs/drivers/{{.driver}}`, out.V{"driver": cc.Driver})
		}
	}

	cmd, err := machine.CommandRunner(host)
	if err != nil {
		return errors.Wrap(err, "command runner")
	}

	data := assets.GenerateTemplateData(cc.KubernetesConfig)
	return enableOrDisableAddonInternal(cc, addon, cmd, data, enable)
}

func isAddonAlreadySet(cc *config.ClusterConfig, addon *assets.Addon, enable bool) bool {
	enabled := addon.IsEnabled(cc)
	if enabled && enable {
		return true
	}

	if !enabled && !enable {
		return true
	}

	return false
}

func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon, cmd command.Runner, data interface{}, enable bool) error {
	deployFiles := []string{}

	for _, addon := range addon.Assets {
		var f assets.CopyableFile
		var err error
		if addon.IsTemplate() {
			f, err = addon.Evaluate(data)
			if err != nil {
				return errors.Wrapf(err, "evaluate bundled addon %s asset", addon.GetSourcePath())
			}

		} else {
			f = addon
		}
		fPath := path.Join(f.GetTargetDir(), f.GetTargetName())

		if enable {
			glog.Infof("installing %s", fPath)
			if err := cmd.Copy(f); err != nil {
				return err
			}
		} else {
			glog.Infof("Removing %+v", fPath)
			defer func() {
				if err := cmd.Remove(f); err != nil {
					glog.Warningf("error removing %s; addon should still be disabled as expected", fPath)
				}
			}()
		}
		if strings.HasSuffix(fPath, ".yaml") {
			deployFiles = append(deployFiles, fPath)
		}
	}

	command := kubectlCommand(cc, deployFiles, enable)

	// Retry, because sometimes we race against an apiserver restart
	apply := func() error {
		_, err := cmd.RunCmd(command)
		if err != nil {
			glog.Warningf("apply failed, will retry: %v", err)
		}
		return err
	}

	return retry.Expo(apply, 250*time.Millisecond, 2*time.Minute)
}

// enableOrDisableStorageClasses enables or disables storage classes
func enableOrDisableStorageClasses(cc *config.ClusterConfig, name string, val string) error {
	glog.Infof("enableOrDisableStorageClasses %s=%v on %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrap(err, "Error parsing boolean")
	}

	class := defaultStorageClassProvisioner
	if name == "storage-provisioner-gluster" {
		class = "glusterfile"
	}

	api, err := machine.NewAPIClient()
	if err != nil {
		return errors.Wrap(err, "machine client")
	}
	defer api.Close()

	cp, err := config.PrimaryControlPlane(cc)
	if err != nil {
		return errors.Wrap(err, "getting control plane")
	}
	if !machine.IsRunning(api, driver.MachineName(*cc, cp)) {
		glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement", driver.MachineName(*cc, cp), name, val)
		return enableOrDisableAddon(cc, name, val)
	}

	storagev1, err := storageclass.GetStoragev1(cc.Name)
	if err != nil {
		return errors.Wrapf(err, "Error getting storagev1 interface %v ", err)
	}

	if enable {
		// Only StorageClass for 'name' should be marked as default
		err = storageclass.SetDefaultStorageClass(storagev1, class)
		if err != nil {
			return errors.Wrapf(err, "Error making %s the default storage class", class)
		}
	} else {
		// Unset the StorageClass as default
		err := storageclass.DisableDefaultStorageClass(storagev1, class)
		if err != nil {
			return errors.Wrapf(err, "Error disabling %s as the default storage class", class)
		}
	}

	return enableOrDisableAddon(cc, name, val)
}

func validateIngress(cc *config.ClusterConfig, name string, val string) error {
	fmt.Println("inside validatr inresssss")
	glog.Infof("Setting addon %s=%s in %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrapf(err, "parsing bool: %s", name)
	}
	if name == "ingress" && enable {
		fmt.Println("validating client")
		client, err := kapi.Client(viper.GetString(config.ProfileName))
		if err != nil {
			return errors.Wrapf(err, "get kube-client to validate ingress addon: %s", name)
		} else {
			fmt.Println("validating deployment.....")
			err = kapi.WaitForDeploymentToStabilize(client, "kube-system", "ingress-nginx-controller", time.Minute*3)
			if err != nil {
				return errors.Wrapf(err, "Failed verifying ingress addon: %s", name)
			}
			fmt.Println("SCUCESSSFULLY validated deployment.....")
		}
	}
	return nil
}

// Start enables the default addons for a profile, plus any additional
func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]bool, additional []string) {
	wg.Add(1)
	defer wg.Done()

	start := time.Now()
	glog.Infof("enableAddons start: toEnable=%v, additional=%s", toEnable, additional)
	defer func() {
		glog.Infof("enableAddons completed in %s", time.Since(start))
	}()

	// Get the default values of any addons not saved to our config
	for name, a := range assets.Addons {
		defaultVal := a.IsEnabled(cc)

		_, exists := toEnable[name]
		if !exists {
			toEnable[name] = defaultVal
		}
	}

	// Apply new addons
	for _, name := range additional {
		// replace heapster as metrics-server because heapster is deprecated
		if name == "heapster" {
			name = "metrics-server"
		}
		// if the specified addon doesn't exist, skip enabling
		_, e := isAddonValid(name)
		if e {
			toEnable[name] = true
		}
	}

	toEnableList := []string{}
	for k, v := range toEnable {
		if v {
			toEnableList = append(toEnableList, k)
		}
	}
	sort.Strings(toEnableList)

	var awg sync.WaitGroup

	defer func() { // making it show after verifications( not perfect till #7613 is closed)
		out.T(out.AddonEnable, "Enabled addons: {{.addons}}", out.V{"addons": strings.Join(toEnableList, ", ")})
	}()
	for _, a := range toEnableList {
		awg.Add(1)
		go func(name string) {
			err := RunCallbacks(cc, name, "true")
			if err != nil {
				out.WarningT("Enabling '{{.name}}' returned an error: {{.error}}", out.V{"name": name, "error": err})
			}
			awg.Done()
		}(a)
	}

	// Wait until all of the addons are enabled before updating the config (not thread safe)
	awg.Wait()
	for _, a := range toEnableList {
		if err := Set(cc, a, "true"); err != nil {
			glog.Errorf("store failed: %v", err)
		}
	}
}
<commit_msg>ingress addon validate deployment after enable<commit_after>/*
Copyright 2019 The Kubernetes Authors All rights reserved.

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 addons

import (
	"fmt"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/pkg/errors"
	"github.com/spf13/viper"

	"k8s.io/minikube/pkg/drivers/kic/oci"
	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/command"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/machine"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/storageclass"
	"k8s.io/minikube/pkg/util/retry"
)

// defaultStorageClassProvisioner is the name of the default storage class provisioner
const defaultStorageClassProvisioner = "standard"

// RunCallbacks runs all actions associated to an addon, but does not set it (thread-safe)
func RunCallbacks(cc *config.ClusterConfig, name string, value string) error {
	glog.Infof("Setting %s=%s in profile %q", name, value, cc.Name)
	a, valid := isAddonValid(name)
	if !valid {
		return errors.Errorf("%s is not a valid addon", name)
	}

	// Run any additional validations for this property
	if err := run(cc, name, value, a.validations); err != nil {
		return errors.Wrap(err, "running validations")
	}

	// Run any callbacks for this property
	if err := run(cc, name, value, a.callbacks); err != nil {
		return errors.Wrap(err, "running callbacks")
	}
	return nil
}

// Set sets a value in the config (not threadsafe)
func Set(cc *config.ClusterConfig, name string, value string) error {
	a, valid := isAddonValid(name)
	if !valid {
		return errors.Errorf("%s is not a valid addon", name)
	}
	return a.set(cc, name, value)
}

// SetAndSave sets a value and saves the config
func SetAndSave(profile string, name string, value string) error {
	cc, err := config.Load(profile)
	if err != nil {
		return errors.Wrap(err, "loading profile")
	}

	if err := RunCallbacks(cc, name, value); err != nil {
		return errors.Wrap(err, "run callbacks")
	}

	if err := Set(cc, name, value); err != nil {
		return errors.Wrap(err, "set")
	}

	glog.Infof("Writing out %q config to set %s=%v...", profile, name, value)
	return config.Write(profile, cc)
}

// Runs all the validation or callback functions and collects errors
func run(cc *config.ClusterConfig, name string, value string, fns []setFn) error {
	var errors []error
	for _, fn := range fns {
		err := fn(cc, name, value)
		if err != nil {
			errors = append(errors, err)
		}
	}
	if len(errors) > 0 {
		return fmt.Errorf("%v", errors)
	}
	return nil
}

// SetBool sets a bool value in the config (not threadsafe)
func SetBool(cc *config.ClusterConfig, name string, val string) error {
	b, err := strconv.ParseBool(val)
	if err != nil {
		return err
	}
	if cc.Addons == nil {
		cc.Addons = map[string]bool{}
	}
	cc.Addons[name] = b
	return nil
}

// enableOrDisableAddon updates addon status executing any commands necessary
func enableOrDisableAddon(cc *config.ClusterConfig, name string, val string) error {
	glog.Infof("Setting addon %s=%s in %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrapf(err, "parsing bool: %s", name)
	}
	addon := assets.Addons[name]

	// check addon status before enabling/disabling it
	if isAddonAlreadySet(cc, addon, enable) {
		glog.Warningf("addon %s should already be in state %v", name, val)
		if !enable {
			return nil
		}
	}

	// to match both ingress and ingress-dns adons
	// 	if strings.HasPrefix(name, "ingress") && enable && driver.IsKIC(cc.Driver) && runtime.GOOS != "linux" {
	// 		exit.UsageT(`Due to {{.driver_name}} networking limitations on {{.os_name}}, {{.addon_name}} addon is not supported for this driver.
	// Alternatively to use this addon you can use a vm-based driver:

	// 	'minikube start --vm=true'

	// To track the update on this work in progress feature please check:
	// https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Driver, "os_name": runtime.GOOS, "addon_name": name})
	// 	}

	if strings.HasPrefix(name, "istio") && enable {
		minMem := 8192
		minCPUs := 4
		if cc.Memory < minMem {
			out.WarningT("Istio needs {{.minMem}}MB of memory -- your configuration only allocates {{.memory}}MB", out.V{"minMem": minMem, "memory": cc.Memory})
		}
		if cc.CPUs < minCPUs {
			out.WarningT("Istio needs {{.minCPUs}} CPUs -- your configuration only allocates {{.cpus}} CPUs", out.V{"minCPUs": minCPUs, "cpus": cc.CPUs})
		}
	}

	// TODO(r2d4): config package should not reference API, pull this out
	api, err := machine.NewAPIClient()
	if err != nil {
		return errors.Wrap(err, "machine client")
	}
	defer api.Close()

	cp, err := config.PrimaryControlPlane(cc)
	if err != nil {
		exit.WithError("Error getting primary control plane", err)
	}

	mName := driver.MachineName(*cc, cp)
	host, err := machine.LoadHost(api, mName)
	if err != nil || !machine.IsRunning(api, mName) {
		glog.Warningf("%q is not running, setting %s=%v and skipping enablement (err=%v)", mName, addon.Name(), enable, err)
		return nil
	}

	if name == "registry" {
		if driver.NeedsPortForward(cc.Driver) {
			port, err := oci.ForwardedPort(cc.Driver, cc.Name, constants.RegistryAddonPort)
			if err != nil {
				return errors.Wrap(err, "registry port")
			}
			out.T(out.Tip, `Registry addon on with {{.driver}} uses {{.port}} please use that instead of default 5000`, out.V{"driver": cc.Driver, "port": port})
			out.T(out.Documentation, `For more information see: https://minikube.sigs.k8s.io/docs/drivers/{{.driver}}`, out.V{"driver": cc.Driver})
		}
	}

	cmd, err := machine.CommandRunner(host)
	if err != nil {
		return errors.Wrap(err, "command runner")
	}

	data := assets.GenerateTemplateData(cc.KubernetesConfig)
	return enableOrDisableAddonInternal(cc, addon, cmd, data, enable)
}

func isAddonAlreadySet(cc *config.ClusterConfig, addon *assets.Addon, enable bool) bool {
	enabled := addon.IsEnabled(cc)
	if enabled && enable {
		return true
	}

	if !enabled && !enable {
		return true
	}

	return false
}

func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon, cmd command.Runner, data interface{}, enable bool) error {
	deployFiles := []string{}

	for _, addon := range addon.Assets {
		var f assets.CopyableFile
		var err error
		if addon.IsTemplate() {
			f, err = addon.Evaluate(data)
			if err != nil {
				return errors.Wrapf(err, "evaluate bundled addon %s asset", addon.GetSourcePath())
			}

		} else {
			f = addon
		}
		fPath := path.Join(f.GetTargetDir(), f.GetTargetName())

		if enable {
			glog.Infof("installing %s", fPath)
			if err := cmd.Copy(f); err != nil {
				return err
			}
		} else {
			glog.Infof("Removing %+v", fPath)
			defer func() {
				if err := cmd.Remove(f); err != nil {
					glog.Warningf("error removing %s; addon should still be disabled as expected", fPath)
				}
			}()
		}
		if strings.HasSuffix(fPath, ".yaml") {
			deployFiles = append(deployFiles, fPath)
		}
	}

	command := kubectlCommand(cc, deployFiles, enable)

	// Retry, because sometimes we race against an apiserver restart
	apply := func() error {
		_, err := cmd.RunCmd(command)
		if err != nil {
			glog.Warningf("apply failed, will retry: %v", err)
		}
		return err
	}

	return retry.Expo(apply, 250*time.Millisecond, 2*time.Minute)
}

// enableOrDisableStorageClasses enables or disables storage classes
func enableOrDisableStorageClasses(cc *config.ClusterConfig, name string, val string) error {
	glog.Infof("enableOrDisableStorageClasses %s=%v on %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrap(err, "Error parsing boolean")
	}

	class := defaultStorageClassProvisioner
	if name == "storage-provisioner-gluster" {
		class = "glusterfile"
	}

	api, err := machine.NewAPIClient()
	if err != nil {
		return errors.Wrap(err, "machine client")
	}
	defer api.Close()

	cp, err := config.PrimaryControlPlane(cc)
	if err != nil {
		return errors.Wrap(err, "getting control plane")
	}
	if !machine.IsRunning(api, driver.MachineName(*cc, cp)) {
		glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement", driver.MachineName(*cc, cp), name, val)
		return enableOrDisableAddon(cc, name, val)
	}

	storagev1, err := storageclass.GetStoragev1(cc.Name)
	if err != nil {
		return errors.Wrapf(err, "Error getting storagev1 interface %v ", err)
	}

	if enable {
		// Only StorageClass for 'name' should be marked as default
		err = storageclass.SetDefaultStorageClass(storagev1, class)
		if err != nil {
			return errors.Wrapf(err, "Error making %s the default storage class", class)
		}
	} else {
		// Unset the StorageClass as default
		err := storageclass.DisableDefaultStorageClass(storagev1, class)
		if err != nil {
			return errors.Wrapf(err, "Error disabling %s as the default storage class", class)
		}
	}

	return enableOrDisableAddon(cc, name, val)
}

func validateIngress(cc *config.ClusterConfig, name string, val string) error {
	glog.Infof("Setting addon %s=%s in %q", name, val, cc.Name)
	enable, err := strconv.ParseBool(val)
	if err != nil {
		return errors.Wrapf(err, "parsing bool: %s", name)
	}
	if name == "ingress" && enable {
		client, err := kapi.Client(viper.GetString(config.ProfileName))
		if err != nil {
			return errors.Wrapf(err, "get kube-client to validate ingress addon: %s", name)
		} else {
			err = kapi.WaitForDeploymentToStabilize(client, "kube-system", "ingress-nginx-controller", time.Minute*3)
			if err != nil {
				return errors.Wrapf(err, "Failed verifying ingress addon deployment: %s", name)
			}
		}
	}
	return nil
}

// Start enables the default addons for a profile, plus any additional
func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]bool, additional []string) {
	wg.Add(1)
	defer wg.Done()

	start := time.Now()
	glog.Infof("enableAddons start: toEnable=%v, additional=%s", toEnable, additional)
	defer func() {
		glog.Infof("enableAddons completed in %s", time.Since(start))
	}()

	// Get the default values of any addons not saved to our config
	for name, a := range assets.Addons {
		defaultVal := a.IsEnabled(cc)

		_, exists := toEnable[name]
		if !exists {
			toEnable[name] = defaultVal
		}
	}

	// Apply new addons
	for _, name := range additional {
		// replace heapster as metrics-server because heapster is deprecated
		if name == "heapster" {
			name = "metrics-server"
		}
		// if the specified addon doesn't exist, skip enabling
		_, e := isAddonValid(name)
		if e {
			toEnable[name] = true
		}
	}

	toEnableList := []string{}
	for k, v := range toEnable {
		if v {
			toEnableList = append(toEnableList, k)
		}
	}
	sort.Strings(toEnableList)

	var awg sync.WaitGroup

	defer func() { // making it show after verifications( not perfect till #7613 is closed)
		out.T(out.AddonEnable, "Enabled addons: {{.addons}}", out.V{"addons": strings.Join(toEnableList, ", ")})
	}()
	for _, a := range toEnableList {
		awg.Add(1)
		go func(name string) {
			err := RunCallbacks(cc, name, "true")
			if err != nil {
				out.WarningT("Enabling '{{.name}}' returned an error: {{.error}}", out.V{"name": name, "error": err})
			}
			awg.Done()
		}(a)
	}

	// Wait until all of the addons are enabled before updating the config (not thread safe)
	awg.Wait()
	for _, a := range toEnableList {
		if err := Set(cc, a, "true"); err != nil {
			glog.Errorf("store failed: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
)

// POST /api/org/users
func AddOrgUserToCurrentOrg(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
	cmd.OrgId = c.OrgId
	return addOrgUserHelper(cmd)
}

// POST /api/orgs/:orgId/users
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
	cmd.OrgId = c.ParamsInt64(":orgId")
	return addOrgUserHelper(cmd)
}

func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
	if !cmd.Role.IsValid() {
		return ApiError(400, "Invalid role specified", nil)
	}

	userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
	err := bus.Dispatch(&userQuery)
	if err != nil {
		return ApiError(404, "User not found", nil)
	}

	userToAdd := userQuery.Result

	// if userToAdd.Id == c.UserId {
	// 	return ApiError(400, "Cannot add yourself as user", nil)
	// }

	cmd.UserId = userToAdd.Id

	if err := bus.Dispatch(&cmd); err != nil {
		return ApiError(500, "Could not add user to organization", err)
	}

	return ApiSuccess("User added to organization")
}

// GET /api/org/users
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
	return getOrgUsersHelper(c.OrgId)
}

// GET /api/orgs/:orgId/users
func GetOrgUsers(c *middleware.Context) Response {
	return getOrgUsersHelper(c.ParamsInt64(":orgId"))
}

func getOrgUsersHelper(orgId int64) Response {
	query := m.GetOrgUsersQuery{OrgId: orgId}

	if err := bus.Dispatch(&query); err != nil {
		return ApiError(500, "Failed to get account user", err)
	}

	return Json(200, query.Result)
}

// PATCH /api/org/users/:userId
func UpdateOrgUserForCurrentOrg(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
	cmd.OrgId = c.OrgId
	cmd.UserId = c.ParamsInt64(":userId")
	return updateOrgUserHelper(cmd)
}

// PATCH /api/orgs/:orgId/users/:userId
func UpdateOrgUser(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
	cmd.OrgId = c.ParamsInt64(":orgId")
	cmd.UserId = c.ParamsInt64(":userId")
	return updateOrgUserHelper(cmd)
}

func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
	if !cmd.Role.IsValid() {
		return ApiError(400, "Invalid role specified", nil)
	}

	if err := bus.Dispatch(&cmd); err != nil {
		if err == m.ErrLastOrgAdmin {
			return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
		}
		return ApiError(500, "Failed update org user", err)
	}

	return ApiSuccess("Organization user updated")
}

// DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *middleware.Context) Response {
	userId := c.ParamsInt64(":userId")
	return removeOrgUserHelper(c.OrgId, userId)
}

// DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *middleware.Context) Response {
	userId := c.ParamsInt64(":userId")
	orgId := c.ParamsInt64(":orgId")
	return removeOrgUserHelper(orgId, userId)
}

func removeOrgUserHelper(orgId int64, userId int64) Response {
	cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}

	if err := bus.Dispatch(&cmd); err != nil {
		if err == m.ErrLastOrgAdmin {
			return ApiError(400, "Cannot remove last organization admin", nil)
		}
		return ApiError(500, "Failed to remove user from organization", err)
	}

	return ApiSuccess("User removed from organization")
}
<commit_msg>[6486] Fix status code when adding an existing user to org (#6678)<commit_after>package api

import (
	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
)

// POST /api/org/users
func AddOrgUserToCurrentOrg(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
	cmd.OrgId = c.OrgId
	return addOrgUserHelper(cmd)
}

// POST /api/orgs/:orgId/users
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) Response {
	cmd.OrgId = c.ParamsInt64(":orgId")
	return addOrgUserHelper(cmd)
}

func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
	if !cmd.Role.IsValid() {
		return ApiError(400, "Invalid role specified", nil)
	}

	userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
	err := bus.Dispatch(&userQuery)
	if err != nil {
		return ApiError(404, "User not found", nil)
	}

	userToAdd := userQuery.Result

	// if userToAdd.Id == c.UserId {
	// 	return ApiError(400, "Cannot add yourself as user", nil)
	// }

	cmd.UserId = userToAdd.Id

	if err := bus.Dispatch(&cmd); err != nil {
		if err == m.ErrOrgUserAlreadyAdded {
			return ApiError(409, "User is already member of this organization", nil)
		}
		return ApiError(500, "Could not add user to organization", err)
	}

	return ApiSuccess("User added to organization")
}

// GET /api/org/users
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
	return getOrgUsersHelper(c.OrgId)
}

// GET /api/orgs/:orgId/users
func GetOrgUsers(c *middleware.Context) Response {
	return getOrgUsersHelper(c.ParamsInt64(":orgId"))
}

func getOrgUsersHelper(orgId int64) Response {
	query := m.GetOrgUsersQuery{OrgId: orgId}

	if err := bus.Dispatch(&query); err != nil {
		return ApiError(500, "Failed to get account user", err)
	}

	return Json(200, query.Result)
}

// PATCH /api/org/users/:userId
func UpdateOrgUserForCurrentOrg(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
	cmd.OrgId = c.OrgId
	cmd.UserId = c.ParamsInt64(":userId")
	return updateOrgUserHelper(cmd)
}

// PATCH /api/orgs/:orgId/users/:userId
func UpdateOrgUser(c *middleware.Context, cmd m.UpdateOrgUserCommand) Response {
	cmd.OrgId = c.ParamsInt64(":orgId")
	cmd.UserId = c.ParamsInt64(":userId")
	return updateOrgUserHelper(cmd)
}

func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
	if !cmd.Role.IsValid() {
		return ApiError(400, "Invalid role specified", nil)
	}

	if err := bus.Dispatch(&cmd); err != nil {
		if err == m.ErrLastOrgAdmin {
			return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
		}
		return ApiError(500, "Failed update org user", err)
	}

	return ApiSuccess("Organization user updated")
}

// DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *middleware.Context) Response {
	userId := c.ParamsInt64(":userId")
	return removeOrgUserHelper(c.OrgId, userId)
}

// DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *middleware.Context) Response {
	userId := c.ParamsInt64(":userId")
	orgId := c.ParamsInt64(":orgId")
	return removeOrgUserHelper(orgId, userId)
}

func removeOrgUserHelper(orgId int64, userId int64) Response {
	cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}

	if err := bus.Dispatch(&cmd); err != nil {
		if err == m.ErrLastOrgAdmin {
			return ApiError(400, "Cannot remove last organization admin", nil)
		}
		return ApiError(500, "Failed to remove user from organization", err)
	}

	return ApiSuccess("User removed from organization")
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package k8s

import (
	"bytes"
	"reflect"
	"regexp"
	"strconv"
	"strings"

	"github.com/golang/glog"

	"github.com/cbroglie/mapstructure"
	"github.com/fatih/structs"

	"github.com/aledbf/ingress-controller/pkg/ingress/defaults"

	"k8s.io/kubernetes/pkg/api"
)

const (
	customHTTPErrors  = "custom-http-errors"
	skipAccessLogUrls = "skip-access-log-urls"
)

var (
	camelRegexp = regexp.MustCompile("[0-9A-Za-z]+")
)

// StandarizeKeyNames ...
func StandarizeKeyNames(data map[string]interface{}) map[string]interface{} {
	return fixKeyNames(structs.Map(data))
}

// MergeConfigMapToStruct merges the content of a ConfigMap that contains
// mapstructure tags to a struct pointer using another pointer of the same
// type.
func MergeConfigMapToStruct(conf *api.ConfigMap, def, to interface{}) {
	//TODO: check def and to are the same type

	metadata := &mapstructure.Metadata{}
	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		TagName:          "structs",
		Result:           &to,
		WeaklyTypedInput: true,
		Metadata:         metadata,
	})

	var errors []int
	if val, ok := conf.Data[customHTTPErrors]; ok {
		delete(conf.Data, customHTTPErrors)
		for _, i := range strings.Split(val, ",") {
			j, err := strconv.Atoi(i)
			if err != nil {
				glog.Warningf("%v is not a valid http code: %v", i, err)
			} else {
				errors = append(errors, j)
			}
		}
	}

	var skipUrls []string
	if val, ok := conf.Data[skipAccessLogUrls]; ok {
		delete(conf.Data, skipAccessLogUrls)
		skipUrls = strings.Split(val, ",")
	}

	err = decoder.Decode(conf.Data)
	if err != nil {
		glog.Infof("%v", err)
	}

	keyMap := getConfigKeyToStructKeyMap(to)
	valCM := reflect.Indirect(reflect.ValueOf(conf))
	for _, key := range metadata.Keys {
		fieldName, ok := keyMap[key]
		if !ok {
			continue
		}

		valDefault := reflect.ValueOf(&def).Elem().FieldByName(fieldName)

		fieldCM := valCM.FieldByName(fieldName)

		if valDefault.IsValid() {
			valDefault.Set(fieldCM)
		}
	}

	b, ok := def.(defaults.Backend)
	if ok {
		b.CustomHTTPErrors = filterErrors(errors)
		b.SkipAccessLogURLs = skipUrls
		if b.Resolver == "" {
			b.Resolver = "" //TODO: ngx.defResolver
		}
	}

}

func filterErrors(errCodes []int) []int {
	var fa []int
	for _, errCode := range errCodes {
		if errCode > 299 && errCode < 600 {
			fa = append(fa, errCode)
		} else {
			glog.Warningf("error code %v is not valid for custom error pages", errCode)
		}
	}

	return fa
}

func fixKeyNames(data map[string]interface{}) map[string]interface{} {
	fixed := make(map[string]interface{})
	for k, v := range data {
		fixed[toCamelCase(k)] = v
	}

	return fixed
}

func toCamelCase(src string) string {
	byteSrc := []byte(src)
	chunks := camelRegexp.FindAll(byteSrc, -1)
	for idx, val := range chunks {
		if idx > 0 {
			chunks[idx] = bytes.Title(val)
		}
	}
	return string(bytes.Join(chunks, nil))
}

// getConfigKeyToStructKeyMap returns a map with the ConfigMapKey as key and the StructName as value.
func getConfigKeyToStructKeyMap(to interface{}) map[string]string {
	keyMap := map[string]string{}
	val := reflect.Indirect(reflect.ValueOf(to))
	for i := 0; i < val.Type().NumField(); i++ {
		fieldSt := val.Type().Field(i)
		configMapKey := strings.Split(fieldSt.Tag.Get("structs"), ",")[0]
		structKey := fieldSt.Name
		keyMap[configMapKey] = structKey
	}
	return keyMap
}
<commit_msg>Fix build<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package k8s

import (
	"bytes"
	"reflect"
	"regexp"
	"strconv"
	"strings"

	"github.com/golang/glog"

	"github.com/cbroglie/mapstructure"
	"github.com/fatih/structs"

	"github.com/aledbf/ingress-controller/pkg/ingress/defaults"

	"k8s.io/kubernetes/pkg/api"
)

const (
	customHTTPErrors  = "custom-http-errors"
	skipAccessLogUrls = "skip-access-log-urls"
)

var (
	camelRegexp = regexp.MustCompile("[0-9A-Za-z]+")
)

// StandarizeKeyNames ...
func StandarizeKeyNames(data interface{}) map[string]interface{} {
	return fixKeyNames(structs.Map(data))
}

// MergeConfigMapToStruct merges the content of a ConfigMap that contains
// mapstructure tags to a struct pointer using another pointer of the same
// type.
func MergeConfigMapToStruct(conf *api.ConfigMap, def, to interface{}) {
	//TODO: check def and to are the same type

	metadata := &mapstructure.Metadata{}
	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		TagName:          "structs",
		Result:           &to,
		WeaklyTypedInput: true,
		Metadata:         metadata,
	})

	var errors []int
	if val, ok := conf.Data[customHTTPErrors]; ok {
		delete(conf.Data, customHTTPErrors)
		for _, i := range strings.Split(val, ",") {
			j, err := strconv.Atoi(i)
			if err != nil {
				glog.Warningf("%v is not a valid http code: %v", i, err)
			} else {
				errors = append(errors, j)
			}
		}
	}

	var skipUrls []string
	if val, ok := conf.Data[skipAccessLogUrls]; ok {
		delete(conf.Data, skipAccessLogUrls)
		skipUrls = strings.Split(val, ",")
	}

	err = decoder.Decode(conf.Data)
	if err != nil {
		glog.Infof("%v", err)
	}

	keyMap := getConfigKeyToStructKeyMap(to)
	valCM := reflect.Indirect(reflect.ValueOf(conf))
	for _, key := range metadata.Keys {
		fieldName, ok := keyMap[key]
		if !ok {
			continue
		}

		valDefault := reflect.ValueOf(&def).Elem().FieldByName(fieldName)

		fieldCM := valCM.FieldByName(fieldName)

		if valDefault.IsValid() {
			valDefault.Set(fieldCM)
		}
	}

	b, ok := def.(defaults.Backend)
	if ok {
		b.CustomHTTPErrors = filterErrors(errors)
		b.SkipAccessLogURLs = skipUrls
		if b.Resolver == "" {
			b.Resolver = "" //TODO: ngx.defResolver
		}
	}

}

func filterErrors(errCodes []int) []int {
	var fa []int
	for _, errCode := range errCodes {
		if errCode > 299 && errCode < 600 {
			fa = append(fa, errCode)
		} else {
			glog.Warningf("error code %v is not valid for custom error pages", errCode)
		}
	}

	return fa
}

func fixKeyNames(data map[string]interface{}) map[string]interface{} {
	fixed := make(map[string]interface{})
	for k, v := range data {
		fixed[toCamelCase(k)] = v
	}

	return fixed
}

func toCamelCase(src string) string {
	byteSrc := []byte(src)
	chunks := camelRegexp.FindAll(byteSrc, -1)
	for idx, val := range chunks {
		if idx > 0 {
			chunks[idx] = bytes.Title(val)
		}
	}
	return string(bytes.Join(chunks, nil))
}

// getConfigKeyToStructKeyMap returns a map with the ConfigMapKey as key and the StructName as value.
func getConfigKeyToStructKeyMap(to interface{}) map[string]string {
	keyMap := map[string]string{}
	val := reflect.Indirect(reflect.ValueOf(to))
	for i := 0; i < val.Type().NumField(); i++ {
		fieldSt := val.Type().Field(i)
		configMapKey := strings.Split(fieldSt.Tag.Get("structs"), ",")[0]
		structKey := fieldSt.Name
		keyMap[configMapKey] = structKey
	}
	return keyMap
}
<|endoftext|>


<commit_before>package local

const Version = "1.1.2"
<commit_msg>Version bump<commit_after>package local

const Version = "1.1.4"
<|endoftext|>


<commit_before>// ClawIO - Scalable Distributed High-Performance Synchronisation and Sharing Service
//
// Copyright (C) 2015  Hugo González Labrador <clawio@hugo.labkode.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. See file COPYNG.

// Package logger defines the logger used by the daemon and libraries to log information.
package logger

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"io"
)

// Logger is the interface that loggers must implement
type Logger interface {
	RID() string
	Err(msg string)
	Errf(format string, a ...interface{})
	Warning(msg string)
	Warningf(format string, a ...interface{})
	Info(msg string)
	Infof(format string, a ...interface{})
	Debug(msg string)
	Debugf(format string, a ...interface{})
}

// New creates a logger that uses logrus to log.
func New(w io.Writer, rid string) Logger {
	lgrus := logrus.New()
	lgrus.Out = w
	lgrus.Level = logrus.DebugLevel
	lgrus.Formatter = &logrus.JSONFormatter{}
	return &logger{log: lgrus, rid: rid}
}

// logger is responsible for log information to a target supported by the log implementation
type logger struct {
	log *logrus.Logger
	rid string
}

func (l *logger) prependRID(msg string) string {
	return fmt.Sprintf("rid=%s msg=%s", l.rid, msg)
}
func (l *logger) RID() string {
	return l.rid
}
func (l *logger) Err(msg string) {
	l.log.Error(l.prependRID(msg))
}
func (l *logger) Errf(format string, a ...interface{}) {
	l.log.Error(l.prependRID(fmt.Sprintf(format, a)))
}
func (l *logger) Warning(msg string) {
	l.log.Warning(l.prependRID(msg))
}
func (l *logger) Warningf(format string, a ...interface{}) {
	l.log.Warning(l.prependRID(fmt.Sprintf(format, a)))
}
func (l *logger) Info(msg string) {
	l.log.Info(l.prependRID(msg))
}
func (l *logger) Infof(format string, a ...interface{}) {
	l.log.Info(l.prependRID(fmt.Sprintf(format, a)))
}
func (l *logger) Debug(msg string) {
	l.log.Debug(l.prependRID(msg))
}
func (l *logger) Debugf(format string, a ...interface{}) {
	l.log.Debug(l.prependRID(fmt.Sprint(l.prependRID(fmt.Sprintf(format, a)))))
}
<commit_msg>Put RID filed as a JSON key<commit_after>// ClawIO - Scalable Distributed High-Performance Synchronisation and Sharing Service
//
// Copyright (C) 2015  Hugo González Labrador <clawio@hugo.labkode.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. See file COPYNG.

// Package logger defines the logger used by the daemon and libraries to log information.
package logger

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"io"
)

// Logger is the interface that loggers must implement
type Logger interface {
	RID() string
	Err(msg string)
	Errf(msg string, a ...interface{})
	Warning(msg string)
	Warningf(format string, a ...interface{})
	Info(msg string)
	Infof(format string, a ...interface{})
	Debug(msg string)
	Debugf(format string, a ...interface{})
}

// New creates a logger that uses logrus to log.
func New(w io.Writer, rid string) Logger {
	lgrus := logrus.New()
	lgrus.Out = w
	lgrus.Level = logrus.DebugLevel
	lgrus.Formatter = &logrus.JSONFormatter{}
	return &logger{log: lgrus, rid: rid}
}

// logger is responsible for log information to a target supported by the log implementation
type logger struct {
	log *logrus.Logger
	rid string
}

func (l *logger) RID() string {
	return l.rid
}
func (l *logger) Err(msg string) {
	l.log.WithField("RID", l.RID()).Error(msg)
}
func (l *logger) Errf(format string, a ...interface{}) {
	l.log.WithField("RID", l.RID()).Error(fmt.Sprintf(format, a))
}
func (l *logger) Warning(msg string) {
	l.log.WithField("RID", l.RID()).Warning(msg)
}
func (l *logger) Warningf(format string, a ...interface{}) {
	l.log.WithField("RID", l.RID()).Warning(fmt.Sprintf(format, a))
}
func (l *logger) Info(msg string) {
	l.log.WithField("RID", l.RID()).Info(msg)
}
func (l *logger) Infof(format string, a ...interface{}) {
	l.log.WithField("RID", l.RID()).Info(fmt.Sprintf(format, a))
}
func (l *logger) Debug(msg string) {
	l.log.WithField("RID", l.RID()).Debug(msg)
}
func (l *logger) Debugf(format string, a ...interface{}) {
	l.log.WithField("RID", l.RID()).Debug(fmt.Sprintf(format, a))
}
<|endoftext|>


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

package mailer

import (
	"crypto/tls"
	"fmt"
	"io"
	"net"
	"net/smtp"
	"os"
	"strings"
	"time"

	"github.com/jaytaylor/html2text"
	log "gopkg.in/clog.v1"
	"gopkg.in/gomail.v2"

	"github.com/G-Node/gogs/pkg/setting"
)

type Message struct {
	Info string // Message information for log purpose.
	*gomail.Message
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
	log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)

	msg := gomail.NewMessage()
	msg.SetHeader("From", from)
	msg.SetHeader("To", to...)
	msg.SetHeader("Subject", subject)
	msg.SetDateHeader("Date", time.Now())

	contentType := "text/html"
	body := htmlBody
	if setting.MailService.UsePlainText {
		plainBody, err := html2text.FromString(htmlBody)
		if err != nil {
			log.Error(2, "html2text.FromString: %v", err)
		} else {
			contentType = "text/plain"
			body = plainBody
		}
	}
	msg.SetBody(contentType, body)

	return &Message{
		Message: msg,
	}
}

// NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message {
	return NewMessageFrom(to, setting.MailService.From, subject, body)
}

type loginAuth struct {
	username, password string
}

// SMTP AUTH LOGIN Auth Handler
func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, fmt.Errorf("unknwon fromServer: %s", string(fromServer))
		}
	}
	return nil, nil
}

type Sender struct {
}

func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
	opts := setting.MailService

	host, port, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return err
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}

	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}
	}

	conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
	if err != nil {
		return err
	}
	defer conn.Close()

	isSecureConn := false
	// Start TLS directly if the port ends with 465 (SMTPS protocol)
	if strings.HasSuffix(port, "465") {
		conn = tls.Client(conn, tlsconfig)
		isSecureConn = true
	}

	client, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("NewClient: %v", err)
	}

	if !opts.DisableHelo {
		hostname := opts.HeloHostname
		if len(hostname) == 0 {
			hostname, err = os.Hostname()
			if err != nil {
				return err
			}
		}

		if err = client.Hello(hostname); err != nil {
			return fmt.Errorf("Hello: %v", err)
		}
	}

	// If not using SMTPS, alway use STARTTLS if available
	hasStartTLS, _ := client.Extension("STARTTLS")
	if !isSecureConn && hasStartTLS {
		if err = client.StartTLS(tlsconfig); err != nil {
			return fmt.Errorf("StartTLS: %v", err)
		}
	}

	canAuth, options := client.Extension("AUTH")
	if canAuth && len(opts.User) > 0 {
		var auth smtp.Auth

		if strings.Contains(options, "CRAM-MD5") {
			auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
		} else if strings.Contains(options, "PLAIN") {
			auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
		} else if strings.Contains(options, "LOGIN") {
			// Patch for AUTH LOGIN
			auth = LoginAuth(opts.User, opts.Passwd)
		}

		if auth != nil {
			if err = client.Auth(auth); err != nil {
				return fmt.Errorf("Auth: %v", err)
			}
		}
	}

	if err = client.Mail(from); err != nil {
		return fmt.Errorf("Mail: %v", err)
	}

	for _, rec := range to {
		if err = client.Rcpt(rec); err != nil {
			return fmt.Errorf("Rcpt: %v", err)
		}
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("Data: %v", err)
	} else if _, err = msg.WriteTo(w); err != nil {
		return fmt.Errorf("WriteTo: %v", err)
	} else if err = w.Close(); err != nil {
		return fmt.Errorf("Close: %v", err)
	}

	return client.Quit()
}

func processMailQueue() {
	sender := &Sender{}

	for {
		select {
		case msg := <-mailQueue:
			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
			if err := gomail.Send(sender, msg.Message); err != nil {
				log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
			} else {
				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
			}
		}
	}
}

var mailQueue chan *Message

func NewContext() {
	// Need to check if mailQueue is nil because in during reinstall (user had installed
	// before but swithed install lock off), this function will be called again
	// while mail queue is already processing tasks, and produces a race condition.
	if setting.MailService == nil || mailQueue != nil {
		return
	}

	mailQueue = make(chan *Message, setting.MailService.QueueLength)
	go processMailQueue()
}

func SendAsync(msg *Message) {
	go func() {
		mailQueue <- msg
	}()
}
<commit_msg>[Mail] send mails to bcc<commit_after>// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package mailer

import (
	"crypto/tls"
	"fmt"
	"io"
	"net"
	"net/smtp"
	"os"
	"strings"
	"time"

	"github.com/jaytaylor/html2text"
	log "gopkg.in/clog.v1"
	"gopkg.in/gomail.v2"

	"github.com/G-Node/gogs/pkg/setting"
)

type Message struct {
	Info string // Message information for log purpose.
	*gomail.Message
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
	log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)

	msg := gomail.NewMessage()
	msg.SetHeader("From", from)
	msg.SetHeader("Bcc", to...)
	msg.SetHeader("Subject", subject)
	msg.SetDateHeader("Date", time.Now())

	contentType := "text/html"
	body := htmlBody
	if setting.MailService.UsePlainText {
		plainBody, err := html2text.FromString(htmlBody)
		if err != nil {
			log.Error(2, "html2text.FromString: %v", err)
		} else {
			contentType = "text/plain"
			body = plainBody
		}
	}
	msg.SetBody(contentType, body)

	return &Message{
		Message: msg,
	}
}

// NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message {
	return NewMessageFrom(to, setting.MailService.From, subject, body)
}

type loginAuth struct {
	username, password string
}

// SMTP AUTH LOGIN Auth Handler
func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, fmt.Errorf("unknwon fromServer: %s", string(fromServer))
		}
	}
	return nil, nil
}

type Sender struct {
}

func (s *Sender) Send(from string, to []string, msg io.WriterTo) error {
	opts := setting.MailService

	host, port, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return err
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}

	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}
	}

	conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
	if err != nil {
		return err
	}
	defer conn.Close()

	isSecureConn := false
	// Start TLS directly if the port ends with 465 (SMTPS protocol)
	if strings.HasSuffix(port, "465") {
		conn = tls.Client(conn, tlsconfig)
		isSecureConn = true
	}

	client, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("NewClient: %v", err)
	}

	if !opts.DisableHelo {
		hostname := opts.HeloHostname
		if len(hostname) == 0 {
			hostname, err = os.Hostname()
			if err != nil {
				return err
			}
		}

		if err = client.Hello(hostname); err != nil {
			return fmt.Errorf("Hello: %v", err)
		}
	}

	// If not using SMTPS, alway use STARTTLS if available
	hasStartTLS, _ := client.Extension("STARTTLS")
	if !isSecureConn && hasStartTLS {
		if err = client.StartTLS(tlsconfig); err != nil {
			return fmt.Errorf("StartTLS: %v", err)
		}
	}

	canAuth, options := client.Extension("AUTH")
	if canAuth && len(opts.User) > 0 {
		var auth smtp.Auth

		if strings.Contains(options, "CRAM-MD5") {
			auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
		} else if strings.Contains(options, "PLAIN") {
			auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
		} else if strings.Contains(options, "LOGIN") {
			// Patch for AUTH LOGIN
			auth = LoginAuth(opts.User, opts.Passwd)
		}

		if auth != nil {
			if err = client.Auth(auth); err != nil {
				return fmt.Errorf("Auth: %v", err)
			}
		}
	}

	if err = client.Mail(from); err != nil {
		return fmt.Errorf("Mail: %v", err)
	}

	for _, rec := range to {
		if err = client.Rcpt(rec); err != nil {
			return fmt.Errorf("Rcpt: %v", err)
		}
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("Data: %v", err)
	} else if _, err = msg.WriteTo(w); err != nil {
		return fmt.Errorf("WriteTo: %v", err)
	} else if err = w.Close(); err != nil {
		return fmt.Errorf("Close: %v", err)
	}

	return client.Quit()
}

func processMailQueue() {
	sender := &Sender{}

	for {
		select {
		case msg := <-mailQueue:
			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
			if err := gomail.Send(sender, msg.Message); err != nil {
				log.Error(3, "Fail to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
			} else {
				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
			}
		}
	}
}

var mailQueue chan *Message

func NewContext() {
	// Need to check if mailQueue is nil because in during reinstall (user had installed
	// before but swithed install lock off), this function will be called again
	// while mail queue is already processing tasks, and produces a race condition.
	if setting.MailService == nil || mailQueue != nil {
		return
	}

	mailQueue = make(chan *Message, setting.MailService.QueueLength)
	go processMailQueue()
}

func SendAsync(msg *Message) {
	go func() {
		mailQueue <- msg
	}()
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
	"fmt"
	"github.com/blang/semver"
	"github.com/golang/glog"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/apis/kops/util"
	"strings"
)

type KopsModelContext struct {
	Region         string
	Cluster        *kops.Cluster
	InstanceGroups []*kops.InstanceGroup

	SSHPublicKeys [][]byte
}

// Will attempt to calculate a meaningful name for an ELB given a prefix
// Will never return a string longer than 32 chars
func (m *KopsModelContext) GetELBName32(prefix string) (string, error) {
	var returnString string
	c := m.Cluster.ObjectMeta.Name
	s := strings.Split(c, ".")

	// TODO: We used to have this...
	//master-{{ replace .ClusterName "." "-" }}
	// TODO: strings.Split cannot return empty
	if len(s) > 0 {
		returnString = fmt.Sprintf("%s-%s", prefix, s[0])
	} else {
		returnString = fmt.Sprintf("%s-%s", prefix, c)
	}
	if len(returnString) > 32 {
		returnString = returnString[:32]
	}
	return returnString, nil
}

func (m *KopsModelContext) ClusterName() string {
	return m.Cluster.ObjectMeta.Name
}

// GatherSubnets maps the subnet names in an InstanceGroup to the ClusterSubnetSpec objects (which are stored on the Cluster)
func (m *KopsModelContext) GatherSubnets(ig *kops.InstanceGroup) ([]*kops.ClusterSubnetSpec, error) {
	var subnets []*kops.ClusterSubnetSpec
	for _, subnetName := range ig.Spec.Subnets {
		var matches []*kops.ClusterSubnetSpec
		for i := range m.Cluster.Spec.Subnets {
			clusterSubnet := &m.Cluster.Spec.Subnets[i]
			if clusterSubnet.Name == subnetName {
				matches = append(matches, clusterSubnet)
			}
		}
		if len(matches) == 0 {
			return nil, fmt.Errorf("subnet not found: %q", subnetName)
		}
		if len(matches) > 1 {
			return nil, fmt.Errorf("found multiple subnets with name: %q", subnetName)
		}
		subnets = append(subnets, matches[0])
	}
	return subnets, nil
}

// FindInstanceGroup returns the instance group with the matching Name (or nil if not found)
func (m *KopsModelContext) FindInstanceGroup(name string) *kops.InstanceGroup {
	for _, ig := range m.InstanceGroups {
		if ig.ObjectMeta.Name == name {
			return ig
		}
	}
	return nil
}

// FindSubnet returns the subnet with the matching Name (or nil if not found)
func (m *KopsModelContext) FindSubnet(name string) *kops.ClusterSubnetSpec {
	for i := range m.Cluster.Spec.Subnets {
		s := &m.Cluster.Spec.Subnets[i]
		if s.Name == name {
			return s
		}
	}
	return nil
}

// MasterInstanceGroups returns InstanceGroups with the master role
func (m *KopsModelContext) MasterInstanceGroups() []*kops.InstanceGroup {
	var groups []*kops.InstanceGroup
	for _, ig := range m.InstanceGroups {
		if !ig.IsMaster() {
			continue
		}
		groups = append(groups, ig)
	}
	return groups
}

// NodeInstanceGroups returns InstanceGroups with the node role
func (m *KopsModelContext) NodeInstanceGroups() []*kops.InstanceGroup {
	var groups []*kops.InstanceGroup
	for _, ig := range m.InstanceGroups {
		if ig.Spec.Role != kops.InstanceGroupRoleNode {
			continue
		}
		groups = append(groups, ig)
	}
	return groups
}

// CloudTagsForInstanceGroup computes the tags to apply to instances in the specified InstanceGroup
func (m *KopsModelContext) CloudTagsForInstanceGroup(ig *kops.InstanceGroup) (map[string]string, error) {
	labels := make(map[string]string)

	// Apply any user-specified labels
	for k, v := range ig.Spec.CloudLabels {
		labels[k] = v
	}

	// The system tags take priority because the cluster likely breaks without them...

	if ig.Spec.Role == kops.InstanceGroupRoleMaster {
		labels["k8s.io/role/master"] = "1"
	}

	if ig.Spec.Role == kops.InstanceGroupRoleNode {
		labels["k8s.io/role/node"] = "1"
	}

	if ig.Spec.Role == kops.InstanceGroupRoleBastion {
		labels["k8s.io/role/bastion"] = "1"
	}

	return labels, nil
}

func (m *KopsModelContext) UsesBastionDns() bool {
	if m.Cluster.Spec.Topology.Bastion != nil && m.Cluster.Spec.Topology.Bastion.BastionPublicName != "" {
		return true
	}
	return false
}

func (m *KopsModelContext) UsesSSHBastion() bool {
	for _, ig := range m.InstanceGroups {
		if ig.Spec.Role == kops.InstanceGroupRoleBastion {
			return true
		}
	}

	return false
}

func (m *KopsModelContext) UseLoadBalancerForAPI() bool {
	if m.Cluster.Spec.API == nil {
		return false
	}
	return m.Cluster.Spec.API.LoadBalancer != nil
}

func (m *KopsModelContext) UsePrivateDNS() bool {
	topology := m.Cluster.Spec.Topology
	if topology != nil && topology.DNS != nil {
		switch topology.DNS.Type {
		case kops.DNSTypePublic:
			return false
		case kops.DNSTypePrivate:
			return true

		default:
			glog.Warningf("Unknown DNS type %q", topology.DNS.Type)
			return false
		}
	}

	return false
}

// KubernetesVersion parses the semver version of kubernetes, from the cluster spec
func (c *KopsModelContext) KubernetesVersion() (semver.Version, error) {
	kubernetesVersion := c.Cluster.Spec.KubernetesVersion

	if kubernetesVersion == "" {
		return semver.Version{}, fmt.Errorf("KubernetesVersion is required")
	}

	sv, err := util.ParseKubernetesVersion(kubernetesVersion)
	if err != nil {
		return semver.Version{}, fmt.Errorf("unable to determine kubernetes version from %q", kubernetesVersion)
	}

	return *sv, nil
}

// VersionGTE is a simplified semver comparison
func VersionGTE(version semver.Version, major uint64, minor uint64) bool {
	if version.Major > major {
		return true
	}
	if version.Major > major {
		return true
	}
	if version.Major == major && version.Minor >= minor {
		return true
	}
	return false
}
<commit_msg>Fixes per code review<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
	"fmt"
	"github.com/blang/semver"
	"github.com/golang/glog"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/apis/kops/util"
	"strings"
)

type KopsModelContext struct {
	Region         string
	Cluster        *kops.Cluster
	InstanceGroups []*kops.InstanceGroup

	SSHPublicKeys [][]byte
}

// Will attempt to calculate a meaningful name for an ELB given a prefix
// Will never return a string longer than 32 chars
func (m *KopsModelContext) GetELBName32(prefix string) (string, error) {
	var returnString string
	c := m.Cluster.ObjectMeta.Name
	s := strings.Split(c, ".")

	// TODO: We used to have this...
	//master-{{ replace .ClusterName "." "-" }}
	// TODO: strings.Split cannot return empty
	if len(s) > 0 {
		returnString = fmt.Sprintf("%s-%s", prefix, s[0])
	} else {
		returnString = fmt.Sprintf("%s-%s", prefix, c)
	}
	if len(returnString) > 32 {
		returnString = returnString[:32]
	}
	return returnString, nil
}

func (m *KopsModelContext) ClusterName() string {
	return m.Cluster.ObjectMeta.Name
}

// GatherSubnets maps the subnet names in an InstanceGroup to the ClusterSubnetSpec objects (which are stored on the Cluster)
func (m *KopsModelContext) GatherSubnets(ig *kops.InstanceGroup) ([]*kops.ClusterSubnetSpec, error) {
	var subnets []*kops.ClusterSubnetSpec
	for _, subnetName := range ig.Spec.Subnets {
		var matches []*kops.ClusterSubnetSpec
		for i := range m.Cluster.Spec.Subnets {
			clusterSubnet := &m.Cluster.Spec.Subnets[i]
			if clusterSubnet.Name == subnetName {
				matches = append(matches, clusterSubnet)
			}
		}
		if len(matches) == 0 {
			return nil, fmt.Errorf("subnet not found: %q", subnetName)
		}
		if len(matches) > 1 {
			return nil, fmt.Errorf("found multiple subnets with name: %q", subnetName)
		}
		subnets = append(subnets, matches[0])
	}
	return subnets, nil
}

// FindInstanceGroup returns the instance group with the matching Name (or nil if not found)
func (m *KopsModelContext) FindInstanceGroup(name string) *kops.InstanceGroup {
	for _, ig := range m.InstanceGroups {
		if ig.ObjectMeta.Name == name {
			return ig
		}
	}
	return nil
}

// FindSubnet returns the subnet with the matching Name (or nil if not found)
func (m *KopsModelContext) FindSubnet(name string) *kops.ClusterSubnetSpec {
	for i := range m.Cluster.Spec.Subnets {
		s := &m.Cluster.Spec.Subnets[i]
		if s.Name == name {
			return s
		}
	}
	return nil
}

// MasterInstanceGroups returns InstanceGroups with the master role
func (m *KopsModelContext) MasterInstanceGroups() []*kops.InstanceGroup {
	var groups []*kops.InstanceGroup
	for _, ig := range m.InstanceGroups {
		if !ig.IsMaster() {
			continue
		}
		groups = append(groups, ig)
	}
	return groups
}

// NodeInstanceGroups returns InstanceGroups with the node role
func (m *KopsModelContext) NodeInstanceGroups() []*kops.InstanceGroup {
	var groups []*kops.InstanceGroup
	for _, ig := range m.InstanceGroups {
		if ig.Spec.Role != kops.InstanceGroupRoleNode {
			continue
		}
		groups = append(groups, ig)
	}
	return groups
}

// CloudTagsForInstanceGroup computes the tags to apply to instances in the specified InstanceGroup
func (m *KopsModelContext) CloudTagsForInstanceGroup(ig *kops.InstanceGroup) (map[string]string, error) {
	labels := make(map[string]string)

	// Apply any user-specified labels
	for k, v := range ig.Spec.CloudLabels {
		labels[k] = v
	}

	// The system tags take priority because the cluster likely breaks without them...

	if ig.Spec.Role == kops.InstanceGroupRoleMaster {
		labels["k8s.io/role/master"] = "1"
	}

	if ig.Spec.Role == kops.InstanceGroupRoleNode {
		labels["k8s.io/role/node"] = "1"
	}

	if ig.Spec.Role == kops.InstanceGroupRoleBastion {
		labels["k8s.io/role/bastion"] = "1"
	}

	return labels, nil
}

func (m *KopsModelContext) UsesBastionDns() bool {
	if m.Cluster.Spec.Topology.Bastion != nil && m.Cluster.Spec.Topology.Bastion.BastionPublicName != "" {
		return true
	}
	return false
}

func (m *KopsModelContext) UsesSSHBastion() bool {
	for _, ig := range m.InstanceGroups {
		if ig.Spec.Role == kops.InstanceGroupRoleBastion {
			return true
		}
	}

	return false
}

func (m *KopsModelContext) UseLoadBalancerForAPI() bool {
	if m.Cluster.Spec.API == nil {
		return false
	}
	return m.Cluster.Spec.API.LoadBalancer != nil
}

func (m *KopsModelContext) UsePrivateDNS() bool {
	topology := m.Cluster.Spec.Topology
	if topology != nil && topology.DNS != nil {
		switch topology.DNS.Type {
		case kops.DNSTypePublic:
			return false
		case kops.DNSTypePrivate:
			return true

		default:
			glog.Warningf("Unknown DNS type %q", topology.DNS.Type)
			return false
		}
	}

	return false
}

// KubernetesVersion parses the semver version of kubernetes, from the cluster spec
func (c *KopsModelContext) KubernetesVersion() (semver.Version, error) {
	// TODO: Remove copy-pasting c.f. https://github.com/kubernetes/kops/blob/master/pkg/model/components/context.go#L32

	kubernetesVersion := c.Cluster.Spec.KubernetesVersion

	if kubernetesVersion == "" {
		return semver.Version{}, fmt.Errorf("KubernetesVersion is required")
	}

	sv, err := util.ParseKubernetesVersion(kubernetesVersion)
	if err != nil {
		return semver.Version{}, fmt.Errorf("unable to determine kubernetes version from %q", kubernetesVersion)
	}

	return *sv, nil
}

// VersionGTE is a simplified semver comparison
func VersionGTE(version semver.Version, major uint64, minor uint64) bool {
	if version.Major > major {
		return true
	}
	if version.Major == major && version.Minor >= minor {
		return true
	}
	return false
}
<|endoftext|>