Copy dependencies into vendor dir, using dep
This commit is contained in:
1 parent
549ccff5eb
commit
b534ea60f5
2857 files changed
+945317
No files matched your search
+191
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
Reference in new issue
Block a user