Merge pull request #10116 from OblateSpheroid/GH8915
Feat (builder/oracle-oci): Allow filtering on base image
This commit is contained in:
commit
799c548c40
|
@ -1,4 +1,4 @@
|
||||||
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
|
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest
|
||||||
|
|
||||||
package oci
|
package oci
|
||||||
|
|
||||||
|
@ -35,6 +35,16 @@ type CreateVNICDetails struct {
|
||||||
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
SubnetId *string `mapstructure:"subnet_id" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListImagesRequest struct {
|
||||||
|
// fields that can be specified under "base_image_filter"
|
||||||
|
CompartmentId *string `mapstructure:"compartment_id"`
|
||||||
|
DisplayName *string `mapstructure:"display_name"`
|
||||||
|
DisplayNameSearch *string `mapstructure:"display_name_search"`
|
||||||
|
OperatingSystem *string `mapstructure:"operating_system"`
|
||||||
|
OperatingSystemVersion *string `mapstructure:"operating_system_version"`
|
||||||
|
Shape *string `mapstructure:"shape"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
@ -70,11 +80,12 @@ type Config struct {
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
BaseImageID string `mapstructure:"base_image_ocid"`
|
BaseImageID string `mapstructure:"base_image_ocid"`
|
||||||
|
BaseImageFilter ListImagesRequest `mapstructure:"base_image_filter"`
|
||||||
ImageName string `mapstructure:"image_name"`
|
ImageName string `mapstructure:"image_name"`
|
||||||
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
ImageCompartmentID string `mapstructure:"image_compartment_ocid"`
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
InstanceName string `mapstructure:"instance_name"`
|
InstanceName *string `mapstructure:"instance_name"`
|
||||||
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
InstanceTags map[string]string `mapstructure:"instance_tags"`
|
||||||
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
|
||||||
Shape string `mapstructure:"shape"`
|
Shape string `mapstructure:"shape"`
|
||||||
|
@ -269,9 +280,17 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||||
errs, errors.New("'subnet_ocid' must be specified"))
|
errs, errors.New("'subnet_ocid' must be specified"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.BaseImageID == "" {
|
if (c.BaseImageID == "") && (c.BaseImageFilter == ListImagesRequest{}) {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("'base_image_ocid' must be specified"))
|
errs, errors.New("'base_image_ocid' or 'base_image_filter' must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BaseImageFilter.CompartmentId == nil {
|
||||||
|
c.BaseImageFilter.CompartmentId = &c.CompartmentID
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BaseImageFilter.Shape == nil {
|
||||||
|
c.BaseImageFilter.Shape = &c.Shape
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
// Validate tag lengths. TODO (hlowndes) maximum number of tags allowed.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
|
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails,ListImagesRequest"; DO NOT EDIT.
|
||||||
package oci
|
package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -76,6 +76,7 @@ type FlatConfig struct {
|
||||||
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
|
||||||
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
|
||||||
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
|
||||||
|
BaseImageFilter *FlatListImagesRequest `mapstructure:"base_image_filter" cty:"base_image_filter" hcl:"base_image_filter"`
|
||||||
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
|
||||||
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
ImageCompartmentID *string `mapstructure:"image_compartment_ocid" cty:"image_compartment_ocid" hcl:"image_compartment_ocid"`
|
||||||
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
|
||||||
|
@ -171,6 +172,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
|
||||||
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
|
||||||
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
|
||||||
|
"base_image_filter": &hcldec.BlockSpec{TypeName: "base_image_filter", Nested: hcldec.ObjectSpec((*FlatListImagesRequest)(nil).HCL2Spec())},
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||||
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
|
"image_compartment_ocid": &hcldec.AttrSpec{Name: "image_compartment_ocid", Type: cty.String, Required: false},
|
||||||
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
|
||||||
|
@ -227,3 +229,36 @@ func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||||
|
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
type FlatListImagesRequest struct {
|
||||||
|
CompartmentId *string `mapstructure:"compartment_id" cty:"compartment_id" hcl:"compartment_id"`
|
||||||
|
DisplayName *string `mapstructure:"display_name" cty:"display_name" hcl:"display_name"`
|
||||||
|
DisplayNameSearch *string `mapstructure:"display_name_search" cty:"display_name_search" hcl:"display_name_search"`
|
||||||
|
OperatingSystem *string `mapstructure:"operating_system" cty:"operating_system" hcl:"operating_system"`
|
||||||
|
OperatingSystemVersion *string `mapstructure:"operating_system_version" cty:"operating_system_version" hcl:"operating_system_version"`
|
||||||
|
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMapstructure returns a new FlatListImagesRequest.
|
||||||
|
// FlatListImagesRequest is an auto-generated flat version of ListImagesRequest.
|
||||||
|
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||||
|
func (*ListImagesRequest) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||||
|
return new(FlatListImagesRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HCL2Spec returns the hcl spec of a ListImagesRequest.
|
||||||
|
// This spec is used by HCL to read the fields of ListImagesRequest.
|
||||||
|
// The decoded values from this spec will then be applied to a FlatListImagesRequest.
|
||||||
|
func (*FlatListImagesRequest) HCL2Spec() map[string]hcldec.Spec {
|
||||||
|
s := map[string]hcldec.Spec{
|
||||||
|
"compartment_id": &hcldec.AttrSpec{Name: "compartment_id", Type: cty.String, Required: false},
|
||||||
|
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
|
||||||
|
"display_name_search": &hcldec.AttrSpec{Name: "display_name_search", Type: cty.String, Required: false},
|
||||||
|
"operating_system": &hcldec.AttrSpec{Name: "operating_system", Type: cty.String, Required: false},
|
||||||
|
"operating_system_version": &hcldec.AttrSpec{Name: "operating_system_version", Type: cty.String, Required: false},
|
||||||
|
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
@ -88,6 +88,36 @@ func TestConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("BaseImageFilterWithoutOCID", func(t *testing.T) {
|
||||||
|
raw := testConfig(cfgFile)
|
||||||
|
raw["base_image_ocid"] = ""
|
||||||
|
raw["base_image_filter"] = map[string]interface{}{
|
||||||
|
"display_name": "hello_world",
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
errs := c.Prepare(raw)
|
||||||
|
|
||||||
|
if errs != nil {
|
||||||
|
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BaseImageFilterDefault", func(t *testing.T) {
|
||||||
|
raw := testConfig(cfgFile)
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
errs := c.Prepare(raw)
|
||||||
|
if errs != nil {
|
||||||
|
t.Fatalf("Unexpected error in configuration %+v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *c.BaseImageFilter.Shape != raw["shape"] {
|
||||||
|
t.Fatalf("Default base_image_filter shape %v does not equal config shape %v",
|
||||||
|
*c.BaseImageFilter.Shape, raw["shape"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("NoAccessConfig", func(t *testing.T) {
|
t.Run("NoAccessConfig", func(t *testing.T) {
|
||||||
raw := testConfig(cfgFile)
|
raw := testConfig(cfgFile)
|
||||||
delete(raw, "access_cfg_file")
|
delete(raw, "access_cfg_file")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
core "github.com/oracle/oci-go-sdk/core"
|
core "github.com/oracle/oci-go-sdk/core"
|
||||||
|
@ -51,22 +52,7 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||||
metadata["user_data"] = d.cfg.UserData
|
metadata["user_data"] = d.cfg.UserData
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceDetails := core.LaunchInstanceDetails{
|
// Create VNIC details for instance
|
||||||
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
|
||||||
CompartmentId: &d.cfg.CompartmentID,
|
|
||||||
DefinedTags: d.cfg.InstanceDefinedTags,
|
|
||||||
FreeformTags: d.cfg.InstanceTags,
|
|
||||||
Shape: &d.cfg.Shape,
|
|
||||||
SubnetId: &d.cfg.SubnetID,
|
|
||||||
Metadata: metadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
// When empty, the default display name is used.
|
|
||||||
if d.cfg.InstanceName != "" {
|
|
||||||
instanceDetails.DisplayName = &d.cfg.InstanceName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass VNIC details, if specified, to the instance
|
|
||||||
CreateVnicDetails := core.CreateVnicDetails{
|
CreateVnicDetails := core.CreateVnicDetails{
|
||||||
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
|
||||||
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
|
||||||
|
@ -79,14 +65,69 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
|
||||||
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceDetails.CreateVnicDetails = &CreateVnicDetails
|
// Determine base image ID
|
||||||
|
var imageId *string
|
||||||
|
if d.cfg.BaseImageID != "" {
|
||||||
|
imageId = &d.cfg.BaseImageID
|
||||||
|
} else {
|
||||||
|
// Pull images and determine which image ID to use, if BaseImageId not specified
|
||||||
|
response, err := d.computeClient.ListImages(ctx, core.ListImagesRequest{
|
||||||
|
CompartmentId: d.cfg.BaseImageFilter.CompartmentId,
|
||||||
|
DisplayName: d.cfg.BaseImageFilter.DisplayName,
|
||||||
|
OperatingSystem: d.cfg.BaseImageFilter.OperatingSystem,
|
||||||
|
OperatingSystemVersion: d.cfg.BaseImageFilter.OperatingSystemVersion,
|
||||||
|
Shape: d.cfg.BaseImageFilter.Shape,
|
||||||
|
LifecycleState: "AVAILABLE",
|
||||||
|
SortBy: "TIMECREATED",
|
||||||
|
SortOrder: "DESC",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(response.Items) == 0 {
|
||||||
|
return "", errors.New("base_image_filter returned no images")
|
||||||
|
}
|
||||||
|
if d.cfg.BaseImageFilter.DisplayNameSearch != nil {
|
||||||
|
// Return most recent image that matches regex
|
||||||
|
imageNameRegex, err := regexp.Compile(*d.cfg.BaseImageFilter.DisplayNameSearch)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, image := range response.Items {
|
||||||
|
if imageNameRegex.MatchString(*image.DisplayName) {
|
||||||
|
imageId = image.Id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if imageId == nil {
|
||||||
|
return "", errors.New("No image matched display_name_search criteria")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no regex provided, simply return most recent image pulled
|
||||||
|
imageId = response.Items[0].Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create Source details which will be used to Launch Instance
|
// Create Source details which will be used to Launch Instance
|
||||||
instanceDetails.SourceDetails = core.InstanceSourceViaImageDetails{
|
InstanceSourceDetails := core.InstanceSourceViaImageDetails{
|
||||||
ImageId: &d.cfg.BaseImageID,
|
ImageId: imageId,
|
||||||
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
BootVolumeSizeInGBs: &d.cfg.BootVolumeSizeInGBs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build instance details
|
||||||
|
instanceDetails := core.LaunchInstanceDetails{
|
||||||
|
AvailabilityDomain: &d.cfg.AvailabilityDomain,
|
||||||
|
CompartmentId: &d.cfg.CompartmentID,
|
||||||
|
CreateVnicDetails: &CreateVnicDetails,
|
||||||
|
DefinedTags: d.cfg.InstanceDefinedTags,
|
||||||
|
DisplayName: d.cfg.InstanceName,
|
||||||
|
FreeformTags: d.cfg.InstanceTags,
|
||||||
|
Shape: &d.cfg.Shape,
|
||||||
|
SourceDetails: InstanceSourceDetails,
|
||||||
|
SubnetId: &d.cfg.SubnetID,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -70,6 +70,24 @@ can also be supplied to override the typical auto-generated key:
|
||||||
[ListImages](https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/latest/Image/ListImages)
|
[ListImages](https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/latest/Image/ListImages)
|
||||||
operation available in the Core Services API.
|
operation available in the Core Services API.
|
||||||
|
|
||||||
|
- `base_image_filter` (map of strings) - As an alternative to providing `base_image_ocid`,
|
||||||
|
the user can supply search criteria, and Packer will use the the most recent image that meets
|
||||||
|
all search criteria. If no image meets all search criteria, Packer returns an error. The
|
||||||
|
following fields, if specified, must match exactly:
|
||||||
|
- `compartment_id` - The OCID of the compartment to find the image. If not specified, will use `compartment_ocid`
|
||||||
|
used for the instance.
|
||||||
|
- `display_name` - The full name of the image, e.g., `Oracle-Linux-7.8-2020.05.26-0`
|
||||||
|
- `operating_system` - The operating system used on the image, e.g., `Oracle Linux`
|
||||||
|
- `operating_system_version` - The version of the operating system on the image, e.g., `7.8`
|
||||||
|
- `shape` - A shape that the image supports. If not specified, will use `shape` used for the instance
|
||||||
|
|
||||||
|
Additionally, the following field takes a regular expression:
|
||||||
|
- `display_name_search` - a regular expression for the display name, e.g., `^Oracle-Linux`. This
|
||||||
|
is ignored if `display_name` is also specified under `base_image_filter`. If no images match
|
||||||
|
the expression, Packer returns an error. If multiple images match, the most recent is used.
|
||||||
|
|
||||||
|
`base_image_filter` is ignored if `base_image_ocid` is also specified.
|
||||||
|
|
||||||
- `compartment_ocid` (string) - The OCID of the
|
- `compartment_ocid` (string) - The OCID of the
|
||||||
[compartment](https://docs.us-phoenix-1.oraclecloud.com/Content/GSG/Tasks/choosingcompartments.htm) that the instance will run in.
|
[compartment](https://docs.us-phoenix-1.oraclecloud.com/Content/GSG/Tasks/choosingcompartments.htm) that the instance will run in.
|
||||||
|
|
||||||
|
@ -274,7 +292,7 @@ to the instance. Depending on network (VCN and subnet) setup, this may be
|
||||||
required for Packer to successfully SSH into the instance. NSGs are a property
|
required for Packer to successfully SSH into the instance. NSGs are a property
|
||||||
of the virtual network interface card (VNIC) attached to the instance, and
|
of the virtual network interface card (VNIC) attached to the instance, and
|
||||||
are listed in `nsg_ids` under `create_vnic_details`.
|
are listed in `nsg_ids` under `create_vnic_details`.
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"name": "base-image-{{isotime \"20060102030405\"}}",
|
"name": "base-image-{{isotime \"20060102030405\"}}",
|
||||||
"type": "oracle-oci",
|
"type": "oracle-oci",
|
||||||
|
@ -303,3 +321,21 @@ are listed in `nsg_ids` under `create_vnic_details`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Base Image Filter Example
|
||||||
|
Note that `base_image_filter` gets passed as a string, then interpreted as a
|
||||||
|
regular expression. This means that all back-slashes must be doubled, e.g.,
|
||||||
|
use `\\w+` to mean `\w+`, and `\\\\` to create the regular expression equivalent
|
||||||
|
of `\\` (which will search for a literal back-slash).
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "base-image-{{isotime \"20060102030405\"}}",
|
||||||
|
"type": "oracle-oci",
|
||||||
|
"availability_domain": "aaaa:PHX-AD-1",
|
||||||
|
"base_image_filter": {
|
||||||
|
"operating_system": "Oracle Linux",
|
||||||
|
"operating_system_version": "7.8",
|
||||||
|
"display_name_search": "^Oracle-Linux-7\\.8-2020\\.\\d+"
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue