engine: resources: Add simple configuration steps to virt builder
This adds some simplistic configuration management / provisioning functionality to this virt:builder resource which makes it easier to kick off special functionality that we might want to build.
This commit is contained in:
@@ -154,6 +154,32 @@ type VirtBuilderRes struct {
|
|||||||
// likely be http URL's like: http://127.0.0.1:2379 or similar.
|
// likely be http URL's like: http://127.0.0.1:2379 or similar.
|
||||||
Seeds []string `lang:"seeds" yaml:"seeds"`
|
Seeds []string `lang:"seeds" yaml:"seeds"`
|
||||||
|
|
||||||
|
// Mkdir creates these directories in the guests. This happens before
|
||||||
|
// CopyIn runs. Directories must be absolute and end with a slash. Any
|
||||||
|
// intermediate directories are created, similar to how `mkdir -p`
|
||||||
|
// works.
|
||||||
|
Mkdir []string `lang:"mkdir" yaml:"mkdir"`
|
||||||
|
|
||||||
|
// CopyIn is a list of local paths to copy into the machine dest. The
|
||||||
|
// dest directory must exist for this to work. Use Mkdir if you need to
|
||||||
|
// make a directory, since that step happens earlier. All paths must be
|
||||||
|
// absolute, and directories must end with a slash. This happens before
|
||||||
|
// the RunCmd stage in case you want to create something to be used
|
||||||
|
// there.
|
||||||
|
CopyIn []*CopyIn `lang:"copy_in" yaml:"copy_in"`
|
||||||
|
|
||||||
|
// RunCmd is a sequence of commands + args (one set per list item) to
|
||||||
|
// run in the build environment. These happen after the CopyIn stage.
|
||||||
|
RunCmd []string `lang:"run_cmd" yaml:"run_cmd"`
|
||||||
|
|
||||||
|
// FirstbootCmd is a sequence of commands + args (one set per list item)
|
||||||
|
// to run once on first boot.
|
||||||
|
// TODO: Consider replacing this with the mgmt firstboot mechanism for
|
||||||
|
// consistency between this platform and other platforms that might not
|
||||||
|
// support the excellent libguestfs version of those scripts. (Make the
|
||||||
|
// logs look more homogeneous.)
|
||||||
|
FirstbootCmd []string `lang:"firstboot_cmd" yaml:"firstboot_cmd"`
|
||||||
|
|
||||||
// LogOutput logs the output of running this command to a file in the
|
// LogOutput logs the output of running this command to a file in the
|
||||||
// special $vardir directory. It defaults to true. Keep in mind that if
|
// special $vardir directory. It defaults to true. Keep in mind that if
|
||||||
// you let virt-builder choose the password randomly, it will be output
|
// you let virt-builder choose the password randomly, it will be output
|
||||||
@@ -319,6 +345,33 @@ func (obj *VirtBuilderRes) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, x := range obj.Mkdir {
|
||||||
|
if x == "" {
|
||||||
|
return fmt.Errorf("empty Mkdir entry")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(x, "/") {
|
||||||
|
return fmt.Errorf("the Mkdir entry must be absolute")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(x, "/") {
|
||||||
|
return fmt.Errorf("the Mkdir entry must be a directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range obj.CopyIn {
|
||||||
|
if err := x.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range obj.RunCmd {
|
||||||
|
if x == "" {
|
||||||
|
return fmt.Errorf("empty RunCmd entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range obj.FirstbootCmd {
|
||||||
|
if x == "" {
|
||||||
|
return fmt.Errorf("empty FirstbootCmd entry")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,6 +588,23 @@ func (obj *VirtBuilderRes) CheckApply(ctx context.Context, apply bool) (bool, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, x := range obj.Mkdir {
|
||||||
|
args := []string{"--mkdir", x}
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
}
|
||||||
|
for _, x := range obj.CopyIn {
|
||||||
|
args := []string{"--copy-in", x.Path + ":" + x.Dest} // LOCALPATH:REMOTEDIR
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
}
|
||||||
|
for _, x := range obj.RunCmd {
|
||||||
|
args := []string{"--run-command", x}
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
}
|
||||||
|
for _, x := range obj.FirstbootCmd {
|
||||||
|
args := []string{"--firstboot-command", x}
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, cmdName, cmdArgs...)
|
cmd := exec.CommandContext(ctx, cmdName, cmdArgs...)
|
||||||
|
|
||||||
// ignore signals sent to parent process (we're in our own group)
|
// ignore signals sent to parent process (we're in our own group)
|
||||||
@@ -683,6 +753,39 @@ func (obj *VirtBuilderRes) Cmp(r engine.Res) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(obj.Mkdir) != len(res.Mkdir) {
|
||||||
|
return fmt.Errorf("the number of Mkdir entries differs")
|
||||||
|
}
|
||||||
|
for i, x := range obj.Mkdir {
|
||||||
|
if s := res.Mkdir[i]; x != s {
|
||||||
|
return fmt.Errorf("the Mkdir entry at index %d differs", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(obj.CopyIn) != len(res.CopyIn) {
|
||||||
|
return fmt.Errorf("the number of CopyIn structs differ")
|
||||||
|
}
|
||||||
|
for i, x := range obj.CopyIn {
|
||||||
|
if err := res.CopyIn[i].Cmp(x); err != nil {
|
||||||
|
return errwrap.Wrapf(err, "the copy in struct at index %d differs", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(obj.RunCmd) != len(res.RunCmd) {
|
||||||
|
return fmt.Errorf("the number of RunCmd entries differs")
|
||||||
|
}
|
||||||
|
for i, x := range obj.RunCmd {
|
||||||
|
if s := res.RunCmd[i]; x != s {
|
||||||
|
return fmt.Errorf("the RunCmd entry at index %d differs", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(obj.FirstbootCmd) != len(res.FirstbootCmd) {
|
||||||
|
return fmt.Errorf("the number of FirstbootCmd entries differs")
|
||||||
|
}
|
||||||
|
for i, x := range obj.FirstbootCmd {
|
||||||
|
if s := res.FirstbootCmd[i]; x != s {
|
||||||
|
return fmt.Errorf("the FirstbootCmd entry at index %d differs", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if obj.LogOutput != res.LogOutput {
|
if obj.LogOutput != res.LogOutput {
|
||||||
return fmt.Errorf("the LogOutput value differs")
|
return fmt.Errorf("the LogOutput value differs")
|
||||||
}
|
}
|
||||||
@@ -821,3 +924,58 @@ func (obj *SSHKeyInfo) Cmp(x *SSHKeyInfo) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CopyIn is a list of local paths to copy into the machine dest.
|
||||||
|
type CopyIn struct {
|
||||||
|
// Path is the local file or directory that we want to copy in.
|
||||||
|
// TODO: Add autoedges
|
||||||
|
Path string `lang:"path" yaml:"path"`
|
||||||
|
|
||||||
|
// Dest is the destination dir that the path gets copied into. This
|
||||||
|
// directory must exist.
|
||||||
|
Dest string `lang:"dest" yaml:"dest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate reports any problems with the struct definition.
|
||||||
|
func (obj *CopyIn) Validate() error {
|
||||||
|
if obj == nil {
|
||||||
|
return fmt.Errorf("nil obj")
|
||||||
|
}
|
||||||
|
if obj.Path == "" {
|
||||||
|
return fmt.Errorf("empty Path")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(obj.Path, "/") {
|
||||||
|
return fmt.Errorf("the Path must be absolute")
|
||||||
|
}
|
||||||
|
if obj.Dest == "" {
|
||||||
|
return fmt.Errorf("empty Dest")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(obj.Dest, "/") {
|
||||||
|
return fmt.Errorf("the Dest must be absolute")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(obj.Dest, "/") {
|
||||||
|
return fmt.Errorf("the dest must be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmp compares two of these and returns an error if they are not equivalent.
|
||||||
|
func (obj *CopyIn) Cmp(x *CopyIn) error {
|
||||||
|
//if (obj == nil) != (x == nil) { // xor
|
||||||
|
// return fmt.Errorf("we differ") // redundant
|
||||||
|
//}
|
||||||
|
if obj == nil || x == nil {
|
||||||
|
// special case since we want to error if either is nil
|
||||||
|
return fmt.Errorf("can't cmp if nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Path != x.Path {
|
||||||
|
return fmt.Errorf("the Path differs")
|
||||||
|
}
|
||||||
|
if obj.Dest != x.Dest {
|
||||||
|
return fmt.Errorf("the Dest differs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ class vm($name, $config) {
|
|||||||
}
|
}
|
||||||
$seeds = $config->seeds || []
|
$seeds = $config->seeds || []
|
||||||
|
|
||||||
|
$mkdir = $config->mkdir || []
|
||||||
|
$copy_in = $config->copy_in || []
|
||||||
|
$run_cmd = $config->run_cmd || []
|
||||||
|
$firstboot_cmd = $config->firstboot_cmd || []
|
||||||
|
|
||||||
virt:builder "${filename}" {
|
virt:builder "${filename}" {
|
||||||
hostname => $name,
|
hostname => $name,
|
||||||
os_version => "${distro}-${version}",
|
os_version => "${distro}-${version}",
|
||||||
@@ -118,6 +123,11 @@ class vm($name, $config) {
|
|||||||
root_password_selector => $root_password_selector,
|
root_password_selector => $root_password_selector,
|
||||||
seeds => $seeds,
|
seeds => $seeds,
|
||||||
|
|
||||||
|
mkdir => $mkdir,
|
||||||
|
copy_in => $copy_in,
|
||||||
|
run_cmd => $run_cmd,
|
||||||
|
firstboot_cmd => $firstboot_cmd,
|
||||||
|
|
||||||
# make sure key exists so that's it's available for injection!
|
# make sure key exists so that's it's available for injection!
|
||||||
Depend => File["/root/.ssh/id_rsa"],
|
Depend => File["/root/.ssh/id_rsa"],
|
||||||
Depend => Svc["virtqemud"],
|
Depend => Svc["virtqemud"],
|
||||||
|
|||||||
Reference in New Issue
Block a user