Files
holos/internal/builder/platform.go
Jeff McCune 6bd54ab856 render: pass build tags from platform to component (#366)
Previously, build tags were not propagated from `holos render platform
-t validate` through to the underlying `holos render component` command.
This is a problem because validators need to be selectively enabled as a
work around until we have an audit mode field.

This patch fixes the problem by propagating command line tags from the
render platform command to the underlying commands.  This patch also
propagates tags for the show command.
2024-11-30 20:56:11 -08:00

124 lines
3.2 KiB
Go

package builder
import (
"context"
"fmt"
"time"
"github.com/holos-run/holos/internal/builder/v1alpha5"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/logger"
"golang.org/x/sync/errgroup"
)
// PlatformOpts represents build options when processing the components in a
// platform.
type PlatformOpts struct {
Fn func(context.Context, int, holos.Component) error
Selector holos.Selector
Concurrency int
InfoEnabled bool
}
// Platform represents a common abstraction over different platform schema
// versions.
type Platform struct {
holos.Platform
}
// Build calls [PlatformOpts] Fn(ctx, component) concurrently.
func (p *Platform) Build(ctx context.Context, opts PlatformOpts) error {
limit := max(opts.Concurrency, 1)
parentStart := time.Now()
components := p.Select(opts.Selector)
total := len(components)
g, ctx := errgroup.WithContext(ctx)
// Limit the number of concurrent goroutines due to CUE memory usage concerns
// while rendering components. One more for the producer.
g.SetLimit(limit + 1)
// Spawn a producer because g.Go() blocks when the group limit is reached.
g.Go(func() error {
for idx := range components {
select {
case <-ctx.Done():
return errors.Wrap(ctx.Err())
default:
// Capture idx to avoid issues with closure. Fixed in Go 1.22.
idx := idx
component := components[idx]
// Worker go routine. Blocks if limit has been reached.
g.Go(func() error {
select {
case <-ctx.Done():
return errors.Wrap(ctx.Err())
default:
start := time.Now()
log := logger.FromContext(ctx).With("num", idx+1, "total", total)
if err := opts.Fn(ctx, idx, component); err != nil {
return errors.Wrap(err)
}
duration := time.Since(start)
msg := fmt.Sprintf("rendered %s in %s", component.Describe(), duration)
if opts.InfoEnabled {
log.InfoContext(ctx, msg, "duration", duration)
} else {
log.DebugContext(ctx, msg, "duration", duration)
}
return nil
}
})
}
}
return nil
})
// Wait for completion and return the first error (if any)
if err := g.Wait(); err != nil {
return err
}
duration := time.Since(parentStart)
msg := fmt.Sprintf("rendered platform in %s", duration)
if opts.InfoEnabled {
logger.FromContext(ctx).InfoContext(ctx, msg, "duration", duration)
} else {
logger.FromContext(ctx).DebugContext(ctx, msg, "duration", duration)
}
return nil
}
func LoadPlatform(i *Instance) (platform Platform, err error) {
err = i.Discriminate(func(tm holos.TypeMeta) error {
if tm.Kind != "Platform" {
return errors.Format("unsupported kind: %s, want Platform", tm.Kind)
}
switch version := tm.APIVersion; version {
case "v1alpha5":
platform = Platform{&v1alpha5.Platform{}}
default:
return errors.Format("unsupported version: %s", version)
}
return nil
})
if err != nil {
return platform, errors.Wrap(err)
}
// Get the holos: field value from cue.
v, err := i.HolosValue()
if err != nil {
return platform, errors.Wrap(err)
}
// Load the platform from the cue value.
if err := platform.Load(v); err != nil {
return platform, errors.Wrap(err)
}
return platform, err
}