added the parser for the enforced block

This commit is contained in:
Hari Om 2026-03-11 11:50:00 +05:30
parent 2c5d23b26a
commit e9230b0b64
16 changed files with 1123 additions and 67 deletions

View file

@ -150,6 +150,20 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
return ret
}
// Fetch and inject enforced provisioners from HCP Packer (if configured)
if !cla.SkipEnforcement {
if err := hcpRegistry.FetchEnforcedBlocks(buildCtx); err != nil {
c.Ui.Error(fmt.Sprintf("Warning: failed to fetch enforced provisioners: %s", err))
}
diags := hcpRegistry.InjectEnforcedProvisioners(builds)
if diags.HasErrors() {
return writeDiags(c.Ui, nil, diags)
}
} else {
c.Ui.Say("Skipping HCP Packer enforced provisioners (--skip-enforcement flag set)")
}
if cla.Debug {
c.Ui.Say("Debug mode enabled. Builds will not be parallelized.")
}
@ -456,6 +470,7 @@ Options:
-warn-on-undeclared-var Display warnings for user variable files containing undeclared variables.
-ignore-prerelease-plugins Disable the loading of prerelease plugin binaries (x.y.z-dev).
-use-sequential-evaluation Fallback to using a sequential approach for local/datasource evaluation.
-skip-enforcement Skip injection of HCP Packer enforced provisioners.
`
return strings.TrimSpace(helpText)

View file

@ -101,6 +101,8 @@ func (ba *BuildArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&ba.ReleaseOnly, "ignore-prerelease-plugins", false, "Disable the loading of prerelease plugin binaries (x.y.z-dev).")
flags.BoolVar(&ba.SkipEnforcement, "skip-enforcement", false, "Skip injection of HCP Packer enforced provisioners. Requires admin privileges.")
ba.MetaArgs.AddFlagSets(flags)
}
@ -136,6 +138,7 @@ type BuildArgs struct {
ParallelBuilds int64
OnError string
ReleaseOnly bool
SkipEnforcement bool
}
func (ia *InitArgs) AddFlagSets(flags *flag.FlagSet) {

52
go.mod
View file

@ -7,7 +7,7 @@ require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/dsnet/compress v0.0.1
github.com/go-git/go-git/v5 v5.16.5
github.com/go-openapi/runtime v0.26.2
github.com/go-openapi/runtime v0.28.0
github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
@ -39,14 +39,14 @@ require (
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db // indirect
github.com/pkg/sftp v1.13.2 // indirect
github.com/posener/complete v1.2.3
github.com/stretchr/testify v1.10.0
github.com/stretchr/testify v1.11.1
github.com/ulikunitz/xz v0.5.15
github.com/zclconf/go-cty v1.13.3
github.com/zclconf/go-cty-yaml v1.0.1
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/mod v0.30.0
golang.org/x/net v0.47.0
golang.org/x/oauth2 v0.27.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
@ -56,7 +56,7 @@ require (
require (
github.com/CycloneDX/cyclonedx-go v0.9.1
github.com/go-openapi/strfmt v0.21.10
github.com/go-openapi/strfmt v0.23.0
github.com/oklog/ulid v1.3.1
github.com/pierrec/lz4/v4 v4.1.18
github.com/shirou/gopsutil/v3 v3.23.4
@ -104,18 +104,29 @@ require (
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/analysis v0.21.5 // indirect
github.com/go-openapi/errors v0.21.0 // indirect
github.com/go-openapi/jsonpointer v0.20.1 // indirect
github.com/go-openapi/jsonreference v0.20.3 // indirect
github.com/go-openapi/loads v0.21.3 // indirect
github.com/go-openapi/spec v0.20.12 // indirect
github.com/go-openapi/swag v0.22.5 // indirect
github.com/go-openapi/validate v0.22.4 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.24.1 // indirect
github.com/go-openapi/swag/cmdutils v0.24.0 // indirect
github.com/go-openapi/swag/conv v0.24.0 // indirect
github.com/go-openapi/swag/fileutils v0.24.0 // indirect
github.com/go-openapi/swag/jsonname v0.24.0 // indirect
github.com/go-openapi/swag/jsonutils v0.24.0 // indirect
github.com/go-openapi/swag/loading v0.24.0 // indirect
github.com/go-openapi/swag/mangling v0.24.0 // indirect
github.com/go-openapi/swag/netutils v0.24.0 // indirect
github.com/go-openapi/swag/stringutils v0.24.0 // indirect
github.com/go-openapi/swag/typeutils v0.24.0 // indirect
github.com/go-openapi/swag/yamlutils v0.24.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/consul/api v1.25.1 // indirect
@ -150,7 +161,7 @@ require (
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@ -182,11 +193,11 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.17.0 // indirect
go.opentelemetry.io/otel/metric v1.17.0 // indirect
go.opentelemetry.io/otel/trace v1.17.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
@ -202,4 +213,7 @@ require (
go 1.24.12
replace github.com/zclconf/go-cty => github.com/nywilken/go-cty v1.13.3 // added by packer-sdc fix as noted in github.com/hashicorp/packer-plugin-sdk/issues/187
replace github.com/zclconf/go-cty => github.com/nywilken/go-cty v1.13.3 // added by packer-sdc fix as noted in github.com/hashicorp/issues/187
// The internal Go SDK has the enforced block types not yet available in the public SDK.
replace github.com/hashicorp/hcp-sdk-go => github.com/hashicorp/hcp-sdk-go-internal v0.0.0-20260304114239-45aa9349dd39

110
go.sum
View file

@ -136,8 +136,6 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -154,26 +152,48 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/analysis v0.21.5 h1:3tHfEBh6Ia8eKc4M7khOGjPOAlWKJ10d877Cr9teujI=
github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M=
github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY=
github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho=
github.com/go-openapi/jsonpointer v0.20.1 h1:MkK4VEIEZMj4wT9PmjaUmGflVBr9nvud4Q4UVFbDoBE=
github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/jsonreference v0.20.3 h1:EjGcjTW8pD1mRis6+w/gmoBdqv5+RbE9B85D1NgDOVQ=
github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM=
github.com/go-openapi/loads v0.21.3 h1:8sSH2FIm/SnbDUGv572md4YqVMFne/a9Eubvcd3anew=
github.com/go-openapi/loads v0.21.3/go.mod h1:Y3aMR24iHbKHppOj91nQ/SHc0cuPbAr4ndY4a02xydc=
github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0=
github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw=
github.com/go-openapi/spec v0.20.12 h1:cgSLbrsmziAP2iais+Vz7kSazwZ8rsUZd6TUzdDgkVI=
github.com/go-openapi/spec v0.20.12/go.mod h1:iSCgnBcwbMW9SfzJb8iYynXvcY6C/QFrI7otzF7xGM4=
github.com/go-openapi/strfmt v0.21.10 h1:JIsly3KXZB/Qf4UzvzJpg4OELH/0ASDQsyk//TTBDDk=
github.com/go-openapi/strfmt v0.21.10/go.mod h1:vNDMwbilnl7xKiO/Ve/8H8Bb2JIInBnH+lqiw6QWgis=
github.com/go-openapi/swag v0.22.5 h1:fVS63IE3M0lsuWRzuom3RLwUMVI2peDH01s6M70ugys=
github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8=
github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg=
github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8=
github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A=
github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I=
github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8=
github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik=
github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c=
github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak=
github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90=
github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k=
github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q=
github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts=
github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0=
github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc=
github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk=
github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk=
github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc=
github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w=
github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM=
github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM=
github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w=
github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw=
github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI=
github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c=
github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@ -201,7 +221,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
@ -229,8 +248,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
@ -304,8 +323,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hashicorp/hcp-sdk-go v0.136.0 h1:NNtb/dYoj7YrVQVvWZ2T7PY2Pwn8vQ5YKIAgaqaKk6A=
github.com/hashicorp/hcp-sdk-go v0.136.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/hcp-sdk-go-internal v0.0.0-20260304114239-45aa9349dd39 h1:HtR5UFigB5Kj5KO0OiMbnsjimqyGlvZXOCsSFQQPgvc=
github.com/hashicorp/hcp-sdk-go-internal v0.0.0-20260304114239-45aa9349dd39/go.mod h1:v2vbpNIrmgUTelW4Z+ur+aQuSPxeaVK3xytFdpEXvSg=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
@ -374,8 +393,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321 h1:AKIJL2PfBX2uie0Mn5pxtG1+zut3hAVMZbRfoXecFzI=
@ -438,7 +457,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
@ -536,8 +554,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0=
@ -560,16 +578,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@ -577,18 +591,18 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY3
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM=
go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0=
go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc=
go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o=
go.opentelemetry.io/otel/sdk v1.17.0 h1:FLN2X66Ke/k5Sg3V623Q7h7nt3cHXaW1FOvKKrW0IpE=
go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ=
go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ=
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -639,8 +653,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View file

@ -0,0 +1,111 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1
package hcl2template
import (
"fmt"
"strconv"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/packer/packer"
"github.com/zclconf/go-cty/cty"
)
var enforcedProvisionerSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: buildProvisionerLabel, LabelNames: []string{"type"}},
},
}
// ParseProvisionerBlocks parses a partial HCL string that contains only
// top-level provisioner blocks and returns the parsed ProvisionerBlock list.
func ParseProvisionerBlocks(blockContent string) ([]*ProvisionerBlock, hcl.Diagnostics) {
parser := &Parser{Parser: hclparse.NewParser()}
file, diags := parser.ParseHCL([]byte(blockContent), "enforced_provisioner.pkr.hcl")
if diags.HasErrors() {
return nil, diags
}
content, moreDiags := file.Body.Content(enforcedProvisionerSchema)
diags = append(diags, moreDiags...)
if diags.HasErrors() {
return nil, diags
}
ectx := &hcl.EvalContext{Variables: map[string]cty.Value{}}
provisioners := make([]*ProvisionerBlock, 0, len(content.Blocks))
for _, block := range content.Blocks {
prov, moreDiags := parser.decodeProvisioner(block, ectx)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
}
provisioners = append(provisioners, prov)
}
return provisioners, diags
}
// GetCoreBuildProvisionerFromBlock converts a ProvisionerBlock to a CoreBuildProvisioner.
// This is used for enforced provisioners that need to be injected into builds.
func (cfg *PackerConfig) GetCoreBuildProvisionerFromBlock(pb *ProvisionerBlock) (packer.CoreBuildProvisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
// Get the provisioner plugin
provisioner, err := cfg.parser.PluginConfig.Provisioners.Start(pb.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to start enforced provisioner %q", pb.PType),
Detail: fmt.Sprintf("The provisioner plugin could not be loaded: %s", err.Error()),
})
return packer.CoreBuildProvisioner{}, diags
}
// Create basic builder variables
builderVars := map[string]interface{}{
"packer_core_version": cfg.CorePackerVersionString,
"packer_debug": strconv.FormatBool(cfg.debug),
"packer_force": strconv.FormatBool(cfg.force),
"packer_on_error": cfg.onError,
"packer_sensitive_variables": []string{},
}
// Create evaluation context
ectx := cfg.EvalContext(BuildContext, nil)
// Create the HCL2Provisioner wrapper
hclProvisioner := &HCL2Provisioner{
Provisioner: provisioner,
provisionerBlock: pb,
evalContext: ectx,
builderVariables: builderVars,
}
// Prepare the provisioner
err = hclProvisioner.HCL2Prepare(nil)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to prepare enforced provisioner %q", pb.PType),
Detail: err.Error(),
})
return packer.CoreBuildProvisioner{}, diags
}
// Wrap provisioner with any special behavior (pause, timeout, retry)
wrappedProvisioner := packer.WrapProvisionerWithOptions(hclProvisioner, packer.ProvisionerWrapOptions{
PauseBefore: pb.PauseBefore,
Timeout: pb.Timeout,
MaxRetries: pb.MaxRetries,
})
return packer.CoreBuildProvisioner{
PType: pb.PType,
PName: pb.PName,
Provisioner: wrappedProvisioner,
}, diags
}

View file

@ -0,0 +1,212 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1
package hcl2template
import (
"testing"
)
func TestParseProvisionerBlocks(t *testing.T) {
tests := []struct {
name string
blockContent string
wantCount int
wantTypes []string
wantErr bool
}{
{
name: "single shell provisioner",
blockContent: `
provisioner "shell" {
inline = ["echo 'Hello from enforced provisioner'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "multiple provisioners",
blockContent: `
provisioner "shell" {
inline = ["echo 'First enforced provisioner'"]
}
provisioner "shell" {
name = "security-scan"
inline = ["echo 'Security scan running...'"]
}
`,
wantCount: 2,
wantTypes: []string{"shell", "shell"},
wantErr: false,
},
{
name: "provisioner with pause_before",
blockContent: `
provisioner "shell" {
pause_before = "10s"
inline = ["echo 'Waiting before execution'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with max_retries",
blockContent: `
provisioner "shell" {
max_retries = 3
inline = ["echo 'Retry test'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with only filter",
blockContent: `
provisioner "shell" {
only = ["amazon-ebs.ubuntu"]
inline = ["echo 'Only for amazon-ebs.ubuntu'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "provisioner with except filter",
blockContent: `
provisioner "shell" {
except = ["null.test"]
inline = ["echo 'Except for null.test'"]
}
`,
wantCount: 1,
wantTypes: []string{"shell"},
wantErr: false,
},
{
name: "empty block content",
blockContent: "",
wantCount: 0,
wantTypes: nil,
wantErr: false,
},
{
name: "invalid HCL syntax",
blockContent: "this is not valid { hcl }}}",
wantCount: 0,
wantTypes: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
blocks, diags := ParseProvisionerBlocks(tt.blockContent)
if tt.wantErr {
if !diags.HasErrors() {
t.Errorf("ParseProvisionerBlocks() expected error but got none")
}
return
}
if diags.HasErrors() {
t.Errorf("ParseProvisionerBlocks() unexpected error: %v", diags)
return
}
if len(blocks) != tt.wantCount {
t.Errorf("ParseProvisionerBlocks() got %d blocks, want %d", len(blocks), tt.wantCount)
return
}
for i, wantType := range tt.wantTypes {
if blocks[i].PType != wantType {
t.Errorf("ParseProvisionerBlocks() block[%d].PType = %q, want %q", i, blocks[i].PType, wantType)
}
}
})
}
}
func TestParseProvisionerBlocksWithPauseBefore(t *testing.T) {
blockContent := `
provisioner "shell" {
pause_before = "30s"
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
// pause_before should be parsed as 30 seconds
if blocks[0].PauseBefore.Seconds() != 30 {
t.Errorf("Expected PauseBefore=30s, got %v", blocks[0].PauseBefore)
}
}
func TestParseProvisionerBlocksWithMaxRetries(t *testing.T) {
blockContent := `
provisioner "shell" {
max_retries = 5
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
if blocks[0].MaxRetries != 5 {
t.Errorf("Expected MaxRetries=5, got %d", blocks[0].MaxRetries)
}
}
func TestParseProvisionerBlocksWithOnlyExcept(t *testing.T) {
blockContent := `
provisioner "shell" {
only = ["amazon-ebs.ubuntu", "azure-arm.windows"]
inline = ["echo 'test'"]
}
`
blocks, diags := ParseProvisionerBlocks(blockContent)
if diags.HasErrors() {
t.Fatalf("ParseProvisionerBlocks() unexpected error: %v", diags)
}
if len(blocks) != 1 {
t.Fatalf("Expected 1 block, got %d", len(blocks))
}
// Check only filter
if len(blocks[0].OnlyExcept.Only) != 2 {
t.Errorf("Expected 2 only values, got %d", len(blocks[0].OnlyExcept.Only))
}
// Skip should return true for sources not in the only list
if !blocks[0].OnlyExcept.Skip("null.test") {
t.Error("Skip() should return true for source not in only list")
}
// Skip should return false for sources in the only list
if blocks[0].OnlyExcept.Skip("amazon-ebs.ubuntu") {
t.Error("Skip() should return false for source in only list")
}
}

View file

@ -6,9 +6,11 @@ package hcl2template
import (
"fmt"
"regexp"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclwrite"
)
type HCPPackerRegistryBlock struct {
@ -97,3 +99,43 @@ func (p *Parser) decodeHCPRegistry(block *hcl.Block, cfg *PackerConfig) (*HCPPac
return par, diags
}
// ExtractBuildProvisionerHCL extracts all provisioner blocks from the build
// blocks in the configuration and returns them as raw HCL content.
// This is used to publish provisioner configurations as enforced blocks
// to HCP Packer, so that other builds against the same bucket will
// automatically have these provisioners injected.
func (cfg *PackerConfig) ExtractBuildProvisionerHCL() (string, error) {
sourceFiles := cfg.parser.Files()
var buf strings.Builder
for filename, file := range sourceFiles {
// hclwrite only supports HCL native syntax, skip JSON and variable files
if !strings.HasSuffix(filename, hcl2FileExt) {
continue
}
wf, diags := hclwrite.ParseConfig(file.Bytes, filename, hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
continue
}
for _, block := range wf.Body().Blocks() {
if block.Type() != buildLabel {
continue
}
for _, inner := range block.Body().Blocks() {
if inner.Type() != buildProvisionerLabel {
continue
}
buf.Write(inner.BuildTokens(nil).Bytes())
buf.WriteString("\n")
}
}
}
return strings.TrimSpace(buf.String()), nil
}

View file

@ -25,6 +25,11 @@ type MockPackerClientService struct {
UpdateChannelCalled bool
TrackCalledServiceMethods bool
// Enforced block tracking
CreateEnforcedBlockCalled, GetEnforcedBlockCalled, ListEnforcedBlocksCalled bool
CreateEnforcedBlockVersionCalled, GetEnforcedBlockVersionsCalled bool
GetEnforcedBlocksByBucketCalled bool
// Mock Creates
CreateBucketResp *hcpPackerModels.HashicorpCloudPacker20230101CreateBucketResponse
CreateVersionResp *hcpPackerModels.HashicorpCloudPacker20230101CreateVersionResponse
@ -33,6 +38,20 @@ type MockPackerClientService struct {
// Mock Gets
GetVersionResp *hcpPackerModels.HashicorpCloudPacker20230101GetVersionResponse
// Mock enforced blocks
CreateEnforcedBlockResp *hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockResponse
CreateEnforcedBlockErr error
GetEnforcedBlockResp *hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockResponse
GetEnforcedBlockErr error
ListEnforcedBlocksResp *hcpPackerModels.HashicorpCloudPacker20230101ListEnforcedBlocksResponse
ListEnforcedBlocksErr error
CreateEnforcedBlockVersionResp *hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockVersionResponse
CreateEnforcedBlockVersionErr error
GetEnforcedBlockVersionsResp *hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockVersionsResponse
GetEnforcedBlockVersionsErr error
GetEnforcedBlocksByBucketResp *hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlocksByBucketResponse
GetEnforcedBlocksByBucketErr error
ExistingBuilds []string
ExistingBuildLabels map[string]string
@ -321,3 +340,163 @@ func (svc *MockPackerClientService) PackerServiceUpdateChannel(
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceCreateEnforcedBlock(
params *hcpPackerService.PackerServiceCreateEnforcedBlockParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceCreateEnforcedBlockOK, error) {
if svc.TrackCalledServiceMethods {
svc.CreateEnforcedBlockCalled = true
}
if svc.CreateEnforcedBlockErr != nil {
return nil, svc.CreateEnforcedBlockErr
}
ok := &hcpPackerService.PackerServiceCreateEnforcedBlockOK{}
if svc.CreateEnforcedBlockResp != nil {
ok.Payload = svc.CreateEnforcedBlockResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockResponse{
EnforcedBlock: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlock{
ID: "enforced-block-id",
Name: params.Body.Name,
},
}
}
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceGetEnforcedBlock(
params *hcpPackerService.PackerServiceGetEnforcedBlockParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceGetEnforcedBlockOK, error) {
if svc.TrackCalledServiceMethods {
svc.GetEnforcedBlockCalled = true
}
if svc.GetEnforcedBlockErr != nil {
return nil, svc.GetEnforcedBlockErr
}
ok := &hcpPackerService.PackerServiceGetEnforcedBlockOK{}
if svc.GetEnforcedBlockResp != nil {
ok.Payload = svc.GetEnforcedBlockResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockResponse{
EnforcedBlock: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlock{
ID: params.EnforcedBlockID,
},
}
}
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceListEnforcedBlocks(
params *hcpPackerService.PackerServiceListEnforcedBlocksParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceListEnforcedBlocksOK, error) {
if svc.TrackCalledServiceMethods {
svc.ListEnforcedBlocksCalled = true
}
if svc.ListEnforcedBlocksErr != nil {
return nil, svc.ListEnforcedBlocksErr
}
ok := &hcpPackerService.PackerServiceListEnforcedBlocksOK{}
if svc.ListEnforcedBlocksResp != nil {
ok.Payload = svc.ListEnforcedBlocksResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101ListEnforcedBlocksResponse{
EnforcedBlocks: []*hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlock{},
}
}
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceCreateEnforcedBlockVersion(
params *hcpPackerService.PackerServiceCreateEnforcedBlockVersionParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceCreateEnforcedBlockVersionOK, error) {
if svc.TrackCalledServiceMethods {
svc.CreateEnforcedBlockVersionCalled = true
}
if svc.CreateEnforcedBlockVersionErr != nil {
return nil, svc.CreateEnforcedBlockVersionErr
}
ok := &hcpPackerService.PackerServiceCreateEnforcedBlockVersionOK{}
if svc.CreateEnforcedBlockVersionResp != nil {
ok.Payload = svc.CreateEnforcedBlockVersionResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockVersionResponse{
EnforcedBlockVersion: &hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockVersion{
ID: "enforced-block-version-id",
EnforcedBlockID: params.EnforcedBlockID,
BlockContent: params.Body.BlockContent,
Version: params.Body.Version,
},
}
}
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceGetEnforcedBlockVersions(
params *hcpPackerService.PackerServiceGetEnforcedBlockVersionsParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceGetEnforcedBlockVersionsOK, error) {
if svc.TrackCalledServiceMethods {
svc.GetEnforcedBlockVersionsCalled = true
}
if svc.GetEnforcedBlockVersionsErr != nil {
return nil, svc.GetEnforcedBlockVersionsErr
}
ok := &hcpPackerService.PackerServiceGetEnforcedBlockVersionsOK{}
if svc.GetEnforcedBlockVersionsResp != nil {
ok.Payload = svc.GetEnforcedBlockVersionsResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockVersionsResponse{
EnforcedBlockDetail: []*hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockDetail{},
}
}
return ok, nil
}
func (svc *MockPackerClientService) PackerServiceGetEnforcedBlocksByBucket(
params *hcpPackerService.PackerServiceGetEnforcedBlocksByBucketParams, _ runtime.ClientAuthInfoWriter,
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceGetEnforcedBlocksByBucketOK, error) {
if svc.TrackCalledServiceMethods {
svc.GetEnforcedBlocksByBucketCalled = true
}
if svc.GetEnforcedBlocksByBucketErr != nil {
return nil, svc.GetEnforcedBlocksByBucketErr
}
ok := &hcpPackerService.PackerServiceGetEnforcedBlocksByBucketOK{}
if svc.GetEnforcedBlocksByBucketResp != nil {
ok.Payload = svc.GetEnforcedBlocksByBucketResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlocksByBucketResponse{
EnforcedBlockDetail: []*hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlockDetail{},
}
}
return ok, nil
}

View file

@ -0,0 +1,151 @@
// Copyright IBM Corp. 2013, 2025
// SPDX-License-Identifier: BUSL-1.1
package api
import (
"context"
hcpPackerService "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service"
hcpPackerModels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models"
)
// CreateEnforcedBlock creates a new enforced block in the HCP Packer registry.
// The block content contains raw HCL provisioner configuration that will be
// enforced on all builds for buckets linked to this enforced block.
func (c *Client) CreateEnforcedBlock(
ctx context.Context,
name string,
blockContent string,
version string,
templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType,
description string,
labels map[string]string,
) (*hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockResponse, error) {
params := hcpPackerService.NewPackerServiceCreateEnforcedBlockParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.Body = &hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockBody{
Name: name,
BlockContent: blockContent,
Version: version,
TemplateType: &templateType,
AdditionalDescription: description,
Labels: labels,
}
resp, err := c.Packer.PackerServiceCreateEnforcedBlock(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}
// GetEnforcedBlock retrieves a single enforced block by its ID.
func (c *Client) GetEnforcedBlock(
ctx context.Context,
enforcedBlockID string,
) (*hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockResponse, error) {
params := hcpPackerService.NewPackerServiceGetEnforcedBlockParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.EnforcedBlockID = enforcedBlockID
resp, err := c.Packer.PackerServiceGetEnforcedBlock(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}
// ListEnforcedBlocks lists all enforced blocks in the current project.
func (c *Client) ListEnforcedBlocks(
ctx context.Context,
) (*hcpPackerModels.HashicorpCloudPacker20230101ListEnforcedBlocksResponse, error) {
params := hcpPackerService.NewPackerServiceListEnforcedBlocksParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
resp, err := c.Packer.PackerServiceListEnforcedBlocks(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}
// CreateEnforcedBlockVersion creates a new version of an existing enforced block.
// This allows updating the block content while keeping a version history.
func (c *Client) CreateEnforcedBlockVersion(
ctx context.Context,
enforcedBlockID string,
blockContent string,
version string,
templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType,
description string,
) (*hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockVersionResponse, error) {
params := hcpPackerService.NewPackerServiceCreateEnforcedBlockVersionParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.EnforcedBlockID = enforcedBlockID
params.Body = &hcpPackerModels.HashicorpCloudPacker20230101CreateEnforcedBlockVersionBody{
BlockContent: blockContent,
Version: version,
TemplateType: &templateType,
AdditionalDescription: description,
}
resp, err := c.Packer.PackerServiceCreateEnforcedBlockVersion(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}
// GetEnforcedBlockVersions retrieves all versions of an enforced block.
func (c *Client) GetEnforcedBlockVersions(
ctx context.Context,
enforcedBlockID string,
) (*hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlockVersionsResponse, error) {
params := hcpPackerService.NewPackerServiceGetEnforcedBlockVersionsParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.EnforcedBlockID = enforcedBlockID
resp, err := c.Packer.PackerServiceGetEnforcedBlockVersions(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}
// GetEnforcedBlocksForBucket fetches all enforced blocks linked to a bucket.
// This is the key method used during packer build to auto-inject provisioners.
// The response includes EnforcedBlockDetail entries each with an active version
// containing the raw HCL block_content to be parsed and injected.
func (c *Client) GetEnforcedBlocksForBucket(
ctx context.Context,
bucketName string,
) (*hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlocksByBucketResponse, error) {
params := hcpPackerService.NewPackerServiceGetEnforcedBlocksByBucketParamsWithContext(ctx)
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.BucketName = bucketName
resp, err := c.Packer.PackerServiceGetEnforcedBlocksByBucket(params, nil)
if err != nil {
return nil, err
}
return resp.Payload, nil
}

View file

@ -43,6 +43,21 @@ func (h *HCLRegistry) PopulateVersion(ctx context.Context) error {
return err
}
// Extract provisioner blocks from the build and publish them as enforced
// blocks to HCP Packer, so other builds against the same bucket will
// automatically have these provisioners injected.
blockContent, err := h.configuration.ExtractBuildProvisionerHCL()
if err != nil {
log.Printf("[WARN] failed to extract provisioner blocks for enforced publishing: %v", err)
} else if blockContent != "" {
blockName := h.bucket.Name + "-provisioners"
if pubErr := h.bucket.PublishEnforcedBlocks(
ctx, blockName, blockContent, hcpPackerModels.HashicorpCloudPacker20230101TemplateTypeHCL2,
); pubErr != nil {
log.Printf("[WARN] failed to publish enforced blocks for bucket %q: %v", h.bucket.Name, pubErr)
}
}
err = h.bucket.populateVersion(ctx)
if err != nil {
return err
@ -91,6 +106,67 @@ func (h *HCLRegistry) VersionStatusSummary() {
h.bucket.Version.statusSummary(h.ui)
}
// FetchEnforcedBlocks fetches enforced provisioner blocks from HCP Packer
func (h *HCLRegistry) FetchEnforcedBlocks(ctx context.Context) error {
return h.bucket.FetchEnforcedBlocks(ctx)
}
// InjectEnforcedProvisioners injects enforced provisioners into the builds
func (h *HCLRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl.Diagnostics {
enforcedBlocks := h.bucket.EnforcedBlocks
if len(enforcedBlocks) == 0 {
return nil
}
var allDiags hcl.Diagnostics
// Parse all enforced blocks into provisioner blocks
for _, eb := range enforcedBlocks {
if eb.BlockContent == "" {
continue
}
provBlocks, diags := hcl2template.ParseProvisionerBlocks(eb.BlockContent)
if diags.HasErrors() {
allDiags = append(allDiags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: fmt.Sprintf("Failed to parse enforced block %q", eb.Name),
Detail: diags.Error(),
})
continue
}
if len(provBlocks) > 0 {
h.ui.Say(fmt.Sprintf("Loaded %d enforced provisioner(s) from HCP block %q", len(provBlocks), eb.Name))
}
// Inject into each build
for _, build := range builds {
for _, pb := range provBlocks {
// Check if this provisioner should be skipped for this build
if pb.OnlyExcept.Skip(build.Type) {
log.Printf("[DEBUG] skipping enforced provisioner %q for build %q due to only/except rules",
pb.PType, build.Name())
continue
}
coreProv, moreDiags := h.configuration.GetCoreBuildProvisionerFromBlock(pb)
if moreDiags.HasErrors() {
allDiags = append(allDiags, moreDiags...)
continue
}
log.Printf("[INFO] injecting enforced provisioner %q from block %q into build %q",
pb.PType, eb.Name, build.Name())
build.Provisioners = append(build.Provisioners, coreProv)
}
}
}
return allDiags
}
func NewHCLRegistry(config *hcl2template.PackerConfig, ui sdkpacker.Ui) (*HCLRegistry, hcl.Diagnostics) {
var diags hcl.Diagnostics
if len(config.Builds) > 1 {

View file

@ -113,3 +113,17 @@ func (h *JSONRegistry) VersionStatusSummary() {
func (h *JSONRegistry) Metadata() Metadata {
return h.metadata
}
// FetchEnforcedBlocks fetches enforced provisioner blocks from HCP Packer
func (h *JSONRegistry) FetchEnforcedBlocks(ctx context.Context) error {
return h.bucket.FetchEnforcedBlocks(ctx)
}
// InjectEnforcedProvisioners injects enforced provisioners into the builds
// Note: JSON templates don't support enforced provisioners as they are a legacy format
func (h *JSONRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl.Diagnostics {
if len(h.bucket.EnforcedBlocks) > 0 {
h.ui.Say("Warning: Enforced provisioners are not supported for legacy JSON templates")
}
return nil
}

View file

@ -6,6 +6,7 @@ package registry
import (
"context"
"github.com/hashicorp/hcl/v2"
sdkpacker "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer"
)
@ -35,3 +36,11 @@ func (r nullRegistry) VersionStatusSummary() {}
func (r nullRegistry) Metadata() Metadata {
return NilMetadata{}
}
func (r nullRegistry) FetchEnforcedBlocks(ctx context.Context) error {
return nil
}
func (r nullRegistry) InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl.Diagnostics {
return nil
}

View file

@ -20,6 +20,10 @@ type Registry interface {
CompleteBuild(ctx context.Context, build *packer.CoreBuild, artifacts []sdkpacker.Artifact, buildErr error) ([]sdkpacker.Artifact, error)
VersionStatusSummary()
Metadata() Metadata
// FetchEnforcedBlocks fetches enforced provisioner blocks from HCP Packer
FetchEnforcedBlocks(ctx context.Context) error
// InjectEnforcedProvisioners injects enforced provisioners into the builds
InjectEnforcedProvisioners(builds []*packer.CoreBuild) hcl.Diagnostics
}
// New instantiates the appropriate registry for the Packer configuration template type.

View file

@ -29,6 +29,15 @@ import (
// build is still alive.
const HeartbeatPeriod = 2 * time.Minute
// EnforcedBlock represents an enforced provisioner block from HCP Packer
type EnforcedBlock struct {
ID string
Name string
BlockContent string // Raw HCL content containing provisioner blocks
VersionID string
Version string
}
// Bucket represents a single bucket on the HCP Packer registry.
type Bucket struct {
Name string
@ -40,6 +49,7 @@ type Bucket struct {
SourceExternalIdentifierToParentVersions map[string]ParentVersion
RunningBuilds map[string]chan struct{}
Version *Version
EnforcedBlocks []*EnforcedBlock
client *hcpPackerAPI.Client
}
@ -142,6 +152,115 @@ func (bucket *Bucket) Initialize(
return bucket.initializeVersion(ctx, templateType)
}
// FetchEnforcedBlocks retrieves all enforced blocks linked to this bucket from HCP Packer.
// These blocks contain provisioner configurations that should be automatically injected
// into builds for this bucket.
func (bucket *Bucket) FetchEnforcedBlocks(ctx context.Context) error {
if bucket.client == nil {
return errors.New("bucket client not initialized, call Initialize first")
}
resp, err := bucket.client.GetEnforcedBlocksForBucket(ctx, bucket.Name)
if err != nil {
// If the API doesn't support enforced blocks yet or returns not found, continue silently
log.Printf("[DEBUG] fetching enforced blocks for bucket %q: %v", bucket.Name, err)
return nil
}
if resp == nil {
return nil
}
bucket.EnforcedBlocks = make([]*EnforcedBlock, 0, len(resp.EnforcedBlockDetail))
for _, detail := range resp.EnforcedBlockDetail {
if detail == nil || detail.Version == nil {
continue
}
block := &EnforcedBlock{
ID: detail.ID,
Name: detail.Name,
BlockContent: detail.Version.BlockContent,
VersionID: detail.Version.ID,
Version: detail.Version.Version,
}
bucket.EnforcedBlocks = append(bucket.EnforcedBlocks, block)
}
log.Printf("[INFO] fetched %d enforced block(s) for bucket %q", len(bucket.EnforcedBlocks), bucket.Name)
return nil
}
// PublishEnforcedBlocks publishes the given provisioner block content as an enforced block
// on HCP Packer, linked to this bucket. If an enforced block with the given name already
// exists and the content has changed, a new version is created. If it doesn't exist,
// a new enforced block is created.
func (bucket *Bucket) PublishEnforcedBlocks(
ctx context.Context,
blockName string,
blockContent string,
templateType hcpPackerModels.HashicorpCloudPacker20230101TemplateType,
) error {
if bucket.client == nil {
return errors.New("bucket client not initialized, call Initialize first")
}
if blockContent == "" {
log.Printf("[DEBUG] no provisioner content to publish as enforced blocks for bucket %q", bucket.Name)
return nil
}
// List existing enforced blocks to check for duplicates
existingResp, err := bucket.client.ListEnforcedBlocks(ctx)
if err != nil {
log.Printf("[WARN] failed to list existing enforced blocks: %v", err)
// Continue anyway — create will fail if there's a conflict
}
// Build a map of existing enforced blocks by name for quick lookup
existingByName := make(map[string]*hcpPackerModels.HashicorpCloudPacker20230101EnforcedBlock)
if existingResp != nil {
for _, eb := range existingResp.EnforcedBlocks {
if eb != nil && eb.Name != "" {
existingByName[eb.Name] = eb
}
}
}
version := "1"
existing, found := existingByName[blockName]
if found {
// Enforced block already exists — check if content changed
if existing.LatestVersion != nil && existing.LatestVersion.BlockContent == blockContent {
log.Printf("[INFO] enforced block %q already up-to-date, skipping", blockName)
return nil
}
// Content changed — create a new version
log.Printf("[INFO] updating enforced block %q with new version", blockName)
_, err := bucket.client.CreateEnforcedBlockVersion(
ctx, existing.ID, blockContent, version, templateType, "",
)
if err != nil {
return fmt.Errorf("failed to create new version for enforced block %q: %w", blockName, err)
}
log.Printf("[INFO] created new version for enforced block %q", blockName)
} else {
// Create new enforced block
log.Printf("[INFO] creating enforced block %q for bucket %q", blockName, bucket.Name)
_, err := bucket.client.CreateEnforcedBlock(
ctx, blockName, blockContent, version, templateType, "", nil,
)
if err != nil {
return fmt.Errorf("failed to create enforced block %q: %w", blockName, err)
}
log.Printf("[INFO] created enforced block %q", blockName)
}
return nil
}
func (bucket *Bucket) RegisterBuildForComponent(sourceName string) {
if bucket == nil {
return

View file

@ -140,6 +140,43 @@ func (h *ProvisionHook) Run(ctx context.Context, name string, ui packersdk.Ui, c
return nil
}
// ProvisionerWrapOptions contains options for wrapping a provisioner with
// additional behavior like pausing, timeouts, and retries.
type ProvisionerWrapOptions struct {
PauseBefore time.Duration
Timeout time.Duration
MaxRetries int
}
// WrapProvisionerWithOptions wraps a provisioner with additional behavior
// based on the provided options.
func WrapProvisionerWithOptions(provisioner packersdk.Provisioner, opts ProvisionerWrapOptions) packersdk.Provisioner {
wrapped := provisioner
if opts.PauseBefore != 0 {
wrapped = &PausedProvisioner{
PauseBefore: opts.PauseBefore,
Provisioner: wrapped,
}
}
if opts.Timeout != 0 {
wrapped = &TimeoutProvisioner{
Timeout: opts.Timeout,
Provisioner: wrapped,
}
}
if opts.MaxRetries != 0 {
wrapped = &RetriedProvisioner{
MaxRetries: opts.MaxRetries,
Provisioner: wrapped,
}
}
return wrapped
}
// PausedProvisioner is a Provisioner implementation that pauses before
// the provisioner is actually run.
type PausedProvisioner struct {

56
test.pkr.hcl Normal file
View file

@ -0,0 +1,56 @@
packer {
required_plugins {
docker = {
version = ">= 1.1.0"
source = "github.com/hashicorp/docker"
}
}
}
# HCP Packer registry provisioner blocks below will be
# automatically published as enforced blocks to this bucket.
hcp_packer_registry {
bucket_name = "ubuntu-test"
description = "Test Ubuntu image with enforced provisioners"
bucket_labels = {
"team" = "platform"
"os" = "ubuntu"
"purpose" = "testing"
}
}
source "docker" "ubuntu" {
image = "ubuntu:22.04"
commit = true
}
build {
name = "ubuntu-test"
sources = ["source.docker.ubuntu"]
provisioner "shell" {
inline = [
"apt-get update -y",
"apt-get install -y curl wget jq"
]
}
provisioner "shell" {
inline = [
"echo 'Creating app user...'",
"useradd -m -s /bin/bash appuser",
"mkdir -p /opt/app",
"chown appuser:appuser /opt/app"
]
}
provisioner "shell" {
inline = [
"echo 'Applying security hardening...'",
"echo 'net.ipv4.ip_forward = 0' >> /etc/sysctl.conf",
"echo 'Build complete!'"
]
}
}