Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Username and password configuration arguments can be omitted if environment vari

- `storage_tier` (string) - The storage tier to use. Available options are `maxiops`, `archive`, and `standard`. Defaults to `maxiops`.

- `storage_size` (int) - The storage size in gigabytes. If not specified, defaults to the image size
(minimum 10GB). When importing compressed images that expand significantly, specify
a larger value to ensure adequate space for the uncompressed content.

- `state_timeout_duration` (duration string | ex: "1h5m2s") - The amount of time to wait for resource state changes. Defaults to `60m`.

<!-- End of code generated from the comments of the Config struct in post-processor/upcloud-import/config.go; -->
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

### Added

- `storage_size` parameter to upcloud-import post-processor

### Fixed

- Update Go version to 1.24.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

- `storage_tier` (string) - The storage tier to use. Available options are `maxiops`, `archive`, and `standard`. Defaults to `maxiops`.

- `storage_size` (int) - The storage size in gigabytes. If not specified, defaults to the image size
(minimum 10GB). When importing compressed images that expand significantly, specify
a larger value to ensure adequate space for the uncompressed content.

- `state_timeout_duration` (duration string | ex: "1h5m2s") - The amount of time to wait for resource state changes. Defaults to `60m`.

<!-- End of code generated from the comments of the Config struct in post-processor/upcloud-import/config.go; -->
19 changes: 19 additions & 0 deletions post-processor/upcloud-import/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ type Config struct {
// The storage tier to use. Available options are `maxiops`, `archive`, and `standard`. Defaults to `maxiops`.
StorageTier string `mapstructure:"storage_tier"`

// The storage size in gigabytes. If not specified, defaults to the image size
// (minimum 10GB). When importing compressed images that expand significantly, specify
// a larger value to ensure adequate space for the uncompressed content.
StorageSize int `mapstructure:"storage_size"`

// The amount of time to wait for resource state changes. Defaults to `60m`.
Timeout time.Duration `mapstructure:"state_timeout_duration"`

Expand Down Expand Up @@ -92,6 +97,20 @@ func (c *Config) validate() *packer.MultiError {
errs = packer.MultiErrorAppend(errs, authErrs.Errors...)
}

// Validate storage size if specified
if c.StorageSize > 0 {
if c.StorageSize < storageMinSizeGB {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("'storage_size' must be at least %dGB", storageMinSizeGB),
)
}
if c.StorageSize > storageMaxSizeGB {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("'storage_size' cannot exceed %dGB", storageMaxSizeGB),
)
}
}

return errs
}

Expand Down
2 changes: 2 additions & 0 deletions post-processor/upcloud-import/config.hcl2spec.go

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

92 changes: 92 additions & 0 deletions post-processor/upcloud-import/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,95 @@ func TestNewConfig_StorageTierDefault(t *testing.T) {
require.NotNil(t, c)
assert.Equal(t, "maxiops", c.StorageTier)
}

func TestNewConfig_StorageSize(t *testing.T) {
t.Parallel()

tests := []struct {
name string
storageSize interface{} // Use interface{} to test both presence and absence
expectError bool
expectedSize int
errorContains string
}{
{
name: "not specified - should default to 0",
storageSize: nil, // Don't include storage_size in config
expectError: false,
expectedSize: 0,
},
{
name: "valid size",
storageSize: 50,
expectError: false,
expectedSize: 50,
},
{
name: "minimum valid size",
storageSize: 10,
expectError: false,
expectedSize: 10,
},
{
name: "maximum valid size",
storageSize: 4096,
expectError: false,
expectedSize: 4096,
},
{
name: "too small - below minimum",
storageSize: 5,
expectError: true,
errorContains: "'storage_size' must be at least 10GB",
},
{
name: "too large - above maximum",
storageSize: 5000,
expectError: true,
errorContains: "'storage_size' cannot exceed 4096GB",
},
{
name: "zero - invalid",
storageSize: 0,
expectError: false, // 0 means not specified, which is valid
expectedSize: 0,
},
{
name: "negative - invalid",
storageSize: -10,
expectError: false, // Negative values are treated as 0 (not specified)
expectedSize: -10, // The value is stored as-is, validation only checks > 0
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

configMap := map[string]interface{}{
"token": "test-token",
"zones": []string{"fi-hel1"},
"template_name": "my-template",
}

if tt.storageSize != nil {
configMap["storage_size"] = tt.storageSize
}

c, err := upcloudimport.NewConfig([]interface{}{configMap}...)

if tt.expectError {
assert.Error(t, err)
if tt.errorContains != "" {
assert.Contains(t, err.Error(), tt.errorContains)
}
} else {
assert.NoError(t, err)
require.NotNil(t, c)
assert.Equal(t, tt.expectedSize, c.StorageSize)
}

require.NotNil(t, c)
})
}
}
14 changes: 10 additions & 4 deletions post-processor/upcloud-import/step_create_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ func (s *stepCreateStorage) Run(ctx context.Context, state multistep.StateBag) m
if err != nil {
return haltOnError(ui, state, err)
}
size := s.image.SizeGB()
if size < storageMinSizeGB {
size = storageMinSizeGB
var size int
if s.postProcessor.config.StorageSize > 0 {
size = s.postProcessor.config.StorageSize
ui.Say(fmt.Sprintf("Creating storage device (%dGB) for '%s' image using manually specified size", size, s.image.File()))
} else {
size = s.image.SizeGB()
if size < storageMinSizeGB {
size = storageMinSizeGB
}
ui.Say(fmt.Sprintf("Creating storage device (%dGB) for '%s' image", size, s.image.File()))
}
ui.Say(fmt.Sprintf("Creating storage device (%dGB) for '%s' image", size, s.image.File()))
storage, err := s.postProcessor.driver.CreateTemplateStorage(ctx,
fmt.Sprintf("%s-%s", BuilderID, time.Now().Format(timestampSuffixLayout)),
s.postProcessor.config.Zones[0],
Expand Down
Loading