Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
7ff549ec
Commit
7ff549ec
authored
Dec 19, 2013
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #733 from mitchellh/f-vagrant-pp-revamp
Vagrant Post-Processor Refactor + "Include" feature
parents
930b844b
99cbe1fc
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
579 additions
and
751 deletions
+579
-751
command/fix/command.go
command/fix/command.go
+1
-8
command/fix/fixer.go
command/fix/fixer.go
+11
-0
command/fix/fixer_pp_vagrant_override.go
command/fix/fixer_pp_vagrant_override.go
+75
-0
command/fix/fixer_pp_vagrant_override_test.go
command/fix/fixer_pp_vagrant_override_test.go
+79
-0
command/fix/help.go
command/fix/help.go
+6
-1
packer/artifact_mock.go
packer/artifact_mock.go
+16
-6
post-processor/vagrant/aws.go
post-processor/vagrant/aws.go
+21
-133
post-processor/vagrant/aws_test.go
post-processor/vagrant/aws_test.go
+2
-7
post-processor/vagrant/digitalocean.go
post-processor/vagrant/digitalocean.go
+16
-131
post-processor/vagrant/digitalocean_test.go
post-processor/vagrant/digitalocean_test.go
+2
-7
post-processor/vagrant/post-processor.go
post-processor/vagrant/post-processor.go
+171
-87
post-processor/vagrant/post-processor_test.go
post-processor/vagrant/post-processor_test.go
+93
-7
post-processor/vagrant/provider.go
post-processor/vagrant/provider.go
+18
-0
post-processor/vagrant/util.go
post-processor/vagrant/util.go
+0
-8
post-processor/vagrant/virtualbox.go
post-processor/vagrant/virtualbox.go
+22
-158
post-processor/vagrant/virtualbox_test.go
post-processor/vagrant/virtualbox_test.go
+2
-7
post-processor/vagrant/vmware.go
post-processor/vagrant/vmware.go
+7
-120
post-processor/vagrant/vmware_test.go
post-processor/vagrant/vmware_test.go
+2
-7
website/source/docs/post-processors/vagrant.html.markdown
website/source/docs/post-processors/vagrant.html.markdown
+35
-64
No files found.
command/fix/command.go
View file @
7ff549ec
...
@@ -49,15 +49,8 @@ func (c Command) Run(env packer.Environment, args []string) int {
...
@@ -49,15 +49,8 @@ func (c Command) Run(env packer.Environment, args []string) int {
// Close the file since we're done with that
// Close the file since we're done with that
tplF
.
Close
()
tplF
.
Close
()
// Run the template through the various fixers
fixers
:=
[]
string
{
"iso-md5"
,
"createtime"
,
"virtualbox-gaattach"
,
}
input
:=
templateData
input
:=
templateData
for
_
,
name
:=
range
fixers
{
for
_
,
name
:=
range
FixerOrder
{
var
err
error
var
err
error
fixer
,
ok
:=
Fixers
[
name
]
fixer
,
ok
:=
Fixers
[
name
]
if
!
ok
{
if
!
ok
{
...
...
command/fix/fixer.go
View file @
7ff549ec
...
@@ -15,10 +15,21 @@ type Fixer interface {
...
@@ -15,10 +15,21 @@ type Fixer interface {
// Fixers is the map of all available fixers, by name.
// Fixers is the map of all available fixers, by name.
var
Fixers
map
[
string
]
Fixer
var
Fixers
map
[
string
]
Fixer
// FixerOrder is the default order the fixers should be run.
var
FixerOrder
[]
string
func
init
()
{
func
init
()
{
Fixers
=
map
[
string
]
Fixer
{
Fixers
=
map
[
string
]
Fixer
{
"iso-md5"
:
new
(
FixerISOMD5
),
"iso-md5"
:
new
(
FixerISOMD5
),
"createtime"
:
new
(
FixerCreateTime
),
"createtime"
:
new
(
FixerCreateTime
),
"pp-vagrant-override"
:
new
(
FixerVagrantPPOverride
),
"virtualbox-gaattach"
:
new
(
FixerVirtualBoxGAAttach
),
"virtualbox-gaattach"
:
new
(
FixerVirtualBoxGAAttach
),
}
}
FixerOrder
=
[]
string
{
"iso-md5"
,
"createtime"
,
"virtualbox-gaattach"
,
"pp-vagrant-override"
,
}
}
}
command/fix/fixer_pp_vagrant_override.go
0 → 100644
View file @
7ff549ec
package
fix
import
(
"github.com/mitchellh/mapstructure"
)
// FixerVagrantPPOvveride is a Fixer that replaces the provider-specific
// overrides for the Vagrant post-processor with the new style introduced
// as part of Packer 0.5.0.
type
FixerVagrantPPOverride
struct
{}
func
(
FixerVagrantPPOverride
)
Fix
(
input
map
[
string
]
interface
{})
(
map
[
string
]
interface
{},
error
)
{
// Our template type we'll use for this fixer only
type
template
struct
{
PostProcessors
[]
interface
{}
`mapstructure:"post-processors"`
}
// Decode the input into our structure, if we can
var
tpl
template
if
err
:=
mapstructure
.
Decode
(
input
,
&
tpl
);
err
!=
nil
{
return
nil
,
err
}
// Go through each post-processor and get out all the complex configs
pps
:=
make
([]
map
[
string
]
interface
{},
0
,
len
(
tpl
.
PostProcessors
))
for
_
,
rawPP
:=
range
tpl
.
PostProcessors
{
switch
pp
:=
rawPP
.
(
type
)
{
case
string
:
case
map
[
string
]
interface
{}
:
pps
=
append
(
pps
,
pp
)
case
[]
interface
{}
:
for
_
,
innerRawPP
:=
range
pp
{
if
innerPP
,
ok
:=
innerRawPP
.
(
map
[
string
]
interface
{});
ok
{
pps
=
append
(
pps
,
innerPP
)
}
}
}
}
// Go through each post-processor and make the fix if necessary
possible
:=
[]
string
{
"aws"
,
"digitalocean"
,
"virtualbox"
,
"vmware"
}
for
_
,
pp
:=
range
pps
{
typeRaw
,
ok
:=
pp
[
"type"
]
if
!
ok
{
continue
}
if
typeName
,
ok
:=
typeRaw
.
(
string
);
!
ok
{
continue
}
else
if
typeName
!=
"vagrant"
{
continue
}
overrides
:=
make
(
map
[
string
]
interface
{})
for
_
,
name
:=
range
possible
{
if
_
,
ok
:=
pp
[
name
];
!
ok
{
continue
}
overrides
[
name
]
=
pp
[
name
]
delete
(
pp
,
name
)
}
if
len
(
overrides
)
>
0
{
pp
[
"override"
]
=
overrides
}
}
input
[
"post-processors"
]
=
tpl
.
PostProcessors
return
input
,
nil
}
func
(
FixerVagrantPPOverride
)
Synopsis
()
string
{
return
`Fixes provider-specific overrides for Vagrant post-processor`
}
command/fix/fixer_pp_vagrant_override_test.go
0 → 100644
View file @
7ff549ec
package
fix
import
(
"reflect"
"testing"
)
func
TestFixerVagrantPPOverride_Impl
(
t
*
testing
.
T
)
{
var
_
Fixer
=
new
(
FixerVagrantPPOverride
)
}
func
TestFixerVagrantPPOverride_Fix
(
t
*
testing
.
T
)
{
var
f
FixerVagrantPPOverride
input
:=
map
[
string
]
interface
{}{
"post-processors"
:
[]
interface
{}{
"foo"
,
map
[
string
]
interface
{}{
"type"
:
"vagrant"
,
"aws"
:
map
[
string
]
interface
{}{
"foo"
:
"bar"
,
},
},
map
[
string
]
interface
{}{
"type"
:
"vsphere"
,
},
[]
interface
{}{
map
[
string
]
interface
{}{
"type"
:
"vagrant"
,
"vmware"
:
map
[
string
]
interface
{}{
"foo"
:
"bar"
,
},
},
},
},
}
expected
:=
map
[
string
]
interface
{}{
"post-processors"
:
[]
interface
{}{
"foo"
,
map
[
string
]
interface
{}{
"type"
:
"vagrant"
,
"override"
:
map
[
string
]
interface
{}{
"aws"
:
map
[
string
]
interface
{}{
"foo"
:
"bar"
,
},
},
},
map
[
string
]
interface
{}{
"type"
:
"vsphere"
,
},
[]
interface
{}{
map
[
string
]
interface
{}{
"type"
:
"vagrant"
,
"override"
:
map
[
string
]
interface
{}{
"vmware"
:
map
[
string
]
interface
{}{
"foo"
:
"bar"
,
},
},
},
},
},
}
output
,
err
:=
f
.
Fix
(
input
)
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
if
!
reflect
.
DeepEqual
(
output
,
expected
)
{
t
.
Fatalf
(
"unexpected: %#v
\n
expected: %#v
\n
"
,
output
,
expected
)
}
}
command/fix/help.go
View file @
7ff549ec
...
@@ -11,6 +11,11 @@ Usage: packer fix [options] TEMPLATE
...
@@ -11,6 +11,11 @@ Usage: packer fix [options] TEMPLATE
Fixes that are run:
Fixes that are run:
iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum"
iso-md5 Replaces "iso_md5" in builders with newer "iso_checksum"
createtime Replaces ".CreateTime" in builder configs with "{{timestamp}}"
virtualbox-gaattach Updates VirtualBox builders using "guest_additions_attach"
to use "guest_additions_mode"
pp-vagrant-override Replaces old-style provider overrides for the Vagrant
post-processor to new-style as of Packer 0.5.0.
`
`
packer/artifact_mock.go
View file @
7ff549ec
...
@@ -2,16 +2,26 @@ package packer
...
@@ -2,16 +2,26 @@ package packer
// MockArtifact is an implementation of Artifact that can be used for tests.
// MockArtifact is an implementation of Artifact that can be used for tests.
type
MockArtifact
struct
{
type
MockArtifact
struct
{
IdValue
string
BuilderIdValue
string
DestroyCalled
bool
FilesValue
[]
string
IdValue
string
DestroyCalled
bool
}
}
func
(
*
MockArtifact
)
BuilderId
()
string
{
func
(
a
*
MockArtifact
)
BuilderId
()
string
{
return
"bid"
if
a
.
BuilderIdValue
==
""
{
return
"bid"
}
return
a
.
BuilderIdValue
}
}
func
(
*
MockArtifact
)
Files
()
[]
string
{
func
(
a
*
MockArtifact
)
Files
()
[]
string
{
return
[]
string
{
"a"
,
"b"
}
if
a
.
FilesValue
==
nil
{
return
[]
string
{
"a"
,
"b"
}
}
return
a
.
FilesValue
}
}
func
(
a
*
MockArtifact
)
Id
()
string
{
func
(
a
*
MockArtifact
)
Id
()
string
{
...
...
post-processor/vagrant/aws.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"
compress/flate
"
"
bytes
"
"fmt"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"strings"
)
"text/template"
type
AWSBoxConfig
struct
{
common
.
PackerConfig
`mapstructure:",squash"`
OutputPath
string
`mapstructure:"output"`
VagrantfileTemplate
string
`mapstructure:"vagrantfile_template"`
CompressionLevel
string
`mapstructure:"compression_level"`
tpl
*
packer
.
ConfigTemplate
}
type
AWSVagrantfileTemplate
struct
{
Images
map
[
string
]
string
}
type
AWSBoxPostProcessor
struct
{
config
AWSBoxConfig
}
func
(
p
*
AWSBoxPostProcessor
)
Configure
(
raws
...
interface
{})
error
{
md
,
err
:=
common
.
DecodeConfig
(
&
p
.
config
,
raws
...
)
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
.
UserVars
=
p
.
config
.
PackerUserVars
// Accumulate any errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
validates
:=
map
[
string
]
*
string
{
"output"
:
&
p
.
config
.
OutputPath
,
"vagrantfile_template"
:
&
p
.
config
.
VagrantfileTemplate
,
"compression_level"
:
&
p
.
config
.
CompressionLevel
,
}
for
n
,
ptr
:=
range
validates
{
"github.com/mitchellh/packer/packer"
if
err
:=
p
.
config
.
tpl
.
Validate
(
*
ptr
);
err
!=
nil
{
)
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Error parsing %s: %s"
,
n
,
err
))
}
}
if
errs
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
type
AWSProvider
struct
{}
return
errs
}
return
nil
func
(
p
*
AWSProvider
)
Process
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
,
dir
string
)
(
vagrantfile
string
,
metadata
map
[
string
]
interface
{},
err
error
)
{
}
// Create the metadata
metadata
=
map
[
string
]
interface
{}{
"provider"
:
"aws"
}
func
(
p
*
AWSBoxPostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
// Build up the template data to build our Vagrantfile
// Determine the regions...
tplData
:=
&
awsVagrantfileTemplate
{
tplData
:=
&
AWSVagrantfileTemplate
{
Images
:
make
(
map
[
string
]
string
),
Images
:
make
(
map
[
string
]
string
),
}
}
for
_
,
regions
:=
range
strings
.
Split
(
artifact
.
Id
(),
","
)
{
for
_
,
regions
:=
range
strings
.
Split
(
artifact
.
Id
(),
","
)
{
parts
:=
strings
.
Split
(
regions
,
":"
)
parts
:=
strings
.
Split
(
regions
,
":"
)
if
len
(
parts
)
!=
2
{
if
len
(
parts
)
!=
2
{
return
nil
,
false
,
fmt
.
Errorf
(
"Poorly formatted artifact ID: %s"
,
artifact
.
Id
())
err
=
fmt
.
Errorf
(
"Poorly formatted artifact ID: %s"
,
artifact
.
Id
())
return
}
}
tplData
.
Images
[
parts
[
0
]]
=
parts
[
1
]
tplData
.
Images
[
parts
[
0
]]
=
parts
[
1
]
}
}
// Compile the output path
// Build up the contents
outputPath
,
err
:=
p
.
config
.
tpl
.
Process
(
p
.
config
.
OutputPath
,
&
OutputPathTemplate
{
var
contents
bytes
.
Buffer
ArtifactId
:
artifact
.
Id
(),
t
:=
template
.
Must
(
template
.
New
(
"vf"
)
.
Parse
(
defaultAWSVagrantfile
))
BuildName
:
p
.
config
.
PackerBuildName
,
err
=
t
.
Execute
(
&
contents
,
tplData
)
Provider
:
"aws"
,
vagrantfile
=
contents
.
String
()
})
return
if
err
!=
nil
{
}
return
nil
,
false
,
err
}
// Create a temporary directory for us to build the contents of the box in
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"packer"
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
os
.
RemoveAll
(
dir
)
// Create the Vagrantfile from the template
vf
,
err
:=
os
.
Create
(
filepath
.
Join
(
dir
,
"Vagrantfile"
))
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
vf
.
Close
()
vagrantfileContents
:=
defaultAWSVagrantfile
if
p
.
config
.
VagrantfileTemplate
!=
""
{
log
.
Printf
(
"Using vagrantfile template: %s"
,
p
.
config
.
VagrantfileTemplate
)
f
,
err
:=
os
.
Open
(
p
.
config
.
VagrantfileTemplate
)
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error opening vagrantfile template: %s"
,
err
)
return
nil
,
false
,
err
}
defer
f
.
Close
()
contents
,
err
:=
ioutil
.
ReadAll
(
f
)
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error reading vagrantfile template: %s"
,
err
)
return
nil
,
false
,
err
}
vagrantfileContents
=
string
(
contents
)
}
vagrantfileContents
,
err
=
p
.
config
.
tpl
.
Process
(
vagrantfileContents
,
tplData
)
if
err
!=
nil
{
return
nil
,
false
,
fmt
.
Errorf
(
"Error writing Vagrantfile: %s"
,
err
)
}
vf
.
Write
([]
byte
(
vagrantfileContents
))
vf
.
Close
()
var
level
int
=
flate
.
DefaultCompression
if
p
.
config
.
CompressionLevel
!=
""
{
level
,
err
=
strconv
.
Atoi
(
p
.
config
.
CompressionLevel
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
// Create the metadata
metadata
:=
map
[
string
]
string
{
"provider"
:
"aws"
}
if
err
:=
WriteMetadata
(
dir
,
metadata
);
err
!=
nil
{
return
nil
,
false
,
err
}
// Compress the directory to the given output path
if
err
:=
DirToBox
(
outputPath
,
dir
,
ui
,
level
);
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error creating box: %s"
,
err
)
return
nil
,
false
,
err
}
return
NewArtifact
(
"aws"
,
outputPath
),
true
,
nil
type
awsVagrantfileTemplate
struct
{
Images
map
[
string
]
string
}
}
var
defaultAWSVagrantfile
=
`
var
defaultAWSVagrantfile
=
`
...
...
post-processor/vagrant/aws_test.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"github.com/mitchellh/packer/packer"
"testing"
"testing"
)
)
func
TestAWSBoxPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
func
TestAWSProvider_impl
(
t
*
testing
.
T
)
{
var
raw
interface
{}
var
_
Provider
=
new
(
AWSProvider
)
raw
=
&
AWSBoxPostProcessor
{}
if
_
,
ok
:=
raw
.
(
packer
.
PostProcessor
);
!
ok
{
t
.
Fatalf
(
"AWS PostProcessor should be a PostProcessor"
)
}
}
}
post-processor/vagrant/digitalocean.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"
compress/flate
"
"
bytes
"
"fmt"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"strings"
"text/template"
)
)
type
DigitalOceanBoxConfig
struct
{
type
digitalOceanVagrantfileTemplate
struct
{
common
.
PackerConfig
`mapstructure:",squash"`
OutputPath
string
`mapstructure:"output"`
VagrantfileTemplate
string
`mapstructure:"vagrantfile_template"`
CompressionLevel
string
`mapstructure:"compression_level"`
tpl
*
packer
.
ConfigTemplate
}
type
DigitalOceanVagrantfileTemplate
struct
{
Image
string
""
Image
string
""
Region
string
""
Region
string
""
}
}
type
DigitalOceanBoxPostProcessor
struct
{
type
DigitalOceanProvider
struct
{}
config
DigitalOceanBoxConfig
}
func
(
p
*
DigitalOceanBoxPostProcessor
)
Configure
(
rDigitalOcean
...
interface
{})
error
{
md
,
err
:=
common
.
DecodeConfig
(
&
p
.
config
,
rDigitalOcean
...
)
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
.
UserVars
=
p
.
config
.
PackerUserVars
// Accumulate any errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
validates
:=
map
[
string
]
*
string
{
func
(
p
*
DigitalOceanProvider
)
Process
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
,
dir
string
)
(
vagrantfile
string
,
metadata
map
[
string
]
interface
{},
err
error
)
{
"output"
:
&
p
.
config
.
OutputPath
,
// Create the metadata
"vagrantfile_template"
:
&
p
.
config
.
VagrantfileTemplate
,
metadata
=
map
[
string
]
interface
{}{
"provider"
:
"digital_ocean"
}
"compression_level"
:
&
p
.
config
.
CompressionLevel
,
}
for
n
,
ptr
:=
range
validates
{
if
err
:=
p
.
config
.
tpl
.
Validate
(
*
ptr
);
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Error parsing %s: %s"
,
n
,
err
))
}
}
if
errs
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
return
errs
}
return
nil
}
func
(
p
*
DigitalOceanBoxPostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
// Determine the image and region...
// Determine the image and region...
tplData
:=
&
D
igitalOceanVagrantfileTemplate
{}
tplData
:=
&
d
igitalOceanVagrantfileTemplate
{}
parts
:=
strings
.
Split
(
artifact
.
Id
(),
":"
)
parts
:=
strings
.
Split
(
artifact
.
Id
(),
":"
)
if
len
(
parts
)
!=
2
{
if
len
(
parts
)
!=
2
{
return
nil
,
false
,
fmt
.
Errorf
(
"Poorly formatted artifact ID: %s"
,
artifact
.
Id
())
err
=
fmt
.
Errorf
(
"Poorly formatted artifact ID: %s"
,
artifact
.
Id
())
return
}
}
tplData
.
Region
=
parts
[
0
]
tplData
.
Region
=
parts
[
0
]
tplData
.
Image
=
parts
[
1
]
tplData
.
Image
=
parts
[
1
]
// Compile the output path
// Build up the Vagrantfile
outputPath
,
err
:=
p
.
config
.
tpl
.
Process
(
p
.
config
.
OutputPath
,
&
OutputPathTemplate
{
var
contents
bytes
.
Buffer
ArtifactId
:
artifact
.
Id
(),
t
:=
template
.
Must
(
template
.
New
(
"vf"
)
.
Parse
(
defaultDigitalOceanVagrantfile
))
BuildName
:
p
.
config
.
PackerBuildName
,
err
=
t
.
Execute
(
&
contents
,
tplData
)
Provider
:
"digitalocean"
,
vagrantfile
=
contents
.
String
()
})
return
if
err
!=
nil
{
return
nil
,
false
,
err
}
// Create a temporary directory for us to build the contents of the box in
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"packer"
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
os
.
RemoveAll
(
dir
)
// Create the Vagrantfile from the template
vf
,
err
:=
os
.
Create
(
filepath
.
Join
(
dir
,
"Vagrantfile"
))
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
vf
.
Close
()
vagrantfileContents
:=
defaultDigitalOceanVagrantfile
if
p
.
config
.
VagrantfileTemplate
!=
""
{
log
.
Printf
(
"Using vagrantfile template: %s"
,
p
.
config
.
VagrantfileTemplate
)
f
,
err
:=
os
.
Open
(
p
.
config
.
VagrantfileTemplate
)
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error opening vagrantfile template: %s"
,
err
)
return
nil
,
false
,
err
}
defer
f
.
Close
()
contents
,
err
:=
ioutil
.
ReadAll
(
f
)
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error reading vagrantfile template: %s"
,
err
)
return
nil
,
false
,
err
}
vagrantfileContents
=
string
(
contents
)
}
vagrantfileContents
,
err
=
p
.
config
.
tpl
.
Process
(
vagrantfileContents
,
tplData
)
if
err
!=
nil
{
return
nil
,
false
,
fmt
.
Errorf
(
"Error writing Vagrantfile: %s"
,
err
)
}
vf
.
Write
([]
byte
(
vagrantfileContents
))
vf
.
Close
()
// Create the metadata
metadata
:=
map
[
string
]
string
{
"provider"
:
"digital_ocean"
}
if
err
:=
WriteMetadata
(
dir
,
metadata
);
err
!=
nil
{
return
nil
,
false
,
err
}
// Compress the directory to the given output path
var
level
int
=
flate
.
DefaultCompression
if
p
.
config
.
CompressionLevel
!=
""
{
level
,
err
=
strconv
.
Atoi
(
p
.
config
.
CompressionLevel
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
if
err
:=
DirToBox
(
outputPath
,
dir
,
ui
,
level
);
err
!=
nil
{
err
=
fmt
.
Errorf
(
"error creating box: %s"
,
err
)
return
nil
,
false
,
err
}
return
NewArtifact
(
"DigitalOcean"
,
outputPath
),
true
,
nil
}
}
var
defaultDigitalOceanVagrantfile
=
`
var
defaultDigitalOceanVagrantfile
=
`
...
@@ -159,5 +45,4 @@ Vagrant.configure("2") do |config|
...
@@ -159,5 +45,4 @@ Vagrant.configure("2") do |config|
digital_ocean.region = "{{ .Region }}"
digital_ocean.region = "{{ .Region }}"
end
end
end
end
`
`
post-processor/vagrant/digitalocean_test.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"github.com/mitchellh/packer/packer"
"testing"
"testing"
)
)
func
TestDigitalOceanBoxPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
func
TestDigitalOceanProvider_impl
(
t
*
testing
.
T
)
{
var
raw
interface
{}
var
_
Provider
=
new
(
DigitalOceanProvider
)
raw
=
&
DigitalOceanBoxPostProcessor
{}
if
_
,
ok
:=
raw
.
(
packer
.
PostProcessor
);
!
ok
{
t
.
Fatalf
(
"Digitalocean PostProcessor should be a PostProcessor"
)
}
}
}
post-processor/vagrant/post-processor.go
View file @
7ff549ec
...
@@ -4,11 +4,15 @@
...
@@ -4,11 +4,15 @@
package
vagrant
package
vagrant
import
(
import
(
"compress/flate"
"fmt"
"fmt"
"github.com/mitchellh/mapstructure"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer"
"log"
)
)
var
builtins
=
map
[
string
]
string
{
var
builtins
=
map
[
string
]
string
{
...
@@ -22,133 +26,213 @@ var builtins = map[string]string{
...
@@ -22,133 +26,213 @@ var builtins = map[string]string{
type
Config
struct
{
type
Config
struct
{
common
.
PackerConfig
`mapstructure:",squash"`
common
.
PackerConfig
`mapstructure:",squash"`
OutputPath
string
`mapstructure:"output"`
CompressionLevel
int
`mapstructure:"compression_level"`
Include
[]
string
`mapstructure:"include"`
OutputPath
string
`mapstructure:"output"`
Override
map
[
string
]
interface
{}
VagrantfileTemplate
string
`mapstructure:"vagrantfile_template"`
tpl
*
packer
.
ConfigTemplate
}
}
type
PostProcessor
struct
{
type
PostProcessor
struct
{
config
Config
configs
map
[
string
]
*
Config
premade
map
[
string
]
packer
.
PostProcessor
extraConfig
map
[
string
]
interface
{}
}
}
func
(
p
*
PostProcessor
)
Configure
(
raws
...
interface
{})
error
{
func
(
p
*
PostProcessor
)
Configure
(
raws
...
interface
{})
error
{
_
,
err
:=
common
.
DecodeConfig
(
&
p
.
config
,
raws
...
)
p
.
configs
=
make
(
map
[
string
]
*
Config
)
if
err
!=
nil
{
p
.
configs
[
""
]
=
new
(
Config
)
if
err
:=
p
.
configureSingle
(
p
.
configs
[
""
],
raws
...
);
err
!=
nil
{
return
err
return
err
}
}
tpl
,
err
:=
packer
.
NewConfigTemplate
()
// Go over any of the provider-specific overrides and load those up.
for
name
,
override
:=
range
p
.
configs
[
""
]
.
Override
{
subRaws
:=
make
([]
interface
{},
len
(
raws
)
+
1
)
copy
(
subRaws
,
raws
)
subRaws
[
len
(
raws
)]
=
override
config
:=
new
(
Config
)
p
.
configs
[
name
]
=
config
if
err
:=
p
.
configureSingle
(
config
,
subRaws
...
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Error configuring %s: %s"
,
name
,
err
)
}
}
return
nil
}
func
(
p
*
PostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
name
,
ok
:=
builtins
[
artifact
.
BuilderId
()]
if
!
ok
{
return
nil
,
false
,
fmt
.
Errorf
(
"Unknown artifact type, can't build box: %s"
,
artifact
.
BuilderId
())
}
provider
:=
providerForName
(
name
)
if
provider
==
nil
{
// This shouldn't happen since we hard code all of these ourselves
panic
(
fmt
.
Sprintf
(
"bad provider name: %s"
,
name
))
}
config
:=
p
.
configs
[
""
]
if
specificConfig
,
ok
:=
p
.
configs
[
name
];
ok
{
config
=
specificConfig
}
ui
.
Say
(
fmt
.
Sprintf
(
"Creating Vagrant box for '%s' provider"
,
name
))
outputPath
,
err
:=
config
.
tpl
.
Process
(
config
.
OutputPath
,
&
outputPathTemplate
{
ArtifactId
:
artifact
.
Id
(),
BuildName
:
config
.
PackerBuildName
,
Provider
:
name
,
})
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
nil
,
false
,
err
}
}
tpl
.
UserVars
=
p
.
config
.
PackerUserVars
// Defaults
// Create a temporary directory for us to build the contents of the box in
if
p
.
config
.
OutputPath
==
""
{
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"packer"
)
p
.
config
.
OutputPath
=
"packer_{{ .BuildName }}_{{.Provider}}.box"
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
os
.
RemoveAll
(
dir
)
// Copy all of the includes files into the temporary directory
for
_
,
src
:=
range
config
.
Include
{
ui
.
Message
(
fmt
.
Sprintf
(
"Copying from include: %s"
,
src
))
dst
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
src
))
if
err
:=
CopyContents
(
dst
,
src
);
err
!=
nil
{
err
=
fmt
.
Errorf
(
"Error copying include file: %s
\n\n
%s"
,
src
,
err
)
return
nil
,
false
,
err
}
}
}
// Accumulate any errors
// Run the provider processing step
errs
:=
new
(
packer
.
MultiError
)
vagrantfile
,
metadata
,
err
:=
provider
.
Process
(
ui
,
artifact
,
dir
)
if
err
:=
tpl
.
Validate
(
p
.
config
.
OutputPath
);
err
!=
nil
{
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
return
nil
,
false
,
err
errs
,
fmt
.
Errorf
(
"Error parsing output template: %s"
,
err
))
}
}
// Write the metadata we got
// Store extra configuration we'll send to each post-processor type
if
err
:=
WriteMetadata
(
dir
,
metadata
);
err
!=
nil
{
p
.
extraConfig
=
make
(
map
[
string
]
interface
{})
return
nil
,
false
,
err
p
.
extraConfig
[
"output"
]
=
p
.
config
.
OutputPath
p
.
extraConfig
[
"packer_build_name"
]
=
p
.
config
.
PackerBuildName
p
.
extraConfig
[
"packer_builder_type"
]
=
p
.
config
.
PackerBuilderType
p
.
extraConfig
[
"packer_debug"
]
=
p
.
config
.
PackerDebug
p
.
extraConfig
[
"packer_force"
]
=
p
.
config
.
PackerForce
p
.
extraConfig
[
"packer_user_variables"
]
=
p
.
config
.
PackerUserVars
// TODO(mitchellh): Properly handle multiple raw configs. This isn't
// very pressing at the moment because at the time of this comment
// only the first member of raws can contain the actual type-overrides.
var
mapConfig
map
[
string
]
interface
{}
if
err
:=
mapstructure
.
Decode
(
raws
[
0
],
&
mapConfig
);
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Failed to decode config: %s"
,
err
))
return
errs
}
}
p
.
premade
=
make
(
map
[
string
]
packer
.
PostProcessor
)
// Write our Vagrantfile
for
k
,
raw
:=
range
mapConfig
{
var
customVagrantfile
string
pp
,
err
:=
p
.
subPostProcessor
(
k
,
raw
,
p
.
extraConfig
)
if
config
.
VagrantfileTemplate
!=
""
{
ui
.
Message
(
fmt
.
Sprintf
(
"Using custom Vagrantfile: %s"
,
config
.
VagrantfileTemplate
))
customBytes
,
err
:=
ioutil
.
ReadFile
(
config
.
VagrantfileTemplate
)
if
err
!=
nil
{
if
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
err
)
return
nil
,
false
,
err
continue
}
}
if
pp
==
nil
{
customVagrantfile
=
string
(
customBytes
)
continue
}
}
p
.
premade
[
k
]
=
pp
f
,
err
:=
os
.
Create
(
filepath
.
Join
(
dir
,
"Vagrantfile"
))
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
if
len
(
errs
.
Errors
)
>
0
{
t
:=
template
.
Must
(
template
.
New
(
"root"
)
.
Parse
(
boxVagrantfileContents
))
return
errs
err
=
t
.
Execute
(
f
,
&
vagrantfileTemplate
{
ProviderVagrantfile
:
vagrantfile
,
CustomVagrantfile
:
customVagrantfile
,
})
f
.
Close
()
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
return
nil
// Create the box
if
err
:=
DirToBox
(
outputPath
,
dir
,
ui
,
config
.
CompressionLevel
);
err
!=
nil
{
return
nil
,
false
,
err
}
return
nil
,
false
,
nil
}
}
func
(
p
*
PostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
func
(
p
*
PostProcessor
)
configureSingle
(
config
*
Config
,
raws
...
interface
{})
error
{
ppName
,
ok
:=
builtins
[
artifact
.
BuilderId
()]
md
,
err
:=
common
.
DecodeConfig
(
config
,
raws
...
)
if
!
ok
{
if
err
!=
nil
{
return
nil
,
false
,
fmt
.
Errorf
(
"Unknown artifact type, can't build box: %s"
,
artifact
.
BuilderId
())
return
err
}
}
// Use the premade PostProcessor if we have one. Otherwise, we
config
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
// create it and configure it here.
if
err
!=
nil
{
pp
,
ok
:=
p
.
premade
[
ppName
]
return
err
if
!
ok
{
}
log
.
Printf
(
"Premade post-processor for '%s' not found. Creating."
,
ppName
)
config
.
tpl
.
UserVars
=
config
.
PackerUserVars
var
err
error
// Defaults
pp
,
err
=
p
.
subPostProcessor
(
ppName
,
nil
,
p
.
extraConfig
)
if
config
.
OutputPath
==
""
{
if
err
!=
nil
{
config
.
OutputPath
=
"packer_{{ .BuildName }}_{{.Provider}}.box"
return
nil
,
false
,
err
}
}
if
pp
==
nil
{
found
:=
false
return
nil
,
false
,
fmt
.
Errorf
(
"Vagrant box post-processor not found: %s"
,
ppName
)
for
_
,
k
:=
range
md
.
Keys
{
if
k
==
"compression_level"
{
found
=
true
break
}
}
}
}
ui
.
Say
(
fmt
.
Sprintf
(
"Creating Vagrant box for '%s' provider"
,
ppName
))
if
!
found
{
return
pp
.
PostProcess
(
ui
,
artifact
)
config
.
CompressionLevel
=
flate
.
DefaultCompression
}
}
// Accumulate any errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
validates
:=
map
[
string
]
*
string
{
"output"
:
&
config
.
OutputPath
,
"vagrantfile_template"
:
&
config
.
VagrantfileTemplate
,
}
func
(
p
*
PostProcessor
)
subPostProcessor
(
key
string
,
specific
interface
{},
extra
map
[
string
]
interface
{})
(
packer
.
PostProcessor
,
error
)
{
for
n
,
ptr
:=
range
validates
{
pp
:=
keyToPostProcessor
(
key
)
if
err
:=
config
.
tpl
.
Validate
(
*
ptr
);
err
!=
nil
{
if
pp
==
nil
{
errs
=
packer
.
MultiErrorAppend
(
return
nil
,
nil
errs
,
fmt
.
Errorf
(
"Error parsing %s: %s"
,
n
,
err
))
}
}
}
if
err
:=
pp
.
Configure
(
extra
,
specific
);
err
!=
nil
{
if
err
s
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
return
nil
,
err
return
errs
}
}
return
pp
,
nil
return
nil
}
}
// keyToPostProcessor maps a configuration key to the actual post-processor
func
providerForName
(
name
string
)
Provider
{
// it will be configuring. This returns a new instance of that post-processor.
switch
name
{
func
keyToPostProcessor
(
key
string
)
packer
.
PostProcessor
{
switch
key
{
case
"aws"
:
return
new
(
AWSBoxPostProcessor
)
case
"digitalocean"
:
return
new
(
DigitalOceanBoxPostProcessor
)
case
"virtualbox"
:
case
"virtualbox"
:
return
new
(
VBoxBoxPostProcessor
)
return
new
(
VBoxProvider
)
case
"vmware"
:
return
new
(
VMwareBoxPostProcessor
)
default
:
default
:
return
nil
return
nil
}
}
}
}
// OutputPathTemplate is the structure that is availalable within the
// OutputPath variables.
type
outputPathTemplate
struct
{
ArtifactId
string
BuildName
string
Provider
string
}
type
vagrantfileTemplate
struct
{
ProviderVagrantfile
string
CustomVagrantfile
string
}
const
boxVagrantfileContents
string
=
`
# The contents below were provided by the Packer Vagrant post-processor
{{ .ProviderVagrantfile }}
# The contents below (if any) are custom contents provided by the
# Packer template during image build.
{{ .CustomVagrantfile }}
`
post-processor/vagrant/post-processor_test.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"bytes"
"compress/flate"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer"
"strings"
"testing"
"testing"
)
)
...
@@ -9,15 +12,55 @@ func testConfig() map[string]interface{} {
...
@@ -9,15 +12,55 @@ func testConfig() map[string]interface{} {
return
map
[
string
]
interface
{}{}
return
map
[
string
]
interface
{}{}
}
}
func
testPP
(
t
*
testing
.
T
)
*
PostProcessor
{
var
p
PostProcessor
if
err
:=
p
.
Configure
(
testConfig
());
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
return
&
p
}
func
testUi
()
*
packer
.
BasicUi
{
return
&
packer
.
BasicUi
{
Reader
:
new
(
bytes
.
Buffer
),
Writer
:
new
(
bytes
.
Buffer
),
}
}
func
TestPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
func
TestPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
var
raw
interface
{}
var
_
packer
.
PostProcessor
=
new
(
PostProcessor
)
raw
=
&
PostProcessor
{}
}
if
_
,
ok
:=
raw
.
(
packer
.
PostProcessor
);
!
ok
{
t
.
Fatalf
(
"AWS PostProcessor should be a PostProcessor"
)
func
TestPostProcessorPrepare_compressionLevel
(
t
*
testing
.
T
)
{
var
p
PostProcessor
// Default
c
:=
testConfig
()
delete
(
c
,
"compression_level"
)
if
err
:=
p
.
Configure
(
c
);
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
config
:=
p
.
configs
[
""
]
if
config
.
CompressionLevel
!=
flate
.
DefaultCompression
{
t
.
Fatalf
(
"bad: %#v"
,
config
.
CompressionLevel
)
}
// Set
c
=
testConfig
()
c
[
"compression_level"
]
=
7
if
err
:=
p
.
Configure
(
c
);
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
config
=
p
.
configs
[
""
]
if
config
.
CompressionLevel
!=
7
{
t
.
Fatalf
(
"bad: %#v"
,
config
.
CompressionLevel
)
}
}
}
}
func
Test
BuilderPrepare_O
utputPath
(
t
*
testing
.
T
)
{
func
Test
PostProcessorPrepare_o
utputPath
(
t
*
testing
.
T
)
{
var
p
PostProcessor
var
p
PostProcessor
// Default
// Default
...
@@ -36,14 +79,57 @@ func TestBuilderPrepare_OutputPath(t *testing.T) {
...
@@ -36,14 +79,57 @@ func TestBuilderPrepare_OutputPath(t *testing.T) {
}
}
}
}
func
Test
BuilderPrepare_PPConfig
(
t
*
testing
.
T
)
{
func
Test
PostProcessorPrepare_subConfigs
(
t
*
testing
.
T
)
{
var
p
PostProcessor
var
p
PostProcessor
// Default
// Default
c
:=
testConfig
()
c
:=
testConfig
()
c
[
"aws"
]
=
map
[
string
]
interface
{}{}
c
[
"compression_level"
]
=
42
c
[
"vagrantfile_template"
]
=
"foo"
c
[
"override"
]
=
map
[
string
]
interface
{}{
"aws"
:
map
[
string
]
interface
{}{
"compression_level"
:
7
,
},
}
err
:=
p
.
Configure
(
c
)
err
:=
p
.
Configure
(
c
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"err: %s"
,
err
)
t
.
Fatalf
(
"err: %s"
,
err
)
}
}
if
p
.
configs
[
""
]
.
CompressionLevel
!=
42
{
t
.
Fatalf
(
"bad: %#v"
,
p
.
configs
[
""
]
.
CompressionLevel
)
}
if
p
.
configs
[
""
]
.
VagrantfileTemplate
!=
"foo"
{
t
.
Fatalf
(
"bad: %#v"
,
p
.
configs
[
""
]
.
VagrantfileTemplate
)
}
if
p
.
configs
[
"aws"
]
.
CompressionLevel
!=
7
{
t
.
Fatalf
(
"bad: %#v"
,
p
.
configs
[
"aws"
]
.
CompressionLevel
)
}
if
p
.
configs
[
"aws"
]
.
VagrantfileTemplate
!=
"foo"
{
t
.
Fatalf
(
"bad: %#v"
,
p
.
configs
[
"aws"
]
.
VagrantfileTemplate
)
}
}
func
TestPostProcessorPostProcess_badId
(
t
*
testing
.
T
)
{
artifact
:=
&
packer
.
MockArtifact
{
BuilderIdValue
:
"invalid.packer"
,
}
_
,
_
,
err
:=
testPP
(
t
)
.
PostProcess
(
testUi
(),
artifact
)
if
!
strings
.
Contains
(
err
.
Error
(),
"artifact type"
)
{
t
.
Fatalf
(
"err: %s"
,
err
)
}
}
func
TestProviderForName
(
t
*
testing
.
T
)
{
if
v
,
ok
:=
providerForName
(
"virtualbox"
)
.
(
*
VBoxProvider
);
!
ok
{
t
.
Fatalf
(
"bad: %#v"
,
v
)
}
if
providerForName
(
"nope"
)
!=
nil
{
t
.
Fatal
(
"should be nil if bad provider"
)
}
}
}
post-processor/vagrant/provider.go
0 → 100644
View file @
7ff549ec
package
vagrant
import
(
"github.com/mitchellh/packer/packer"
)
// Provider is the interface that each provider must implement in order
// to package the artifacts into a Vagrant-compatible box.
type
Provider
interface
{
// Process is called to process an artifact into a Vagrant box. The
// artifact is given as well as the temporary directory path to
// put things.
//
// The Provider should return the contents for the Vagrantfile,
// any metadata (including the provider type in that), and an error
// if any.
Process
(
packer
.
Ui
,
packer
.
Artifact
,
string
)
(
vagrantfile
string
,
metadata
map
[
string
]
interface
{},
err
error
)
}
post-processor/vagrant/util.go
View file @
7ff549ec
...
@@ -13,14 +13,6 @@ import (
...
@@ -13,14 +13,6 @@ import (
"path/filepath"
"path/filepath"
)
)
// OutputPathTemplate is the structure that is availalable within the
// OutputPath variables.
type
OutputPathTemplate
struct
{
ArtifactId
string
BuildName
string
Provider
string
}
// Copies a file by copying the contents of the file to another place.
// Copies a file by copying the contents of the file to another place.
func
CopyContents
(
dst
,
src
string
)
error
{
func
CopyContents
(
dst
,
src
string
)
error
{
srcF
,
err
:=
os
.
Open
(
src
)
srcF
,
err
:=
os
.
Open
(
src
)
...
...
post-processor/vagrant/virtualbox.go
View file @
7ff549ec
...
@@ -2,10 +2,8 @@ package vagrant
...
@@ -2,10 +2,8 @@ package vagrant
import
(
import
(
"archive/tar"
"archive/tar"
"compress/flate"
"errors"
"errors"
"fmt"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer"
"io"
"io"
"io/ioutil"
"io/ioutil"
...
@@ -15,183 +13,49 @@ import (
...
@@ -15,183 +13,49 @@ import (
"regexp"
"regexp"
)
)
type
VBoxBoxConfig
struct
{
type
VBoxProvider
struct
{}
common
.
PackerConfig
`mapstructure:",squash"`
Include
[]
string
`mapstructure:"include"`
func
(
p
*
VBoxProvider
)
Process
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
,
dir
string
)
(
vagrantfile
string
,
metadata
map
[
string
]
interface
{},
err
error
)
{
OutputPath
string
`mapstructure:"output"`
// Create the metadata
VagrantfileTemplate
string
`mapstructure:"vagrantfile_template"`
metadata
=
map
[
string
]
interface
{}{
"provider"
:
"virtualbox"
}
CompressionLevel
int
`mapstructure:"compression_level"`
tpl
*
packer
.
ConfigTemplate
}
type
VBoxVagrantfileTemplate
struct
{
BaseMacAddress
string
}
type
VBoxBoxPostProcessor
struct
{
config
VBoxBoxConfig
}
func
(
p
*
VBoxBoxPostProcessor
)
Configure
(
raws
...
interface
{})
error
{
md
,
err
:=
common
.
DecodeConfig
(
&
p
.
config
,
raws
...
)
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
.
UserVars
=
p
.
config
.
PackerUserVars
// Defaults
found
:=
false
for
_
,
k
:=
range
md
.
Keys
{
println
(
k
)
if
k
==
"compression_level"
{
found
=
true
break
}
}
if
!
found
{
p
.
config
.
CompressionLevel
=
flate
.
DefaultCompression
}
// Accumulate any errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
validates
:=
map
[
string
]
*
string
{
"output"
:
&
p
.
config
.
OutputPath
,
"vagrantfile_template"
:
&
p
.
config
.
VagrantfileTemplate
,
}
for
n
,
ptr
:=
range
validates
{
if
err
:=
p
.
config
.
tpl
.
Validate
(
*
ptr
);
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Error parsing %s: %s"
,
n
,
err
))
}
}
if
errs
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
return
errs
}
return
nil
}
func
(
p
*
VBoxBoxPostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
var
err
error
// Compile the output path
outputPath
,
err
:=
p
.
config
.
tpl
.
Process
(
p
.
config
.
OutputPath
,
&
OutputPathTemplate
{
ArtifactId
:
artifact
.
Id
(),
BuildName
:
p
.
config
.
PackerBuildName
,
Provider
:
"virtualbox"
,
})
if
err
!=
nil
{
return
nil
,
false
,
err
}
// Create a temporary directory for us to build the contents of the box in
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"packer"
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
os
.
RemoveAll
(
dir
)
// Copy all of the includes files into the temporary directory
for
_
,
src
:=
range
p
.
config
.
Include
{
ui
.
Message
(
fmt
.
Sprintf
(
"Copying from include: %s"
,
src
))
dst
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
src
))
if
err
:=
CopyContents
(
dst
,
src
);
err
!=
nil
{
err
=
fmt
.
Errorf
(
"Error copying include file: %s
\n\n
%s"
,
src
,
err
)
return
nil
,
false
,
err
}
}
// Copy all of the original contents into the temporary directory
// Copy all of the original contents into the temporary directory
for
_
,
path
:=
range
artifact
.
Files
()
{
for
_
,
path
:=
range
artifact
.
Files
()
{
// We treat OVA files specially, we unpack those into the temporary
// We treat OVA files specially, we unpack those into the temporary
// directory so we can get the resulting disk and OVF.
// directory so we can get the resulting disk and OVF.
if
extension
:=
filepath
.
Ext
(
path
);
extension
==
".ova"
{
if
extension
:=
filepath
.
Ext
(
path
);
extension
==
".ova"
{
ui
.
Message
(
fmt
.
Sprintf
(
"Unpacking OVA: %s"
,
path
))
ui
.
Message
(
fmt
.
Sprintf
(
"Unpacking OVA: %s"
,
path
))
if
err
:
=
DecompressOva
(
dir
,
path
);
err
!=
nil
{
if
err
=
DecompressOva
(
dir
,
path
);
err
!=
nil
{
return
nil
,
false
,
err
return
}
}
}
else
{
}
else
{
ui
.
Message
(
fmt
.
Sprintf
(
"Copying from artifact: %s"
,
path
))
ui
.
Message
(
fmt
.
Sprintf
(
"Copying from artifact: %s"
,
path
))
dstPath
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
path
))
dstPath
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
path
))
if
err
:
=
CopyContents
(
dstPath
,
path
);
err
!=
nil
{
if
err
=
CopyContents
(
dstPath
,
path
);
err
!=
nil
{
return
nil
,
false
,
err
return
}
}
}
}
}
}
// Create the Vagrantfile from the template
tplData
:=
&
VBoxVagrantfileTemplate
{}
tplData
.
BaseMacAddress
,
err
=
p
.
findBaseMacAddress
(
dir
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
vf
,
err
:=
os
.
Create
(
filepath
.
Join
(
dir
,
"Vagrantfile"
))
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
vf
.
Close
()
vagrantfileContents
:=
defaultVBoxVagrantfile
if
p
.
config
.
VagrantfileTemplate
!=
""
{
ui
.
Message
(
fmt
.
Sprintf
(
"Using Vagrantfile template: %s"
,
p
.
config
.
VagrantfileTemplate
))
f
,
err
:=
os
.
Open
(
p
.
config
.
VagrantfileTemplate
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
f
.
Close
()
contents
,
err
:=
ioutil
.
ReadAll
(
f
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
vagrantfileContents
=
string
(
contents
)
}
vagrantfileContents
,
err
=
p
.
config
.
tpl
.
Process
(
vagrantfileContents
,
tplData
)
if
err
!=
nil
{
return
nil
,
false
,
fmt
.
Errorf
(
"Error writing Vagrantfile: %s"
,
err
)
}
vf
.
Write
([]
byte
(
vagrantfileContents
))
vf
.
Close
()
// Create the metadata
metadata
:=
map
[
string
]
string
{
"provider"
:
"virtualbox"
}
if
err
:=
WriteMetadata
(
dir
,
metadata
);
err
!=
nil
{
return
nil
,
false
,
err
}
// Rename the OVF file to box.ovf, as required by Vagrant
// Rename the OVF file to box.ovf, as required by Vagrant
ui
.
Message
(
"Renaming the OVF to box.ovf..."
)
ui
.
Message
(
"Renaming the OVF to box.ovf..."
)
if
err
:
=
p
.
renameOVF
(
dir
);
err
!=
nil
{
if
err
=
p
.
renameOVF
(
dir
);
err
!=
nil
{
return
nil
,
false
,
err
return
}
}
// Compress the directory to the given output path
// Create the Vagrantfile from the template
ui
.
Message
(
fmt
.
Sprintf
(
"Compressing box..."
))
var
baseMacAddress
string
if
err
:=
DirToBox
(
outputPath
,
dir
,
ui
,
p
.
config
.
CompressionLevel
);
err
!=
nil
{
baseMacAddress
,
err
=
p
.
findBaseMacAddress
(
dir
)
return
nil
,
false
,
err
if
err
!=
nil
{
return
}
}
return
NewArtifact
(
"virtualbox"
,
outputPath
),
false
,
nil
vagrantfile
=
fmt
.
Sprintf
(
vboxVagrantfile
,
baseMacAddress
)
return
}
}
func
(
p
*
VBox
BoxPostProcesso
r
)
findOvf
(
dir
string
)
(
string
,
error
)
{
func
(
p
*
VBox
Provide
r
)
findOvf
(
dir
string
)
(
string
,
error
)
{
log
.
Println
(
"Looking for OVF in artifact..."
)
log
.
Println
(
"Looking for OVF in artifact..."
)
file_matches
,
err
:=
filepath
.
Glob
(
filepath
.
Join
(
dir
,
"*.ovf"
))
file_matches
,
err
:=
filepath
.
Glob
(
filepath
.
Join
(
dir
,
"*.ovf"
))
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -209,7 +73,7 @@ func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
...
@@ -209,7 +73,7 @@ func (p *VBoxBoxPostProcessor) findOvf(dir string) (string, error) {
return
file_matches
[
0
],
err
return
file_matches
[
0
],
err
}
}
func
(
p
*
VBox
BoxPostProcesso
r
)
renameOVF
(
dir
string
)
error
{
func
(
p
*
VBox
Provide
r
)
renameOVF
(
dir
string
)
error
{
log
.
Println
(
"Looking for OVF to rename..."
)
log
.
Println
(
"Looking for OVF to rename..."
)
ovf
,
err
:=
p
.
findOvf
(
dir
)
ovf
,
err
:=
p
.
findOvf
(
dir
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -220,7 +84,7 @@ func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
...
@@ -220,7 +84,7 @@ func (p *VBoxBoxPostProcessor) renameOVF(dir string) error {
return
os
.
Rename
(
ovf
,
filepath
.
Join
(
dir
,
"box.ovf"
))
return
os
.
Rename
(
ovf
,
filepath
.
Join
(
dir
,
"box.ovf"
))
}
}
func
(
p
*
VBox
BoxPostProcesso
r
)
findBaseMacAddress
(
dir
string
)
(
string
,
error
)
{
func
(
p
*
VBox
Provide
r
)
findBaseMacAddress
(
dir
string
)
(
string
,
error
)
{
log
.
Println
(
"Looking for OVF for base mac address..."
)
log
.
Println
(
"Looking for OVF for base mac address..."
)
ovf
,
err
:=
p
.
findOvf
(
dir
)
ovf
,
err
:=
p
.
findOvf
(
dir
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -295,8 +159,8 @@ func DecompressOva(dir, src string) error {
...
@@ -295,8 +159,8 @@ func DecompressOva(dir, src string) error {
return
nil
return
nil
}
}
var
defaultVB
oxVagrantfile
=
`
var
vb
oxVagrantfile
=
`
Vagrant.configure("2") do |config|
Vagrant.configure("2") do |config|
config.vm.base_mac = "{{ .BaseMacAddress }}
"
config.vm.base_mac = "%s
"
end
end
`
`
post-processor/vagrant/virtualbox_test.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"github.com/mitchellh/packer/packer"
"testing"
"testing"
)
)
func
TestVBoxBoxPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
func
TestVBoxProvider_impl
(
t
*
testing
.
T
)
{
var
raw
interface
{}
var
_
Provider
=
new
(
VBoxProvider
)
raw
=
&
VBoxBoxPostProcessor
{}
if
_
,
ok
:=
raw
.
(
packer
.
PostProcessor
);
!
ok
{
t
.
Fatalf
(
"VBox PostProcessor should be a PostProcessor"
)
}
}
}
post-processor/vagrant/vmware.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"compress/flate"
"fmt"
"fmt"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"path/filepath"
"path/filepath"
"strconv"
)
)
type
VMwareBoxConfig
struct
{
type
VMwareProvider
struct
{}
common
.
PackerConfig
`mapstructure:",squash"`
OutputPath
string
`mapstructure:"output"`
func
(
p
*
VMwareProvider
)
Process
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
,
dir
string
)
(
vagrantfile
string
,
metadata
map
[
string
]
interface
{},
err
error
)
{
VagrantfileTemplate
string
`mapstructure:"vagrantfile_template"`
// Create the metadata
CompressionLevel
string
`mapstructure:"compression_level"`
metadata
=
map
[
string
]
interface
{}{
"provider"
:
"vmware_desktop"
}
tpl
*
packer
.
ConfigTemplate
}
type
VMwareBoxPostProcessor
struct
{
config
VMwareBoxConfig
}
func
(
p
*
VMwareBoxPostProcessor
)
Configure
(
raws
...
interface
{})
error
{
md
,
err
:=
common
.
DecodeConfig
(
&
p
.
config
,
raws
...
)
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
,
err
=
packer
.
NewConfigTemplate
()
if
err
!=
nil
{
return
err
}
p
.
config
.
tpl
.
UserVars
=
p
.
config
.
PackerUserVars
// Accumulate any errors
errs
:=
common
.
CheckUnusedConfig
(
md
)
validates
:=
map
[
string
]
*
string
{
"output"
:
&
p
.
config
.
OutputPath
,
"vagrantfile_template"
:
&
p
.
config
.
VagrantfileTemplate
,
"compression_level"
:
&
p
.
config
.
CompressionLevel
,
}
for
n
,
ptr
:=
range
validates
{
if
err
:=
p
.
config
.
tpl
.
Validate
(
*
ptr
);
err
!=
nil
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
fmt
.
Errorf
(
"Error parsing %s: %s"
,
n
,
err
))
}
}
if
errs
!=
nil
&&
len
(
errs
.
Errors
)
>
0
{
return
errs
}
return
nil
}
func
(
p
*
VMwareBoxPostProcessor
)
PostProcess
(
ui
packer
.
Ui
,
artifact
packer
.
Artifact
)
(
packer
.
Artifact
,
bool
,
error
)
{
// Compile the output path
outputPath
,
err
:=
p
.
config
.
tpl
.
Process
(
p
.
config
.
OutputPath
,
&
OutputPathTemplate
{
ArtifactId
:
artifact
.
Id
(),
BuildName
:
p
.
config
.
PackerBuildName
,
Provider
:
"vmware"
,
})
if
err
!=
nil
{
return
nil
,
false
,
err
}
// Create a temporary directory for us to build the contents of the box in
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"packer"
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
os
.
RemoveAll
(
dir
)
// Copy all of the original contents into the temporary directory
// Copy all of the original contents into the temporary directory
for
_
,
path
:=
range
artifact
.
Files
()
{
for
_
,
path
:=
range
artifact
.
Files
()
{
ui
.
Message
(
fmt
.
Sprintf
(
"Copying: %s"
,
path
))
ui
.
Message
(
fmt
.
Sprintf
(
"Copying: %s"
,
path
))
dstPath
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
path
))
dstPath
:=
filepath
.
Join
(
dir
,
filepath
.
Base
(
path
))
if
err
:=
CopyContents
(
dstPath
,
path
);
err
!=
nil
{
if
err
=
CopyContents
(
dstPath
,
path
);
err
!=
nil
{
return
nil
,
false
,
err
return
}
}
if
p
.
config
.
VagrantfileTemplate
!=
""
{
f
,
err
:=
os
.
Open
(
p
.
config
.
VagrantfileTemplate
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
defer
f
.
Close
()
contents
,
err
:=
ioutil
.
ReadAll
(
f
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
// Create the Vagrantfile from the template
vf
,
err
:=
os
.
Create
(
filepath
.
Join
(
dir
,
"Vagrantfile"
))
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
defer
vf
.
Close
()
vagrantfileContents
,
err
:=
p
.
config
.
tpl
.
Process
(
string
(
contents
),
nil
)
if
err
!=
nil
{
return
nil
,
false
,
fmt
.
Errorf
(
"Error writing Vagrantfile: %s"
,
err
)
}
vf
.
Write
([]
byte
(
vagrantfileContents
))
vf
.
Close
()
}
var
level
int
=
flate
.
DefaultCompression
if
p
.
config
.
CompressionLevel
!=
""
{
level
,
err
=
strconv
.
Atoi
(
p
.
config
.
CompressionLevel
)
if
err
!=
nil
{
return
nil
,
false
,
err
}
}
// Create the metadata
metadata
:=
map
[
string
]
string
{
"provider"
:
"vmware_desktop"
}
if
err
:=
WriteMetadata
(
dir
,
metadata
);
err
!=
nil
{
return
nil
,
false
,
err
}
// Compress the directory to the given output path
ui
.
Message
(
fmt
.
Sprintf
(
"Compressing box..."
))
if
err
:=
DirToBox
(
outputPath
,
dir
,
ui
,
level
);
err
!=
nil
{
return
nil
,
false
,
err
}
}
return
NewArtifact
(
"vmware"
,
outputPath
),
false
,
nil
return
}
}
post-processor/vagrant/vmware_test.go
View file @
7ff549ec
package
vagrant
package
vagrant
import
(
import
(
"github.com/mitchellh/packer/packer"
"testing"
"testing"
)
)
func
TestVMwareBoxPostProcessor_ImplementsPostProcessor
(
t
*
testing
.
T
)
{
func
TestVMwareProvider_impl
(
t
*
testing
.
T
)
{
var
raw
interface
{}
var
_
Provider
=
new
(
VMwareProvider
)
raw
=
&
VMwareBoxPostProcessor
{}
if
_
,
ok
:=
raw
.
(
packer
.
PostProcessor
);
!
ok
{
t
.
Fatalf
(
"VMware PostProcessor should be a PostProcessor"
)
}
}
}
website/source/docs/post-processors/vagrant.html.markdown
View file @
7ff549ec
...
@@ -18,7 +18,7 @@ documentation on [using post-processors](/docs/templates/post-processors.html)
...
@@ -18,7 +18,7 @@ documentation on [using post-processors](/docs/templates/post-processors.html)
in templates. This knowledge will be expected for the remainder of
in templates. This knowledge will be expected for the remainder of
this document.
this document.
Because Vagrant boxes are
[
provider-specific
](
#
)
,
Because Vagrant boxes are
[
provider-specific
](
http://docs.vagrantup.com/v2/boxes/format.html
)
,
the Vagrant post-processor is hardcoded to understand how to convert
the Vagrant post-processor is hardcoded to understand how to convert
the artifacts of certain builders into proper boxes for their
the artifacts of certain builders into proper boxes for their
respective providers.
respective providers.
...
@@ -27,6 +27,7 @@ Currently, the Vagrant post-processor can create boxes for the following
...
@@ -27,6 +27,7 @@ Currently, the Vagrant post-processor can create boxes for the following
providers.
providers.
*
AWS
*
AWS
*
DigitalOcean
*
VirtualBox
*
VirtualBox
*
VMware
*
VMware
...
@@ -47,82 +48,52 @@ However, if you want to configure things a bit more, the post-processor
...
@@ -47,82 +48,52 @@ However, if you want to configure things a bit more, the post-processor
does expose some configuration options. The available options are listed
does expose some configuration options. The available options are listed
below, with more details about certain options in following sections.
below, with more details about certain options in following sections.
*
`compression_level`
(integer) - An integer repesenting the
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression. By default, compression is enabled at level 1.
*
`include`
(array of strings) - Paths to files to include in the
Vagrant box. These files will each be copied into the top level directory
of the Vagrant box (regardless of their paths). They can then be used
from the Vagrantfile.
*
`output`
(string) - The full path to the box file that will be created
*
`output`
(string) - The full path to the box file that will be created
by this post-processor. This is a
by this post-processor. This is a
[
configuration template
](
/docs/templates/configuration-templates.html
)
.
[
configuration template
](
/docs/templates/configuration-templates.html
)
.
The variable
`Provider`
is replaced by the Vagrant provider the box is for.
The variable
`Provider`
is replaced by the Vagrant provider the box is for.
The variable
`ArtifactId`
is replaced by the ID of the input artifact.
The variable
`ArtifactId`
is replaced by the ID of the input artifact.
The variable
`BuildName`
is replaced with the name of the build.
By default, the value of this config is
`packer_{{.BuildName}}_{{.Provider}}.box`
.
By default, the value of this config is
`packer_{{.BuildName}}_{{.Provider}}.box`
.
*
`aws`
,
`virtualbox`
, or
`vmware`
(objects) - These are used to configure
the specific options for certain providers. A reference of available
configuration parameters for each is in the section below.
### AWS Provider
The AWS provider itself can be configured with specific options:
*
`vagrantfile_template`
(string) - Path to a template to use for the
*
`vagrantfile_template`
(string) - Path to a template to use for the
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
Vagrantfile that is packaged with the box.
[
text template
](
http://golang.org/pkg/text/template
)
. By default
this is a template that simply sets the AMIs for the various regions
of the AWS build.
*
`compression_level`
(integer) - An integer repesenting the
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
The
`vagrantfile_template`
has the
`Images`
variable which is a map
of region (string) to AMI ID (string). An example Vagrantfile template for
AWS is shown below. The example simply sets the AMI for each region.
```
Vagrant.configure("2") do |config|
config.vm.provider "aws" do |aws|
{{ range $region, $ami := .Images }}
aws.region_config "{{ $region }}", ami: "{{ $ami }}"
{{ end }}
end
end
```
##
# VirtualBox Provider
##
Provider-Specific Overrides
The VirtualBox provider itself can be configured with specific options:
If you have a Packer template with multiple builder types within it,
you may want to configure the box creation for each type a little differently.
*
`vagrantfile_template`
(string) - Path to a template to use for the
For example, the contents of the Vagrantfile for a Vagrant box for AWS might
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
be different from the contents of the Vagrantfile you want for VMware.
[
text template
](
http://golang.org/pkg/text/template
)
. By default this is
The post-processor lets you do this.
a template that just sets the base MAC address so that networking works.
*
`compression_level`
(integer) - An integer repesenting the
Specify overrides within the
`override`
configuration by provider name:
compression level to use when creating the Vagrant box. Valid
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
The
`vagrantfile_template`
has the
`BaseMACAddress`
variable which is a string
```
json
containing the MAC address of the first network interface. This must be set
{
in the Vagrantfile for networking to work properly with Vagrant. An example
"type"
:
"vagrant"
,
Vagrantfile template is shown below:
"compression_level"
:
1
,
"override"
:
{
"vmware"
:
{
"compression_level"
:
0
}
}
}
```
```
Vagrant.configure("2") do |config|
config.vm.base_mac = "{{ .BaseMacAddress }}"
end
```
### VMware Provider
The VMware provider itself can be configured with specific options:
In the example above, the compression level will be set to 1 except for
VMware, where it will be set to 0.
*
`vagrantfile_template`
(string) - Path to a template to use for the
Vagrantfile that is packaged with the box. The contents of the file must be a valid Go
[
text template
](
http://golang.org/pkg/text/template
)
. By default no
Vagrantfile is packaged with the box. Note that currently no variables
are available in the template, but this may change in the future.
*
`compression_level`
(integer) - An integer repesenting the
The available provider names are:
`aws`
,
`digitalocean`
,
`virtualbox`
,
compression level to use when creating the Vagrant box. Valid
and
`vmware`
.
values range from 0 to 9, with 0 being no compression and 9 being
the best compression.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment