Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Specialized Field for Shared Image Gallery Destination #295

Merged
merged 7 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions builder/azure/arm/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,12 +512,15 @@ func (b *Builder) configureStateBag(stateBag multistep.StateBag) {
stateBag.Put(constants.ArmKeepOSDisk, b.config.KeepOSDisk)

stateBag.Put(constants.ArmIsSIGImage, b.config.isPublishToSIG())
// Set Specialized as false so that we can pull it from the state later even if we're not publishing to SIG
stateBag.Put(constants.ArmSharedImageGalleryDestinationSpecialized, false)
if b.config.isPublishToSIG() {
stateBag.Put(constants.ArmManagedImageSigPublishResourceGroup, b.config.SharedGalleryDestination.SigDestinationResourceGroup)
stateBag.Put(constants.ArmManagedImageSharedGalleryName, b.config.SharedGalleryDestination.SigDestinationGalleryName)
stateBag.Put(constants.ArmManagedImageSharedGalleryImageName, b.config.SharedGalleryDestination.SigDestinationImageName)
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersion, b.config.SharedGalleryDestination.SigDestinationImageVersion)
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionStorageAccountType, b.config.SharedGalleryDestination.SigDestinationStorageAccountType)
stateBag.Put(constants.ArmSharedImageGalleryDestinationSpecialized, b.config.SharedGalleryDestination.SigDestinationSpecialized)
stateBag.Put(constants.ArmManagedImageSubscription, b.config.ClientConfig.SubscriptionID)
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionEndOfLifeDate, b.config.SharedGalleryImageVersionEndOfLifeDate)
stateBag.Put(constants.ArmManagedImageSharedGalleryImageVersionReplicaCount, b.config.SharedGalleryImageVersionReplicaCount)
Expand Down
11 changes: 9 additions & 2 deletions builder/azure/arm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ type SharedImageGalleryDestination struct {
// Specify a storage account type for the Shared Image Gallery Image Version.
// Defaults to `Standard_LRS`. Accepted values are `Standard_LRS`, `Standard_ZRS` and `Premium_LRS`
SigDestinationStorageAccountType string `mapstructure:"storage_account_type"`
// Set to true if publishing to a Specialized Gallery, this skips a call to set the build VM's OS state as Generalized
SigDestinationSpecialized bool `mapstructure:"specialized"`
}

type Spot struct {
Expand Down Expand Up @@ -1094,8 +1096,13 @@ func assertRequiredParametersSet(c *Config, errs *packersdk.MultiError) {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD."))
}

if (c.SecureBootEnabled || c.VTpmEnabled) && (c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "") {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A managed image (managed_image_name, managed_image_resource_group_name) can not set SecureBoot or VTpm, these features are only supported when directly publishing to a Shared Image Gallery"))
if c.ManagedImageName != "" || c.ManagedImageResourceGroupName != "" {
if c.SecureBootEnabled || c.VTpmEnabled {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A managed image (managed_image_name, managed_image_resource_group_name) can not set SecureBoot or VTpm, these features are only supported when directly publishing to a Shared Image Gallery"))
}
if c.SharedGalleryDestination.SigDestinationSpecialized {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("A managed image (managed_image_name, managed_image_resource_group_name) can not be Specialized (shared_image_gallery_destination.specialized can not be set), Specialized images are only supported when directly publishing to a Shared Image Gallery"))
}
}

if (c.CaptureContainerName != "" || c.CaptureNamePrefix != "" || c.ManagedImageName != "") && c.DiskEncryptionSetId != "" {
Expand Down
2 changes: 2 additions & 0 deletions builder/azure/arm/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions builder/azure/arm/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,36 @@ func TestConfigShouldRejectVTPMWhenPublishingToAManagedImage(t *testing.T) {
}
}

func TestConfigShouldRejectSpecializedWhenPublishingManagedImage(t *testing.T) {
expectedErrorMessage := "A managed image (managed_image_name, managed_image_resource_group_name) can not be Specialized (shared_image_gallery_destination.specialized can not be set), Specialized images are only supported when directly publishing to a Shared Image Gallery"
config := map[string]interface{}{
"image_offer": "ignore",
"image_publisher": "ignore",
"image_sku": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
"shared_image_gallery_destination": map[string]interface{}{
"resource_group": "ignore",
"gallery_name": "ignore",
"image_name": "ignore",
"image_version": "1.0.0",
"specialized": "true",
},
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}

var c Config
_, err := c.Prepare(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject managed image with secure boot, secure boot is only allowed when direct publishing to SIG")
} else if !strings.Contains(err.Error(), expectedErrorMessage) {
t.Fatalf("unexpected rejection reason, expected %s to contain %s", err.Error(), expectedErrorMessage)
}
}
func TestConfigShouldAcceptPlatformManagedImageBuild(t *testing.T) {
config := map[string]interface{}{
"image_offer": "ignore",
Expand Down
11 changes: 8 additions & 3 deletions builder/azure/arm/step_capture_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func (s *StepCaptureImage) captureImage(ctx context.Context, resourceGroupName s
}

func (s *StepCaptureImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
s.say("Generalizing machine ...")

var computeName = state.Get(constants.ArmComputeName).(string)
var location = state.Get(constants.ArmLocation).(string)
Expand All @@ -79,12 +78,18 @@ func (s *StepCaptureImage) Run(ctx context.Context, state multistep.StateBag) mu

var isManagedImage = state.Get(constants.ArmIsManagedImage).(bool)
var isSIGImage = state.Get(constants.ArmIsSIGImage).(bool)

var skipGeneralization = state.Get(constants.ArmSharedImageGalleryDestinationSpecialized).(bool)
s.say(fmt.Sprintf(" -> Compute ResourceGroupName : '%s'", resourceGroupName))
s.say(fmt.Sprintf(" -> Compute Name : '%s'", computeName))
s.say(fmt.Sprintf(" -> Compute Location : '%s'", location))

err := s.generalizeVM(resourceGroupName, computeName)
var err error
if skipGeneralization {
s.say("Skipping generalization of Compute Gallery Image")
} else {
s.say("Generalizing machine ...")
err = s.generalizeVM(resourceGroupName, computeName)
}

if err == nil {
if isManagedImage {
Expand Down
61 changes: 61 additions & 0 deletions builder/azure/arm/step_capture_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,66 @@ func TestStepCaptureImageShouldPassIfCapturePasses(t *testing.T) {
}
}

func TestStepCaptureImageShouldCallGeneralizeIfSpecializedIsFalse(t *testing.T) {
generalizeCount := 0
var testSubject = &StepCaptureImage{
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error { return nil },
generalizeVM: func(string, string) error {
generalizeCount++
return nil
},
get: func(client *AzureClient) *CaptureTemplate {
return nil
},
say: func(message string) {},
error: func(e error) {},
}

stateBag := createTestStateBagStepCaptureImage()
stateBag.Put(constants.ArmSharedImageGalleryDestinationSpecialized, false)
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}

if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
if generalizeCount != 1 {
t.Fatalf("Expected generalize to be called 1, was called %d times", generalizeCount)
}
}

func TestStepCaptureImageShouldNotCallGeneralizeIfSpecializedIsTrue(t *testing.T) {
generalizeCount := 0
var testSubject = &StepCaptureImage{
captureVhd: func(context.Context, string, string, *compute.VirtualMachineCaptureParameters) error { return nil },
generalizeVM: func(string, string) error {
generalizeCount++
return nil
},
get: func(client *AzureClient) *CaptureTemplate {
return nil
},
say: func(message string) {},
error: func(e error) {},
}

stateBag := createTestStateBagStepCaptureImage()
stateBag.Put(constants.ArmSharedImageGalleryDestinationSpecialized, true)
var result = testSubject.Run(context.Background(), stateBag)
if result != multistep.ActionContinue {
t.Fatalf("Expected the step to return 'ActionContinue', but got '%d'.", result)
}

if _, ok := stateBag.GetOk(constants.Error); ok == true {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
if generalizeCount != 0 {
t.Fatalf("Expected generalize to not be called, was called %d times", generalizeCount)
}
}

func TestStepCaptureImageShouldTakeStepArgumentsFromStateBag(t *testing.T) {
cancelCh := make(chan<- struct{})
defer close(cancelCh)
Expand Down Expand Up @@ -136,6 +196,7 @@ func createTestStateBagStepCaptureImage() multistep.StateBag {
stateBag.Put(constants.ArmManagedImageName, "")
stateBag.Put(constants.ArmImageParameters, &compute.Image{})
stateBag.Put(constants.ArmIsSIGImage, false)
stateBag.Put(constants.ArmSharedImageGalleryDestinationSpecialized, false)

return stateBag
}
1 change: 1 addition & 0 deletions builder/azure/common/constants/stateBag.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
ArmManagedImageSharedGalleryImageVersionReplicaCount string = "arm.ArmManagedImageSharedGalleryImageVersionReplicaCount"
ArmManagedImageSharedGalleryImageVersionExcludeFromLatest string = "arm.ArmManagedImageSharedGalleryImageVersionExcludeFromLatest"
ArmManagedImageSharedGalleryImageVersionStorageAccountType string = "arm.ArmManagedImageSharedGalleryImageVersionStorageAccountType"
ArmSharedImageGalleryDestinationSpecialized string = "arm.ArmSharedImageGalleryDestinationSpecialized"
ArmManagedImageSubscription string = "arm.ArmManagedImageSubscription"
ArmAsyncResourceGroupDelete string = "arm.AsyncResourceGroupDelete"
ArmManagedImageOSDiskSnapshotName string = "arm.ManagedImageOSDiskSnapshotName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
- `storage_account_type` (string) - Specify a storage account type for the Shared Image Gallery Image Version.
Defaults to `Standard_LRS`. Accepted values are `Standard_LRS`, `Standard_ZRS` and `Premium_LRS`

- `specialized` (bool) - Set to true if publishing to a Specialized Gallery, this skips a call to set the resulting VM's OS state as Generalized

<!-- End of code generated from the comments of the SharedImageGalleryDestination struct in builder/azure/arm/config.go; -->