Files
Packer-Cn/packer/registry_builder.go
T
Lucas Bajolet 0a41694ee5 registry: add heartbeat call for running builds (#11846)
* registry: add heartbeat call for running builds

When a build is running, we send periodic heartbeats to HCP Packer's
API.

This will be used on the service side to detect if a build is stalled on
the core side because of a crash or any other malfunction that caused
Packer not to send an update on the status of a build.

* registry: only update status on status update

Prior to this commit, we'd send updates to both the labels and the
cloud-provider whenever an update to the status of a build would be
sent.

This would cause a bug in which once a build reached the post-processing
state, and its cloud-provider was set, the status could not be updated
anymore, as the cloud-provider would be set and further updates are
rejected by the platform.

To avoid this problem, we only transmit the status when doing a status
update, and no other fields along with it.

* internal: publicise CompleteBuild function

The markBuildComplete private function used to be called from a final
status update to DONE, through the UpdateBuildStatus function.

The problem being that the (now) CompleteBuild function not only updates
the status of the build, but also all its metadata.

For consistency, we remove the indirection, and explicitely call
CompleteBuild when we want to finish a build.

* internal: block status updates on DONE builds
2022-07-05 14:37:39 -04:00

103 lines
3.2 KiB
Go

package packer
import (
"context"
"fmt"
"log"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2021-04-30/models"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
registryimage "github.com/hashicorp/packer-plugin-sdk/packer/registry/image"
packerregistry "github.com/hashicorp/packer/internal/registry"
"github.com/mitchellh/mapstructure"
)
type RegistryBuilder struct {
Name string
ArtifactMetadataPublisher *packerregistry.Bucket
packersdk.Builder
}
func (b *RegistryBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
return b.Builder.Prepare(raws...)
}
// Run is where the actual build should take place. It takes a Build and a Ui.
func (b *RegistryBuilder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
if !b.ArtifactMetadataPublisher.IsExpectingBuildForComponent(b.Name) {
ui.Error(fmt.Sprintf("The build for %q in iteration %q has already been marked as DONE; Skipping build to prevent drift.", b.Name, b.ArtifactMetadataPublisher.Iteration.ID))
return nil, nil
}
runCompleted := make(chan struct{})
go func() {
for {
select {
case <-ctx.Done():
log.Printf("[TRACE] marking build %q as cancelled in HCP Packer registry", b.Name)
if err := b.ArtifactMetadataPublisher.UpdateBuildStatus(context.TODO(), b.Name, models.HashicorpCloudPackerBuildStatusCANCELLED); err != nil {
log.Printf("[TRACE] failed to update HCP Packer registry status for %q: %s", b.Name, err)
}
return
case <-runCompleted:
return
}
}
}()
if err := b.ArtifactMetadataPublisher.UpdateBuildStatus(ctx, b.Name, models.HashicorpCloudPackerBuildStatusRUNNING); err != nil {
log.Printf("[TRACE] failed to update HCP Packer registry status for %q: %s", b.Name, err)
}
cleanupHeartbeat, err := b.ArtifactMetadataPublisher.HeartbeatBuild(ctx, b.Name)
if err != nil {
log.Printf("[ERROR] failed to start heartbeat function")
}
if cleanupHeartbeat != nil {
defer cleanupHeartbeat()
}
ui.Say(fmt.Sprintf("Publishing build details for %s to the HCP Packer registry", b.Name))
artifact, err := b.Builder.Run(ctx, ui, hook)
if err != nil {
if parErr := b.ArtifactMetadataPublisher.UpdateBuildStatus(ctx, b.Name, models.HashicorpCloudPackerBuildStatusFAILED); parErr != nil {
log.Printf("[TRACE] failed to update HCP Packer registry status for %q: %s", b.Name, parErr)
}
}
// close chan to mark completion
close(runCompleted)
if err != nil {
return nil, err
}
if artifact == nil {
return nil, nil
}
var images []registryimage.Image
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &images,
WeaklyTypedInput: true,
ErrorUnused: false,
})
if err != nil {
return artifact, fmt.Errorf("failed to create decoder for HCP Packer registry image: %w", err)
}
state := artifact.State(registryimage.ArtifactStateURI)
err = decoder.Decode(state)
if err != nil {
return artifact, fmt.Errorf("failed to obtain HCP Packer registry image from build artifact: %w", err)
}
err = b.ArtifactMetadataPublisher.UpdateImageForBuild(b.Name, images...)
if err != nil {
return artifact, fmt.Errorf("failed to add image artifact for %q: %s", b.Name, err)
}
return artifact, nil
}