Compare commits

...

3 Commits

Author SHA1 Message Date
Jeff McCune
d3888a884f (#175) go mod tidy 2024-05-20 06:32:53 -07:00
Jeff McCune
3845871368 (#175) holos pull platform config
This patch adds a subcommand to pull the data necessary to construct a
PlatformConfig DTO.  The PlatformConfig message contains all of the
fields and values necessary to build a platform and the platform
components.  This is an alternative to holos passing multiple tags to
CUE.  The PlatformConfig is marshalled and passed once.

The platform config is also stored in the local filesystem in the root
directory of the platform.  This enables repeated local building and
rendering without making an rpc call.

The build / render pipeline is expected to cache the PlatformConfig once
at the start of the pipeline using the pull subcommand.
2024-05-19 08:27:21 -07:00
Jeff McCune
a3b2d19adb (#175) Render the platform with the model
The `holos render platform` command is unimplemented.  This patch
partially implements platform rendering by fetching the platform model
from the PlatformService and providing it to CUE using a tag.

CUE returns a `kind: Platform` resource to `holos` which will eventually
process a Buildlan for each platform component listed in the Platform
spec.

For now, however, it's sufficient to have the current platform model
available to CUE.
2024-05-18 11:40:30 -07:00
19 changed files with 453 additions and 65 deletions

2
go.mod
View File

@@ -16,7 +16,6 @@ require (
github.com/fullstorydev/grpcurl v1.9.1
github.com/go-jose/go-jose/v3 v3.0.3
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gogo/protobuf v1.3.2
github.com/int128/kubelogin v1.28.0
github.com/jackc/pgx/v5 v5.5.5
github.com/lmittmann/tint v1.0.4
@@ -115,6 +114,7 @@ require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect

View File

@@ -17,6 +17,7 @@ import (
"github.com/holos-run/holos/api/v1alpha1"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/client"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
)
@@ -70,7 +71,7 @@ func (b *Builder) Cluster() string {
}
// Instances returns the cue build instances being built.
func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
func (b *Builder) Instances(ctx context.Context, cfg *client.Config) ([]*build.Instance, error) {
log := logger.FromContext(ctx)
mod, err := b.findCueMod()
@@ -79,7 +80,18 @@ func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
}
dir := string(mod)
cfg := load.Config{Dir: dir}
cueConfig := load.Config{Dir: dir}
// Get the platform model from the PlatformConfig
pc, err := client.LoadPlatformConfig(ctx, dir)
if err != nil {
return nil, errors.Wrap(err)
}
data, err := json.Marshal(pc)
if err != nil {
return nil, errors.Wrap(err)
}
cueConfig.Tags = append(cueConfig.Tags, "platform_config="+string(data))
// Make args relative to the module directory
args := make([]string, len(b.cfg.args))
@@ -100,17 +112,17 @@ func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
// Refer to https://github.com/cue-lang/cue/blob/v0.7.0/cmd/cue/cmd/common.go#L429
if b.Cluster() != "" {
cfg.Tags = append(cfg.Tags, "cluster="+b.Cluster())
cueConfig.Tags = append(cueConfig.Tags, "cluster="+b.Cluster())
}
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cfg.Tags))
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cueConfig.Tags))
return load.Instances(args, &cfg), nil
return load.Instances(args, &cueConfig), nil
}
func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err error) {
func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*v1alpha1.Result, err error) {
log := logger.FromContext(ctx)
log.DebugContext(ctx, "cue: building instances")
instances, err := b.Instances(ctx)
instances, err := b.Instances(ctx, cfg)
if err != nil {
return nil, err
}
@@ -166,6 +178,7 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
decoder.DisallowUnknownFields()
switch tm.Kind {
// TODO(jeff) Process a v1alpha1.Result here, the result is tightly coupled to a BuildPlan.
case "BuildPlan":
var bp v1alpha1.BuildPlan
if err = decoder.Decode(&bp); err != nil {
@@ -173,6 +186,7 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
return
}
results, err = b.buildPlan(ctx, &bp, path)
// TODO(jeff) Platform should not return a Result like an individual holos component does.
case "Platform":
var pf v1alpha1.Platform
if err = decoder.Decode(&pf); err != nil {
@@ -187,9 +201,30 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
return
}
// buildPlatform builds all of the holos components specified in a Platform resource returned from CUE.
//
// TODO(jeff): There is technical debt here in the way results are handled.
// Results were intended as a way to define how holos should build various kinds
// of individual components, not a whole platform though. After launch,
// refactor the platform building to return something else. The result itself
// also needs to be refactored to an interface instead of a struct. Each kind
// of component should implement the interface.
func (b *Builder) buildPlatform(ctx context.Context, pf *v1alpha1.Platform) (results []*v1alpha1.Result, err error) {
log := logger.FromContext(ctx)
log.ErrorContext(ctx, "not implemented", "platform", pf)
// TODO: Iterate over each platform component in Platform.spec.components.
// each component should note the cluster name and path to the holos
// component.
data, err := pf.Spec.Model.MarshalJSON()
if err != nil {
return nil, errors.Wrap(err)
}
if len(data) > 0 {
data = append(data, '\n')
}
buf := bytes.NewBuffer(data)
if _, err := buf.WriteTo(os.Stdout); err != nil {
log.ErrorContext(ctx, "could not write", "err", err)
}
return nil, errors.Wrap(fmt.Errorf("not implemeneted"))
}

View File

@@ -7,16 +7,18 @@ import (
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/client"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/spf13/cobra"
)
// makeBuildRunFunc returns the internal implementation of the build cli command
func makeBuildRunFunc(cfg *holos.Config) command.RunFunc {
func makeBuildRunFunc(cfg *client.Config) command.RunFunc {
return func(cmd *cobra.Command, args []string) error {
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.ClusterName()))
results, err := build.Run(cmd.Context())
ctx := cmd.Root().Context()
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.Holos().ClusterName()))
results, err := build.Run(ctx, cfg)
if err != nil {
return err
}
@@ -42,7 +44,12 @@ func New(cfg *holos.Config) *cobra.Command {
cmd := command.New("build [directory...]")
cmd.Args = cobra.MinimumNArgs(1)
cmd.Short = "build kubernetes api objects from a directory"
cmd.RunE = makeBuildRunFunc(cfg)
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
config := client.NewConfig(cfg)
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
cmd.RunE = makeBuildRunFunc(config)
return cmd
}

81
internal/cli/pull/pull.go Normal file
View File

@@ -0,0 +1,81 @@
// Package pull pulls resources from the PlatformService and caches them in the
// local filesystem.
package pull
import (
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/client"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/server/middleware/logger"
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
"github.com/spf13/cobra"
)
func New(cfg *holos.Config) *cobra.Command {
cmd := command.New("pull")
cmd.Short = "pull resources from holos server"
cmd.Args = cobra.NoArgs
config := client.NewConfig(cfg)
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
cmd.AddCommand(NewPlatform(config))
return cmd
}
func NewPlatform(cfg *client.Config) *cobra.Command {
cmd := command.New("platform")
cmd.Short = "pull platform resources"
cmd.Args = cobra.NoArgs
cmd.AddCommand(NewPlatformConfig(cfg))
return cmd
}
func NewPlatformConfig(cfg *client.Config) *cobra.Command {
cmd := command.New("config")
cmd.Short = "pull platform config"
cmd.Args = cobra.MinimumNArgs(1)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Root().Context()
if ctx == nil {
return errors.Wrap(errors.New("cannot execute: no context"))
}
ctx = logger.NewContext(ctx, logger.FromContext(ctx).With("server", cfg.Client().Server()))
rpc := client.New(cfg)
for _, name := range args {
// Get the platform metadata for the platform id.
pmd, err := client.LoadPlatform(ctx, name)
if err != nil {
return errors.Wrap(err)
}
log := logger.FromContext(ctx).With("platform_id", pmd.GetId())
// Get the platform model
model, err := rpc.PlatformModel(ctx, pmd.GetId())
if err != nil {
return errors.Wrap(err)
}
log.Info("pulled platform model")
// Build the PlatformConfig
pc := &object.PlatformConfig{
PlatformId: pmd.GetId(),
PlatformModel: model,
}
// Save the PlatformConfig
path, err := client.SavePlatformConfig(ctx, name, pc)
if err != nil {
return errors.Wrap(err)
}
log.Info("saved platform config", "path", path)
}
return nil
}
return cmd
}

View File

@@ -54,7 +54,7 @@ func NewPlatformForm(cfg *client.Config) *cobra.Command {
rpc := client.New(cfg)
for _, name := range args {
// Get the platform metadata for the platform id.
p, err := push.LoadPlatform(ctx, name)
p, err := client.LoadPlatform(ctx, name)
if err != nil {
return errors.Wrap(err)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/client"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/logger"
@@ -22,6 +23,10 @@ func New(cfg *holos.Config) *cobra.Command {
cmd.Flags().AddGoFlagSet(cfg.WriteFlagSet())
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
config := client.NewConfig(cfg)
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
var printInstances bool
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
flagSet.BoolVar(&printInstances, "print-instances", false, "expand /... paths for xargs")
@@ -33,7 +38,7 @@ func New(cfg *holos.Config) *cobra.Command {
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.ClusterName()))
if printInstances {
instances, err := build.Instances(ctx)
instances, err := build.Instances(ctx, config)
if err != nil {
return errors.Wrap(err)
}
@@ -43,13 +48,14 @@ func New(cfg *holos.Config) *cobra.Command {
return nil
}
results, err := build.Run(cmd.Context())
results, err := build.Run(ctx, config)
if err != nil {
return errors.Wrap(err)
}
// TODO: Avoid accidental over-writes if to holos component instances result in
// the same file path. Write files into a blank temporary directory, error if a
// file exists, then move the directory into place.
// TODO: Avoid accidental over-writes if two or more holos component
// instances result in the same file path. Write files into a blank
// temporary directory, error if a file exists, then move the directory into
// place.
var result Result
for _, result = range results {
if result.Continue() {

View File

@@ -22,6 +22,7 @@ import (
"github.com/holos-run/holos/internal/cli/login"
"github.com/holos-run/holos/internal/cli/logout"
"github.com/holos-run/holos/internal/cli/preflight"
"github.com/holos-run/holos/internal/cli/pull"
"github.com/holos-run/holos/internal/cli/push"
"github.com/holos-run/holos/internal/cli/register"
"github.com/holos-run/holos/internal/cli/render"
@@ -73,6 +74,7 @@ func New(cfg *holos.Config) *cobra.Command {
rootCmd.AddCommand(rpc.New(cfg))
rootCmd.AddCommand(generate.New(cfg))
rootCmd.AddCommand(register.New(cfg))
rootCmd.AddCommand(pull.New(cfg))
rootCmd.AddCommand(push.New(cfg))
rootCmd.AddCommand(newOrgCmd())

View File

@@ -15,6 +15,7 @@ import (
"github.com/holos-run/holos/service/gen/holos/platform/v1alpha1/platformconnect"
"github.com/holos-run/holos/service/gen/holos/user/v1alpha1/userconnect"
"google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
)
func New(cfg *Config) *Client {
@@ -67,3 +68,19 @@ func (c *Client) UpdateForm(ctx context.Context, platformID string, form *object
log.DebugContext(ctx, "updated platform", "platform_id", platformID, "duration", time.Since(start))
return nil
}
// PlatformModel gets the platform model from the PlatformService.
func (c *Client) PlatformModel(ctx context.Context, platformID string) (*structpb.Struct, error) {
start := time.Now()
req := &platform.GetPlatformRequest{
PlatformId: platformID,
FieldMask: &fieldmaskpb.FieldMask{Paths: []string{"spec.model"}},
}
pf, err := c.pltSvc.GetPlatform(ctx, connect.NewRequest(req))
if err != nil {
return nil, errors.Wrap(err)
}
log := logger.FromContext(ctx)
log.DebugContext(ctx, "get platform", "platform_id", platformID, "duration", time.Since(start))
return pf.Msg.GetPlatform().GetSpec().GetModel(), nil
}

View File

@@ -61,3 +61,11 @@ func (c *Config) Context() *holos.ClientContext {
}
return c.context
}
// Holos returns the *holos.Config
func (c *Config) Holos() *holos.Config {
if c == nil {
return nil
}
return c.holos
}

View File

@@ -0,0 +1,68 @@
package client
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/holos-run/holos/internal/server/middleware/logger"
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
"google.golang.org/protobuf/encoding/protojson"
)
// PlatformMetadataFile is the platform metadata json file name located in the root
// of a platform directory.
const PlatformMetadataFile = "platform.metadata.json"
// PlatformConfigFile is the marshaled json representation of the PlatformConfig
// DTO used to cache the data holos passes from the PlatformService to CUE when
// rendering platform components.
const PlatformConfigFile = "platform.config.json"
// LoadPlatform loads the platform.metadata.json file from a named path. Useful
// to obtain a platform id for PlatformService rpc methods.
func LoadPlatform(ctx context.Context, name string) (*platform.Platform, error) {
data, err := os.ReadFile(filepath.Join(name, PlatformMetadataFile))
if err != nil {
return nil, fmt.Errorf("could not load platform metadata: %w", err)
}
p := &platform.Platform{}
if err := protojson.Unmarshal(data, p); err != nil {
return nil, fmt.Errorf("could not load platform metadata: %w", err)
}
return p, nil
}
// LoadPlatformConfig loads the PlatformConfig DTO from the platform.config.json
// file. Useful to provide all values necessary to render cue config without an
// rpc to the HolosService.
func LoadPlatformConfig(ctx context.Context, name string) (*object.PlatformConfig, error) {
data, err := os.ReadFile(filepath.Join(name, PlatformConfigFile))
if err != nil {
return nil, fmt.Errorf("could not load platform config: %w", err)
}
pc := &object.PlatformConfig{}
if err := protojson.Unmarshal(data, pc); err != nil {
return nil, fmt.Errorf("could not load platform config: %w", err)
}
return pc, nil
}
// SavePlatformConfig writes pc to the platform root directory path identified by name.
func SavePlatformConfig(ctx context.Context, name string, pc *object.PlatformConfig) (string, error) {
data, err := protojson.Marshal(pc)
if err != nil {
return "", err
}
if len(data) > 0 {
data = append(data, '\n')
}
path := filepath.Join(name, PlatformConfigFile)
if err := os.WriteFile(path, data, 0644); err != nil {
return "", fmt.Errorf("could not write platform config: %w", err)
}
logger.FromContext(ctx).DebugContext(ctx, "wrote", "path", path)
return path, nil
}

View File

@@ -367,3 +367,53 @@ export class Form extends Message<Form> {
}
}
/**
* PlatformConfig represents the data passed from the holos cli to CUE when
* rendering configuration.
*
* @generated from message holos.object.v1alpha1.PlatformConfig
*/
export class PlatformConfig extends Message<PlatformConfig> {
/**
* Platform UUID.
*
* @generated from field: string platform_id = 1;
*/
platformId = "";
/**
* Platform Model.
*
* @generated from field: optional google.protobuf.Struct platform_model = 2;
*/
platformModel?: Struct;
constructor(data?: PartialMessage<PlatformConfig>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.object.v1alpha1.PlatformConfig";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "platform_model", kind: "message", T: Struct, opt: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PlatformConfig {
return new PlatformConfig().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): PlatformConfig {
return new PlatformConfig().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): PlatformConfig {
return new PlatformConfig().fromJsonString(jsonString, options);
}
static equals(a: PlatformConfig | PlainMessage<PlatformConfig> | undefined, b: PlatformConfig | PlainMessage<PlatformConfig> | undefined): boolean {
return proto3.util.equals(PlatformConfig, a, b);
}
}

View File

@@ -72,11 +72,10 @@ func GeneratePlatform(ctx context.Context, rpc *client.Client, orgID string, nam
data = append(data, '\n')
}
log = log.With("platform_id", rpcPlatform.GetId())
path := "platform.metadata.json"
if err := os.WriteFile(path, data, 0644); err != nil {
if err := os.WriteFile(client.PlatformMetadataFile, data, 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write platform metadata: %w", err))
}
log.InfoContext(ctx, "wrote "+path, "path", filepath.Join(getCwd(ctx), path))
log.InfoContext(ctx, "wrote "+client.PlatformMetadataFile, "path", filepath.Join(getCwd(ctx), client.PlatformMetadataFile))
// Copy the cue.mod directory
if err := copyEmbedFS(ctx, platforms, filepath.Join(root, "cue.mod"), "cue.mod"); err != nil {

View File

@@ -3,6 +3,12 @@ package holos
import "encoding/json"
import v1 "github.com/holos-run/holos/api/v1alpha1"
import dto "github.com/holos-run/holos/service/gen/holos/object/v1alpha1:object"
// _PlatformConfig represents all of the data passed from holos to cue.
// Intended to carry the platform model and project models.
_PlatformConfig: dto.#PlatformConfig & json.Unmarshal(_PlatformConfigJSON)
_PlatformConfigJSON: string | *"{}" @tag(platform_config, type=string)
// _Platform provides a platform resource to the holos cli for rendering. The
// field is hidden because most components need to refer to platform data,
@@ -15,8 +21,6 @@ _Platform: v1.#Platform & {
// spec is the platform specification
spec: {
// model represents the web form values provided by the user.
model: json.Unmarshal(_model)
// _model is the json representation of model injected into CUE from holos.
_model: string | *"{}" @tag(platform_model, type=string)
model: _PlatformConfig.platform_model
}
}

View File

@@ -114,3 +114,13 @@ _#isResourceOwner_ResourceOwner: _
// organized by section.
field_configs?: [...null | structpb.#Struct] @go(FieldConfigs,[]*structpb.Struct) @protobuf(1,bytes,rep,json=fieldConfigs,proto3)
}
// PlatformConfig represents the data passed from the holos cli to CUE when
// rendering configuration.
#PlatformConfig: {
// Platform UUID.
platform_id?: string @go(PlatformId) @protobuf(1,bytes,opt,json=platformId,proto3)
// Platform Model.
platform_model?: null | structpb.#Struct @go(PlatformModel,*structpb.Struct) @protobuf(2,bytes,opt,json=platformModel,proto3,oneof)
}

View File

@@ -170,15 +170,25 @@ func (c *Config) Finalize() error {
// Vet validates the config.
func (c *Config) Vet() error {
if c == nil || c.logConfig == nil {
return fmt.Errorf("cannot vet: not configured")
}
return c.logConfig.Vet()
}
// Logger returns a *slog.Logger configured by the user.
// Logger returns a *slog.Logger configured by the user or the default logger if
// no logger has been configured by the user.
func (c *Config) Logger() *slog.Logger {
if c == nil {
return slog.Default()
}
if c.logger != nil {
return c.logger
}
return c.logConfig.NewLogger(c.options.stderr)
if c.logConfig == nil {
return slog.Default()
}
return c.logConfig.NewLogger(c.Stderr())
}
// NewTopLevelLogger returns a *slog.Logger with a handler that filters source
@@ -189,21 +199,33 @@ func (c *Config) NewTopLevelLogger() *slog.Logger {
// Stdin should be used instead of os.Stdin to capture input from tests.
func (c *Config) Stdin() io.Reader {
if c == nil || c.options == nil {
return os.Stdin
}
return c.options.stdin
}
// Stdout should be used instead of os.Stdout to capture output for tests.
func (c *Config) Stdout() io.Writer {
if c == nil || c.options == nil {
return os.Stdout
}
return c.options.stdout
}
// Stderr should be used instead of os.Stderr to capture output for tests.
func (c *Config) Stderr() io.Writer {
if c == nil || c.options == nil {
return os.Stderr
}
return c.options.stderr
}
// WriteTo returns the write to path configured by flags.
func (c *Config) WriteTo() string {
if c == nil {
return ""
}
return c.writeTo
}
@@ -230,6 +252,9 @@ func (c *Config) Write(p []byte) {
// ClusterName returns the cluster name configured by flags.
func (c *Config) ClusterName() string {
if c == nil {
return ""
}
return c.clusterName
}
@@ -243,7 +268,7 @@ func (c *Config) KVKubeconfig() string {
// KVNamespace returns the configured namespace to operate against in the provisioner cluster.
func (c *Config) KVNamespace() string {
if c.kvNamespace == nil {
if c == nil || c.kvNamespace == nil {
return DefaultProvisionerNamespace
}
return *c.kvNamespace
@@ -251,7 +276,7 @@ func (c *Config) KVNamespace() string {
// TxtarIndex returns the
func (c *Config) TxtarIndex() int {
if c.txtarIndex == nil {
if c == nil || c.txtarIndex == nil {
return 0
}
return *c.txtarIndex
@@ -259,7 +284,7 @@ func (c *Config) TxtarIndex() int {
// ProvisionerClientset returns a kubernetes client set for the provisioner cluster.
func (c *Config) ProvisionerClientset() (kubernetes.Interface, error) {
if c.provisionerClientset == nil {
if c == nil || c.provisionerClientset == nil {
kcfg, err := clientcmd.BuildConfigFromFlags("", c.KVKubeconfig())
if err != nil {
return nil, errors.Wrap(err)

View File

@@ -5,29 +5,13 @@ import (
"bytes"
"context"
"encoding/json"
"os"
"path/filepath"
"github.com/gogo/protobuf/jsonpb"
"github.com/holos-run/holos/api/v1alpha1"
"github.com/holos-run/holos/internal/errors"
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
)
// LoadPlatform loads the platform.metadata.json file from a named path.
func LoadPlatform(ctx context.Context, name string) (*platform.Platform, error) {
data, err := os.ReadFile(filepath.Join(name, "platform.metadata.json"))
if err != nil {
return nil, err
}
p := &platform.Platform{}
if err := jsonpb.Unmarshal(bytes.NewReader(data), p); err != nil {
return nil, err
}
return p, nil
}
// PlatformForm builds a json powered web form from CUE code. The CUE code is
// expected to be derived from the code generated by the `holos generate
// platform` command.

View File

@@ -534,6 +534,65 @@ func (x *Form) GetFieldConfigs() []*structpb.Struct {
return nil
}
// PlatformConfig represents the data passed from the holos cli to CUE when
// rendering configuration.
type PlatformConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Platform UUID.
PlatformId string `protobuf:"bytes,1,opt,name=platform_id,json=platformId,proto3" json:"platform_id,omitempty"`
// Platform Model.
PlatformModel *structpb.Struct `protobuf:"bytes,2,opt,name=platform_model,json=platformModel,proto3,oneof" json:"platform_model,omitempty"`
}
func (x *PlatformConfig) Reset() {
*x = PlatformConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_holos_object_v1alpha1_object_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PlatformConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PlatformConfig) ProtoMessage() {}
func (x *PlatformConfig) ProtoReflect() protoreflect.Message {
mi := &file_holos_object_v1alpha1_object_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PlatformConfig.ProtoReflect.Descriptor instead.
func (*PlatformConfig) Descriptor() ([]byte, []int) {
return file_holos_object_v1alpha1_object_proto_rawDescGZIP(), []int{7}
}
func (x *PlatformConfig) GetPlatformId() string {
if x != nil {
return x.PlatformId
}
return ""
}
func (x *PlatformConfig) GetPlatformModel() *structpb.Struct {
if x != nil {
return x.PlatformModel
}
return nil
}
var File_holos_object_v1alpha1_object_proto protoreflect.FileDescriptor
var file_holos_object_v1alpha1_object_proto_rawDesc = []byte{
@@ -619,12 +678,21 @@ var file_holos_object_v1alpha1_object_proto_rawDesc = []byte{
0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x73, 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x6f, 0x6c, 0x6f, 0x73, 0x2d, 0x72, 0x75, 0x6e, 0x2f, 0x68, 0x6f,
0x6c, 0x6f, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f,
0x68, 0x6f, 0x6c, 0x6f, 0x73, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x76, 0x31, 0x61,
0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x0e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f,
0x72, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x74,
0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba,
0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72,
0x6d, 0x49, 0x64, 0x12, 0x43, 0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f,
0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
0x72, 0x75, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x70, 0x6c, 0x61,
0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x45, 0x5a, 0x43, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x6f, 0x6c, 0x6f, 0x73, 0x2d,
0x72, 0x75, 0x6e, 0x2f, 0x68, 0x6f, 0x6c, 0x6f, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x68, 0x6f, 0x6c, 0x6f, 0x73, 0x2f, 0x6f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x6f, 0x62, 0x6a, 0x65,
0x63, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -639,7 +707,7 @@ func file_holos_object_v1alpha1_object_proto_rawDescGZIP() []byte {
return file_holos_object_v1alpha1_object_proto_rawDescData
}
var file_holos_object_v1alpha1_object_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_holos_object_v1alpha1_object_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_holos_object_v1alpha1_object_proto_goTypes = []interface{}{
(*Detail)(nil), // 0: holos.object.v1alpha1.Detail
(*Subject)(nil), // 1: holos.object.v1alpha1.Subject
@@ -648,21 +716,23 @@ var file_holos_object_v1alpha1_object_proto_goTypes = []interface{}{
(*ResourceEditor)(nil), // 4: holos.object.v1alpha1.ResourceEditor
(*ResourceOwner)(nil), // 5: holos.object.v1alpha1.ResourceOwner
(*Form)(nil), // 6: holos.object.v1alpha1.Form
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 8: google.protobuf.Struct
(*PlatformConfig)(nil), // 7: holos.object.v1alpha1.PlatformConfig
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 9: google.protobuf.Struct
}
var file_holos_object_v1alpha1_object_proto_depIdxs = []int32{
4, // 0: holos.object.v1alpha1.Detail.created_by:type_name -> holos.object.v1alpha1.ResourceEditor
7, // 1: holos.object.v1alpha1.Detail.created_at:type_name -> google.protobuf.Timestamp
8, // 1: holos.object.v1alpha1.Detail.created_at:type_name -> google.protobuf.Timestamp
4, // 2: holos.object.v1alpha1.Detail.updated_by:type_name -> holos.object.v1alpha1.ResourceEditor
7, // 3: holos.object.v1alpha1.Detail.updated_at:type_name -> google.protobuf.Timestamp
8, // 3: holos.object.v1alpha1.Detail.updated_at:type_name -> google.protobuf.Timestamp
1, // 4: holos.object.v1alpha1.UserRef.subject:type_name -> holos.object.v1alpha1.Subject
8, // 5: holos.object.v1alpha1.Form.field_configs:type_name -> google.protobuf.Struct
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
9, // 5: holos.object.v1alpha1.Form.field_configs:type_name -> google.protobuf.Struct
9, // 6: holos.object.v1alpha1.PlatformConfig.platform_model:type_name -> google.protobuf.Struct
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_holos_object_v1alpha1_object_proto_init() }
@@ -755,6 +825,18 @@ func file_holos_object_v1alpha1_object_proto_init() {
return nil
}
}
file_holos_object_v1alpha1_object_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PlatformConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_holos_object_v1alpha1_object_proto_msgTypes[0].OneofWrappers = []interface{}{}
file_holos_object_v1alpha1_object_proto_msgTypes[2].OneofWrappers = []interface{}{
@@ -773,13 +855,14 @@ func file_holos_object_v1alpha1_object_proto_init() {
(*ResourceOwner_OrgId)(nil),
(*ResourceOwner_UserId)(nil),
}
file_holos_object_v1alpha1_object_proto_msgTypes[7].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_holos_object_v1alpha1_object_proto_rawDesc,
NumEnums: 0,
NumMessages: 7,
NumMessages: 8,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -87,3 +87,12 @@ message Form {
// organized by section.
repeated google.protobuf.Struct field_configs = 1;
}
// PlatformConfig represents the data passed from the holos cli to CUE when
// rendering configuration.
message PlatformConfig {
// Platform UUID.
string platform_id = 1 [(buf.validate.field).string.uuid = true];
// Platform Model.
optional google.protobuf.Struct platform_model = 2;
}

View File

@@ -1 +1 @@
0
1