VMware vSphere Integrated Containers provider (#206)

* Add Virtual Kubelet provider for VIC

Initial virtual kubelet provider for VMware VIC.  This provider currently
handles creating and starting of a pod VM via the VIC portlayer and persona
server.  Image store handling via the VIC persona server.  This provider
currently requires the feature/wolfpack branch of VIC.

* Added pod stop and delete.  Also added node capacity.

Added the ability to stop and delete pod VMs via VIC.  Also retrieve
node capacity information from the VCH.

* Cleanup and readme file

Some file clean up and added a Readme.md markdown file for the VIC
provider.

* Cleaned up errors, added function comments, moved operation code

1. Cleaned up error handling.  Set standard for creating errors.
2. Added method prototype comments for all interface functions.
3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder.

* Add mocking code and unit tests for podcache, podcreator, and podstarter

Used the unit test framework used in VIC to handle assertions in the provider's
unit test.  Mocking code generated using OSS project mockery, which is compatible
with the testify assertion framework.

* Vendored packages for the VIC provider

Requires feature/wolfpack branch of VIC and a few specific commit sha of
projects used within VIC.

* Implementation of POD Stopper and Deleter unit tests (#4)

* Updated files for initial PR
This commit is contained in:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

View File

@@ -0,0 +1,466 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/certificate"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
// CertFactory has all input parameters for vic-machine certificate commands needed to create a certificate
type CertFactory struct {
Networks
CertPath string
DisplayName string
Scert string
Skey string
Ccert string
Ckey string
Cacert string
Cakey string
ClientCert *tls.Certificate
ClientCAsArg cli.StringSlice `arg:"tls-ca"`
ClientCAs []byte
EnvFile string
Cname string
Org cli.StringSlice
KeySize int
NoTLS bool
NoTLSverify bool
KeyPEM []byte
CertPEM []byte
NoSaveToDisk bool
}
func (c *CertFactory) CertFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "tls-server-key",
Value: "",
Usage: "Virtual Container Host private key file (server certificate)",
Destination: &c.Skey,
},
cli.StringFlag{
Name: "tls-server-cert",
Value: "",
Usage: "Virtual Container Host x509 certificate file (server certificate)",
Destination: &c.Scert,
},
cli.StringFlag{
Name: "tls-cname",
Value: "",
Usage: "Common Name to use in generated CA certificate when requiring client certificate authentication",
Destination: &c.Cname,
},
cli.StringFlag{
Name: "tls-cert-path",
Value: "",
Usage: "The path to check for existing certificates and in which to save generated certificates. Defaults to './<vch name>/'",
Destination: &c.CertPath,
},
cli.BoolFlag{
Name: "no-tlsverify, kv",
Usage: "Disable authentication via client certificates - for more tls options see advanced help (-x)",
Destination: &c.NoTLSverify,
},
cli.StringSliceFlag{
Name: "organization",
Usage: "A list of identifiers to record in the generated certificates. Defaults to VCH name and IP/FQDN if not provided.",
Value: &c.Org,
Hidden: true,
},
cli.IntFlag{
Name: "certificate-key-size, ksz",
Usage: "Size of key to use when generating certificates",
Value: 2048,
Destination: &c.KeySize,
Hidden: true,
},
cli.StringSliceFlag{
Name: "tls-ca, ca",
Usage: "Specify a list of certificate authority files to use for client verification",
Value: &c.ClientCAsArg,
Hidden: true,
},
}
}
func (c *CertFactory) ProcessCertificates(op trace.Operation, displayName string, force bool, debug int) error {
// set up the locations for the certificates and env file
if c.CertPath == "" {
c.CertPath = displayName
}
c.EnvFile = fmt.Sprintf("%s/%s.env", c.CertPath, displayName)
// check for insecure case
if c.NoTLS {
op.Warn("Configuring without TLS - all communications will be insecure")
return nil
}
if c.Scert != "" && c.Skey == "" {
return cli.NewExitError("key and cert should be specified at the same time", 1)
}
if c.Scert == "" && c.Skey != "" {
return cli.NewExitError("key and cert should be specified at the same time", 1)
}
// if we've not got a specific CommonName but do have a static IP then go with that.
if c.Cname == "" {
if c.ClientNetworkIP != "" {
c.Cname = c.ClientNetworkIP
op.Infof("Using client-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
} else if c.PublicNetworkIP != "" && (c.PublicNetworkName == c.ClientNetworkName || c.ClientNetworkName == "") {
c.Cname = c.PublicNetworkIP
op.Infof("Using public-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
} else if c.ManagementNetworkIP != "" && (c.ManagementNetworkName == c.ClientNetworkName || (c.ClientNetworkName == "" && c.ManagementNetworkName == c.PublicNetworkName)) {
c.Cname = c.ManagementNetworkIP
op.Infof("Using management-network-ip as cname where needed - use --tls-cname to override: %s", c.Cname)
}
if c.Cname != "" {
// Strip network mask from IP address if set.
// #nosec: Errors unhandled.
if cnameIP, _, _ := net.ParseCIDR(c.Cname); cnameIP != nil {
c.Cname = cnameIP.String()
}
}
}
// load what certificates we can
cas, keypair, err := c.loadCertificates(op, debug)
if err != nil {
op.Errorf("Unable to load certificates: %s", err)
if !force {
return err
}
op.Warnf("Ignoring error loading certificates due to --force")
cas = nil
keypair = nil
err = nil
}
// we need to generate some part of the certificate configuration
gcas, gkeypair, err := c.generateCertificates(op, keypair == nil, !c.NoTLSverify && len(cas) == 0)
if err != nil {
op.Error("cannot continue: unable to generate certificates")
return err
}
if keypair != nil {
c.KeyPEM = keypair.KeyPEM
c.CertPEM = keypair.CertPEM
} else if gkeypair != nil {
c.KeyPEM = gkeypair.KeyPEM
c.CertPEM = gkeypair.CertPEM
}
if len(cas) == 0 {
cas = gcas
}
if len(c.KeyPEM) == 0 {
return errors.New("Failed to load or generate server certificates")
}
if len(cas) == 0 && !c.NoTLSverify {
return errors.New("Failed to load or generate certificate authority")
}
// do we have key, cert, and --no-tlsverify
if c.NoTLSverify || len(cas) == 0 {
op.Warnf("Configuring without TLS verify - certificate-based authentication disabled")
return nil
}
c.ClientCAs = cas
return nil
}
// loadCertificates returns the client CA pool and the keypair for server certificates on success
func (c *CertFactory) loadCertificates(op trace.Operation, debug int) ([]byte, *certificate.KeyPair, error) {
defer trace.End(trace.Begin("", op))
// reads each of the files specified, assuming that they are PEM encoded certs,
// and constructs a byte array suitable for passing to CertPool.AppendCertsFromPEM
var certs []byte
for _, f := range c.ClientCAsArg {
b, err := ioutil.ReadFile(f)
if err != nil {
err = errors.Errorf("Failed to load authority from file %s: %s", f, err)
return nil, nil, err
}
certs = append(certs, b...)
op.Infof("Loaded CA from %s", f)
}
var keypair *certificate.KeyPair
// default names
skey := filepath.Join(c.CertPath, certificate.ServerKey)
scert := filepath.Join(c.CertPath, certificate.ServerCert)
ca := filepath.Join(c.CertPath, certificate.CACert)
ckey := filepath.Join(c.CertPath, certificate.ClientKey)
ccert := filepath.Join(c.CertPath, certificate.ClientCert)
// if specific files are supplied, use those
explicit := false
if c.Scert != "" && c.Skey != "" {
explicit = true
skey = c.Skey
scert = c.Scert
}
// load the server certificate
keypair = certificate.NewKeyPair(scert, skey, nil, nil)
if err := keypair.LoadCertificate(); err != nil {
if explicit || !os.IsNotExist(err) {
// if these files were explicit paths, or anything other than not found, fail
op.Errorf("Failed to load certificate: %s", err)
return certs, nil, err
}
op.Debugf("Unable to locate existing server certificate in cert path")
return nil, nil, nil
}
// check that any supplied cname matches the server cert CN
cert, err := keypair.Certificate()
if err != nil {
op.Errorf("Failed to parse certificate: %s", err)
return certs, nil, err
}
if cert.Leaf == nil {
op.Warnf("Failed to load x509 leaf: Unable to confirm server certificate cname matches provided cname %q. Continuing...", c.Cname)
} else {
// We just do a direct equality check here - trying to be clever is liable to lead to hard
// to diagnose errors
if cert.Leaf.Subject.CommonName != c.Cname {
op.Errorf("Provided cname does not match that in existing server certificate: %s", cert.Leaf.Subject.CommonName)
if debug > 2 {
op.Debugf("Certificate does not match provided cname: %#+v", cert.Leaf)
}
return certs, nil, fmt.Errorf("cname option doesn't match existing server certificate in certificate path %s", c.CertPath)
}
}
op.Infof("Loaded server certificate %s", scert)
c.Skey = skey
c.Scert = scert
// only try for CA certificate if no-tlsverify has NOT been specified and we haven't already loaded an authority cert
if !c.NoTLSverify && len(certs) == 0 {
b, err := ioutil.ReadFile(ca)
if err != nil {
if os.IsNotExist(err) {
op.Debugf("Unable to locate existing CA in cert path")
return certs, keypair, nil
}
// if the CA exists but cannot be loaded then it's an error
op.Errorf("Failed to load authority from certificate path %s: %s", c.CertPath, err)
return certs, keypair, errors.New("failed to load certificate authority")
}
c.Cacert = ca
op.Infof("Loaded CA with default name from certificate path %s", c.CertPath)
certs = b
// load client certs - we ensure the client certs validate with the provided CA or ignore any we find
cpair := certificate.NewKeyPair(ccert, ckey, nil, nil)
if err := cpair.LoadCertificate(); err != nil {
op.Warnf("Unable to load client certificate - validation of API endpoint will be best effort only: %s", err)
}
clientCert, err := certificate.VerifyClientCert(certs, cpair)
if err != nil {
switch err.(type) {
case certificate.CertParseError, certificate.CreateCAPoolError:
op.Debug(err)
case certificate.CertVerifyError:
op.Warnf("%s - continuing without client certificate", err)
}
return certs, keypair, nil
}
c.Ckey = ckey
c.Ccert = ccert
c.ClientCert = clientCert
op.Infof("Loaded client certificate with default name from certificate path %s", c.CertPath)
}
return certs, keypair, nil
}
func (c *CertFactory) generateCertificates(op trace.Operation, server bool, client bool) ([]byte, *certificate.KeyPair, error) {
defer trace.End(trace.Begin("", op))
if !server && !client {
op.Debug("Not generating server or client certs, nothing for generateCertificates to do")
return nil, nil, nil
}
var certs []byte
// generate the certs and keys with names conforming the default the docker client expects
files, err := ioutil.ReadDir(c.CertPath)
if len(files) > 0 {
return nil, nil, fmt.Errorf("Specified directory to store certificates is not empty. Specify a new path in which to store generated certificates using --tls-cert-path or remove the contents of \"%s\" and run vic-machine again.", c.CertPath)
}
if !c.NoSaveToDisk {
err = os.MkdirAll(c.CertPath, 0700)
if err != nil {
op.Errorf("Unable to make directory \"%s\" to hold certificates (set via --tls-cert-path)", c.CertPath)
return nil, nil, err
}
}
c.Skey = filepath.Join(c.CertPath, certificate.ServerKey)
c.Scert = filepath.Join(c.CertPath, certificate.ServerCert)
c.Ckey = filepath.Join(c.CertPath, certificate.ClientKey)
c.Ccert = filepath.Join(c.CertPath, certificate.ClientCert)
cakey := filepath.Join(c.CertPath, certificate.CAKey)
c.Cacert = filepath.Join(c.CertPath, certificate.CACert)
if server && !client {
op.Infof("Generating self-signed certificate/key pair - private key in %s", c.Skey)
keypair := certificate.NewKeyPair(c.Scert, c.Skey, nil, nil)
err := keypair.CreateSelfSigned(c.Cname, nil, c.KeySize)
if err != nil {
op.Errorf("Failed to generate self-signed certificate: %s", err)
return nil, nil, err
}
if !c.NoSaveToDisk {
if err = keypair.SaveCertificate(); err != nil {
op.Errorf("Failed to save server certificates: %s", err)
return nil, nil, err
}
}
return certs, keypair, nil
}
// client auth path
if c.Cname == "" {
op.Error("Common Name must be provided when generating certificates for client authentication:")
op.Info(" --tls-cname=<FQDN or static IP> # for the appliance VM")
op.Info(" --tls-cname=<*.yourdomain.com> # if DNS has entries in that form for DHCP addresses (less secure)")
op.Info(" --no-tlsverify # disables client authentication (anyone can connect to the VCH)")
op.Info(" --no-tls # disables TLS entirely")
op.Info("")
return certs, nil, errors.New("provide Common Name for server certificate")
}
// for now re-use the display name as the organisation if unspecified
if len(c.Org) == 0 {
c.Org = []string{c.DisplayName}
}
if len(c.Org) == 1 && !strings.HasPrefix(c.Cname, "*") {
// Add in the cname if it's not a wildcard
c.Org = append(c.Org, c.Cname)
}
// Certificate authority
op.Infof("Generating CA certificate/key pair - private key in %s", cakey)
cakp := certificate.NewKeyPair(c.Cacert, cakey, nil, nil)
err = cakp.CreateRootCA(c.Cname, c.Org, c.KeySize)
if err != nil {
op.Errorf("Failed to generate CA: %s", err)
return nil, nil, err
}
if !c.NoSaveToDisk {
if err = cakp.SaveCertificate(); err != nil {
op.Errorf("Failed to save CA certificates: %s", err)
return nil, nil, err
}
}
// Server certificates
var skp *certificate.KeyPair
if server {
op.Infof("Generating server certificate/key pair - private key in %s", c.Skey)
skp = certificate.NewKeyPair(c.Scert, c.Skey, nil, nil)
err = skp.CreateServerCertificate(c.Cname, c.Org, c.KeySize, cakp)
if err != nil {
op.Errorf("Failed to generate server certificates: %s", err)
return nil, nil, err
}
if !c.NoSaveToDisk {
if err = skp.SaveCertificate(); err != nil {
op.Errorf("Failed to save server certificates: %s", err)
return nil, nil, err
}
}
}
// Client certificates
if client {
op.Infof("Generating client certificate/key pair - private key in %s", c.Ckey)
ckp := certificate.NewKeyPair(c.Ccert, c.Ckey, nil, nil)
err = ckp.CreateClientCertificate(c.Cname, c.Org, c.KeySize, cakp)
if err != nil {
op.Errorf("Failed to generate client certificates: %s", err)
return nil, nil, err
}
if !c.NoSaveToDisk {
if err = ckp.SaveCertificate(); err != nil {
op.Errorf("Failed to save client certificates: %s", err)
return nil, nil, err
}
}
c.ClientCert, err = ckp.Certificate()
if err != nil {
op.Warnf("Failed to stash client certificate for later application level validation: %s", err)
}
// If openssl is present, try to generate a browser friendly pfx file (a bundle of the public certificate AND the private key)
// The pfx file can be imported directly into keychains for client certificate authentication
certPath := filepath.Clean(c.CertPath)
args := strings.Split(fmt.Sprintf("pkcs12 -export -out %[1]s/cert.pfx -inkey %[1]s/key.pem -in %[1]s/cert.pem -certfile %[1]s/ca.pem -password pass:", certPath), " ")
// #nosec: Subprocess launching with variable
pfx := exec.Command("openssl", args...)
out, err := pfx.CombinedOutput()
if err != nil {
op.Debug(out)
op.Warnf("Failed to generate browser friendly PFX client certificate: %s", err)
} else {
op.Infof("Generated browser friendly PFX client certificate - certificate in %s/cert.pfx", certPath)
}
}
return cakp.CertPEM, skp, nil
}

View File

@@ -0,0 +1,60 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"context"
"flag"
"fmt"
"os"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
var (
cs = &CertFactory{}
)
func TestGenKey(t *testing.T) {
log.SetLevel(log.DebugLevel)
os.Args = []string{"cmd", "create"}
flag.Parse()
cs.NoTLS = false
cs.CertPath = "install-test"
cs.Cname = "common name"
cs.KeySize = 1024
op := trace.NewOperation(context.Background(), "TestGenKey")
ca, kp, err := cs.generateCertificates(op, true, true)
defer os.RemoveAll(fmt.Sprintf("./%s", cs.CertPath))
assert.NoError(t, err, "Expected to cleanly generate certificates")
assert.NotEmpty(t, ca, "Expected CA to contain data")
assert.NotNil(t, kp, "Expected keypair to contain data")
assert.NotEmpty(t, kp.CertPEM, "Expected certificate to contain data")
assert.NotEmpty(t, kp.CertPEM, "Expected key to contain data")
ca, kp, err = cs.loadCertificates(op, 2)
assert.NoError(t, err, "Expected to cleanly load certificates")
assert.NotEmpty(t, ca, "Expected CA to contain data")
assert.NotNil(t, kp, "Expected keypair to contain data")
assert.NotEmpty(t, kp.CertPEM, "Expected certificate to contain data")
assert.NotEmpty(t, kp.CertPEM, "Expected key to contain data")
}

View File

@@ -0,0 +1,317 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"encoding"
"fmt"
"net"
"strings"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/lib/config/executor"
"github.com/vmware/vic/pkg/ip"
"github.com/vmware/vic/pkg/trace"
)
// CNetworks holds user input from container network flags
type CNetworks struct {
ContainerNetworks cli.StringSlice `arg:"container-network"`
ContainerNetworksGateway cli.StringSlice `arg:"container-network-gateway"`
ContainerNetworksIPRanges cli.StringSlice `arg:"container-network-ip-range"`
ContainerNetworksDNS cli.StringSlice `arg:"container-network-dns"`
ContainerNetworksFirewall cli.StringSlice `arg:"container-network-firewall"`
IsSet bool
}
// ContainerNetworks holds container network data after processing
type ContainerNetworks struct {
MappedNetworks map[string]string `cmd:"parent" label:"key-value"`
MappedNetworksGateways map[string]net.IPNet `cmd:"gateway" label:"key-value"`
MappedNetworksIPRanges map[string][]ip.Range `cmd:"ip-range" label:"key-value"`
MappedNetworksDNS map[string][]net.IP `cmd:"dns" label:"key-value"`
MappedNetworksFirewalls map[string]executor.TrustLevel `cmd:"firewall" label:"key-value"`
}
func (c *ContainerNetworks) IsSet() bool {
return len(c.MappedNetworks) > 0 ||
len(c.MappedNetworksGateways) > 0 ||
len(c.MappedNetworksIPRanges) > 0 ||
len(c.MappedNetworksDNS) > 0 ||
len(c.MappedNetworksFirewalls) > 0
}
func (c *CNetworks) CNetworkFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "container-network, cn",
Value: &c.ContainerNetworks,
Usage: "vSphere network list that containers can use directly with labels, e.g. vsphere-net:backend. Defaults to DCHP - see advanced help (-x).",
},
cli.StringSliceFlag{
Name: "container-network-gateway, cng",
Value: &c.ContainerNetworksGateway,
Usage: "Gateway for the container network's subnet in CONTAINER-NETWORK:SUBNET format, e.g. vsphere-net:172.16.0.1/16",
Hidden: hidden,
},
cli.StringSliceFlag{
Name: "container-network-ip-range, cnr",
Value: &c.ContainerNetworksIPRanges,
Usage: "IP range for the container network in CONTAINER-NETWORK:IP-RANGE format, e.g. vsphere-net:172.16.0.0/24, vsphere-net:172.16.0.10-172.16.0.20",
Hidden: hidden,
},
cli.StringSliceFlag{
Name: "container-network-dns, cnd",
Value: &c.ContainerNetworksDNS,
Usage: "DNS servers for the container network in CONTAINER-NETWORK:DNS format, e.g. vsphere-net:8.8.8.8. Ignored if no static IP assigned.",
Hidden: hidden,
},
cli.StringSliceFlag{
Name: "container-network-firewall, cnf",
Value: &c.ContainerNetworksFirewall,
Usage: "Container network trust level in CONTAINER-NETWORK:LEVEL format. Options: Closed, Outbound, Peers, Published, Open.",
Hidden: hidden,
},
}
}
func parseContainerNetworkGateways(cgs []string) (map[string]net.IPNet, error) {
gws := make(map[string]net.IPNet)
for _, cg := range cgs {
m := &ipNetUnmarshaler{}
vnet, err := parseVnetParam(cg, m)
if err != nil {
return nil, err
}
if _, ok := gws[vnet]; ok {
return nil, fmt.Errorf("Duplicate gateway specified for container network %s", vnet)
}
gws[vnet] = net.IPNet{IP: m.ip, Mask: m.ipnet.Mask}
}
return gws, nil
}
func parseContainerNetworkIPRanges(cps []string) (map[string][]ip.Range, error) {
pools := make(map[string][]ip.Range)
for _, cp := range cps {
ipr := &ip.Range{}
vnet, err := parseVnetParam(cp, ipr)
if err != nil {
return nil, err
}
pools[vnet] = append(pools[vnet], *ipr)
}
return pools, nil
}
func parseContainerNetworkDNS(cds []string) (map[string][]net.IP, error) {
dns := make(map[string][]net.IP)
for _, cd := range cds {
var ip net.IP
vnet, err := parseVnetParam(cd, &ip)
if err != nil {
return nil, err
}
if ip == nil {
return nil, fmt.Errorf("DNS IP not specified for container network %s", vnet)
}
dns[vnet] = append(dns[vnet], ip)
}
return dns, nil
}
func parseContainerNetworkFirewalls(cfs []string) (map[string]executor.TrustLevel, error) {
firewalls := make(map[string]executor.TrustLevel)
for _, cf := range cfs {
vnet, value, err := splitVnetParam(cf)
if err != nil {
return nil, fmt.Errorf("Error parsing container network parameter %s: %s", cf, err)
}
trust, err := executor.ParseTrustLevel(value)
if err != nil {
return nil, err
}
firewalls[vnet] = trust
}
return firewalls, nil
}
func splitVnetParam(p string) (vnet string, value string, err error) {
mapped := strings.Split(p, ":")
if len(mapped) == 0 || len(mapped) > 2 {
err = fmt.Errorf("Invalid value for parameter %s", p)
return
}
vnet = mapped[0]
if vnet == "" {
err = fmt.Errorf("Container network not specified in parameter %s", p)
return
}
// If the supplied vSphere network contains spaces then the user must supply a network alias. Guest info won't receive a name with spaces.
if strings.Contains(vnet, " ") && (len(mapped) == 1 || (len(mapped) == 2 && len(mapped[1]) == 0)) {
err = fmt.Errorf("A network alias must be supplied when network name %q contains spaces.", p)
return
}
if len(mapped) > 1 {
// Make sure the alias does not contain spaces
if strings.Contains(mapped[1], " ") {
err = fmt.Errorf("The network alias supplied in %q cannot contain spaces.", p)
return
}
value = mapped[1]
}
return
}
func parseVnetParam(p string, m encoding.TextUnmarshaler) (vnet string, err error) {
vnet, v, err := splitVnetParam(p)
if err != nil {
return "", fmt.Errorf("Error parsing container network parameter %s: %s", p, err)
}
if err = m.UnmarshalText([]byte(v)); err != nil {
return "", fmt.Errorf("Error parsing container network parameter %s: %s", p, err)
}
return vnet, nil
}
type ipNetUnmarshaler struct {
ipnet *net.IPNet
ip net.IP
}
func (m *ipNetUnmarshaler) UnmarshalText(text []byte) error {
s := string(text)
ip, ipnet, err := net.ParseCIDR(s)
if err != nil {
return err
}
m.ipnet = ipnet
m.ip = ip
return nil
}
// ProcessContainerNetworks parses container network settings and returns a
// struct containing all processed container network fields on success.
func (c *CNetworks) ProcessContainerNetworks(op trace.Operation) (ContainerNetworks, error) {
cNetworks := ContainerNetworks{
MappedNetworks: make(map[string]string),
MappedNetworksGateways: make(map[string]net.IPNet),
MappedNetworksIPRanges: make(map[string][]ip.Range),
MappedNetworksDNS: make(map[string][]net.IP),
MappedNetworksFirewalls: make(map[string]executor.TrustLevel),
}
if c.ContainerNetworks != nil || c.ContainerNetworksGateway != nil ||
c.ContainerNetworksIPRanges != nil || c.ContainerNetworksDNS != nil {
c.IsSet = true
}
gws, err := parseContainerNetworkGateways([]string(c.ContainerNetworksGateway))
if err != nil {
return cNetworks, cli.NewExitError(err.Error(), 1)
}
pools, err := parseContainerNetworkIPRanges([]string(c.ContainerNetworksIPRanges))
if err != nil {
return cNetworks, cli.NewExitError(err.Error(), 1)
}
dns, err := parseContainerNetworkDNS([]string(c.ContainerNetworksDNS))
if err != nil {
return cNetworks, cli.NewExitError(err.Error(), 1)
}
firewalls, err := parseContainerNetworkFirewalls([]string(c.ContainerNetworksFirewall))
if err != nil {
return cNetworks, cli.NewExitError(err.Error(), 1)
}
// Parse container networks
for _, cn := range c.ContainerNetworks {
vnet, v, err := splitVnetParam(cn)
if err != nil {
return cNetworks, cli.NewExitError(err.Error(), 1)
}
alias := vnet
if v != "" {
alias = v
}
cNetworks.MappedNetworks[alias] = vnet
cNetworks.MappedNetworksGateways[alias] = gws[vnet]
cNetworks.MappedNetworksIPRanges[alias] = pools[vnet]
cNetworks.MappedNetworksDNS[alias] = dns[vnet]
cNetworks.MappedNetworksFirewalls[alias] = firewalls[vnet]
delete(gws, vnet)
delete(pools, vnet)
delete(dns, vnet)
delete(firewalls, vnet)
}
var hasError bool
fmtMsg := "The following container network %s is set, but CONTAINER-NETWORK cannot be found. Please check the --container-network and %s settings"
if len(gws) > 0 {
op.Errorf(fmtMsg, "gateway", "--container-network-gateway")
for key, value := range gws {
mask, _ := value.Mask.Size()
op.Errorf("\t%s:%s/%d, %q should be a vSphere network name", key, value.IP, mask, key)
}
hasError = true
}
if len(pools) > 0 {
op.Errorf(fmtMsg, "ip range", "--container-network-ip-range")
for key, value := range pools {
op.Errorf("\t%s:%s, %q should be a vSphere network name", key, value, key)
}
hasError = true
}
if len(dns) > 0 {
op.Errorf(fmtMsg, "dns", "--container-network-dns")
for key, value := range dns {
op.Errorf("\t%s:%s, %q should be a vSphere network name", key, value, key)
}
hasError = true
}
if len(firewalls) > 0 {
op.Errorf(fmtMsg, "firewall", "--container-network-firewall")
for key := range firewalls {
op.Errorf("\t%q should be a vSphere network name", key)
}
hasError = true
}
if hasError {
return cNetworks, cli.NewExitError("Inconsistent container network configuration.", 1)
}
return cNetworks, nil
}

View File

@@ -0,0 +1,211 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"bytes"
"fmt"
"net"
"testing"
"github.com/vmware/vic/pkg/ip"
)
func TestParseContainerNetworkGateways(t *testing.T) {
var tests = []struct {
cgs []string
gws map[string]net.IPNet
err error
}{
{[]string{""}, nil, fmt.Errorf("")},
{[]string{"foo:"}, nil, fmt.Errorf("")},
{[]string{":"}, nil, fmt.Errorf("")},
{[]string{":10.10.10.10/24"}, nil, fmt.Errorf("")},
{[]string{":foo"}, nil, fmt.Errorf("")},
{[]string{"foo:10"}, nil, fmt.Errorf("")},
{[]string{"foo:10.10.10.10/24", "foo:10.10.10.2/24"}, nil, fmt.Errorf("")},
{
[]string{"foo:10.10.10.10/24", "bar:10.10.9.1/16"},
map[string]net.IPNet{
"foo": {IP: net.ParseIP("10.10.10.10"), Mask: net.CIDRMask(24, 32)},
"bar": {IP: net.ParseIP("10.10.9.1"), Mask: net.CIDRMask(16, 32)},
},
nil,
},
}
for _, te := range tests {
gws, err := parseContainerNetworkGateways(te.cgs)
if te.err != nil {
if err == nil {
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, nil) want (nil, err)", te.cgs, gws)
}
continue
}
if err != te.err ||
gws == nil ||
len(gws) != len(te.gws) {
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
}
for v, g := range gws {
if g2, ok := te.gws[v]; !ok {
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
} else if !g2.IP.Equal(g.IP) || bytes.Compare(g2.Mask, g.Mask) != 0 {
t.Fatalf("parseContainerNetworkGateways(%s) => (%v, %s) want (%v, nil)", te.cgs, gws, err, te.gws)
}
}
}
}
func TestParseContainerNetworkIPRanges(t *testing.T) {
var tests = []struct {
cps []string
iprs map[string][]*ip.Range
err error
}{
{[]string{""}, nil, fmt.Errorf("")},
{[]string{"foo:"}, nil, fmt.Errorf("")},
{[]string{":"}, nil, fmt.Errorf("")},
{[]string{":10.10.10.10-24"}, nil, fmt.Errorf("")},
{[]string{":foo"}, nil, fmt.Errorf("")},
{[]string{"foo:10"}, nil, fmt.Errorf("")},
{[]string{"foo:10.10.10.10-9"}, nil, fmt.Errorf("")},
{[]string{"foo:10.10.10.10-10.10.10.9"}, nil, fmt.Errorf("")},
{
[]string{"foo:10.10.10.10-24"},
map[string][]*ip.Range{"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))}}, nil},
{
[]string{"foo:10.10.10.10-10.10.10.24"},
map[string][]*ip.Range{"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))}},
nil,
},
{
[]string{"foo:10.10.10.10-10.10.10.24", "foo:10.10.10.100-10.10.10.105"},
map[string][]*ip.Range{
"foo": {
ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24")),
ip.NewRange(net.ParseIP("10.10.10.100"), net.ParseIP("10.10.10.105")),
},
},
nil,
},
{
[]string{"foo:10.10.10.10-10.10.10.24", "bar:10.10.9.1-10.10.9.10"},
map[string][]*ip.Range{
"foo": {ip.NewRange(net.ParseIP("10.10.10.10"), net.ParseIP("10.10.10.24"))},
"bar": {ip.NewRange(net.ParseIP("10.10.9.1"), net.ParseIP("10.10.9.10"))},
},
nil,
},
}
for _, te := range tests {
iprs, err := parseContainerNetworkIPRanges(te.cps)
if te.err != nil {
if err == nil {
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, nil) want (nil, err)", te.cps, iprs)
}
continue
}
if err != te.err ||
len(iprs) != len(te.iprs) {
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
}
for v, ipr := range iprs {
if ipr2, ok := te.iprs[v]; !ok {
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
} else {
for _, i := range ipr {
found := false
for _, i2 := range ipr2 {
if i.Equal(i2) {
found = true
break
}
}
if !found {
t.Fatalf("parseContainerNetworkIPRanges(%s) => (%v, %s) want (%v, %s)", te.cps, iprs, err, te.iprs, te.err)
}
}
}
}
}
}
func TestParseContainerNetworkDNS(t *testing.T) {
var tests = []struct {
cds []string
dns map[string][]net.IP
err error
}{
{[]string{""}, nil, fmt.Errorf("")},
{[]string{"foo:"}, nil, fmt.Errorf("")},
{[]string{":"}, nil, fmt.Errorf("")},
{[]string{":10.10.10.10"}, nil, fmt.Errorf("")},
{[]string{":foo"}, nil, fmt.Errorf("")},
{[]string{"foo:10"}, nil, fmt.Errorf("")},
{[]string{"foo:10.10.10.109"}, map[string][]net.IP{"foo": {net.ParseIP("10.10.10.109")}}, nil},
{[]string{"foo:10.10.10.109", "foo:10.10.10.110", "bar:10.10.9.109", "bar:10.10.9.110"},
map[string][]net.IP{
"foo": {net.ParseIP("10.10.10.109"), net.ParseIP("10.10.10.110")},
"bar": {net.ParseIP("10.10.9.109"), net.ParseIP("10.10.9.110")},
},
nil,
},
}
for _, te := range tests {
dns, err := parseContainerNetworkDNS(te.cds)
if te.err != nil {
if err == nil {
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, nil) want (nil, err)", te.cds, dns)
}
continue
}
if err != te.err ||
len(dns) != len(te.dns) {
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
}
for v, d := range dns {
if d2, ok := te.dns[v]; !ok {
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
} else {
for _, i := range d {
found := false
for _, i2 := range d2 {
if i.Equal(i2) {
found = true
break
}
}
if !found {
t.Fatalf("parseContainerNetworkDNS(%s) => (%v, %s) want (%v, %s)", te.cds, dns, err, te.dns, te.err)
}
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2016 VMware, Inc. 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 common
import "gopkg.in/urfave/cli.v1"
type Compute struct {
ComputeResourcePath string `cmd:"compute-resource"`
DisplayName string `cmd:"name"`
}
func (c *Compute) ComputeFlags() []cli.Flag {
nameFlag := []cli.Flag{
cli.StringFlag{
Name: "name, n",
Value: "virtual-container-host",
Usage: "The name of the Virtual Container Host",
Destination: &c.DisplayName,
},
}
return append(nameFlag, c.ComputeFlagsNoName()...)
}
func (c *Compute) ComputeFlagsNoName() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "compute-resource, r",
Value: "",
Usage: "Compute resource path, e.g. myCluster",
Destination: &c.ComputeResourcePath,
},
}
}

View File

@@ -0,0 +1,41 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"gopkg.in/urfave/cli.v1"
)
type ContainerConfig struct {
// NameConvention
ContainerNameConvention string `cmd:"container-name-convention"`
}
func (c *ContainerConfig) ContainerFlags() []cli.Flag {
return []cli.Flag{
// container naming convention
cli.StringFlag{
Name: "container-name-convention, cnc",
Value: "",
Usage: "Provide a naming convention. Allows a token of '{name}' or '{id}', that will be replaced.",
Destination: &c.ContainerNameConvention,
Hidden: true,
},
// other container flags to to added"
// default container memory
// default container cpu
// default container network
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2016-2017 VMware, Inc. 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 common
import (
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/flags"
)
type Debug struct {
Debug *int `cmd:"debug"`
}
func (d *Debug) DebugFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "debug, v",
Value: flags.NewOptionalInt(&d.Debug),
Usage: "[0(default),1...n], 0 is disabled, 1 is enabled, >= 1 may alter behaviour",
Hidden: hidden,
},
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"net"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
// general dns
type DNS struct {
DNS cli.StringSlice `arg:"dns-server"`
IsSet bool
}
func (d *DNS) DNSFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "dns-server",
Value: &d.DNS,
Usage: "DNS server for the client, public, and management networks. Defaults to 8.8.8.8 and 8.8.4.4 when VCH uses static IP",
Hidden: hidden,
},
}
}
// processDNSServers parses DNS servers used for client, public, mgmt networks
func (d *DNS) ProcessDNSServers(op trace.Operation) ([]net.IP, error) {
var parsedDNS []net.IP
if len(d.DNS) > 0 {
d.IsSet = true
}
for _, n := range d.DNS {
if n != "" {
s := net.ParseIP(n)
if s == nil {
return nil, errors.Errorf("Invalid DNS server specified: %s", n)
}
parsedDNS = append(parsedDNS, s)
}
}
if len(parsedDNS) > 3 {
op.Warn("Maximum of 3 DNS servers allowed. Additional servers specified will be ignored.")
}
op.Debugf("VCH DNS servers: %s", parsedDNS)
return parsedDNS, nil
}

View File

@@ -0,0 +1,35 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"gopkg.in/urfave/cli.v1"
)
type VCHID struct {
// VCH id
ID string
}
func (i *VCHID) IDFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "id",
Value: "",
Usage: "The ID of the Virtual Container Host, e.g. vm-220",
Destination: &i.ID,
},
}
}

View File

@@ -0,0 +1,196 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
"github.com/vmware/vic/pkg/version"
)
const (
ApplianceImageKey = "core"
LinuxImageKey = "linux"
ApplianceImageName = "appliance.iso"
LinuxImageName = "bootstrap.iso"
// An ISO 9660 sector is normally 2 KiB long. Although the specification allows for alternative sector sizes, you will rarely find anything other than 2 KiB.
ISO9660SectorSize = 2048
ISOVolumeSector = 0x10
PublisherOffset = 318
)
var (
images = map[string][]string{
ApplianceImageKey: {ApplianceImageName},
LinuxImageKey: {LinuxImageName},
}
)
type Images struct {
ApplianceISO string
BootstrapISO string
OSType string
}
func (i *Images) ImageFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "appliance-iso, ai",
Value: "",
Usage: "The appliance iso",
Destination: &i.ApplianceISO,
Hidden: hidden,
},
cli.StringFlag{
Name: "bootstrap-iso, bi",
Value: "",
Usage: "The bootstrap iso",
Destination: &i.BootstrapISO,
Hidden: hidden,
},
}
}
func (i *Images) CheckImagesFiles(op trace.Operation, force bool) (map[string]string, error) {
defer trace.End(trace.Begin("", op))
i.OSType = "linux"
// detect images files
osImgs, ok := images[i.OSType]
if !ok {
return nil, fmt.Errorf("Specified OS %q is not known to this installer", i.OSType)
}
imgs := make(map[string]string)
result := make(map[string]string)
if i.ApplianceISO == "" {
i.ApplianceISO = images[ApplianceImageKey][0]
}
imgs[ApplianceImageName] = i.ApplianceISO
if i.BootstrapISO == "" {
i.BootstrapISO = osImgs[0]
}
imgs[LinuxImageName] = i.BootstrapISO
for name, img := range imgs {
_, err := os.Stat(img)
if os.IsNotExist(err) {
var dir string
dir, err = filepath.Abs(filepath.Dir(os.Args[0]))
_, err = os.Stat(filepath.Join(dir, img))
if err == nil {
img = filepath.Join(dir, img)
}
}
if os.IsNotExist(err) {
op.Warnf("\t\tUnable to locate %s in the current or installer directory.", img)
return nil, err
}
version, err := i.checkImageVersion(op, img, force)
if err != nil {
op.Error(err)
return nil, err
}
versionedName := fmt.Sprintf("%s-%s", version, name)
result[versionedName] = img
if name == ApplianceImageName {
i.ApplianceISO = versionedName
} else {
i.BootstrapISO = versionedName
}
}
return result, nil
}
// GetImageVersion will read iso file version from Primary Volume Descriptor, field "Publisher Identifier"
func (i *Images) GetImageVersion(op trace.Operation, img string) (string, error) {
defer trace.End(trace.Begin("", op))
f, err := os.Open(img)
if err != nil {
return "", errors.Errorf("failed to open iso file %q: %s", img, err)
}
defer f.Close()
// System area goes from sectors 0x00 to 0x0F. Volume descriptors can be
// found starting at sector 0x10
_, err = f.Seek(int64(ISOVolumeSector*ISO9660SectorSize)+PublisherOffset, 0)
if err != nil {
return "", errors.Errorf("failed to locate iso version section in file %q: %s", img, err)
}
publisherBytes := make([]byte, 128)
size, err := f.Read(publisherBytes)
if err != nil {
return "", errors.Errorf("failed to read iso version in file %q: %s", img, err)
}
if size == 0 {
return "", errors.Errorf("version is not set in iso file %q", img)
}
versions := strings.Fields(string(publisherBytes[:size]))
if len(versions) > 0 {
return versions[len(versions)-1], nil
}
return "version-unknown", nil
}
func (i *Images) checkImageVersion(op trace.Operation, img string, force bool) (string, error) {
defer trace.End(trace.Begin("", op))
ver, err := i.GetImageVersion(op, img)
if err != nil {
return "", err
}
sv := i.getNoCommitHashVersion(op, ver)
if sv == "" {
op.Debugf("Version is not set in %q", img)
ver = ""
}
installerSV := i.getNoCommitHashVersion(op, version.GetBuild().ShortVersion())
// here compare version without last commit hash, to make developer life easier
if !strings.EqualFold(installerSV, sv) {
message := fmt.Sprintf("iso file %q version %q inconsistent with installer version %q", img, strings.ToLower(ver), version.GetBuild().ShortVersion())
if !force {
return "", errors.Errorf("%s. Specify --force to force create. ", message)
}
op.Warn(message)
}
return ver, nil
}
func (i *Images) getNoCommitHashVersion(op trace.Operation, version string) string {
defer trace.End(trace.Begin("", op))
j := strings.LastIndex(version, "-")
if j == -1 {
return ""
}
return version[:j]
}

View File

@@ -0,0 +1,121 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"context"
"io/ioutil"
"os"
"testing"
log "github.com/Sirupsen/logrus"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/trace"
)
var (
image = &Images{}
)
func TestImageNotFound(t *testing.T) {
log.SetLevel(log.InfoLevel)
tmpfile, err := ioutil.TempFile("", "appIso")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
op := trace.NewOperation(context.Background(), "TestImageNotFound")
image.ApplianceISO = tmpfile.Name()
image.OSType = "linux"
if _, err = image.CheckImagesFiles(op, false); err == nil {
t.Errorf("Error is expected for boot iso file is not found.")
}
}
func writeImageVersion(fileName string, version string) error {
f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
return err
}
defer f.Close()
if err := f.Truncate(int64(0x10*2048) + 318); err != nil {
return err
}
if _, err := f.WriteAt([]byte(version), int64(0x10*2048)+318); err != nil {
return err
}
return nil
}
func TestImageChecks(t *testing.T) {
log.SetLevel(log.InfoLevel)
tmpfile, err := ioutil.TempFile("", "bootIso")
if err != nil {
log.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
op := trace.NewOperation(context.Background(), "TestImageChecks")
_, err = os.Create("appliance.iso")
if err != nil {
t.Errorf("Failed to create default appliance iso file")
}
defer os.Remove("appliance.iso")
image.ApplianceISO = ""
image.BootstrapISO = tmpfile.Name()
image.OSType = "linux"
var imageFiles map[string]string
if _, err = image.CheckImagesFiles(op, false); err == nil {
t.Errorf("Error is expected")
}
if err = writeImageVersion("appliance.iso", "Inc. 0.1-000-abcd"); err != nil {
t.Error(err)
}
if err = writeImageVersion(tmpfile.Name(), "Inc. 0.1-000-abcd"); err != nil {
t.Error(err)
}
cliContext := &cli.Context{
App: &cli.App{
Version: "Inconsistent",
},
}
if _, err = image.CheckImagesFiles(op, false); err == nil {
t.Errorf("Error is expected")
}
cliContext.App.Version = "0.1-000-abcd"
if imageFiles, err = image.CheckImagesFiles(op, true); err != nil {
t.Errorf("Error is returned: %s", err)
}
found := false
for _, file := range imageFiles {
if file == tmpfile.Name() {
found = true
break
}
}
if !found {
t.Errorf("Image file list does not contain input, %s", imageFiles)
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2018 VMware, Inc. 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 common
import (
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/flags"
"github.com/vmware/vic/pkg/trace"
)
// Kubelet holds credentials for the VCH operations user
type Kubelet struct {
ServerAddress *string `cmd:"kubelet"`
ConfigFile *string
}
func (v *Kubelet) Flags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "k8s-server-address",
Value: flags.NewOptionalString(&v.ServerAddress),
Usage: "The Kubernetes Server URL, <hostname/ip>:<port>",
Hidden: hidden,
},
cli.GenericFlag{
Name: "k8s-config",
Value: flags.NewOptionalString(&v.ConfigFile),
Usage: "Kubernetes client config file",
Hidden: hidden,
},
}
}
func (v *Kubelet) ProcessKubelet(op trace.Operation, isCreateOp bool) error {
if isCreateOp {
if v.ServerAddress != nil && v.ConfigFile == nil {
return errors.Errorf("A Kubernetes Config File must be specified when specifying a Kubernetes Server Address")
}
if v.ServerAddress == nil && v.ConfigFile != nil {
return errors.Errorf("A Kubernetes Server Address must be specified when specifying a Kubernetes Config File")
}
}
return nil
}

View File

@@ -0,0 +1,15 @@
// Copyright 2018 VMware, Inc. 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 common

View File

@@ -0,0 +1,78 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"gopkg.in/urfave/cli.v1"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/vic/pkg/flags"
)
type ResourceLimits struct {
VCHCPULimitsMHz *int `cmd:"cpu"`
VCHCPUReservationsMHz *int `cmd:"cpu-reservation"`
VCHCPUShares *types.SharesInfo `cmd:"cpu-shares"`
VCHMemoryLimitsMB *int `cmd:"memory"`
VCHMemoryReservationsMB *int `cmd:"memory-reservation"`
VCHMemoryShares *types.SharesInfo `cmd:"memory-shares"`
IsSet bool
}
func (r *ResourceLimits) VCHMemoryLimitFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "memory, mem",
Value: flags.NewOptionalInt(&r.VCHMemoryLimitsMB),
Usage: "VCH resource pool memory limit in MB (unlimited=0)",
},
cli.GenericFlag{
Name: "memory-reservation, memr",
Value: flags.NewOptionalInt(&r.VCHMemoryReservationsMB),
Usage: "VCH resource pool memory reservation in MB",
Hidden: hidden,
},
cli.GenericFlag{
Name: "memory-shares, mems",
Value: flags.NewSharesFlag(&r.VCHMemoryShares),
Usage: "VCH resource pool memory shares in level or share number, e.g. high, normal, low, or 163840",
Hidden: hidden,
},
}
}
func (r *ResourceLimits) VCHCPULimitFlags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "cpu",
Value: flags.NewOptionalInt(&r.VCHCPULimitsMHz),
Usage: "VCH resource pool vCPUs limit in MHz (unlimited=0)",
},
cli.GenericFlag{
Name: "cpu-reservation, cpur",
Value: flags.NewOptionalInt(&r.VCHCPUReservationsMHz),
Usage: "VCH resource pool reservation in MHz",
Hidden: hidden,
},
cli.GenericFlag{
Name: "cpu-shares, cpus",
Value: flags.NewSharesFlag(&r.VCHCPUShares),
Usage: "VCH VCH resource pool vCPUs shares, in level or share number, e.g. high, normal, low, or 4000",
Hidden: hidden,
},
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2017 VMware, Inc. 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 common
type Networks struct {
ClientNetworkName string
ClientNetworkIP string
ClientNetworkGateway string
PublicNetworkName string
PublicNetworkIP string
PublicNetworkGateway string
ManagementNetworkName string
ManagementNetworkIP string
ManagementNetworkGateway string
}

View File

@@ -0,0 +1,38 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"context"
"github.com/Sirupsen/logrus"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/trace"
)
func NewOperation(clic *cli.Context, debug *int) trace.Operation {
op := trace.NewOperation(context.Background(), clic.App.Name)
op.Logger = logrus.New()
op.Logger.Out = clic.App.Writer
if debug != nil && *debug > 0 {
logrus.SetLevel(logrus.DebugLevel)
trace.Logger.Level = logrus.DebugLevel
op.Logger.Level = logrus.DebugLevel
}
return op
}

View File

@@ -0,0 +1,112 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"fmt"
"os"
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/flags"
"github.com/vmware/vic/pkg/trace"
)
// OpsCredentials holds credentials for the VCH operations user
type OpsCredentials struct {
OpsUser *string `cmd:"ops-user"`
OpsPassword *string
GrantPerms *bool
IsSet bool
}
func (o *OpsCredentials) Flags(hidden bool) []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "ops-user",
Value: flags.NewOptionalString(&o.OpsUser),
Usage: "The user with which the VCH operates after creation. Defaults to the credential supplied with target",
Hidden: hidden,
},
cli.GenericFlag{
Name: "ops-password",
Value: flags.NewOptionalString(&o.OpsPassword),
Usage: "Password or token for the operations user. Defaults to the credential supplied with target",
Hidden: hidden,
},
cli.GenericFlag{
Name: "ops-grant-perms",
Value: flags.NewOptionalBool(&o.GrantPerms),
Usage: "Create roles and grant required permissions to the specified ops-use",
Hidden: hidden,
},
}
}
// ProcessOpsCredentials processes fields for the VCH operations user. When invoked
// during a VCH create operation, adminUser and adminPassword must be supplied to
// be used as ops credentials if they are not specified by the user. For a configure
// operation, adminUser and adminPassword are not needed.
func (o *OpsCredentials) ProcessOpsCredentials(op trace.Operation, isCreateOp bool, adminUser string, adminPassword *string) error {
if o.OpsUser == nil && o.OpsPassword != nil {
return errors.New("Password for operations user specified without user having been specified")
}
if isCreateOp {
if o.OpsUser == nil {
// Check if there was a request to setup ops-user Roles and Permissions
if o.GrantPerms != nil {
// If true return error
if *o.GrantPerms {
return errors.Errorf("Invalid use of flag: --ops-grant-perms. Cannot setup Roles and Permissions for administrative user.")
}
// If false ignore
o.GrantPerms = nil
}
op.Warn("Using administrative user for VCH operation - use --ops-user to improve security (see -x for advanced help)")
o.OpsUser = &adminUser
if adminPassword == nil {
return errors.New("Unable to use nil password from administrative user for operations user")
}
o.OpsPassword = adminPassword
return nil
}
} else {
if o.OpsUser != nil {
o.IsSet = true
}
}
if o.OpsPassword != nil {
return nil
}
// Prompt for the ops password only during a create operation or a configure
// operation where the ops user is specified.
if isCreateOp || o.IsSet {
op.Infof("vSphere password for %s: ", *o.OpsUser)
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
message := fmt.Sprintf("Failed to read password from stdin: %s", err)
cli.NewExitError(message, 1)
}
sb := string(b)
o.OpsPassword = &sb
}
return nil
}

View File

@@ -0,0 +1,109 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/vmware/vic/pkg/trace"
)
func TestProcessOpsCredentials(t *testing.T) {
createOps := &OpsCredentials{}
isCreateOp := true
adminUser := "admin"
adminPassword := ""
op := trace.NewOperation(context.Background(), "TestProcessOpsCredentials")
// There should be an error if the admin password is not specified for a create operation.
err := createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, nil)
assert.NotNil(t, err)
err = createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, &adminPassword)
assert.NoError(t, err)
assert.Equal(t, *createOps.OpsUser, adminUser)
assert.Equal(t, *createOps.OpsPassword, adminPassword)
opsUser := "op"
opsPassword := "opPass"
createOps.OpsUser = &opsUser
createOps.OpsPassword = &opsPassword
err = createOps.ProcessOpsCredentials(op, isCreateOp, adminUser, &adminPassword)
assert.NoError(t, err)
assert.Equal(t, *createOps.OpsUser, opsUser)
assert.Equal(t, *createOps.OpsPassword, opsPassword)
// Ensure that fields are set correctly for a configure operation.
configureOps := &OpsCredentials{
OpsUser: &opsUser,
OpsPassword: &opsPassword,
}
isCreateOp = false
err = configureOps.ProcessOpsCredentials(op, isCreateOp, "", nil)
assert.NoError(t, err)
assert.True(t, configureOps.IsSet)
assert.Equal(t, *createOps.OpsUser, opsUser)
assert.Equal(t, *createOps.OpsPassword, opsPassword)
// There should be an error if the ops-password is specified without ops-user.
configureOps.OpsUser = nil
err = configureOps.ProcessOpsCredentials(op, isCreateOp, "", nil)
assert.NotNil(t, err)
// Test correct grant permissions
createOps = &OpsCredentials{}
createOps.OpsUser = &opsUser
createOps.OpsPassword = &opsPassword
grantPerms := true
createOps.GrantPerms = &grantPerms
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
assert.NoError(t, err)
assert.Equal(t, *createOps.OpsUser, opsUser)
assert.Equal(t, *createOps.OpsPassword, opsPassword)
assert.True(t, *createOps.GrantPerms)
// Create Negative test: grantPerms is set to true but there is no ops-user,
// grantPerms should be reset to false
createOps = &OpsCredentials{}
createOps.OpsUser = nil
createOps.OpsPassword = nil
grantPerms = true
createOps.GrantPerms = &grantPerms
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
assert.Error(t, err)
// Create Negative test: grantPerms is set to true but there is no ops-user,
// grantPerms should be reset to false
createOps = &OpsCredentials{}
createOps.OpsUser = nil
createOps.OpsPassword = nil
grantPerms = false
createOps.GrantPerms = &grantPerms
err = createOps.ProcessOpsCredentials(op, true, adminUser, &adminPassword)
assert.NoError(t, err)
assert.Nil(t, createOps.GrantPerms)
// Configure test: grantPerms is set to true but there is no ops-user,
// grantPerms should be true as the ops-user may come from the config
grantPerms = true
createOps.GrantPerms = &grantPerms
err = createOps.ProcessOpsCredentials(op, false, adminUser, &adminPassword)
assert.NoError(t, err)
assert.True(t, *createOps.GrantPerms)
}

View File

@@ -0,0 +1,70 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"fmt"
"net/url"
"github.com/vmware/vic/pkg/flags"
"gopkg.in/urfave/cli.v1"
)
type Proxies struct {
HTTPSProxy *string
HTTPProxy *string
IsSet bool
}
func (p *Proxies) ProxyFlags(hidden bool) []cli.Flag {
return []cli.Flag{
// proxies
cli.GenericFlag{
Name: "https-proxy",
Value: flags.NewOptionalString(&p.HTTPSProxy),
Usage: "An HTTPS proxy for use when fetching images, in the form https://fqdn_or_ip:port",
Hidden: hidden,
},
cli.GenericFlag{
Name: "http-proxy",
Value: flags.NewOptionalString(&p.HTTPProxy),
Usage: "An HTTP proxy for use when fetching images, in the form http://fqdn_or_ip:port",
Hidden: hidden,
},
}
}
func (p *Proxies) ProcessProxies() (hproxy, sproxy *url.URL, err error) {
if p.HTTPProxy != nil || p.HTTPSProxy != nil {
p.IsSet = true
}
if p.HTTPProxy != nil && *p.HTTPProxy != "" {
hproxy, err = url.Parse(*p.HTTPProxy)
if err != nil || hproxy.Host == "" || hproxy.Scheme != "http" {
err = cli.NewExitError(fmt.Sprintf("Could not parse HTTP proxy - expected format http://fqnd_or_ip:port: %s", *p.HTTPProxy), 1)
return
}
}
if p.HTTPSProxy != nil && *p.HTTPSProxy != "" {
sproxy, err = url.Parse(*p.HTTPSProxy)
if err != nil || sproxy.Host == "" || sproxy.Scheme != "https" {
err = cli.NewExitError(fmt.Sprintf("Could not parse HTTPS proxy - expected format https://fqnd_or_ip:port: %s", *p.HTTPSProxy), 1)
return
}
}
return
}

View File

@@ -0,0 +1,82 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"io/ioutil"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
// Registries contains metadata used to create/configure registry CA data
type Registries struct {
RegistryCAsArg cli.StringSlice `arg:"registry-ca"`
InsecureRegistriesArg cli.StringSlice `arg:"insecure-registry"`
WhitelistRegistriesArg cli.StringSlice `arg:"whitelist-registry"`
RegistryCAs []byte
InsecureRegistries []string `cmd:"insecure-registry"`
WhitelistRegistries []string `cmd:"whitelist-registry"`
}
// Flags generates command line flags
func (r *Registries) Flags() []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "registry-ca, rc",
Usage: "Specify a list of additional certificate authority files to use to verify secure registry servers",
Value: &r.RegistryCAsArg,
},
}
}
// LoadRegistryCAs loads additional CA certs for docker registry usage
func (r *Registries) loadRegistryCAs(op trace.Operation) ([]byte, error) {
defer trace.End(trace.Begin("", op))
var registryCerts []byte
for _, f := range r.RegistryCAsArg {
b, err := ioutil.ReadFile(f)
if err != nil {
err = errors.Errorf("Failed to load authority from file %s: %s", f, err)
return nil, err
}
registryCerts = append(registryCerts, b...)
op.Infof("Loaded registry CA from %s", f)
}
return registryCerts, nil
}
func (r *Registries) ProcessRegistries(op trace.Operation) error {
// load additional certificate authorities for use with registries
if len(r.RegistryCAsArg) > 0 {
registryCAs, err := r.loadRegistryCAs(op)
if err != nil {
return errors.Errorf("Unable to load CA certificates for registry logins: %s", err)
}
r.RegistryCAs = registryCAs
}
r.InsecureRegistries = r.InsecureRegistriesArg.Value()
r.WhitelistRegistries = r.WhitelistRegistriesArg.Value()
return nil
}

View File

@@ -0,0 +1,118 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"fmt"
"net/url"
"os"
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/flags"
"github.com/vmware/vic/pkg/trace"
)
type Target struct {
URL *url.URL `cmd:"target"`
User string
Password *string
CloneTicket string
Thumbprint string `cmd:"thumbprint"`
}
func NewTarget() *Target {
return &Target{}
}
func (t *Target) TargetFlags() []cli.Flag {
return []cli.Flag{
cli.GenericFlag{
Name: "target, t",
Value: flags.NewURLFlag(&t.URL),
Usage: "REQUIRED. ESXi or vCenter connection URL, specifying a datacenter if multiple exist e.g. root:password@VC-FQDN/datacenter",
EnvVar: "VIC_MACHINE_TARGET",
},
cli.StringFlag{
Name: "user, u",
Value: "",
Usage: "ESX or vCenter user",
Destination: &t.User,
EnvVar: "VIC_MACHINE_USER",
},
cli.GenericFlag{
Name: "password, p",
Value: flags.NewOptionalString(&t.Password),
Usage: "ESX or vCenter password",
EnvVar: "VIC_MACHINE_PASSWORD",
},
cli.StringFlag{
Name: "thumbprint",
Value: "",
Destination: &t.Thumbprint,
Usage: "ESX or vCenter host certificate thumbprint",
EnvVar: "VIC_MACHINE_THUMBPRINT",
},
}
}
// HasCredentials check that the credentials have been supplied by any of the permitted mechanisms
func (t *Target) HasCredentials(op trace.Operation) error {
if t.URL == nil {
return cli.NewExitError("--target argument must be specified", 1)
}
// assume if a vsphere session key exists, we want to use that instead of user/pass
if t.CloneTicket != "" {
t.URL.User = nil // necessary?
return nil
}
var urlUser string
var urlPassword *string
if t.URL.User != nil {
urlUser = t.URL.User.Username()
if passwd, set := t.URL.User.Password(); set {
urlPassword = &passwd
}
}
if t.User == "" && urlUser == "" {
return cli.NewExitError("vSphere user must be specified, either with --user or as part of --target", 1)
} else if t.User == "" && urlUser != "" {
t.User = urlUser
}
//prompt for passwd if not specified
if t.Password == nil && urlPassword == nil {
op.Infof("vSphere password for %s: ", t.User)
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
message := fmt.Sprintf("Failed to read password from stdin: %s", err)
cli.NewExitError(message, 1)
}
sb := string(b)
t.Password = &sb
} else if t.Password == nil && urlPassword != nil {
t.Password = urlPassword
}
// Used by vic-machine for Session login
t.URL.User = url.UserPassword(t.User, *t.Password)
return nil
}

View File

@@ -0,0 +1,92 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"context"
"net/url"
"testing"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/vic/pkg/trace"
)
func TestFlags(t *testing.T) {
target := NewTarget()
flags := target.TargetFlags()
if len(flags) != 4 {
t.Errorf("Wrong flag numbers")
}
}
func TestProcess(t *testing.T) {
op := trace.NewOperation(context.Background(), "TestProcess")
passwd := "pass"
url1, _ := soap.ParseURL("127.0.0.1")
url2, _ := soap.ParseURL("root:@127.0.0.1")
url3, _ := soap.ParseURL("line:password@127.0.0.1")
url4, _ := soap.ParseURL("root:pass@127.0.0.1")
result, _ := url.Parse("https://root:pass@127.0.0.1/sdk")
passEmpty := ""
result1, _ := url.Parse("https://root:@127.0.0.1/sdk")
tests := []struct {
URL *url.URL
User string
Password *string
err error
result *url.URL
}{
{nil, "", nil, cli.NewExitError("--target argument must be specified", 1), nil},
{nil, "root", nil, cli.NewExitError("--target argument must be specified", 1), nil},
{nil, "root", &passwd, cli.NewExitError("--target argument must be specified", 1), nil},
{url1, "root", &passwd, nil, result},
{url4, "", nil, nil, result},
{url3, "root", &passwd, nil, result},
{url2, "", &passwd, nil, result},
{url1, "root", &passEmpty, nil, result1},
}
for _, test := range tests {
target := NewTarget()
target.URL = test.URL
target.User = test.User
target.Password = test.Password
if target.URL != nil {
t.Logf("Before processing, url: %s", target.URL.String())
}
e := target.HasCredentials(op)
if test.err != nil {
if e == nil {
t.Errorf("Empty error")
}
if e.Error() != test.err.Error() {
t.Errorf("Unexpected error message: %s", e.Error())
}
} else if e != nil {
t.Errorf("Unexpected error %s", e.Error())
} else {
if target.URL != test.URL {
t.Errorf("unexpected result url: %s", target.URL.String())
} else {
t.Logf("result url: %s", target.URL.String())
}
}
}
}

View File

@@ -0,0 +1,76 @@
// Copyright 2016 VMware, Inc. 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 common
import (
"fmt"
"regexp"
"gopkg.in/urfave/cli.v1"
"github.com/vmware/vic/pkg/errors"
"github.com/vmware/vic/pkg/trace"
)
const (
// scheme string for nfs volume store targets
NfsScheme = "nfs"
// scheme string for ds volume store targets
DsScheme = "ds"
// scheme string for volume store targets without a scheme
EmptyScheme = ""
)
// https://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2046088
const unsuppCharsRegex = `%|&|\*|\$|#|@|!|\\|/|:|\?|"|<|>|;|'|\|`
// Same as unsuppCharsRegex but allows / and : for datastore paths
const unsuppCharsDatastoreRegex = `%|&|\*|\$|#|@|!|\\|\?|"|<|>|;|'|\|`
var reUnsupp = regexp.MustCompile(unsuppCharsRegex)
var reUnsuppDatastore = regexp.MustCompile(unsuppCharsDatastoreRegex)
func LogErrorIfAny(op trace.Operation, clic *cli.Context, err error) error {
if err == nil {
return nil
}
op.Errorf("--------------------")
op.Errorf("%s %s failed: %s\n", clic.App.Name, clic.Command.Name, errors.ErrorStack(err))
return cli.NewExitError("", 1)
}
// CheckUnsupportedChars returns an error if string contains special characters
func CheckUnsupportedChars(s string) error {
return checkUnsupportedChars(s, reUnsupp)
}
// CheckUnsupportedCharsDatastore returns an error if a datastore string contains special characters
func CheckUnsupportedCharsDatastore(s string) error {
return checkUnsupportedChars(s, reUnsuppDatastore)
}
func checkUnsupportedChars(s string, re *regexp.Regexp) error {
st := []byte(s)
var v []int
// this is validation step for characters in a datastore URI
if v = re.FindIndex(st); v == nil {
return nil
}
return fmt.Errorf("unsupported character %q in %q", s[v[0]:v[1]], s)
}

View File

@@ -0,0 +1,121 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"testing"
)
func TestCheckUnsupportedchars(t *testing.T) {
tests := []struct {
S string
valid bool
}{
{"anjunabeats", true},
{"tony-1", true},
{"paavo_1", true},
{"jono(1)", true},
{"oceanlab (1)", true},
{"test%", false},
{"test&", false},
{"test*", false},
{"test$", false},
{"test#", false},
{"test@", false},
{"test!", false},
{`test\`, false},
{"test/", false}, // U+002F
{"test\u002f", false}, // U+002F
{`testЯ`, true}, // U+042F
{"test\u042f", true}, // U+042F
{`testį`, true}, // U+012F
{"test\u012f", true}, // U+012F
{"test:", false},
{"test?", false},
{`test"`, false},
{"test<", false},
{"test>", false},
{"test;", false},
{"test'", false},
{"test|", false},
{"test|", false},
}
for _, test := range tests {
err := CheckUnsupportedChars(test.S)
if err != nil {
if test.valid {
t.Errorf("got %q, expected pass for %q", err, test.S)
}
t.Logf("test case %q passed", test.S)
continue
}
if test.valid {
t.Logf("test case %q passed", test.S)
continue
}
t.Errorf("got %q, expected error for %q", err, test.S)
}
}
func TestCheckUnsupportedCharsDatastore(t *testing.T) {
tests := []struct {
S string
valid bool
}{
{"anjunabeats", true},
{"tony-1", true},
{"paavo_1", true},
{"jono(1)", true},
{"oceanlab (1)", true},
{"waawn/", true},
{"tristate:", true},
{"test%", false},
{"test&", false},
{"test*", false},
{"test$", false},
{"test#", false},
{"test@", false},
{"test!", false}, // U+0021
{"test\u0021", false}, // U+0021
{`testġ`, true}, // U+0121
{"test\u0121", true}, // U+0121
{`test\`, false},
{"test?", false},
{`test"`, false},
{"test<", false},
{"test>", false},
{"test;", false},
{"test'", false},
{"test|", false},
}
for _, test := range tests {
err := CheckUnsupportedCharsDatastore(test.S)
if err != nil {
if test.valid {
t.Errorf("got %q, expected pass for %q", err, test.S)
}
t.Logf("test case %q passed", test.S)
continue
}
if test.valid {
t.Logf("test case %q passed", test.S)
continue
}
t.Errorf("got %q, expected error for %q", err, test.S)
}
}

View File

@@ -0,0 +1,111 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"fmt"
"net/url"
"strings"
"gopkg.in/urfave/cli.v1"
)
const (
dsInputFormat = "<datastore url w/ path>:label"
nfsInputFormat = "nfs://<host>/<url-path>?<mount option as query parameters>:<label>"
)
type VolumeStores struct {
VolumeStores cli.StringSlice `arg:"volume-store"`
}
func (v *VolumeStores) Flags() []cli.Flag {
return []cli.Flag{
cli.StringSliceFlag{
Name: "volume-store, vs",
Value: &v.VolumeStores,
Usage: "Specify a list of location and label for volume store, nfs stores can have mount options specified as query parameters in the url target. \n\t Examples for a vsphere backed volume store are: \"datastore/path:label\" or \"datastore:label\" or \"ds://my-datastore-name:store-label\"\n\t Examples for nfs back volume stores are: \"nfs://127.0.0.1/path/to/share/point?uid=1234&gid=5678&proto=tcp:my-volume-store-label\" or \"nfs://my-store/path/to/share/point:my-label\"",
},
}
}
func (v *VolumeStores) ProcessVolumeStores() (map[string]*url.URL, error) {
volumeLocations := make(map[string]*url.URL)
for _, arg := range v.VolumeStores {
urlTarget, rawTarget, label, err := processVolumeStoreParam(arg)
if err != nil {
return nil, err
}
switch urlTarget.Scheme {
case NfsScheme:
// nothing needs to be done here. parsing the url is enough for pre-validation checking of an nfs target.
case EmptyScheme, DsScheme:
// a datastore target is our default assumption
urlTarget.Scheme = DsScheme
if err := CheckUnsupportedCharsDatastore(rawTarget); err != nil {
return nil, fmt.Errorf("--volume-store contains unsupported characters for datastore target: %s Allowed characters are alphanumeric, space and symbols - _ ( ) / : ,", err)
}
if len(urlTarget.RawQuery) > 0 {
return nil, fmt.Errorf("volume store input must be in format datastore/path:label or %s", nfsInputFormat)
}
default:
return nil, fmt.Errorf("%s", "Please specify a datastore or nfs target. See -vs usage for examples.")
}
volumeLocations[label] = urlTarget
}
return volumeLocations, nil
}
// processVolumeStoreParam will pull apart the raw input for -vs and return the parts for the actual store that are needed for validation
func processVolumeStoreParam(rawVolumeStore string) (*url.URL, string, string, error) {
errVolStoreFormat := fmt.Errorf("volume store input must be in format %s or %s", dsInputFormat, nfsInputFormat)
splitMeta := strings.Split(rawVolumeStore, ":")
if len(splitMeta) < 2 {
return nil, "", "", errVolStoreFormat
}
// divide out the label with the target
lastIndex := len(splitMeta)
label := splitMeta[lastIndex-1]
rawTarget := strings.Join(splitMeta[0:lastIndex-1], ":")
if label == "" || rawTarget == "" {
return nil, "", "", errVolStoreFormat
}
// This case will check if part of the url is assigned as the label (e.g. ds://No.label.target/some/path)
if err := CheckUnsupportedChars(label); err != nil {
return nil, "", "", errVolStoreFormat
}
// raw target input should be in the form of a url
stripRawTarget := rawTarget
if strings.HasPrefix(stripRawTarget, DsScheme+"://") {
stripRawTarget = strings.Replace(rawTarget, DsScheme+"://", "", -1)
}
urlTarget, err := url.Parse(stripRawTarget)
if err != nil {
return nil, "", "", fmt.Errorf("parsed url for option --volume-store could not be parsed as a url, valid inputs are datastore/path:label or %s. See -h for usage examples.", nfsInputFormat)
}
return urlTarget, rawTarget, label, nil
}

View File

@@ -0,0 +1,59 @@
// Copyright 2017 VMware, Inc. 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 common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestProcessVolumeStoreParam(t *testing.T) {
positiveTestCases := []string{
"nfs://Shared.Volumes.Org/path/to/store:nfs-volumes",
"ds://vsphere.target.here/:root-path",
"no.scheme.target:/with/path:ds-store",
"looooooooooooooooooooooooooooooong.hoooooooooooooooooooooooooooooooost/short/path:long-check",
"nfs://0.0.0.0/ip/check:simple-target",
"nfs://prod.shared.storage/vch_prod/volumes:test-label",
"ds://0.0.0.0/ip/check?myArg=simple&complex=anotherArg:simple-target:test-label",
}
negativeTestCases := []string{
"ds://vsphere.rocks.com/no/label/here",
"junk-text-%^()!@#:with-label",
"junk-text-%^()!@#-no-label",
":no-text",
"no-label:",
"no-label/with/path",
}
for _, v := range positiveTestCases {
target, rawString, label, err := processVolumeStoreParam(v)
assert.NotNil(t, target, v)
assert.NotEqual(t, "", rawString, v)
assert.NotEqual(t, "", label, v)
assert.Nil(t, err, v)
}
for _, v := range negativeTestCases {
target, _, _, err := processVolumeStoreParam(v)
// here "" is possible for rawString and label so we check for err and nil target.
assert.Nil(t, target, v)
assert.NotNil(t, err, v)
}
}