Skip to content
Draft
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
53 changes: 46 additions & 7 deletions command/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (
"sync"
"time"

"github.com/hashicorp/go-uuid"
"github.com/hashicorp/hcl/v2"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/internal/hcp/registry"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/version"
"golang.org/x/sync/semaphore"

"github.com/hako/durafmt"
Expand Down Expand Up @@ -150,12 +152,49 @@ 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 {
// Resolve and inject enforced provisioners from HCP Packer (RFC vNext).
if cla.SkipEnforcement {
// Skip governance (RFC 10): a closed-enum reason code is required.
if cla.SkipReasonCode == "" {
return writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
Summary: "HCP: fetching enforced provisioners failed",
Severity: hcl.DiagError,
Summary: "HCP: --skip-enforcement requires --skip-reason-code",
Detail: fmt.Sprintf("--skip-enforcement must be accompanied by --skip-reason-code=<code>, one of: %s.",
strings.Join(registry.ValidSkipReasonCodes, ", ")),
},
})
}
if !registry.IsValidSkipReasonCode(cla.SkipReasonCode) {
return writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "HCP: invalid --skip-reason-code",
Detail: fmt.Sprintf("%q is not a valid skip reason code. Must be one of: %s.",
cla.SkipReasonCode, strings.Join(registry.ValidSkipReasonCodes, ", ")),
},
})
}

hcpRegistry.RecordEnforcementSkip(cla.SkipReasonCode, cla.SkipReasonNote)
c.Ui.Say(fmt.Sprintf("Skipping HCP Packer enforced provisioners (--skip-enforcement; reason_code=%s).", cla.SkipReasonCode))
if cla.SkipReasonNote != "" {
c.Ui.Say(fmt.Sprintf(" reason note: %s", cla.SkipReasonNote))
}
} else {
buildCorrelationID, err := uuid.GenerateUUID()
if err != nil {
buildCorrelationID = ""
}
opts := registry.EnforcementOptions{
CLIVersion: version.Version,
BuildCorrelationID: buildCorrelationID,
}

if err := hcpRegistry.FetchEnforcedBlocks(buildCtx, opts); err != nil {
return writeDiags(c.Ui, nil, hcl.Diagnostics{
&hcl.Diagnostic{
Summary: "HCP: resolving enforced provisioners failed",
Severity: hcl.DiagError,
Detail: err.Error(),
},
Expand All @@ -166,8 +205,6 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
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 {
Expand Down Expand Up @@ -476,7 +513,9 @@ 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.
-skip-enforcement Skip injection of HCP Packer enforced provisioners. Requires admin privileges and -skip-reason-code.
-skip-reason-code=code Reason code required with -skip-enforcement. One of: breakglass_incident, resolver_outage, verified_exception, migration_compatibility.
-skip-reason-note=text Optional free-text note accompanying -skip-reason-code.
`

return strings.TrimSpace(helpText)
Expand Down
6 changes: 5 additions & 1 deletion command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ 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.")
flags.BoolVar(&ba.SkipEnforcement, "skip-enforcement", false, "Skip injection of HCP Packer enforced provisioners. Requires admin privileges and --skip-reason-code.")
flags.StringVar(&ba.SkipReasonCode, "skip-reason-code", "", "Reason code required with --skip-enforcement. One of: breakglass_incident, resolver_outage, verified_exception, migration_compatibility.")
flags.StringVar(&ba.SkipReasonNote, "skip-reason-note", "", "Optional free-text note accompanying --skip-reason-code.")

ba.MetaArgs.AddFlagSets(flags)
}
Expand Down Expand Up @@ -139,6 +141,8 @@ type BuildArgs struct {
OnError string
ReleaseOnly bool
SkipEnforcement bool
SkipReasonCode string
SkipReasonNote string
}

func (ia *InitArgs) AddFlagSets(flags *flag.FlagSet) {
Expand Down
41 changes: 41 additions & 0 deletions internal/hcp/api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"strconv"
"strings"

"google.golang.org/grpc/codes"
)
Expand Down Expand Up @@ -50,3 +51,43 @@ func CheckErrorCode(err error, code codes.Code) bool {
errCode, _ := strconv.Atoi(matches[1])
return errCode == int(code)
}

// Canonical enforced-provisioner wire error_code reasons (RFC 14.3). These are
// carried in the resolver error body via google.rpc.ErrorInfo and rendered into
// the gateway JSON error, so they are detectable in the error string.
const (
EnforcementReasonResolverUnavailable = "enforcement_resolver_unavailable"
EnforcementReasonResolutionIncomplete = "enforcement_resolution_incomplete"
EnforcementReasonRevokedLinkBlocking = "enforcement_revoked_link_blocking"
EnforcementReasonDataIntegrityError = "enforcement_data_integrity_error"
EnforcementReasonClientUpgradeRequired = "enforcement_client_upgrade_required"
)

// EnforcementErrorReason extracts the canonical enforced-provisioner error_code
// reason from an error returned by the resolver, if present. Returns "" when no
// known reason is found.
func EnforcementErrorReason(err error) string {
if err == nil {
return ""
}
msg := err.Error()
for _, reason := range []string{
EnforcementReasonResolverUnavailable,
EnforcementReasonResolutionIncomplete,
EnforcementReasonRevokedLinkBlocking,
EnforcementReasonDataIntegrityError,
EnforcementReasonClientUpgradeRequired,
} {
if strings.Contains(msg, reason) {
return reason
}
}
return ""
}

// IsClientUpgradeRequired reports whether the resolver rejected this CLI as too
// old to enforce a mandatory bucket (RFC 6.4 / 12.4 / 14.3, HTTP 426).
func IsClientUpgradeRequired(err error) bool {
return EnforcementErrorReason(err) == EnforcementReasonClientUpgradeRequired ||
CheckErrorCode(err, codes.Code(26)) // gateway maps 426 → no native gRPC code; reason match is primary
}
33 changes: 32 additions & 1 deletion internal/hcp/api/mock_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
TrackCalledServiceMethods bool

// Enforced block tracking
GetEnforcedBlocksByBucketCalled bool
GetEnforcedBlocksByBucketCalled bool
ResolveEnforcedProvisionersCalled bool
ResolveEnforcedProvisionersLastIfMatch string

// Mock Creates
CreateBucketResp *hcpPackerModels.HashicorpCloudPacker20230101CreateBucketResponse
Expand All @@ -41,6 +43,10 @@
GetEnforcedBlocksByBucketResp *hcpPackerModels.HashicorpCloudPacker20230101GetEnforcedBlocksByBucketResponse
GetEnforcedBlocksByBucketErr error

// Mock resolver
ResolveEnforcedProvisionersResp *hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 47 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 47 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 47 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 47 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse
ResolveEnforcedProvisionersErr error

ExistingBuilds []string
ExistingBuildLabels map[string]string

Expand Down Expand Up @@ -358,3 +364,28 @@

return ok, nil
}

func (svc *MockPackerClientService) PackerServiceResolveEnforcedProvisioners(
params *hcpPackerService.PackerServiceResolveEnforcedProvisionersParams, _ runtime.ClientAuthInfoWriter,

Check failure on line 369 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersParams

Check failure on line 369 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersParams

Check failure on line 369 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersParams

Check failure on line 369 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersParams
opts ...hcpPackerService.ClientOption,
) (*hcpPackerService.PackerServiceResolveEnforcedProvisionersOK, error) {

Check failure on line 371 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 371 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 371 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 371 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

if svc.TrackCalledServiceMethods {
svc.ResolveEnforcedProvisionersCalled = true
}

if svc.ResolveEnforcedProvisionersErr != nil {
return nil, svc.ResolveEnforcedProvisionersErr
}

ok := &hcpPackerService.PackerServiceResolveEnforcedProvisionersOK{}

Check failure on line 381 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 381 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 381 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK

Check failure on line 381 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerService.PackerServiceResolveEnforcedProvisionersOK
if svc.ResolveEnforcedProvisionersResp != nil {
ok.Payload = svc.ResolveEnforcedProvisionersResp
} else {
ok.Payload = &hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse{

Check failure on line 385 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 385 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 385 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 385 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse
EffectiveProvisioners: []*hcpPackerModels.HashicorpCloudPacker20230101EffectiveProvisioner{},

Check failure on line 386 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101EffectiveProvisioner

Check failure on line 386 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101EffectiveProvisioner

Check failure on line 386 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerModels.HashicorpCloudPacker20230101EffectiveProvisioner

Check failure on line 386 in internal/hcp/api/mock_service.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101EffectiveProvisioner
}
}

return ok, nil
}
56 changes: 56 additions & 0 deletions internal/hcp/api/service_enforced_provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import (
"context"

"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
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"
)
Expand All @@ -31,3 +33,57 @@

return resp.Payload, nil
}

// ResolveEnforcedProvisioners calls the vNext resolver endpoint
// (POST .../buckets/{bucket_name}:resolve-enforced-provisioners). The resolver
// is the source of truth for the ordered effective provisioner set, the bucket
// policy mode (mandatory/advisory), and the resolution audit context.
//
// ifNoneMatch, when non-empty, is sent as the If-None-Match cache-validation
// header. The freshness token of record for subsequent calls is the body's
// audit_context.etag (the generated SDK does not model the ETag response header
// or a 304 response).
func (c *Client) ResolveEnforcedProvisioners(
ctx context.Context,
bucketName string,
ifNoneMatch string,
buildCorrelationID string,
cliVersion string,
) (*hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse, error) {

Check failure on line 52 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 52 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 52 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

Check failure on line 52 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersResponse

params := hcpPackerService.NewPackerServiceResolveEnforcedProvisionersParamsWithContext(ctx)

Check failure on line 54 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerService.NewPackerServiceResolveEnforcedProvisionersParamsWithContext

Check failure on line 54 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerService.NewPackerServiceResolveEnforcedProvisionersParamsWithContext

Check failure on line 54 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerService.NewPackerServiceResolveEnforcedProvisionersParamsWithContext

Check failure on line 54 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerService.NewPackerServiceResolveEnforcedProvisionersParamsWithContext
params.LocationOrganizationID = c.OrganizationID
params.LocationProjectID = c.ProjectID
params.BucketName = bucketName
params.Body = &hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersBody{

Check failure on line 58 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersBody

Check failure on line 58 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Linux go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersBody

Check failure on line 58 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersBody

Check failure on line 58 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Windows go tests

undefined: hcpPackerModels.HashicorpCloudPacker20230101ResolveEnforcedProvisionersBody
BuildCorrelationID: buildCorrelationID,
CliVersion: cliVersion,
}

resp, err := c.Packer.PackerServiceResolveEnforcedProvisioners(params, nil, withIfNoneMatch(ifNoneMatch))

Check failure on line 63 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

c.Packer.PackerServiceResolveEnforcedProvisioners undefined (type "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service".ClientService has no field or method PackerServiceResolveEnforcedProvisioners)

Check failure on line 63 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Linux go tests

c.Packer.PackerServiceResolveEnforcedProvisioners undefined (type "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service".ClientService has no field or method PackerServiceResolveEnforcedProvisioners)

Check failure on line 63 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Lint

c.Packer.PackerServiceResolveEnforcedProvisioners undefined (type "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service".ClientService has no field or method PackerServiceResolveEnforcedProvisioners)

Check failure on line 63 in internal/hcp/api/service_enforced_provisioner.go

View workflow job for this annotation

GitHub Actions / Windows go tests

c.Packer.PackerServiceResolveEnforcedProvisioners undefined (type "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/client/packer_service".ClientService has no field or method PackerServiceResolveEnforcedProvisioners)
if err != nil {
return nil, err
}

return resp.Payload, nil
}

// withIfNoneMatch returns a ClientOption that sets the If-None-Match request
// header on the resolver call, wrapping the params writer so the header is added
// after the generated body/path params are written. A no-op when etag is empty.
func withIfNoneMatch(etag string) hcpPackerService.ClientOption {
return func(op *runtime.ClientOperation) {
if etag == "" {
return
}
inner := op.Params
op.Params = runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
if inner != nil {
if err := inner.WriteToRequest(req, reg); err != nil {
return err
}
}
return req.SetHeaderParam("If-None-Match", etag)
})
}
}
Loading
Loading