Copy dependencies into vendor dir, using dep

This commit is contained in:
Bryan Boreham committed 2018-04-27 14:34:48 +01:00
1 parent 549ccff5eb
commit b534ea60f5
2857 files changed
+945317

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+18
View File
@@ -0,0 +1,18 @@
package auth
type Options struct {
CertDir string
CaCertPath string
CaPrivateKeyPath string
CaCertRemotePath string
ServerCertPath string
ServerKeyPath string
ClientKeyPath string
ServerCertRemotePath string
ServerKeyRemotePath string
ClientCertPath string
ServerCertSANs []string
// StorePath is left in for historical reasons, but not really meant to
// be used directly.
StorePath string
}
+136
View File
@@ -0,0 +1,136 @@
package cert
import (
"errors"
"fmt"
"os"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnutils"
)
func createCACert(authOptions *auth.Options, caOrg string, bits int) error {
caCertPath := authOptions.CaCertPath
caPrivateKeyPath := authOptions.CaPrivateKeyPath
log.Infof("Creating CA: %s", caCertPath)
// check if the key path exists; if so, error
if _, err := os.Stat(caPrivateKeyPath); err == nil {
return errors.New("certificate authority key already exists")
}
if err := GenerateCACertificate(caCertPath, caPrivateKeyPath, caOrg, bits); err != nil {
return fmt.Errorf("generating CA certificate failed: %s", err)
}
return nil
}
func createCert(authOptions *auth.Options, org string, bits int) error {
certDir := authOptions.CertDir
caCertPath := authOptions.CaCertPath
caPrivateKeyPath := authOptions.CaPrivateKeyPath
clientCertPath := authOptions.ClientCertPath
clientKeyPath := authOptions.ClientKeyPath
log.Infof("Creating client certificate: %s", clientCertPath)
if _, err := os.Stat(certDir); err != nil {
if os.IsNotExist(err) {
if err := os.Mkdir(certDir, 0700); err != nil {
return fmt.Errorf("failure creating machine client cert dir: %s", err)
}
} else {
return err
}
}
// check if the key path exists; if so, error
if _, err := os.Stat(clientKeyPath); err == nil {
return errors.New("client key already exists")
}
// Used to generate the client certificate.
certOptions := &Options{
Hosts: []string{""},
CertFile: clientCertPath,
KeyFile: clientKeyPath,
CAFile: caCertPath,
CAKeyFile: caPrivateKeyPath,
Org: org,
Bits: bits,
SwarmMaster: false,
}
if err := GenerateCert(certOptions); err != nil {
return fmt.Errorf("failure generating client certificate: %s", err)
}
return nil
}
func BootstrapCertificates(authOptions *auth.Options) error {
certDir := authOptions.CertDir
caCertPath := authOptions.CaCertPath
clientCertPath := authOptions.ClientCertPath
clientKeyPath := authOptions.ClientKeyPath
caPrivateKeyPath := authOptions.CaPrivateKeyPath
// TODO: I'm not super happy about this use of "org", the user should
// have to specify it explicitly instead of implicitly basing it on
// $USER.
caOrg := mcnutils.GetUsername()
org := caOrg + ".<bootstrap>"
bits := 2048
if _, err := os.Stat(certDir); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(certDir, 0700); err != nil {
return fmt.Errorf("creating machine certificate dir failed: %s", err)
}
} else {
return err
}
}
if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
if err := createCACert(authOptions, caOrg, bits); err != nil {
return err
}
} else {
current, err := CheckCertificateDate(caCertPath)
if err != nil {
return err
}
if !current {
log.Info("CA certificate is outdated and needs to be regenerated")
os.Remove(caPrivateKeyPath)
if err := createCACert(authOptions, caOrg, bits); err != nil {
return err
}
}
}
if _, err := os.Stat(clientCertPath); os.IsNotExist(err) {
if err := createCert(authOptions, org, bits); err != nil {
return err
}
} else {
current, err := CheckCertificateDate(clientCertPath)
if err != nil {
return err
}
if !current {
log.Info("Client certificate is outdated and needs to be regenerated")
os.Remove(clientKeyPath)
if err := createCert(authOptions, org, bits); err != nil {
return err
}
}
}
return nil
}
+294
View File
@@ -0,0 +1,294 @@
package cert
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io/ioutil"
"math/big"
"net"
"os"
"time"
"errors"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/log"
)
var defaultGenerator = NewX509CertGenerator()
type Options struct {
Hosts []string
CertFile, KeyFile, CAFile, CAKeyFile, Org string
Bits int
SwarmMaster bool
}
type Generator interface {
GenerateCACertificate(certFile, keyFile, org string, bits int) error
GenerateCert(opts *Options) error
ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error)
ValidateCertificate(addr string, authOptions *auth.Options) (bool, error)
}
type X509CertGenerator struct{}
func NewX509CertGenerator() Generator {
return &X509CertGenerator{}
}
func GenerateCACertificate(certFile, keyFile, org string, bits int) error {
return defaultGenerator.GenerateCACertificate(certFile, keyFile, org, bits)
}
func GenerateCert(opts *Options) error {
return defaultGenerator.GenerateCert(opts)
}
func ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) {
return defaultGenerator.ValidateCertificate(addr, authOptions)
}
func ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) {
return defaultGenerator.ReadTLSConfig(addr, authOptions)
}
func SetCertGenerator(cg Generator) {
defaultGenerator = cg
}
func (xcg *X509CertGenerator) getTLSConfig(caCert, cert, key []byte, allowInsecure bool) (*tls.Config, error) {
// TLS config
var tlsConfig tls.Config
tlsConfig.InsecureSkipVerify = allowInsecure
certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(caCert)
if !ok {
return &tlsConfig, errors.New("There was an error reading certificate")
}
tlsConfig.RootCAs = certPool
keypair, err := tls.X509KeyPair(cert, key)
if err != nil {
return &tlsConfig, err
}
tlsConfig.Certificates = []tls.Certificate{keypair}
return &tlsConfig, nil
}
func (xcg *X509CertGenerator) newCertificate(org string) (*x509.Certificate, error) {
now := time.Now()
// need to set notBefore slightly in the past to account for time
// skew in the VMs otherwise the certs sometimes are not yet valid
notBefore := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-5, 0, 0, time.Local)
notAfter := notBefore.Add(time.Hour * 24 * 1080)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{org},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement,
BasicConstraintsValid: true,
}, nil
}
// GenerateCACertificate generates a new certificate authority from the specified org
// and bit size and stores the resulting certificate and key file
// in the arguments.
func (xcg *X509CertGenerator) GenerateCACertificate(certFile, keyFile, org string, bits int) error {
template, err := xcg.newCertificate(org)
if err != nil {
return err
}
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign
template.KeyUsage |= x509.KeyUsageKeyEncipherment
template.KeyUsage |= x509.KeyUsageKeyAgreement
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
if err != nil {
return err
}
certOut, err := os.Create(certFile)
if err != nil {
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
return nil
}
// GenerateCert generates a new certificate signed using the provided
// certificate authority files and stores the result in the certificate
// file and key provided. The provided host names are set to the
// appropriate certificate fields.
func (xcg *X509CertGenerator) GenerateCert(opts *Options) error {
template, err := xcg.newCertificate(opts.Org)
if err != nil {
return err
}
// client
if len(opts.Hosts) == 1 && opts.Hosts[0] == "" {
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
template.KeyUsage = x509.KeyUsageDigitalSignature
} else { // server
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
if opts.SwarmMaster {
// Extend the Swarm master's server certificate
// permissions to also be able to connect to downstream
// nodes as a client.
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
}
for _, h := range opts.Hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
}
tlsCert, err := tls.LoadX509KeyPair(opts.CAFile, opts.CAKeyFile)
if err != nil {
return err
}
priv, err := rsa.GenerateKey(rand.Reader, opts.Bits)
if err != nil {
return err
}
x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
if err != nil {
return err
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, x509Cert, &priv.PublicKey, tlsCert.PrivateKey)
if err != nil {
return err
}
certOut, err := os.Create(opts.CertFile)
if err != nil {
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
keyOut, err := os.OpenFile(opts.KeyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
return nil
}
// ReadTLSConfig reads the tls config for a machine.
func (xcg *X509CertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) {
caCertPath := authOptions.CaCertPath
clientCertPath := authOptions.ClientCertPath
clientKeyPath := authOptions.ClientKeyPath
log.Debugf("Reading CA certificate from %s", caCertPath)
caCert, err := ioutil.ReadFile(caCertPath)
if err != nil {
return nil, err
}
log.Debugf("Reading client certificate from %s", clientCertPath)
clientCert, err := ioutil.ReadFile(clientCertPath)
if err != nil {
return nil, err
}
log.Debugf("Reading client key from %s", clientKeyPath)
clientKey, err := ioutil.ReadFile(clientKeyPath)
if err != nil {
return nil, err
}
return xcg.getTLSConfig(caCert, clientCert, clientKey, false)
}
// ValidateCertificate validate the certificate installed on the vm.
func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) {
tlsConfig, err := xcg.ReadTLSConfig(addr, authOptions)
if err != nil {
return false, err
}
dialer := &net.Dialer{
Timeout: time.Second * 20,
}
_, err = tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
if err != nil {
return false, err
}
return true, nil
}
func CheckCertificateDate(certPath string) (bool, error) {
log.Debugf("Reading certificate data from %s", certPath)
certBytes, err := ioutil.ReadFile(certPath)
if err != nil {
return false, err
}
log.Debug("Decoding PEM data...")
pemBlock, _ := pem.Decode(certBytes)
if pemBlock == nil {
return false, errors.New("Failed to decode PEM data")
}
log.Debug("Parsing certificate...")
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return false, err
}
if time.Now().After(cert.NotAfter) {
return false, nil
}
return true, nil
}
+84
View File
@@ -0,0 +1,84 @@
package log
import (
"fmt"
"io"
"os"
)
type FmtMachineLogger struct {
outWriter io.Writer
errWriter io.Writer
debug bool
history *HistoryRecorder
}
// NewFmtMachineLogger creates a MachineLogger implementation used by the drivers
func NewFmtMachineLogger() MachineLogger {
return &FmtMachineLogger{
outWriter: os.Stdout,
errWriter: os.Stderr,
debug: false,
history: NewHistoryRecorder(),
}
}
func (ml *FmtMachineLogger) SetDebug(debug bool) {
ml.debug = debug
}
func (ml *FmtMachineLogger) SetOutWriter(out io.Writer) {
ml.outWriter = out
}
func (ml *FmtMachineLogger) SetErrWriter(err io.Writer) {
ml.errWriter = err
}
func (ml *FmtMachineLogger) Debug(args ...interface{}) {
ml.history.Record(args...)
if ml.debug {
fmt.Fprintln(ml.errWriter, args...)
}
}
func (ml *FmtMachineLogger) Debugf(fmtString string, args ...interface{}) {
ml.history.Recordf(fmtString, args...)
if ml.debug {
fmt.Fprintf(ml.errWriter, fmtString+"\n", args...)
}
}
func (ml *FmtMachineLogger) Error(args ...interface{}) {
ml.history.Record(args...)
fmt.Fprintln(ml.errWriter, args...)
}
func (ml *FmtMachineLogger) Errorf(fmtString string, args ...interface{}) {
ml.history.Recordf(fmtString, args...)
fmt.Fprintf(ml.errWriter, fmtString+"\n", args...)
}
func (ml *FmtMachineLogger) Info(args ...interface{}) {
ml.history.Record(args...)
fmt.Fprintln(ml.outWriter, args...)
}
func (ml *FmtMachineLogger) Infof(fmtString string, args ...interface{}) {
ml.history.Recordf(fmtString, args...)
fmt.Fprintf(ml.outWriter, fmtString+"\n", args...)
}
func (ml *FmtMachineLogger) Warn(args ...interface{}) {
ml.history.Record(args...)
fmt.Fprintln(ml.outWriter, args...)
}
func (ml *FmtMachineLogger) Warnf(fmtString string, args ...interface{}) {
ml.history.Recordf(fmtString, args...)
fmt.Fprintf(ml.outWriter, fmtString+"\n", args...)
}
func (ml *FmtMachineLogger) History() []string {
return ml.history.records
}
+34
View File
@@ -0,0 +1,34 @@
package log
import (
"fmt"
"sync"
)
type HistoryRecorder struct {
lock *sync.Mutex
records []string
}
func NewHistoryRecorder() *HistoryRecorder {
return &HistoryRecorder{
lock: &sync.Mutex{},
records: []string{},
}
}
func (ml *HistoryRecorder) History() []string {
return ml.records
}
func (ml *HistoryRecorder) Record(args ...interface{}) {
ml.lock.Lock()
defer ml.lock.Unlock()
ml.records = append(ml.records, fmt.Sprint(args...))
}
func (ml *HistoryRecorder) Recordf(fmtString string, args ...interface{}) {
ml.lock.Lock()
defer ml.lock.Unlock()
ml.records = append(ml.records, fmt.Sprintf(fmtString, args...))
}
+74
View File
@@ -0,0 +1,74 @@
package log
import (
"io"
"regexp"
)
const redactedText = "<REDACTED>"
var (
logger = NewFmtMachineLogger()
// (?s) enables '.' to match '\n' -- see https://golang.org/pkg/regexp/syntax/
certRegex = regexp.MustCompile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----")
keyRegex = regexp.MustCompile("(?s)-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----")
)
func stripSecrets(original []string) []string {
stripped := []string{}
for _, line := range original {
line = certRegex.ReplaceAllString(line, redactedText)
line = keyRegex.ReplaceAllString(line, redactedText)
stripped = append(stripped, line)
}
return stripped
}
func Debug(args ...interface{}) {
logger.Debug(args...)
}
func Debugf(fmtString string, args ...interface{}) {
logger.Debugf(fmtString, args...)
}
func Error(args ...interface{}) {
logger.Error(args...)
}
func Errorf(fmtString string, args ...interface{}) {
logger.Errorf(fmtString, args...)
}
func Info(args ...interface{}) {
logger.Info(args...)
}
func Infof(fmtString string, args ...interface{}) {
logger.Infof(fmtString, args...)
}
func Warn(args ...interface{}) {
logger.Warn(args...)
}
func Warnf(fmtString string, args ...interface{}) {
logger.Warnf(fmtString, args...)
}
func SetDebug(debug bool) {
logger.SetDebug(debug)
}
func SetOutWriter(out io.Writer) {
logger.SetOutWriter(out)
}
func SetErrWriter(err io.Writer) {
logger.SetErrWriter(err)
}
func History() []string {
return stripSecrets(logger.History())
}
+24
View File
@@ -0,0 +1,24 @@
package log
import "io"
type MachineLogger interface {
SetDebug(debug bool)
SetOutWriter(io.Writer)
SetErrWriter(io.Writer)
Debug(args ...interface{})
Debugf(fmtString string, args ...interface{})
Error(args ...interface{})
Errorf(fmtString string, args ...interface{})
Info(args ...interface{})
Infof(fmtString string, args ...interface{})
Warn(args ...interface{})
Warnf(fmtString string, args ...interface{})
History() []string
}
+542
View File
@@ -0,0 +1,542 @@
package mcnutils
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/version"
)
const (
defaultURL = "https://api.github.com/repos/boot2docker/boot2docker/releases"
defaultISOFilename = "boot2docker.iso"
defaultVolumeIDOffset = int64(0x8028)
versionPrefix = "-v"
defaultVolumeIDLength = 32
)
var (
GithubAPIToken string
)
var (
errGitHubAPIResponse = errors.New(`failure getting a version tag from the Github API response (are you getting rate limited by Github?)`)
)
var (
AUFSBugB2DVersions = map[string]string{
"v1.9.1": "https://github.com/docker/docker/issues/18180",
}
)
func defaultTimeout(network, addr string) (net.Conn, error) {
return net.Dial(network, addr)
}
func getClient() *http.Client {
transport := http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
Dial: defaultTimeout,
}
return &http.Client{
Transport: &transport,
}
}
func getRequest(apiURL string) (*http.Request, error) {
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return nil, err
}
if GithubAPIToken != "" {
req.Header.Add("Authorization", fmt.Sprintf("token %s", GithubAPIToken))
}
return req, nil
}
// releaseGetter is a client that gets release information of a product and downloads it.
type releaseGetter interface {
// filename returns filename of the product.
filename() string
// getReleaseTag gets a release tag from the given URL.
getReleaseTag(apiURL string) (string, error)
// getReleaseURL gets the latest release download URL from the given URL.
getReleaseURL(apiURL string) (string, error)
// download downloads a file from the given dlURL and saves it under dir.
download(dir, file, dlURL string) error
}
// b2dReleaseGetter implements the releaseGetter interface for getting the release of Boot2Docker.
type b2dReleaseGetter struct {
isoFilename string
}
func (b *b2dReleaseGetter) filename() string {
if b == nil {
return ""
}
return b.isoFilename
}
// getReleaseTag gets the release tag of Boot2Docker from apiURL.
func (*b2dReleaseGetter) getReleaseTag(apiURL string) (string, error) {
if apiURL == "" {
apiURL = defaultURL
}
if !version.RC() {
// Just go straight to the convenience URL for "/latest" if we
// are a non-release candidate version. "/latest" won't return
// non-RCs, so that's what we use for stable releases of
// Machine.
apiURL = apiURL + "/latest"
}
client := getClient()
req, err := getRequest(apiURL)
if err != nil {
return "", err
}
rsp, err := client.Do(req)
if err != nil {
return "", err
}
defer rsp.Body.Close()
// If we call the API endpoint
// "/repos/boot2docker/boot2docker/releases" without specifying
// "/latest", we will receive a list of releases instead of a single
// one, and we should decode accordingly.
if version.RC() {
var tags []struct {
TagName string `json:"tag_name"`
}
if err := json.NewDecoder(rsp.Body).Decode(&tags); err != nil {
return "", err
}
t := tags[0]
if t.TagName == "" {
return "", errGitHubAPIResponse
}
return t.TagName, nil
}
// Otherwise, we get back just one release, which we can decode to get
// the tag.
var t struct {
TagName string `json:"tag_name"`
}
if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil {
return "", err
}
if t.TagName == "" {
return "", errGitHubAPIResponse
}
return t.TagName, nil
}
// getReleaseURL gets the latest release URL of Boot2Docker.
func (b *b2dReleaseGetter) getReleaseURL(apiURL string) (string, error) {
if apiURL == "" {
apiURL = defaultURL
}
// match github (enterprise) release urls:
// https://api.github.com/repos/../../releases or
// https://some.github.enterprise/api/v3/repos/../../releases
re := regexp.MustCompile("(https?)://([^/]+)(/api/v3)?/repos/([^/]+)/([^/]+)/releases")
matches := re.FindStringSubmatch(apiURL)
if len(matches) != 6 {
// does not match a github releases api URL
return apiURL, nil
}
scheme, host, org, repo := matches[1], matches[2], matches[4], matches[5]
if host == "api.github.com" {
host = "github.com"
}
tag, err := b.getReleaseTag(apiURL)
if err != nil {
return "", err
}
log.Infof("Latest release for %s/%s/%s is %s", host, org, repo, tag)
bugURL, ok := AUFSBugB2DVersions[tag]
if ok {
log.Warnf(`
Boot2Docker %s has a known issue with AUFS.
See here for more details: %s
Consider specifying another storage driver (e.g. 'overlay') using '--engine-storage-driver' instead.
`, tag, bugURL)
}
url := fmt.Sprintf("%s://%s/%s/%s/releases/download/%s/%s", scheme, host, org, repo, tag, b.isoFilename)
return url, nil
}
func (*b2dReleaseGetter) download(dir, file, isoURL string) error {
u, err := url.Parse(isoURL)
var src io.ReadCloser
if u.Scheme == "file" || u.Scheme == "" {
s, err := os.Open(u.Path)
if err != nil {
return err
}
src = s
} else {
client := getClient()
s, err := client.Get(isoURL)
if err != nil {
return err
}
src = &ReaderWithProgress{
ReadCloser: s.Body,
out: os.Stdout,
expectedLength: s.ContentLength,
}
}
defer src.Close()
// Download to a temp file first then rename it to avoid partial download.
f, err := ioutil.TempFile(dir, file+".tmp")
if err != nil {
return err
}
defer func() {
if err := removeFileIfExists(f.Name()); err != nil {
log.Warnf("Error removing file: %s", err)
}
}()
if _, err := io.Copy(f, src); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
// Dest is the final path of the boot2docker.iso file.
dest := filepath.Join(dir, file)
// Windows can't rename in place, so remove the old file before
// renaming the temporary downloaded file.
if err := removeFileIfExists(dest); err != nil {
return err
}
return os.Rename(f.Name(), dest)
}
// iso is an ISO volume.
type iso interface {
// path returns the path of the ISO.
path() string
// exists reports whether the ISO exists.
exists() bool
// version returns version information of the ISO.
version() (string, error)
}
// b2dISO represents a Boot2Docker ISO. It implements the ISO interface.
type b2dISO struct {
// path of Boot2Docker ISO
commonIsoPath string
// offset and length of ISO volume ID
// cf. http://serverfault.com/questions/361474/is-there-a-way-to-change-a-iso-files-volume-id-from-the-command-line
volumeIDOffset int64
volumeIDLength int
}
func (b *b2dISO) path() string {
if b == nil {
return ""
}
return b.commonIsoPath
}
func (b *b2dISO) exists() bool {
if b == nil {
return false
}
_, err := os.Stat(b.commonIsoPath)
return !os.IsNotExist(err)
}
// version scans the volume ID in b and returns its version tag.
func (b *b2dISO) version() (string, error) {
if b == nil {
return "", nil
}
iso, err := os.Open(b.commonIsoPath)
if err != nil {
return "", err
}
defer iso.Close()
isoMetadata := make([]byte, b.volumeIDLength)
_, err = iso.ReadAt(isoMetadata, b.volumeIDOffset)
if err != nil {
return "", err
}
trimmedVersion := strings.TrimSpace(string(isoMetadata))
versionIndex := strings.Index(trimmedVersion, versionPrefix)
if versionIndex == -1 {
return "", fmt.Errorf("Did not find prefix %q in version string", versionPrefix)
}
// Original magic file string looks similar to this: "Boot2Docker-v0.1.0 "
// This will return "v0.1.0" given the above string
vers := trimmedVersion[versionIndex+1:]
log.Debug("local Boot2Docker ISO version: ", vers)
return vers, nil
}
func removeFileIfExists(name string) error {
if _, err := os.Stat(name); err == nil {
if err := os.Remove(name); err != nil {
return fmt.Errorf("Error removing temporary download file: %s", err)
}
}
return nil
}
type B2dUtils struct {
releaseGetter
iso
storePath string
imgCachePath string
}
func NewB2dUtils(storePath string) *B2dUtils {
imgCachePath := filepath.Join(storePath, "cache")
return &B2dUtils{
releaseGetter: &b2dReleaseGetter{isoFilename: defaultISOFilename},
iso: &b2dISO{
commonIsoPath: filepath.Join(imgCachePath, defaultISOFilename),
volumeIDOffset: defaultVolumeIDOffset,
volumeIDLength: defaultVolumeIDLength,
},
storePath: storePath,
imgCachePath: imgCachePath,
}
}
// DownloadISO downloads boot2docker ISO image for the given tag and save it at dest.
func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error {
log.Infof("Downloading %s from %s...", b.path(), isoURL)
return b.download(dir, file, isoURL)
}
type ReaderWithProgress struct {
io.ReadCloser
out io.Writer
bytesTransferred int64
expectedLength int64
nextPercentToPrint int64
}
func (r *ReaderWithProgress) Read(p []byte) (int, error) {
n, err := r.ReadCloser.Read(p)
if n > 0 {
r.bytesTransferred += int64(n)
percentage := r.bytesTransferred * 100 / r.expectedLength
for percentage >= r.nextPercentToPrint {
if r.nextPercentToPrint%10 == 0 {
fmt.Fprintf(r.out, "%d%%", r.nextPercentToPrint)
} else if r.nextPercentToPrint%2 == 0 {
fmt.Fprint(r.out, ".")
}
r.nextPercentToPrint += 2
}
}
return n, err
}
func (r *ReaderWithProgress) Close() error {
fmt.Fprintln(r.out)
return r.ReadCloser.Close()
}
func (b *B2dUtils) DownloadLatestBoot2Docker(apiURL string) error {
latestReleaseURL, err := b.getReleaseURL(apiURL)
if err != nil {
return err
}
return b.DownloadISOFromURL(latestReleaseURL)
}
func (b *B2dUtils) DownloadISOFromURL(latestReleaseURL string) error {
return b.DownloadISO(b.imgCachePath, b.filename(), latestReleaseURL)
}
func (b *B2dUtils) UpdateISOCache(isoURL string) error {
// recreate the cache dir if it has been manually deleted
if _, err := os.Stat(b.imgCachePath); os.IsNotExist(err) {
log.Infof("Image cache directory does not exist, creating it at %s...", b.imgCachePath)
if err := os.Mkdir(b.imgCachePath, 0700); err != nil {
return err
}
}
exists := b.exists()
if isoURL != "" {
if exists {
// Warn that the b2d iso won't be updated if isoURL is set
log.Warnf("Boot2Docker URL was explicitly set to %q at create time, so Docker Machine cannot upgrade this machine to the latest version.", isoURL)
}
// Non-default B2D are not cached
return nil
}
if !exists {
log.Info("No default Boot2Docker ISO found locally, downloading the latest release...")
return b.DownloadLatestBoot2Docker("")
}
latest := b.isLatest()
if !latest {
log.Info("Default Boot2Docker ISO is out-of-date, downloading the latest release...")
return b.DownloadLatestBoot2Docker("")
}
return nil
}
func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error {
if err := b.UpdateISOCache(isoURL); err != nil {
return err
}
// TODO: This is a bit off-color.
machineDir := filepath.Join(b.storePath, "machines", machineName)
machineIsoPath := filepath.Join(machineDir, b.filename())
// By default just copy the existing "cached" iso to the machine's directory...
if isoURL == "" {
log.Infof("Copying %s to %s...", b.path(), machineIsoPath)
return CopyFile(b.path(), machineIsoPath)
}
// if ISO is specified, check if it matches a github releases url or fallback to a direct download
downloadURL, err := b.getReleaseURL(isoURL)
if err != nil {
return err
}
return b.DownloadISO(machineDir, b.filename(), downloadURL)
}
// isLatest checks the latest release tag and
// reports whether the local ISO cache is the latest version.
//
// It returns false if failing to get the local ISO version
// and true if failing to fetch the latest release tag.
func (b *B2dUtils) isLatest() bool {
localVer, err := b.version()
if err != nil {
log.Warn("Unable to get the local Boot2Docker ISO version: ", err)
return false
}
latestVer, err := b.getReleaseTag("")
if err != nil {
log.Warn("Unable to get the latest Boot2Docker ISO release version: ", err)
return true
}
return localVer == latestVer
}
// MakeDiskImage makes a boot2docker VM disk image.
// See https://github.com/boot2docker/boot2docker/blob/master/rootfs/rootfs/etc/rc.d/automount
func MakeDiskImage(publicSSHKeyPath string) (*bytes.Buffer, error) {
magicString := "boot2docker, please format-me"
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
// magicString first so the automount script knows to format the disk
file := &tar.Header{Name: magicString, Size: int64(len(magicString))}
log.Debug("Writing magic tar header")
if err := tw.WriteHeader(file); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(magicString)); err != nil {
return nil, err
}
// .ssh/key.pub => authorized_keys
file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700}
if err := tw.WriteHeader(file); err != nil {
return nil, err
}
log.Debug("Writing SSH key tar header")
pubKey, err := ioutil.ReadFile(publicSSHKeyPath)
if err != nil {
return nil, err
}
file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644}
if err := tw.WriteHeader(file); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(pubKey)); err != nil {
return nil, err
}
file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644}
if err := tw.WriteHeader(file); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(pubKey)); err != nil {
return nil, err
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf, nil
}
+133
View File
@@ -0,0 +1,133 @@
package mcnutils
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"os"
"runtime"
"strconv"
"time"
)
type MultiError struct {
Errs []error
}
func (e MultiError) Error() string {
aggregate := ""
for _, err := range e.Errs {
aggregate += err.Error() + "\n"
}
return aggregate
}
// GetHomeDir returns the home directory
// TODO: Having this here just strikes me as dangerous, but some of the drivers
// depend on it ;_;
func GetHomeDir() string {
if runtime.GOOS == "windows" {
return os.Getenv("USERPROFILE")
}
return os.Getenv("HOME")
}
func GetUsername() string {
u := "unknown"
osUser := ""
switch runtime.GOOS {
case "darwin", "linux":
osUser = os.Getenv("USER")
case "windows":
osUser = os.Getenv("USERNAME")
}
if osUser != "" {
u = osUser
}
return u
}
func CopyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
if _, err = io.Copy(out, in); err != nil {
return err
}
fi, err := os.Stat(src)
if err != nil {
return err
}
return os.Chmod(dst, fi.Mode())
}
func WaitForSpecificOrError(f func() (bool, error), maxAttempts int, waitInterval time.Duration) error {
for i := 0; i < maxAttempts; i++ {
stop, err := f()
if err != nil {
return err
}
if stop {
return nil
}
time.Sleep(waitInterval)
}
return fmt.Errorf("Maximum number of retries (%d) exceeded", maxAttempts)
}
func WaitForSpecific(f func() bool, maxAttempts int, waitInterval time.Duration) error {
return WaitForSpecificOrError(func() (bool, error) {
return f(), nil
}, maxAttempts, waitInterval)
}
func WaitFor(f func() bool) error {
return WaitForSpecific(f, 60, 3*time.Second)
}
// TruncateID returns a shorten id
// Following two functions are from github.com/docker/docker/utils module. It
// was way overkill to include the whole module, so we just have these bits
// that we're using here.
func TruncateID(id string) string {
shortLen := 12
if len(id) < shortLen {
shortLen = len(id)
}
return id[:shortLen]
}
// GenerateRandomID returns an unique id
func GenerateRandomID() string {
for {
id := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, id); err != nil {
panic(err) // This shouldn't happen
}
value := hex.EncodeToString(id)
// if we try to parse the truncated for as an int and we don't have
// an error then the value is all numeric and causes issues when
// used as a hostname. ref #3869
if _, err := strconv.ParseInt(TruncateID(value), 10, 64); err == nil {
continue
}
return value
}
}
+23
View File
@@ -0,0 +1,23 @@
package version
import (
"fmt"
"strings"
)
var (
Version = "0.14.0"
// GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD"
)
// FullVersion formats the version to be printed
func FullVersion() string {
return fmt.Sprintf("%s, build %s", Version, GitCommit)
}
// RC checks if the Machine version is a release candidate or not
func RC() bool {
return strings.Contains(Version, "rc")
}