44//go:generate packer-sdc struct-markdown
55//go:generate packer-sdc mapstructure-to-hcl2 -type Config,DatasourceOutput
66
7- package proxmoxtemplate
7+ package virtualmachine
88
99import (
1010 "crypto/tls"
@@ -63,24 +63,24 @@ type Config struct {
6363 // `task_timeout` (duration string | ex: "10m") - The timeout for
6464 // Promox API operations, e.g. clones. Defaults to 1 minute.
6565 TaskTimeout time.Duration `mapstructure:"task_timeout"`
66- // Filter that returns `vm_id` for guest which name exactly matches this value.
66+ // Filter that returns `vm_id` for virtual machine which name exactly matches this value.
6767 // Options `name` and `name_regex` are mutually exclusive.
6868 Name string `mapstructure:"name"`
69- // Filter that returns `vm_id` for guest which name matches the regular expression.
69+ // Filter that returns `vm_id` for virtual machine which name matches the regular expression.
7070 // Expression must use [Go Regex Syntax](https://pkg.go.dev/regexp/syntax).
7171 // Options `name` and `name_regex` are mutually exclusive.
7272 NameRegex string `mapstructure:"name_regex"`
73- // Filter that returns guest `vm_id` only when guest is template.
73+ // Filter that returns virtual machine `vm_id` only when virtual machine is template.
7474 Template bool `mapstructure:"template"`
75- // Filter that returns `vm_id` only when guest is located on the specified PVE node.
75+ // Filter that returns `vm_id` only when virtual machine is located on the specified PVE node.
7676 Node string `mapstructure:"node"`
77- // Filter that returns `vm_id` for guest which has all these tags. When you need to
77+ // Filter that returns `vm_id` for virtual machine which has all these tags. When you need to
7878 // specify more than one tag, use semicolon as separator (`"tag1;tag2"`).
79- // Every specified tag must exist in guest .
79+ // Every specified tag must exist in virtual machine .
8080 VmTags string `mapstructure:"vm_tags"`
81- // This filter determines how to handle multiple guests that were matched with all
82- // previous filters. Guest creation time is being used to find latest.
83- // By default, multiple matching guests results in an error.
81+ // This filter determines how to handle multiple virtual machines that were matched with all
82+ // previous filters. Virtual machine creation time is being used to find latest.
83+ // By default, multiple matching virtual machines results in an error.
8484 Latest bool `mapstructure:"latest"`
8585}
8686
@@ -89,11 +89,11 @@ type Datasource struct {
8989}
9090
9191type DatasourceOutput struct {
92- // Identifier of the found guest .
92+ // Identifier of the found virtual machine .
9393 VmId uint `mapstructure:"vm_id"`
94- // Name of the found guest .
94+ // Name of the found virtual machine .
9595 VmName string `mapstructure:"vm_name"`
96- // Tags of the found guest separated with semicolon.
96+ // Tags of the found virtual machine separated with semicolon.
9797 VmTags string `mapstructure:"vm_tags"`
9898}
9999
@@ -180,7 +180,7 @@ func (d *Datasource) Execute() (cty.Value, error) {
180180
181181 filteredVms := filterGuests (d .config , vmList )
182182 if len (filteredVms ) == 0 {
183- return cty .NullVal (cty .EmptyObject ), errors .New ("not a single vm matches the configured filters" )
183+ return cty .NullVal (cty .EmptyObject ), errors .New ("no virtual machine matches the filters" )
184184 }
185185
186186 if d .config .Latest {
@@ -195,11 +195,11 @@ func (d *Datasource) Execute() (cty.Value, error) {
195195 }
196196
197197 vmId = latestConfig ["vmid" ].(uint )
198- vmName = latestConfig [ "name" ].( string )
199- vmTags = latestConfig [ "tags" ].( string )
198+ vmName = configValueOrEmpty ( & latestConfig , "name" )
199+ vmTags = configValueOrEmpty ( & latestConfig , "tags" )
200200 } else {
201201 if len (filteredVms ) > 1 {
202- return cty .NullVal (cty .EmptyObject ), errors .New ("more than one guest passed filters, cannot return vm_id " )
202+ return cty .NullVal (cty .EmptyObject ), errors .New ("more than one virtual machine matched the filters " )
203203 }
204204 vmId = filteredVms [0 ].Id
205205 vmName = filteredVms [0 ].Name
@@ -230,13 +230,13 @@ func findLatestConfig(configs []vmConfig) (vmConfig, error) {
230230 result = configs [i ]
231231 }
232232 } else {
233- return nil , errors .New ("no meta field in the guest config" )
233+ return nil , errors .New ("no meta field in the virtual machine config" )
234234 }
235235 }
236236 return result , nil
237237}
238238
239- // Get configs from PVE in 'map[string]interface{}' format for all VMs in the list.
239+ // getVmConfigs retrieves configs from PVE in 'map[string]interface{}' format for all VMs in the list.
240240// Also add value of VM ID to every config (useful for further steps).
241241func getVmConfigs (client * proxmox.Client , vmList []proxmox.GuestResource ) ([]vmConfig , error ) {
242242 var result []vmConfig
@@ -253,7 +253,7 @@ func getVmConfigs(client *proxmox.Client, vmList []proxmox.GuestResource) ([]vmC
253253 return result , nil
254254}
255255
256- // filterGuests removes guests from the `guests` list that do not match some filters in the datasource config.
256+ // filterGuests removes virtual machines from the `guests` list that do not match some filters in the datasource config.
257257func filterGuests (config Config , guests []proxmox.GuestResource ) []proxmox.GuestResource {
258258 filterFuncs := make ([]func (proxmox.GuestResource ) bool , 0 )
259259
@@ -292,6 +292,9 @@ func filterGuests(config Config, guests []proxmox.GuestResource) []proxmox.Guest
292292 result := make ([]proxmox.GuestResource , 0 )
293293 for _ , guest := range guests {
294294 var ok bool
295+ if len (filterFuncs ) == 0 {
296+ ok = true
297+ }
295298 for _ , guestPassedFilter := range filterFuncs {
296299 if ok = guestPassedFilter (guest ); ! ok {
297300 break
@@ -305,6 +308,8 @@ func filterGuests(config Config, guests []proxmox.GuestResource) []proxmox.Guest
305308 return result
306309}
307310
311+ // configTagsMatchNodeTags compares two lists of strings and returns true only when all
312+ // elements from the first list are present in the second list.
308313func configTagsMatchNodeTags (configTags []string , nodeTags []proxmox.Tag ) bool {
309314 var countOfMatchedTags int
310315 for _ , configTag := range configTags {
@@ -325,14 +330,15 @@ func configTagsMatchNodeTags(configTags []string, nodeTags []proxmox.Tag) bool {
325330 return true
326331}
327332
333+ // newProxmoxClient creates new client and tries to connect and log in to Proxmox instance.
328334func newProxmoxClient (config Config ) (* proxmox.Client , error ) {
329335 tlsConfig := & tls.Config {
330336 InsecureSkipVerify : config .SkipCertValidation ,
331337 }
332338
333339 client , err := proxmox .NewClient (strings .TrimSuffix (config .proxmoxURL .String (), "/" ), nil , "" , tlsConfig , "" , int (config .TaskTimeout .Seconds ()))
334340 if err != nil {
335- return nil , err
341+ return nil , fmt . Errorf ( "could not connect to Proxmox: %w" , err )
336342 }
337343
338344 * proxmox .Debug = config .PackerDebug
@@ -346,17 +352,19 @@ func newProxmoxClient(config Config) (*proxmox.Client, error) {
346352 log .Print ("using password auth" )
347353 err = client .Login (config .Username , config .Password , "" )
348354 if err != nil {
349- return nil , err
355+ return nil , fmt . Errorf ( "could not log in to Proxmox: %w" , err )
350356 }
351357 }
352358
353359 return client , nil
354360}
355361
362+ // parseMetaField parses the string from the `meta` field and returns integer value
363+ // representing the creation date of the virtual machine in epoch seconds format.
356364func parseMetaField (field string ) (int , error ) {
357365 re , err := regexp .Compile (`.*ctime=(?P<ctime>[0-9]+).*` )
358366 if err != nil {
359- return 0 , err
367+ return 0 , fmt . Errorf ( "could not compile regex to parse meta field: %w" , err )
360368 }
361369
362370 matched := re .MatchString (field )
@@ -366,15 +374,34 @@ func parseMetaField(field string) (int, error) {
366374 valueStr := re .ReplaceAllString (field , "${ctime}" )
367375 value , err := strconv .Atoi (valueStr )
368376 if err != nil {
369- return 0 , err
377+ return 0 , fmt . Errorf ( "could not convert date field to int: %w" , err )
370378 }
371379 return value , nil
372380}
373381
382+ // joinTags used to combine list of strings into one string with defined separator.
374383func joinTags (tags []proxmox.Tag , separator string ) string {
375384 tagsAsStrings := make ([]string , len (tags ))
376385 for i , tag := range tags {
377386 tagsAsStrings [i ] = string (tag )
378387 }
379388 return strings .Join (tagsAsStrings , separator )
380389}
390+
391+ // configValueOrEmpty tries to retrieve string by key from dynamic map of interfaces.
392+ // In case when key not found or there was an error, this function returns empty string.
393+ func configValueOrEmpty (values * vmConfig , key string ) string {
394+ result := ""
395+ if values != nil {
396+ value , exists := (* values )[key ]
397+ if ! exists {
398+ return result
399+ }
400+ strValue , ok := value .(string )
401+ if ! ok {
402+ return result
403+ }
404+ result = strValue
405+ }
406+ return result
407+ }
0 commit comments