Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28c80a648c | |||
| 9c72861438 | |||
| ac75d5329e | |||
| 37c20e2bf0 | |||
| dec1b65a20 | |||
| bb6db46962 | |||
| d8cde46e76 | |||
| f3f3d1fe73 | |||
| 2f5f6c159b | |||
| 56745e14f5 | |||
| bf0c326cd5 | |||
| b2811a8252 | |||
| d00271aab3 | |||
| 4da4150abe | |||
| 3a681d0c0c | |||
| 518ad704b7 | |||
| fb39fa2cc6 | |||
| e3ab74e09f | |||
| 39a8ba1365 | |||
| 241903d0b0 | |||
| c615539929 | |||
| 9365a43179 | |||
| ae064207d5 | |||
| 224bb78175 | |||
| f27505626f | |||
| 9b97726621 | |||
| a49f263e90 | |||
| 618d2f3158 | |||
| 4dccc54191 | |||
| e7092ff5c5 | |||
| 29e6194e49 | |||
| 8e3559c3b1 | |||
| c614bb703d | |||
| 733376fa54 | |||
| 985cd94952 | |||
| 6343d8f16a | |||
| a3bf467378 | |||
| b3eacc5c2b | |||
| 78174dae4e | |||
| da20c36454 | |||
| 28a13111b8 | |||
| d2339b7ccc | |||
| 4128a49f7a | |||
| 7e64e90670 | |||
| 034040d2a7 | |||
| ff2e07771e | |||
| 1de5171857 | |||
| 683d6a73ec | |||
| 0c0ff5a25b | |||
| d99a1fab86 | |||
| 183beedb12 | |||
| 132b289b0e | |||
| a6269671ec | |||
| 1c71eaaa91 | |||
| 775450f46a | |||
| bd6c31c2d9 | |||
| da749e2470 | |||
| fcb7967bf5 | |||
| 750e09d51d | |||
| eaf8550e50 | |||
| 6d6b3e1ac2 | |||
| db10f7c0ce | |||
| cc9549eac8 | |||
| 0cee01261c | |||
| 98e7f2a341 | |||
| ed463bccda | |||
| c416f6e14b |
@@ -1,3 +1,19 @@
|
||||
## 0.8.2 (July 17, 2015)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* builder/docker: Add option to use a Pty [GH-2425]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* core: Fix crash when `min_packer_version` is specified in a template. [GH-2385]
|
||||
* builder/amazon: Fix EC2 devices being included in EBS mappings [GH-2459]
|
||||
* builder/googlecompute: Fix default name for GCE images [GH-2400]
|
||||
* builder/null: Fix error message with missing ssh_host [GH-2407]
|
||||
* builder/virtualbox: Use --portcount on VirtualBox 5.x [GH-2438]
|
||||
* provisioner/puppet: Packer now correctly handles a directory for manifest_file [GH-2463]
|
||||
* provisioner/winrm: Fix potential crash with WinRM [GH-2416]
|
||||
|
||||
## 0.8.1 (July 2, 2015)
|
||||
|
||||
IMPROVEMENTS:
|
||||
@@ -28,6 +44,7 @@ BUG FIXES:
|
||||
now works [GH-2323]
|
||||
* provisioner/chef: Use knife config file vs command-line params to
|
||||
clean up nodes so full set of features can be used [GH-2306]
|
||||
* post-processor/compress: Fixed crash in compress post-processor plugin [GH-2311]
|
||||
|
||||
## 0.8.0 (June 23, 2015)
|
||||
|
||||
|
||||
+2
-2
@@ -53,8 +53,8 @@ it raises the chances we can quickly merge or address your contributions.
|
||||
If you have never worked with Go before, you will have to complete the
|
||||
following steps in order to be able to compile and test Packer.
|
||||
|
||||
1. Install Go. Make sure the Go version is at least Go 1.2. Packer will not work with anything less than
|
||||
Go 1.2. On a Mac, you can `brew install go` to install Go 1.2.
|
||||
1. Install Go. Make sure the Go version is at least Go 1.4. Packer will not work with anything less than
|
||||
Go 1.4. On a Mac, you can `brew install go` to install Go 1.4.
|
||||
|
||||
2. Set and export the `GOPATH` environment variable and update your `PATH`.
|
||||
For example, you can add to your `.bash_profile`.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
@@ -47,11 +49,14 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
|
||||
}
|
||||
|
||||
mapping := &ec2.BlockDeviceMapping{
|
||||
EBS: ebsBlockDevice,
|
||||
DeviceName: aws.String(blockDevice.DeviceName),
|
||||
VirtualName: aws.String(blockDevice.VirtualName),
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
|
||||
mapping.EBS = ebsBlockDevice
|
||||
}
|
||||
|
||||
if blockDevice.NoDevice {
|
||||
mapping.NoDevice = aws.String("")
|
||||
}
|
||||
|
||||
@@ -17,17 +17,15 @@ func TestBlockDevice(t *testing.T) {
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VirtualName: "ephemeral0",
|
||||
SnapshotId: "snap-1234",
|
||||
VolumeType: "standard",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
IOPS: 1000,
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("/dev/sdb"),
|
||||
VirtualName: aws.String("ephemeral0"),
|
||||
VirtualName: aws.String(""),
|
||||
EBS: &ec2.EBSBlockDevice{
|
||||
SnapshotID: aws.String("snap-1234"),
|
||||
VolumeType: aws.String("standard"),
|
||||
@@ -55,7 +53,6 @@ func TestBlockDevice(t *testing.T) {
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VirtualName: "ephemeral0",
|
||||
VolumeType: "io1",
|
||||
VolumeSize: 8,
|
||||
DeleteOnTermination: true,
|
||||
@@ -64,7 +61,7 @@ func TestBlockDevice(t *testing.T) {
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("/dev/sdb"),
|
||||
VirtualName: aws.String("ephemeral0"),
|
||||
VirtualName: aws.String(""),
|
||||
EBS: &ec2.EBSBlockDevice{
|
||||
VolumeType: aws.String("io1"),
|
||||
VolumeSize: aws.Long(8),
|
||||
@@ -73,6 +70,17 @@ func TestBlockDevice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: &BlockDevice{
|
||||
DeviceName: "/dev/sdb",
|
||||
VirtualName: "ephemeral0",
|
||||
},
|
||||
|
||||
Result: &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("/dev/sdb"),
|
||||
VirtualName: aws.String("ephemeral0"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -84,11 +92,14 @@ func TestBlockDevice(t *testing.T) {
|
||||
expected := []*ec2.BlockDeviceMapping{tc.Result}
|
||||
got := blockDevices.BuildAMIDevices()
|
||||
if !reflect.DeepEqual(expected, got) {
|
||||
t.Fatalf("Bad block device, \nexpected: %s\n\ngot: %s", awsutil.StringValue(expected), awsutil.StringValue(got))
|
||||
t.Fatalf("Bad block device, \nexpected: %s\n\ngot: %s",
|
||||
awsutil.StringValue(expected), awsutil.StringValue(got))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, blockDevices.BuildLaunchDevices()) {
|
||||
t.Fatalf("Bad block device, \nexpected: %s\n\ngot: %s", awsutil.StringValue(expected), awsutil.StringValue(blockDevices.BuildLaunchDevices()))
|
||||
t.Fatalf("Bad block device, \nexpected: %s\n\ngot: %s",
|
||||
awsutil.StringValue(expected),
|
||||
awsutil.StringValue(blockDevices.BuildLaunchDevices()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type Communicator struct {
|
||||
HostDir string
|
||||
ContainerDir string
|
||||
Version *version.Version
|
||||
|
||||
Config *Config
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
@@ -45,7 +45,11 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error {
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if c.canExec() {
|
||||
cmd = exec.Command("docker", "exec", "-i", c.ContainerId, "/bin/sh")
|
||||
if c.Config.Pty {
|
||||
cmd = exec.Command("docker", "exec", "-i", "-t", c.ContainerId, "/bin/sh")
|
||||
} else {
|
||||
cmd = exec.Command("docker", "exec", "-i", c.ContainerId, "/bin/sh")
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("docker", "attach", c.ContainerId)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type Config struct {
|
||||
LoginUsername string `mapstructure:"login_username"`
|
||||
LoginPassword string `mapstructure:"login_password"`
|
||||
LoginServer string `mapstructure:"login_server"`
|
||||
Pty bool
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
type StepConnectDocker struct{}
|
||||
|
||||
func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
containerId := state.Get("container_id").(string)
|
||||
driver := state.Get("driver").(Driver)
|
||||
tempDir := state.Get("temp_dir").(string)
|
||||
@@ -25,6 +26,7 @@ func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction {
|
||||
HostDir: tempDir,
|
||||
ContainerDir: "/packer-files",
|
||||
Version: version,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
state.Put("communicator", comm)
|
||||
|
||||
@@ -59,6 +59,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
|
||||
// Set defaults.
|
||||
if c.Network == "" {
|
||||
c.Network = "default"
|
||||
@@ -73,7 +75,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
}
|
||||
|
||||
if c.ImageName == "" {
|
||||
c.ImageName = "packer-{{timestamp}}"
|
||||
img, err := interpolate.Render("packer-{{timestamp}}", nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Unable to parse image name: %s ", err))
|
||||
c.ImageName = img
|
||||
}
|
||||
}
|
||||
|
||||
if c.InstanceName == "" {
|
||||
@@ -96,7 +103,6 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
c.Comm.SSHUsername = "root"
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
@@ -2,48 +2,10 @@ package googlecompute
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig(t *testing.T) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"account_file": testAccountFile(t),
|
||||
"project_id": "hashicorp",
|
||||
"source_image": "foo",
|
||||
"zone": "us-east-1a",
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
c, warns, errs := NewConfig(testConfig(t))
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error, extra string) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error: %s", extra)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPrepare(t *testing.T) {
|
||||
cases := []struct {
|
||||
Key string
|
||||
@@ -181,6 +143,54 @@ func TestConfigDefaults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
c, _, _ := NewConfig(testConfig(t))
|
||||
if strings.Contains(c.ImageName, "{{timestamp}}") {
|
||||
t.Errorf("ImageName should be interpolated; found %s", c.ImageName)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stuff below
|
||||
|
||||
func testConfig(t *testing.T) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"account_file": testAccountFile(t),
|
||||
"project_id": "hashicorp",
|
||||
"source_image": "foo",
|
||||
"zone": "us-east-1a",
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigStruct(t *testing.T) *Config {
|
||||
c, warns, errs := NewConfig(testConfig(t))
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", len(warns))
|
||||
}
|
||||
if errs != nil {
|
||||
t.Fatalf("bad: %#v", errs)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func testConfigErr(t *testing.T, warns []string, err error, extra string) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("should error: %s", extra)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccountFile(t *testing.T) string {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
|
||||
@@ -33,7 +33,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||
}
|
||||
if c.CommConfig.SSHHost == "" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("host must be specified"))
|
||||
fmt.Errorf("ssh_host must be specified"))
|
||||
}
|
||||
|
||||
if c.CommConfig.SSHUsername == "" {
|
||||
|
||||
@@ -22,7 +22,7 @@ func (d *VBox42Driver) CreateSATAController(vmName string, name string) error {
|
||||
}
|
||||
|
||||
portCountArg := "--sataportcount"
|
||||
if strings.HasPrefix(version, "4.3") {
|
||||
if strings.HasPrefix(version, "4.3") || strings.HasPrefix(version, "5.") {
|
||||
portCountArg = "--portcount"
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,13 @@ func fatalCommand(t *testing.T, m Meta) {
|
||||
err.String())
|
||||
}
|
||||
|
||||
func outputCommand(t *testing.T, m Meta) (string, string) {
|
||||
ui := m.Ui.(*packer.BasicUi)
|
||||
out := ui.Writer.(*bytes.Buffer)
|
||||
err := ui.ErrorWriter.(*bytes.Buffer)
|
||||
return out.String(), err.String()
|
||||
}
|
||||
|
||||
func testFixture(n string) string {
|
||||
return filepath.Join(fixturesDir, n)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
|
||||
config := *m.CoreConfig
|
||||
config.Template = tpl
|
||||
config.Variables = m.flagVars
|
||||
config.Version = m.Version
|
||||
|
||||
// Init the core
|
||||
core, err := packer.NewCore(&config)
|
||||
|
||||
@@ -122,10 +122,8 @@ func TestPush_noName(t *testing.T) {
|
||||
|
||||
func TestPush_cliName(t *testing.T) {
|
||||
var actual []string
|
||||
var actualOpts *uploadOpts
|
||||
uploadFn := func(r io.Reader, opts *uploadOpts) (<-chan struct{}, <-chan error, error) {
|
||||
actual = testArchive(t, r)
|
||||
actualOpts = opts
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
close(doneCh)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"builders":[
|
||||
{
|
||||
"type":"file",
|
||||
"target":"chocolate.txt",
|
||||
"content":"chocolate"
|
||||
}
|
||||
],
|
||||
"min_packer_version":"101.0.0"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateCommandOKVersion(t *testing.T) {
|
||||
c := &ValidateCommand{
|
||||
Meta: testMetaFile(t),
|
||||
}
|
||||
args := []string{
|
||||
filepath.Join(testFixture("validate"), "template.json"),
|
||||
}
|
||||
|
||||
// This should pass with a valid configuration version
|
||||
c.CoreConfig.Version = "102.0.0"
|
||||
if code := c.Run(args); code != 0 {
|
||||
fatalCommand(t, c.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCommandBadVersion(t *testing.T) {
|
||||
c := &ValidateCommand{
|
||||
Meta: testMetaFile(t),
|
||||
}
|
||||
args := []string{
|
||||
filepath.Join(testFixture("validate"), "template.json"),
|
||||
}
|
||||
|
||||
// This should fail with an invalid configuration version
|
||||
c.CoreConfig.Version = "100.0.0"
|
||||
if code := c.Run(args); code != 1 {
|
||||
t.Errorf("Expected exit code 1")
|
||||
}
|
||||
|
||||
stdout, stderr := outputCommand(t, c.Meta)
|
||||
expected := `Error initializing core: This template requires Packer version 101.0.0 or higher; using 100.0.0
|
||||
`
|
||||
if stderr != expected {
|
||||
t.Fatalf("Expected:\n%s\nFound:\n%s\n", expected, stderr)
|
||||
}
|
||||
t.Log(stdout)
|
||||
}
|
||||
@@ -85,8 +85,17 @@ func (c *Communicator) Start(rc *packer.RemoteCmd) error {
|
||||
func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
|
||||
defer shell.Close()
|
||||
|
||||
go io.Copy(rc.Stdout, cmd.Stdout)
|
||||
go io.Copy(rc.Stderr, cmd.Stderr)
|
||||
if rc.Stdout != nil && cmd.Stdout != nil {
|
||||
go io.Copy(rc.Stdout, cmd.Stdout)
|
||||
} else {
|
||||
log.Printf("[WARN] Failed to read stdout for command '%s'", rc.Command)
|
||||
}
|
||||
|
||||
if rc.Stderr != nil && cmd.Stderr != nil {
|
||||
go io.Copy(rc.Stderr, cmd.Stderr)
|
||||
} else {
|
||||
log.Printf("[WARN] Failed to read stderr for command '%s'", rc.Command)
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
|
||||
+1
-2
@@ -246,8 +246,7 @@ func (c *Core) validate() error {
|
||||
|
||||
if versionActual.LessThan(versionMin) {
|
||||
return fmt.Errorf(
|
||||
"This template requires a minimum Packer version of %s,\n"+
|
||||
"but version %s is running.",
|
||||
"This template requires Packer version %s or higher; using %s",
|
||||
versionMin,
|
||||
versionActual)
|
||||
}
|
||||
|
||||
@@ -270,28 +270,43 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s
|
||||
return "", fmt.Errorf("Error creating manifests directory: %s", err)
|
||||
}
|
||||
|
||||
// Upload the main manifest
|
||||
f, err := os.Open(p.config.ManifestFile)
|
||||
// NOTE! manifest_file may either be a directory or a file, as puppet apply
|
||||
// now accepts either one.
|
||||
|
||||
fi, err := os.Stat(p.config.ManifestFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
manifestFilename := p.config.ManifestFile
|
||||
if fi, err := os.Stat(p.config.ManifestFile); err != nil {
|
||||
return "", fmt.Errorf("Error inspecting manifest file: %s", err)
|
||||
} else if !fi.IsDir() {
|
||||
manifestFilename = filepath.Base(manifestFilename)
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
// If manifest_file is a directory we'll upload the whole thing
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Uploading manifest directory from: %s", p.config.ManifestFile))
|
||||
|
||||
remoteManifestDir := fmt.Sprintf("%s/manifests", p.config.StagingDir)
|
||||
err := p.uploadDirectory(ui, comm, remoteManifestDir, p.config.ManifestFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error uploading manifest dir: %s", err)
|
||||
}
|
||||
return remoteManifestDir, nil
|
||||
} else {
|
||||
ui.Say("WARNING: manifest_file should be a file. Use manifest_dir for directories")
|
||||
}
|
||||
// Otherwise manifest_file is a file and we'll upload it
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Uploading manifest file from: %s", p.config.ManifestFile))
|
||||
|
||||
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
|
||||
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.Open(p.config.ManifestFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return remoteManifestFile, nil
|
||||
manifestFilename := filepath.Base(p.config.ManifestFile)
|
||||
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
|
||||
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return remoteManifestFile, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ package main
|
||||
var GitCommit string
|
||||
|
||||
// The main version number that is being run at the moment.
|
||||
const Version = "0.8.1"
|
||||
const Version = "0.8.2"
|
||||
|
||||
// A pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
+31
-26
@@ -1,6 +1,6 @@
|
||||
GIT
|
||||
remote: git://github.com/hashicorp/middleman-hashicorp.git
|
||||
revision: 7796ba44d303ac8e1b566e855e2766e6d0f695fc
|
||||
revision: 76f0f284ad44cea0457484ea83467192f02daf87
|
||||
specs:
|
||||
middleman-hashicorp (0.1.0)
|
||||
bootstrap-sass (~> 3.3)
|
||||
@@ -11,6 +11,7 @@ GIT
|
||||
middleman-minify-html (~> 3.4)
|
||||
middleman-syntax (~> 2.0)
|
||||
rack-contrib (~> 1.2)
|
||||
rack-protection (~> 1.5)
|
||||
rack-rewrite (~> 1.5)
|
||||
rack-ssl-enforcer (~> 0.2)
|
||||
redcarpet (~> 3.2)
|
||||
@@ -20,18 +21,18 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (4.1.10)
|
||||
activesupport (4.1.12)
|
||||
i18n (~> 0.6, >= 0.6.9)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 1.1)
|
||||
autoprefixer-rails (5.1.11)
|
||||
autoprefixer-rails (5.2.1)
|
||||
execjs
|
||||
json
|
||||
bootstrap-sass (3.3.4.1)
|
||||
bootstrap-sass (3.3.5.1)
|
||||
autoprefixer-rails (>= 5.0.0.1)
|
||||
sass (>= 3.2.19)
|
||||
sass (>= 3.3.0)
|
||||
builder (3.2.2)
|
||||
celluloid (0.16.0)
|
||||
timers (~> 4.0.0)
|
||||
@@ -53,29 +54,30 @@ GEM
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
compass-import-once (1.0.5)
|
||||
sass (>= 3.2, < 3.5)
|
||||
daemons (1.2.2)
|
||||
daemons (1.2.3)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.7)
|
||||
execjs (2.5.2)
|
||||
ffi (1.9.8)
|
||||
ffi (1.9.10)
|
||||
git-version-bump (0.15.1)
|
||||
haml (4.0.6)
|
||||
tilt
|
||||
hike (1.2.3)
|
||||
hitimes (1.2.2)
|
||||
hooks (0.4.0)
|
||||
uber (~> 0.0.4)
|
||||
htmlcompressor (0.1.2)
|
||||
htmlcompressor (0.2.0)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.7.0)
|
||||
json (1.8.2)
|
||||
kramdown (1.7.0)
|
||||
json (1.8.3)
|
||||
kramdown (1.8.0)
|
||||
less (2.6.0)
|
||||
commonjs (~> 0.2.7)
|
||||
libv8 (3.16.14.7)
|
||||
listen (2.10.0)
|
||||
libv8 (3.16.14.11)
|
||||
listen (2.10.1)
|
||||
celluloid (~> 0.16.0)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
@@ -106,8 +108,8 @@ GEM
|
||||
em-websocket (~> 0.5.1)
|
||||
middleman-core (>= 3.3)
|
||||
rack-livereload (~> 0.3.15)
|
||||
middleman-minify-html (3.4.0)
|
||||
htmlcompressor (~> 0.1.0)
|
||||
middleman-minify-html (3.4.1)
|
||||
htmlcompressor (~> 0.2.0)
|
||||
middleman-core (>= 3.2)
|
||||
middleman-sprockets (3.4.2)
|
||||
middleman-core (>= 3.3)
|
||||
@@ -117,31 +119,34 @@ GEM
|
||||
middleman-syntax (2.0.0)
|
||||
middleman-core (~> 3.2)
|
||||
rouge (~> 1.0)
|
||||
minitest (5.6.1)
|
||||
multi_json (1.11.0)
|
||||
minitest (5.7.0)
|
||||
multi_json (1.11.2)
|
||||
padrino-helpers (0.12.5)
|
||||
i18n (~> 0.6, >= 0.6.7)
|
||||
padrino-support (= 0.12.5)
|
||||
tilt (~> 1.4.1)
|
||||
padrino-support (0.12.5)
|
||||
activesupport (>= 3.1)
|
||||
rack (1.6.1)
|
||||
rack-contrib (1.2.0)
|
||||
rack (>= 0.9.1)
|
||||
rack-livereload (0.3.15)
|
||||
rack (1.6.4)
|
||||
rack-contrib (1.3.0)
|
||||
git-version-bump (~> 0.15)
|
||||
rack (~> 1.4)
|
||||
rack-livereload (0.3.16)
|
||||
rack
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-rewrite (1.5.1)
|
||||
rack-ssl-enforcer (0.2.8)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rb-fsevent (0.9.4)
|
||||
rb-fsevent (0.9.5)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
redcarpet (3.2.3)
|
||||
ref (1.0.5)
|
||||
rouge (1.8.0)
|
||||
sass (3.4.13)
|
||||
sprockets (2.12.3)
|
||||
redcarpet (3.3.2)
|
||||
ref (2.0.0)
|
||||
rouge (1.9.1)
|
||||
sass (3.4.16)
|
||||
sprockets (2.12.4)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
all: build
|
||||
|
||||
init:
|
||||
bundle
|
||||
|
||||
dev: init
|
||||
PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman server
|
||||
|
||||
build: init
|
||||
PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman build
|
||||
+1
-2
@@ -16,8 +16,7 @@ Running the site locally is simple. Clone this repo and run the following
|
||||
commands:
|
||||
|
||||
```
|
||||
$ bundle
|
||||
$ PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman server
|
||||
make dev
|
||||
```
|
||||
|
||||
Then open up `localhost:4567`. Note that some URLs you may need to append
|
||||
|
||||
@@ -3,6 +3,17 @@ require "rack/contrib/not_found"
|
||||
require "rack/contrib/response_headers"
|
||||
require "rack/contrib/static_cache"
|
||||
require "rack/contrib/try_static"
|
||||
require "rack/protection"
|
||||
|
||||
# Protect against various bad things
|
||||
use Rack::Protection::JsonCsrf
|
||||
use Rack::Protection::RemoteReferrer
|
||||
use Rack::Protection::HttpOrigin
|
||||
use Rack::Protection::EscapedParams
|
||||
use Rack::Protection::XSSHeader
|
||||
use Rack::Protection::FrameOptions
|
||||
use Rack::Protection::PathTraversal
|
||||
use Rack::Protection::IPSpoofing
|
||||
|
||||
# Properly compress the output if the client can handle it.
|
||||
use Rack::Deflater
|
||||
|
||||
@@ -158,7 +158,7 @@ AMI if one with the same name already exists. Default `false`.
|
||||
generate a temporary keypair. `ssh_private_key_file` must be specified
|
||||
with this.
|
||||
|
||||
* `ssh_private_ip` (bool) - If true, then SSH will always use the private
|
||||
* `ssh_private_ip` (boolean) - If true, then SSH will always use the private
|
||||
IP if available.
|
||||
|
||||
* `subnet_id` (string) - If using VPC, the ID of the subnet, such as
|
||||
|
||||
@@ -197,7 +197,7 @@ AMI if one with the same name already exists. Default `false`.
|
||||
generate a temporary keypair. `ssh_private_key_file` must be specified
|
||||
with this.
|
||||
|
||||
* `ssh_private_ip` (bool) - If true, then SSH will always use the private
|
||||
* `ssh_private_ip` (boolean) - If true, then SSH will always use the private
|
||||
IP if available.
|
||||
|
||||
* `subnet_id` (string) - If using VPC, the ID of the subnet, such as
|
||||
|
||||
@@ -116,11 +116,11 @@ can be configured for this builder.
|
||||
the default is 10 seconds.
|
||||
|
||||
* `disk_cache` (string) - The cache mode to use for disk. Allowed values
|
||||
values include any of "writethrough", "writeback", "none", "unsafe" or
|
||||
"directsync".
|
||||
include any of "writethrough", "writeback", "none", "unsafe" or
|
||||
"directsync". By default, this is set to "writeback".
|
||||
|
||||
* `disk_discard` (string) - The discard mode to use for disk. Allowed values
|
||||
include any of "unmap" or "ignore".
|
||||
include any of "unmap" or "ignore". By default, this is set to "ignore".
|
||||
|
||||
* `disk_image` (boolean) - Packer defaults to building from an ISO file,
|
||||
this parameter controls whether the ISO URL supplied is actually a bootable
|
||||
@@ -148,7 +148,7 @@ can be configured for this builder.
|
||||
* `format` (string) - Either "qcow2" or "raw", this specifies the output
|
||||
format of the virtual machine image. This defaults to "qcow2".
|
||||
|
||||
* `headless` (boolean) - Packer defaults to building virtual machines by
|
||||
* `headless` (boolean) - Packer defaults to building QEMU virtual machines by
|
||||
launching a GUI that shows the console of the machine being built.
|
||||
When this value is set to true, the machine will start without a console.
|
||||
|
||||
@@ -188,6 +188,11 @@ can be configured for this builder.
|
||||
By default this is "output-BUILDNAME" where "BUILDNAME" is the name
|
||||
of the build.
|
||||
|
||||
* `qemu_binary` (string) - The name of the Qemu binary to look for. This
|
||||
defaults to "qemu-system-x86_64", but may need to be changed for some
|
||||
platforms. For example "qemu-kvm", or "qemu-system-i386" may be a better
|
||||
choice for some systems.
|
||||
|
||||
* `qemuargs` (array of array of strings) - Allows complete control over
|
||||
the qemu command line (though not, at this time, qemu-img). Each array
|
||||
of strings makes up a command line switch that overrides matching default
|
||||
@@ -225,11 +230,6 @@ qemu-system-x86 command. The arguments are all printed for review.
|
||||
qemu-system-x86 -m 1024m --no-acpi -netdev user,id=mynet0,hostfwd=hostip:hostport-guestip:guestport -device virtio-net,netdev=mynet0"
|
||||
</pre>
|
||||
|
||||
* `qemu_binary` (string) - The name of the Qemu binary to look for. This
|
||||
defaults to "qemu-system-x86_64", but may need to be changed for some
|
||||
platforms. For example "qemu-kvm", or "qemu-system-i386" may be a better
|
||||
choice for some systems.
|
||||
|
||||
* `shutdown_command` (string) - The command to use to gracefully shut down
|
||||
the machine once all the provisioning is done. By default this is an empty
|
||||
string, which tells Packer to just forcefully shut down the machine.
|
||||
|
||||
@@ -33,7 +33,6 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
|
||||
"iso_checksum_type": "md5",
|
||||
"ssh_username": "packer",
|
||||
"ssh_password": "packer",
|
||||
"ssh_wait_timeout": "30s",
|
||||
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
|
||||
}
|
||||
```
|
||||
@@ -200,7 +199,7 @@ can be configured for this builder.
|
||||
Packer will choose a randomly available port in this range to use as the
|
||||
host port.
|
||||
|
||||
* `ssh_skip_nat_mapping` (bool) - Defaults to false. When enabled, Packer does
|
||||
* `ssh_skip_nat_mapping` (boolean) - Defaults to false. When enabled, Packer does
|
||||
not setup forwarded port mapping for SSH requests and uses `ssh_port` on the
|
||||
host to communicate to the virtual machine
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ the settings here.
|
||||
"source_path": "source.ovf",
|
||||
"ssh_username": "packer",
|
||||
"ssh_password": "packer",
|
||||
"ssh_wait_timeout": "30s",
|
||||
"shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
|
||||
}
|
||||
```
|
||||
@@ -175,7 +174,7 @@ can be configured for this builder.
|
||||
Packer will choose a randomly available port in this range to use as the
|
||||
host port.
|
||||
|
||||
* `ssh_skip_nat_mapping` (bool) - Defaults to false. When enabled, Packer does
|
||||
* `ssh_skip_nat_mapping` (boolean) - Defaults to false. When enabled, Packer does
|
||||
not setup forwarded port mapping for SSH requests and uses `ssh_port` on the
|
||||
host to communicate to the virtual machine
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ Ubuntu to self-install. Still, the example serves to show the basic configuratio
|
||||
"iso_checksum": "af5f788aee1b32c4b2634734309cc9e9",
|
||||
"iso_checksum_type": "md5",
|
||||
"ssh_username": "packer",
|
||||
"ssh_wait_timeout": "30s",
|
||||
"shutdown_command": "shutdown -P now"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -34,7 +34,7 @@ If you want more control over how the archive is created you can specify the fol
|
||||
that support it, from 1 through 9 inclusive. Typically higher compression
|
||||
levels take longer but produce smaller files. Defaults to `6`
|
||||
|
||||
* `keep_input_artifact` (bool) - Keep source files; defaults to `false`
|
||||
* `keep_input_artifact` (boolean) - Keep source files; defaults to `false`
|
||||
|
||||
### Supported Formats
|
||||
|
||||
|
||||
@@ -41,15 +41,16 @@ Optional:
|
||||
* `extra_arguments` (array of strings) - An array of extra arguments to pass to the
|
||||
ansible command. By default, this is empty.
|
||||
|
||||
* `inventory_groups` (string) - You can let Packer generate a temporary inventory
|
||||
for you. It will contains only `127.0.0.1`. Thanks to `inventory_groups`,
|
||||
packer will set the current machine into different groups and will
|
||||
generate an inventory like:
|
||||
* `inventory_groups` (string) - A comma-separated list of groups to which
|
||||
packer will assign the host `127.0.0.1`. A value of `my_group_1,my_group_2`
|
||||
will generate an Ansible inventory like:
|
||||
|
||||
```text
|
||||
[my_group_1]
|
||||
127.0.0.1
|
||||
[my_group_2]
|
||||
127.0.0.1
|
||||
```
|
||||
|
||||
* `inventory_file` (string) - The inventory file to be used by ansible.
|
||||
This file must exist on your local system and will be uploaded to the
|
||||
|
||||
@@ -40,9 +40,12 @@ The reference of available configuration options is listed below.
|
||||
|
||||
Required parameters:
|
||||
|
||||
* `manifest_file` (string) - The manifest file for Puppet to use in order
|
||||
to compile and run a catalog. This file must exist on your local system
|
||||
and will be uploaded to the remote machine.
|
||||
* `manifest_file` (string) - This is either a path to a puppet manifest (`.pp`
|
||||
file) _or_ a directory containing multiple manifests that puppet will apply
|
||||
(the ["main manifest"][1]). These file(s) must exist on your local system and
|
||||
will be uploaded to the remote machine.
|
||||
|
||||
[1]: https://docs.puppetlabs.com/puppet/latest/reference/dirs_manifest.html
|
||||
|
||||
Optional parameters:
|
||||
|
||||
@@ -64,6 +67,10 @@ Optional parameters:
|
||||
the `manifest_file`. It is a separate directory that will be set as
|
||||
the "manifestdir" setting on Puppet.
|
||||
|
||||
~> `manifest_dir` is passed to `puppet apply` as the `--manifestdir` option.
|
||||
This option was deprecated in puppet 3.6, and removed in puppet 4.0. If you
|
||||
have multiple manifests you should use `manifest_file` instead.
|
||||
|
||||
* `module_paths` (array of strings) - This is an array of paths to module
|
||||
directories on your local filesystem. These will be uploaded to the remote
|
||||
machine. By default, this is empty.
|
||||
|
||||
@@ -51,7 +51,7 @@ required. They are listed below:
|
||||
* `facter` (object of key/value strings) - Additional Facter facts to make available to the
|
||||
Puppet run.
|
||||
|
||||
* `ignore_exit_codes` (bool) - If true, Packer will never consider the
|
||||
* `ignore_exit_codes` (boolean) - If true, Packer will never consider the
|
||||
provisioner a failure.
|
||||
|
||||
* `options` (string) - Additional command line options to pass
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ use. For example, the Docker builder has a "docker" communicator that uses
|
||||
## Using a Communicator
|
||||
|
||||
By default, the SSH communicator is usually used. Additional configuration
|
||||
may not even be necesssary, since some builders such as Amazon automatically
|
||||
may not even be necessary, since some builders such as Amazon automatically
|
||||
configure everything.
|
||||
|
||||
However, to specify a communicator, you set the `communicator` key within
|
||||
@@ -67,7 +67,7 @@ The SSH communicator has the following options:
|
||||
* `ssh_private_key_file` (string) - Path to a PEM encoded private key
|
||||
file to use to authentiate with SSH.
|
||||
|
||||
* `ssh_pty` (bool) - If true, a PTY will be requested for the SSH connection.
|
||||
* `ssh_pty` (boolean) - If true, a PTY will be requested for the SSH connection.
|
||||
This defaults to false.
|
||||
|
||||
* `ssh_timeout` (string) - The time to wait for SSH to become available.
|
||||
@@ -77,7 +77,7 @@ The SSH communicator has the following options:
|
||||
* `ssh_handshake_attempts` (int) - The number of handshakes to attempt with
|
||||
SSH once it can connect. This defaults to 10.
|
||||
|
||||
* `ssh_disable_agent` (bool) - If true, SSH agent forwarding will be disabled.
|
||||
* `ssh_disable_agent` (boolean) - If true, SSH agent forwarding will be disabled.
|
||||
|
||||
* `ssh_bastion_host` (string) - A bastion host to use for the actual
|
||||
SSH connection.
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ each category, the available configuration keys are alphabetized.
|
||||
* `token` (string) - An access token to use to authenticate to the build
|
||||
service.
|
||||
|
||||
* `vcs` (bool) - If true, Packer will detect your VCS (if there is one)
|
||||
* `vcs` (boolean) - If true, Packer will detect your VCS (if there is one)
|
||||
and only upload the files that are tracked by the VCS. This is useful
|
||||
for automatically excluding ignored files. This defaults to false.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ the [documentation](/docs). The documentation is less of a guide and
|
||||
more of a reference of all the overall features and options of Packer.
|
||||
|
||||
If you're interested in learning more about how Packer fits into the
|
||||
HashiCorp ecosystem of tools, read our [Atlas getting started overview](https://atlas.hashicorp.com/help/getting-started/getting-started-overview).
|
||||
HashiCorp ecosystem of tools, read our [Atlas getting started overview](https://atlas.hashicorp.com/help/intro/getting-started).
|
||||
|
||||
As you use Packer more, please voice your comments and concerns on
|
||||
the [mailing list or IRC](/community). Additionally, Packer is
|
||||
|
||||
@@ -12,9 +12,9 @@ description: |-
|
||||
|
||||
HashiCorp is the creator of the open source projects Vagrant, Packer, Terraform, Serf, and Consul, and the commercial product Atlas. Packer is just one piece of the ecosystem HashiCorp has built to make application delivery a versioned, auditable, repeatable, and collaborative process. To learn more about our beliefs on the qualities of the modern datacenter and responsible application delivery, read [The Atlas Mindset: Version Control for Infrastructure](https://hashicorp.com/blog/atlas-mindset.html/?utm_source=packer&utm_campaign=HashicorpEcosystem).
|
||||
|
||||
If you are using Packer to build machine images and deployable artifacts, it’s likely that you need a solution for deploying those artifacts. Terraform is our tool for creating, combining, and modifying infrastructure.
|
||||
If you are using Packer to build machine images and deployable artifacts, it's likely that you need a solution for deploying those artifacts. Terraform is our tool for creating, combining, and modifying infrastructure.
|
||||
|
||||
Below are summaries of HashiCorp’s open source projects and a graphic showing how Atlas connects them to create a full application delivery workflow.
|
||||
Below are summaries of HashiCorp's open source projects and a graphic showing how Atlas connects them to create a full application delivery workflow.
|
||||
|
||||
# HashiCorp Ecosystem
|
||||

|
||||
@@ -27,6 +27,6 @@ Below are summaries of HashiCorp’s open source projects and a graphic showing
|
||||
|
||||
[Consul](https://consul.io/?utm_source=packer&utm_campaign=HashicorpEcosystem) is a HashiCorp tool for service discovery, service registry, and health checks. In the Atlas workflow Consul is configured at the Packer build stage and identifies the service(s) contained in each artifact. Since Consul is configured at the build phase with Packer, when the artifact is deployed with Terraform, it is fully configured with dependencies and service discovery pre-baked. This greatly reduces the risk of an unhealthy node in production due to configuration failure at runtime.
|
||||
|
||||
[Serf](https://serfdom.io/?utm_source=packer&utm_campaign=HashicorpEcosystem) is a HashiCorp tool for cluster membership and failure detection. Consul uses Serf’s gossip protocol as the foundation for service discovery.
|
||||
[Serf](https://serfdom.io/?utm_source=packer&utm_campaign=HashicorpEcosystem) is a HashiCorp tool for cluster membership and failure detection. Consul uses Serf's gossip protocol as the foundation for service discovery.
|
||||
|
||||
[Vagrant](https://www.vagrantup.com/?utm_source=packer&utm_campaign=HashicorpEcosystem) is a HashiCorp tool for managing development environments that mirror production. Vagrant environments reduce the friction of developing a project and reduce the risk of unexpected behavior appearing after deployment. Vagrant boxes can be built in parallel with production artifacts with Packer to maintain parity between development and production.
|
||||
|
||||
@@ -51,12 +51,15 @@
|
||||
<div class="col-md-12">
|
||||
<ul>
|
||||
<li class="packer"><a href="/">Packer</a></li>
|
||||
<li>
|
||||
A <a href="http://www.hashicorp.com/">HashiCorp</a> project.
|
||||
</li>
|
||||
<a href="http://www.hashicorp.com">
|
||||
<li class="pull-right hashi-logo">&nbps;</li>
|
||||
</a>
|
||||
<li>
|
||||
A <a href="http://www.hashicorp.com/">HashiCorp</a> project.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/mitchellh/packer/blob/master/website/source/<%= current_page.path %>.markdown">Edit this page</a>
|
||||
</li>
|
||||
<a href="http://www.hashicorp.com">
|
||||
<li class="pull-right hashi-logo">&nbps;</li>
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user