diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 98e24dd81..1abc8d494 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -150,7 +150,7 @@ type Config struct { winrmCertificate string Comm communicator.Config `mapstructure:",squash"` - ctx *interpolate.Context + ctx interpolate.Context //Cleanup AsyncResourceGroupDelete bool `mapstructure:"async_resourcegroup_delete"` @@ -258,10 +258,10 @@ func (c *Config) createCertificate() (string, error) { func newConfig(raws ...interface{}) (*Config, []string, error) { var c Config - + c.ctx.Funcs = TemplateFuncs err := config.Decode(&c, &config.DecodeOpts{ Interpolate: true, - InterpolateContext: c.ctx, + InterpolateContext: &c.ctx, }, raws...) if err != nil { @@ -299,7 +299,7 @@ func newConfig(raws ...interface{}) (*Config, []string, error) { } var errs *packer.MultiError - errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(c.ctx)...) + errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) assertRequiredParametersSet(&c, errs) assertTagProperties(&c, errs) diff --git a/builder/azure/arm/template_funcs.go b/builder/azure/arm/template_funcs.go new file mode 100644 index 000000000..7e0d6601f --- /dev/null +++ b/builder/azure/arm/template_funcs.go @@ -0,0 +1,43 @@ +package arm + +import ( + "bytes" + "text/template" +) + +func isValidByteValue(b byte) bool { + if '0' <= b && b <= '9' { + return true + } + if 'a' <= b && b <= 'z' { + return true + } + if 'A' <= b && b <= 'Z' { + return true + } + return b == '.' || b == '_' || b == '-' +} + +// Clean up image name by replacing invalid characters with "-" +// Names are not allowed to end in '.', '-', or '_' and are trimmed. +func templateCleanImageName(s string) string { + if ok, _ := assertManagedImageName(s, ""); ok { + return s + } + b := []byte(s) + newb := make([]byte, len(b)) + for i := range newb { + if isValidByteValue(b[i]) { + newb[i] = b[i] + } else { + newb[i] = '-' + } + } + + newb = bytes.TrimRight(newb, "-_.") + return string(newb) +} + +var TemplateFuncs = template.FuncMap{ + "clean_image_name": templateCleanImageName, +} diff --git a/builder/azure/arm/template_funcs_test.go b/builder/azure/arm/template_funcs_test.go new file mode 100644 index 000000000..68acefba8 --- /dev/null +++ b/builder/azure/arm/template_funcs_test.go @@ -0,0 +1,49 @@ +package arm + +import "testing" + +func TestTemplateCleanImageName(t *testing.T) { + vals := []struct { + origName string + expected string + }{ + // test that valid name is unchanged + { + origName: "abcde-012345xyz", + expected: "abcde-012345xyz", + }, + // test that colons are converted to hyphens + { + origName: "abcde-012345v1.0:0", + expected: "abcde-012345v1.0-0", + }, + // Name starting with number is not valid, but not in scope of this + // function to correct + { + origName: "012345v1.0:0", + expected: "012345v1.0-0", + }, + // Name over 80 chars is not valid, but not corrected by this function. + { + origName: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", + expected: "l012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", + }, + // Name cannot end in a -Name over 80 chars is not valid, but not corrected by this function. + { + origName: "abcde-:_", + expected: "abcde", + }, + // Lost of special characters + { + origName: "My()./-_:&^ $%[]#'@name", + expected: "My--.--_-----------name", + }, + } + + for _, v := range vals { + name := templateCleanImageName(v.origName) + if name != v.expected { + t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name) + } + } +} diff --git a/website/source/docs/templates/engine.html.md b/website/source/docs/templates/engine.html.md index dd5c9e0fe..f8dbc6468 100644 --- a/website/source/docs/templates/engine.html.md +++ b/website/source/docs/templates/engine.html.md @@ -73,6 +73,26 @@ Here is a full list of the available functions for reference. fail because the image name will start with a number, which is why in the above example we prepend the isotime with "mybuild". +#### Specific to Azure builders: + +- `clean_image_name` - Azure managed image names can only contain + certain characters and the maximum length is 80. This function + will replace illegal characters with a "-" character. Example: + + `"mybuild-{{isotime | clean_image_name}}"` will become + `mybuild-2017-10-18t02-06-30z`. + + Note: Valid Azure image names must match the regex + `^[^_\\W][\\w-._)]{0,79}$` + + This engine does not guarantee that the final image name will + match the regex; it will not truncate your name if it exceeds 80 + characters, and it will not validate that the beginning and end of + the engine's output are valid. It will truncate invalid + characters from the end of the name when converting illegal + characters. For example, `"managed_image_name: "My-Name::"` will + be converted to `"managed_image_name: "My-Name"` + ## Template variables Template variables are special variables automatically set by Packer at build time. Some builders, provisioners and other components have template variables that are available only for that component. Template variables are recognizable because they're prefixed by a period, such as `{{ .Name }}`. For example, when using the [`shell`](/docs/builders/vmware-iso.html) builder template variables are available to customize the [`execute_command`](/docs/provisioners/shell.html#execute_command) parameter used to determine how Packer will run the shell command.