Compare commits

...

2 Commits

Author SHA1 Message Date
Jeff McCune
bac7aec0ba (#167) Restructure the bare platform
This patch restructures the bare platform in preparation for a
`Platform` kind of output from CUE in addition to the existing
`BuildPlan` kind.

This patch establishes a pattern where our own CUE defined code goes
into the two CUE module paths:

1. `internal/platforms/cue.mod/gen/github.com/holos-run/holos/api/v1alpha1`
2. `internal/platforms/cue.mod/pkg/github.com/holos-run/holos/api/v1alpha1`
3. `internal/platforms/cue.mod/usr/github.com/holos-run/holos/api/v1alpha1`

The first path is automatically generated from Go structs.  The second
path is where we override and provide additional cue level integration.

The third path is reserved for the end user to further refine and
constrain our definitions.
2024-05-07 16:53:00 -07:00
Jeff McCune
42f916af41 (#164) Use quay.io/holos/oauth2-proxy:v7.6.0-1-g77a03ae2
Custom build to set samesite=none on the csrf cookie.
2024-05-06 16:18:32 -07:00
30 changed files with 510 additions and 492 deletions

View File

@@ -39,3 +39,14 @@ func (bp *BuildPlan) Validate() error {
}
return nil
}
func (bp *BuildPlan) ResultCapacity() (count int) {
if bp == nil {
return 0
}
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}

9
api/v1alpha1/platform.go Normal file
View File

@@ -0,0 +1,9 @@
package v1alpha1
// Platform represents a platform to manage. A Platform resource tells holos
// which components to build. The primary use case is to specify the cluster
// names, cluster types, and holos components to build.
type Platform struct {
TypeMeta `json:",inline" yaml:",inline"`
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

View File

@@ -19,6 +19,14 @@ type Result struct {
accumulatedOutput string
}
// Continue returns true if Skip is true indicating the result is to be skipped over.
func (r *Result) Continue() bool {
if r == nil {
return false
}
return r.Skip
}
func (r *Result) Name() string {
return r.Metadata.Name
}
@@ -32,6 +40,11 @@ func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
}
// KustomizationContent returns the kustomization file contents to write.
func (r *Result) KustomizationContent() string {
return r.KsContent
}
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
return r.accumulatedOutput

View File

@@ -8,3 +8,13 @@ type TypeMeta struct {
func (tm *TypeMeta) GetKind() string {
return tm.Kind
}
func (tm *TypeMeta) GetAPIVersion() string {
return tm.Kind
}
// Discriminator is an interface to discriminate the kind api object.
type Discriminator interface {
GetKind() string
GetAPIVersion() string
}

View File

@@ -89,7 +89,8 @@ _IngressAuthProxy: {
spec: {
securityContext: seccompProfile: type: "RuntimeDefault"
containers: [{
image: "quay.io/oauth2-proxy/oauth2-proxy:v7.6.0"
// image: "quay.io/oauth3-proxy/oauth2-proxy:v7.6.0"
image: "quay.io/holos/oauth2-proxy:v7.6.0-1-g77a03ae2"
imagePullPolicy: "IfNotPresent"
name: "oauth2-proxy"
volumeMounts: [{

273
internal/builder/builder.go Normal file
View File

@@ -0,0 +1,273 @@
// Package builder is responsible for building fully rendered kubernetes api
// objects from various input directories. A directory may contain a platform
// spec or a component spec.
package builder
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/holos-run/holos/api/v1alpha1"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
)
const (
KubernetesObjects = v1alpha1.KubernetesObjectsKind
// Helm is the value of the kind field of holos build output indicating helm
// values and helm command information.
Helm = v1alpha1.HelmChartKind
// Skip is the value when the instance should be skipped
Skip = "Skip"
// KustomizeBuild is the value of the kind field of cue output indicating holos should process the component using kustomize build to render output.
KustomizeBuild = v1alpha1.KustomizeBuildKind
)
// An Option configures a Builder
type Option func(*config)
type config struct {
args []string
cluster string
}
type Builder struct {
cfg config
}
// New returns a new *Builder configured by opts Option.
func New(opts ...Option) *Builder {
var cfg config
for _, f := range opts {
f(&cfg)
}
b := &Builder{cfg: cfg}
return b
}
// Entrypoints configures the leaf directories Builder builds.
func Entrypoints(args []string) Option {
return func(cfg *config) { cfg.args = args }
}
// Cluster configures the cluster name for the holos component instance.
func Cluster(name string) Option {
return func(cfg *config) { cfg.cluster = name }
}
// Cluster returns the cluster name of the component instance being built.
func (b *Builder) Cluster() string {
return b.cfg.cluster
}
// Instances returns the cue build instances being built.
func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
log := logger.FromContext(ctx)
mod, err := b.findCueMod()
if err != nil {
return nil, errors.Wrap(err)
}
dir := string(mod)
cfg := load.Config{Dir: dir}
// Make args relative to the module directory
args := make([]string, len(b.cfg.args))
for idx, path := range b.cfg.args {
target, err := filepath.Abs(path)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not find absolute path: %w", err))
}
relPath, err := filepath.Rel(dir, target)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("invalid argument, must be relative to cue.mod: %w", err))
}
relPath = "./" + relPath
args[idx] = relPath
equiv := fmt.Sprintf("cue export --out yaml -t cluster=%v %v", b.Cluster(), relPath)
log.Debug("cue: equivalent command: " + equiv)
}
// Refer to https://github.com/cue-lang/cue/blob/v0.7.0/cmd/cue/cmd/common.go#L429
cfg.Tags = append(cfg.Tags, "cluster="+b.Cluster())
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cfg.Tags))
return load.Instances(args, &cfg), nil
}
func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err error) {
log := logger.FromContext(ctx)
log.DebugContext(ctx, "cue: building instances")
instances, err := b.Instances(ctx)
if err != nil {
return nil, err
}
results = make([]*v1alpha1.Result, 0, len(instances)*8)
// Each CUE instance provides a BuildPlan
for idx, instance := range instances {
log.DebugContext(ctx, "cue: building instance", "idx", idx, "dir", instance.Dir)
r, err := b.runInstance(ctx, instance)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not run: %w", err))
}
results = append(results, r...)
}
return results, nil
}
func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (results []*v1alpha1.Result, err error) {
path := holos.InstancePath(instance.Dir)
log := logger.FromContext(ctx).With("dir", path)
if err := instance.Err; err != nil {
return nil, errors.Wrap(fmt.Errorf("could not load: %w", err))
}
cueCtx := cuecontext.New()
value := cueCtx.BuildInstance(instance)
if err := value.Err(); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not build %s: %w", instance.Dir, err))
}
log.DebugContext(ctx, "cue: validating instance")
if err := value.Validate(); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not validate: %w", err))
}
log.DebugContext(ctx, "cue: decoding holos build plan")
jsonBytes, err := value.MarshalJSON()
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
// Discriminate the type of build plan.
tm := &v1alpha1.TypeMeta{}
err = decoder.Decode(tm)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("invalid BuildPlan: %s: %w", instance.Dir, err))
}
log.DebugContext(ctx, "cue: discriminated build kind: "+tm.Kind, "kind", tm.Kind, "apiVersion", tm.APIVersion)
// New decoder for the full object
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.DisallowUnknownFields()
switch tm.Kind {
case "BuildPlan":
var bp v1alpha1.BuildPlan
if err = decoder.Decode(&bp); err != nil {
err = errors.Wrap(fmt.Errorf("could not decode BuildPlan %s: %w", instance.Dir, err))
return
}
results, err = b.buildPlan(ctx, &bp, path)
case "Platform":
var pf v1alpha1.Platform
if err = decoder.Decode(&pf); err != nil {
err = errors.Wrap(fmt.Errorf("could not decode Platform %s: %w", instance.Dir, err))
return
}
results, err = b.buildPlatform(ctx, &pf)
default:
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
}
return
}
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)
return nil, errors.Wrap(fmt.Errorf("not implemeneted"))
}
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan, path holos.InstancePath) (results []*v1alpha1.Result, err error) {
log := logger.FromContext(ctx)
if err := buildPlan.Validate(); err != nil {
log.WarnContext(ctx, "could not validate", "skipped", true, "err", err)
return nil, errors.Wrap(fmt.Errorf("could not validate %w", err))
}
if buildPlan.Spec.Disabled {
log.DebugContext(ctx, "skipped: spec.disabled is true", "skipped", true)
return
}
// TODO: concurrent renders
results = make([]*v1alpha1.Result, 0, buildPlan.ResultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", buildPlan.ResultCapacity())
for _, component := range buildPlan.Spec.Components.Resources {
if result, err := component.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
if result, err := component.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.HelmChartList {
if result, err := component.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
if result, err := component.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
log.DebugContext(ctx, "returning results", "len", len(results))
return results, nil
}
// findCueMod returns the root module location containing the cue.mod file or
// directory or an error if the builder arguments do not share a common root
// module.
func (b *Builder) findCueMod() (dir holos.PathCueMod, err error) {
for _, origPath := range b.cfg.args {
absPath, err := filepath.Abs(origPath)
if err != nil {
return "", err
}
path := holos.PathCueMod(absPath)
for {
if _, err := os.Stat(filepath.Join(string(path), "cue.mod")); err == nil {
if dir != "" && dir != path {
return "", fmt.Errorf("multiple modules not supported: %v is not %v", dir, path)
}
dir = path
break
} else if !os.IsNotExist(err) {
return "", err
}
parentPath := holos.PathCueMod(filepath.Dir(string(path)))
if parentPath == path {
return "", fmt.Errorf("no cue.mod from root to leaf: %v", origPath)
}
path = parentPath
}
}
return dir, nil
}

View File

@@ -2,12 +2,13 @@ package build
import (
"fmt"
"log/slog"
"strings"
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/internal/builder"
"github.com/spf13/cobra"
)
@@ -20,10 +21,12 @@ func makeBuildRunFunc(cfg *holos.Config) command.RunFunc {
return err
}
outs := make([]string, 0, len(results))
for _, result := range results {
if result.Skip {
for idx, result := range results {
if result == nil || result.Skip {
slog.Debug("skip result", "idx", idx, "result", result)
continue
}
slog.Debug("append result", "idx", idx, "result.kind", result.Kind)
outs = append(outs, result.AccumulatedOutput())
}
out := strings.Join(outs, "---\n")

View File

@@ -1,13 +1,14 @@
package render
import (
"context"
"flag"
"fmt"
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/holos"
"github.com/holos-run/holos/internal/internal/builder"
"github.com/holos-run/holos/internal/logger"
"github.com/spf13/cobra"
)
@@ -53,8 +54,9 @@ func New(cfg *holos.Config) *cobra.Command {
// 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.
for _, result := range results {
if result.Skip {
var result Result
for _, result = range results {
if result.Continue() {
continue
}
// API Objects
@@ -64,7 +66,7 @@ func New(cfg *holos.Config) *cobra.Command {
}
// Kustomization
path = result.KustomizationFilename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.KsContent); err != nil {
if err := result.Save(ctx, path, result.KustomizationContent()); err != nil {
return errors.Wrap(err)
}
log.InfoContext(ctx, "rendered "+result.Name(), "status", "ok", "action", "rendered", "name", result.Name())
@@ -73,3 +75,13 @@ func New(cfg *holos.Config) *cobra.Command {
}
return cmd
}
type Result interface {
Continue() bool
Name() string
Filename(writeTo string, cluster string) string
KustomizationFilename(writeTo string, cluster string) string
Save(ctx context.Context, path string, content string) error
AccumulatedOutput() string
KustomizationContent() string
}

View File

@@ -1,219 +0,0 @@
// Package builder is responsible for building fully rendered kubernetes api
// objects from various input directories. A directory may contain a platform
// spec or a component spec.
package builder
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/holos-run/holos/api/v1alpha1"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
)
const (
KubernetesObjects = v1alpha1.KubernetesObjectsKind
// Helm is the value of the kind field of holos build output indicating helm
// values and helm command information.
Helm = v1alpha1.HelmChartKind
// Skip is the value when the instance should be skipped
Skip = "Skip"
// KustomizeBuild is the value of the kind field of cue output indicating holos should process the component using kustomize build to render output.
KustomizeBuild = v1alpha1.KustomizeBuildKind
)
// An Option configures a Builder
type Option func(*config)
type config struct {
args []string
cluster string
}
type Builder struct {
cfg config
}
// New returns a new *Builder configured by opts Option.
func New(opts ...Option) *Builder {
var cfg config
for _, f := range opts {
f(&cfg)
}
b := &Builder{cfg: cfg}
return b
}
// Entrypoints configures the leaf directories Builder builds.
func Entrypoints(args []string) Option {
return func(cfg *config) { cfg.args = args }
}
// Cluster configures the cluster name for the holos component instance.
func Cluster(name string) Option {
return func(cfg *config) { cfg.cluster = name }
}
// Cluster returns the cluster name of the component instance being built.
func (b *Builder) Cluster() string {
return b.cfg.cluster
}
// Instances returns the cue build instances being built.
func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
log := logger.FromContext(ctx)
mod, err := b.findCueMod()
if err != nil {
return nil, errors.Wrap(err)
}
dir := string(mod)
cfg := load.Config{Dir: dir}
// Make args relative to the module directory
args := make([]string, len(b.cfg.args))
for idx, path := range b.cfg.args {
target, err := filepath.Abs(path)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not find absolute path: %w", err))
}
relPath, err := filepath.Rel(dir, target)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("invalid argument, must be relative to cue.mod: %w", err))
}
relPath = "./" + relPath
args[idx] = relPath
equiv := fmt.Sprintf("cue export --out yaml -t cluster=%v %v", b.Cluster(), relPath)
log.Debug("cue: equivalent command: " + equiv)
}
// Refer to https://github.com/cue-lang/cue/blob/v0.7.0/cmd/cue/cmd/common.go#L429
cfg.Tags = append(cfg.Tags, "cluster="+b.Cluster())
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cfg.Tags))
return load.Instances(args, &cfg), nil
}
func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err error) {
results = make([]*v1alpha1.Result, 0, len(b.cfg.args))
cueCtx := cuecontext.New()
logger.FromContext(ctx).DebugContext(ctx, "cue: building instances")
instances, err := b.Instances(ctx)
if err != nil {
return results, err
}
// Each CUE instance provides a BuildPlan
for _, instance := range instances {
var buildPlan v1alpha1.BuildPlan
log := logger.FromContext(ctx).With("dir", instance.Dir)
if err := instance.Err; err != nil {
return nil, errors.Wrap(fmt.Errorf("could not load: %w", err))
}
log.DebugContext(ctx, "cue: building instance")
value := cueCtx.BuildInstance(instance)
if err := value.Err(); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not build %s: %w", instance.Dir, err))
}
log.DebugContext(ctx, "cue: validating instance")
if err := value.Validate(); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not validate: %w", err))
}
log.DebugContext(ctx, "cue: decoding holos build plan")
// Hack to catch unknown fields https://github.com/holos-run/holos/issues/72
jsonBytes, err := value.MarshalJSON()
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.DisallowUnknownFields()
err = decoder.Decode(&buildPlan)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("invalid BuildPlan: %s: %w", instance.Dir, err))
}
if err := buildPlan.Validate(); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not validate %s: %w", instance.Dir, err))
}
if buildPlan.Spec.Disabled {
log.DebugContext(ctx, "skipped: spec.disabled is true", "skipped", true)
continue
}
// TODO: concurrent renders
for _, component := range buildPlan.Spec.Components.Resources {
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.HelmChartList {
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
if result, err := component.Render(ctx, holos.InstancePath(instance.Dir)); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
}
return results, nil
}
// findCueMod returns the root module location containing the cue.mod file or
// directory or an error if the builder arguments do not share a common root
// module.
func (b *Builder) findCueMod() (dir holos.PathCueMod, err error) {
for _, origPath := range b.cfg.args {
absPath, err := filepath.Abs(origPath)
if err != nil {
return "", err
}
path := holos.PathCueMod(absPath)
for {
if _, err := os.Stat(filepath.Join(string(path), "cue.mod")); err == nil {
if dir != "" && dir != path {
return "", fmt.Errorf("multiple modules not supported: %v is not %v", dir, path)
}
dir = path
break
} else if !os.IsNotExist(err) {
return "", err
}
parentPath := holos.PathCueMod(filepath.Dir(string(path)))
if parentPath == path {
return "", fmt.Errorf("no cue.mod from root to leaf: %v", origPath)
}
path = parentPath
}
}
return dir, nil
}

View File

@@ -0,0 +1,4 @@
package holos
// #ClusterName is the --cluster-name flag value provided by the holos cli.
#ClusterName: string @tag(cluster, type=string)

View File

@@ -1,26 +1,20 @@
package holos
import ( "encoding/yaml"
import "encoding/yaml"
import v1 "github.com/holos-run/holos/api/v1alpha1"
)
let PLATFORM = {message: "TODO: Load the platform from the API."}
// The platform configmap is a simple component that manages a configmap named
// platform in the default namespace. The purpose is to exercise end to end
// validation of platform configuration values provided by the holos web ui to
// each cluster in the platform.
platform: #Platform & {metadata: name: "bare"}
let PLATFORM = platform
// spec represents the output provided to holos
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
// Provide a BuildPlan to the holos cli to render k8s api objects.
v1.#BuildPlan & {
spec: components: resources: platformConfigmap: {
metadata: name: "platform-configmap"
apiObjectMap: OBJECTS.apiObjectMap
},
]
}
}
// OBJECTS represents the kubernetes api objects to manage.
let OBJECTS = #APIObjects & {
let OBJECTS = v1.#APIObjects & {
apiObjects: ConfigMap: platform: {
metadata: {
name: "platform"

View File

@@ -1,12 +1,12 @@
package forms
import formsv1 "github.com/holos-run/forms/v1alpha1"
import v1 "github.com/holos-run/holos/v1alpha1"
let Platform = formsv1.#Platform & {
name: "bare"
displayName: "Bare Platform"
// Provides a concrete v1.#Form
FormBuilder.Output
sections: org: {
let FormBuilder = v1.#FormBuilder & {
Sections: org: {
displayName: "Organization"
description: "Organization config values are used to derive more specific configuration values throughout the platform."
@@ -64,7 +64,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: cloud: {
Sections: cloud: {
displayName: "Cloud Providers"
description: "Select the services that provide resources for the platform."
@@ -91,7 +91,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: aws: {
Sections: aws: {
displayName: "Amazon Web Services"
description: "Provide the information necessary for Holos to manage AWS resources to provide the platform."
@@ -128,7 +128,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: gcp: {
Sections: gcp: {
displayName: "Google Cloud Platform"
description: "Use this form to configure platform level GCP settings."
@@ -208,7 +208,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: cloudflare: {
Sections: cloudflare: {
displayName: "Cloudflare"
description: "Cloudflare is primarily used for DNS automation."
@@ -228,7 +228,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: github: {
Sections: github: {
displayName: "GitHub"
description: "GitHub is primarily used to host Git repositories and execute Actions workflows."
@@ -253,7 +253,7 @@ let Platform = formsv1.#Platform & {
}
}
sections: backups: {
Sections: backups: {
displayName: "Backups"
description: "Configure platform level data backup settings. Requires AWS."
@@ -278,76 +278,8 @@ let Platform = formsv1.#Platform & {
}
}
}
_sections: privacy: {
displayName: "Data Privacy"
description: "Configure data privacy aspects of the platform."
fieldConfigs: {
country: {
// https://formly.dev/docs/api/ui/material/select/
type: "select"
props: {
label: "Select Planet"
description: "Juridiction of applicable data privacy laws."
options: [
{value: "mercury", label: "Mercury"},
{value: "venus", label: "Venus"},
{value: "earth", label: "Earth"},
{value: "mars", label: "Mars"},
{value: "jupiter", label: "Jupiter"},
{value: "saturn", label: "Saturn"},
{value: "uranus", label: "Uranus"},
{value: "neptune", label: "Neptune"},
]
}
}
regions: {
// https://formly.dev/docs/api/ui/material/select/
type: "select"
props: {
label: "Select Regions"
description: "Select the regions this platform operates in."
multiple: true
selectAllOption: "Select All"
options: [
{value: "us-east-2", label: "Ohio"},
{value: "us-west-2", label: "Oregon"},
{value: "eu-west-1", label: "Ireland"},
{value: "eu-west-2", label: "London", disabled: true},
]
}
}
}
}
_sections: terms: {
displayName: "Terms and Conditions"
description: "Example of a boolean checkbox."
fieldConfigs: {
// platform.spec.config.user.sections.terms.fields.didAgree
didAgree: {
type: "checkbox"
props: {
label: "Accept terms"
description: "In order to proceed, please accept terms"
pattern: "true"
required: true
}
validation: {
messages: {
pattern: "Please accept the terms"
}
}
}
}
}
}
// Provide the output form fields
Platform.Form
let GCPRegions = [
{value: "africa-south1", label: "africa-south1"},
{value: "asia-east1", label: "asia-east1"},

View File

@@ -1,63 +0,0 @@
package holos
import (
h "github.com/holos-run/holos/api/v1alpha1"
"encoding/yaml"
)
// CUE provides a #BuildPlan to the holos cli. Holos requires the output of CUE
// to conform to the #BuildPlan schema.
{} & h.#BuildPlan
// #HolosComponent defines struct fields common to all holos component types.
#HolosComponent: {
h.#HolosComponent
metadata: name: string
_NameLengthConstraint: len(metadata.name) & >=1
...
}
#KubernetesObjects: #HolosComponent & h.#KubernetesObjects
// #HolosTypeMeta is similar to kubernetes api TypeMeta, but for holos api
// objects such as the Platform config resource.
#HolosTypeMeta: {
kind: string @go(Kind)
apiVersion: string @go(APIVersion)
}
// #HolosObjectMeta is similar to kubernetes api ObjectMeta, but for holos api
// objects.
#HolosObjectMeta: {
name: string @go(Name)
labels: {[string]: string} @go(Labels,map[string]string)
annotations: {[string]: string} @go(Annotations,map[string]string)
}
// #APIObjects defines the output format for kubernetes api objects. The holos
// cli expects the yaml representation of each api object in the apiObjectMap
// field.
#APIObjects: {
// apiObjects represents the un-marshalled form of each kubernetes api object
// managed by a holos component.
apiObjects: {
[Kind=_]: {
[string]: {
kind: Kind
...
}
}
ConfigMap?: [Name=_]: #ConfigMap & {metadata: name: Name}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
package holos
import (
corev1 "k8s.io/api/core/v1"
)
_NamespaceObject: {
metadata: name: string
metadata: namespace: string
metadata: labels: "app.holos.run/managed": "true"
}
#ConfigMap: _NamespaceObject & corev1.#ConfigMap

View File

@@ -1,39 +0,0 @@
package holos
// #Platform represents the user supplied platform configuration.
#Platform: {
#HolosTypeMeta
kind: "Platform"
apiVersion: "app.holos.run/v1alpha1"
metadata: #HolosObjectMeta
spec: #PlatformSpec
holos: #Holos
}
// #Holos represents the holos reserved field in the #Platform schema defined by the holos development team.
#Holos: {
// flags represents config values provided by holos command line flags.
flags: {
// cluster represents the holos render --cluster-name flag.
cluster: string @tag(cluster, type=string)
}
}
// #PlatformSpec represents configuration values defined by the platform
// designer. Config values are organized by section, then simple strings for
// each section.
#PlatformSpec: {
config: [string]: _
config: user: #UserDefinedConfig
}
// #PlatformUserConfig represents configuration fields and values defined by the
// user.
#UserDefinedConfig: {
sections: [string]: fields: [string]: _
}
// #PlatformConfig represents the platform config data returned from the Holos API. Useful for cue vet.
#PlatformConfig: {
platform: spec: #PlatformSpec
}

View File

@@ -1,20 +0,0 @@
{
"platform": {
"spec": {
"config": {
"user": {
"sections": {
"org": {
"fields": {
"contactEmail": "jeff@openinfrastructure.co",
"displayName": "Open Infrastructure Services LLC",
"domain": "ois.run",
"name": "ois"
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
Bare Platform
| Folder | Description |
| - | - |
| forms | Contains Platform and Project form and model definitions |
| platform | Contains the Platform resource that defines how to render the configuration for all Platform Components |
| components | Contains BuildPlan resources which define how to render individual Platform Components |
## Forms
To populate the form, the platform must already be created in the Web UI:
```bash
platformId="018f36fb-e3ff-7f7f-a5d1-7ca2bf499e94"
cue export ./forms/platform/ --out json \
| jq '{platform_id: "'$platformId'", fields: .spec.fields}' \
| grpcurl -H "x-oidc-id-token: $(holos token)" -d @ \
app.dev.k2.holos.run:443 \
holos.v1alpha1.PlatformService.PutForm
```

View File

@@ -1 +0,0 @@
package holos

View File

@@ -0,0 +1,33 @@
package v1alpha1
import "encoding/yaml"
import core "k8s.io/api/core/v1"
// #APIObjects defines the output format for kubernetes api objects. The holos
// cli expects the yaml representation of each api object in the apiObjectMap
// field.
#APIObjects: {
// apiObjects represents the un-marshalled form of each kubernetes api object
// managed by a holos component.
apiObjects: {
[Kind=string]: {
[string]: {
kind: Kind
...
}
}
ConfigMap: [string]: core.#ConfigMap & {apiVersion: "v1"}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
package v1alpha1
#BuildPlan: {
apiVersion: #APIVersion
kind: #BuildPlanKind
}

View File

@@ -1,35 +1,31 @@
package v1alpha1
#Platform: {
name: string // short dns label name
displayName: string // Display name
description: string // Plaform description
// #Form represents a web app form to provide to the Holos API for display in
// the web app. A form is implemented as an Formly FieldConfig array using
// Angular Material form field components.
#Form: {
#TypeMeta
apiVersion: #APIVersion
kind: "Form"
sections: {[NAME=string]: #ConfigSection & {name: NAME}}
spec: fields: [...#FieldConfig]
}
Form: {
let Name = name
apiVersion: "forms.holos.run/v1alpha1"
kind: "PlatformForm"
metadata: name: Name
spec: #PlatformFormSpec
// #FormBuilder provides a concrete #Form via the Output field.
#FormBuilder: {
Name: string
Sections: {[NAME=string]: #FormSection & {name: NAME}}
Output: #Form & {
spec: fields: [for s in Sections {s.wrapper}]
}
let Sections = sections
// Collapse all sections into one fields list.
// Refer to https://formly.dev/docs/examples/other/nested-formly-forms
Form: spec: fields: [for s in Sections {s.wrapper}]
}
#PlatformFormSpec: {
fields: [...#FieldConfig]
}
// #ConfigSection represents a configuration section of the front end UI. For
// example, Organization config values. The fields of the section map to form
// input fields.
#ConfigSection: {
// #FormSection represents a configuration section of the front end UI. The
// wrapper field provides a concrete #FieldConfig for the form section. The
// fields of the section map to form input fields.
// Refer to: to https://formly.dev/docs/examples/other/nested-formly-forms
#FormSection: {
name: string // e.g. "org"
displayName: string // e.g. "Organization"
description: string
@@ -58,14 +54,7 @@ package v1alpha1
}
}
// REMOVE
#ConfigSectionOutput: {
name: string
displayName: string
description: string
fieldConfigs: [...#FieldConfig]
}
// #FieldConfig represents a Formly Field Config.
// Refer to https://formly.dev/docs/api/core#formlyfieldconfig
// Refer to https://formly.dev/docs/api/ui/material/select
#FieldConfig: {

View File

@@ -0,0 +1,16 @@
package v1alpha1
// #HolosComponent defines struct fields common to all holos platform
// component types.
#HolosComponent: {
metadata: name: string
_NameLengthConstraint: len(metadata.name) & >=1
...
}
// #KubernetesObjects is a Holos Component BuildPlan which has k8s api objects
// embedded into the build plan itself.
#KubernetesObjects: #HolosComponent & {
apiVersion: #APIVersion
kind: #KubernetesObjectsKind
}

View File

@@ -0,0 +1,9 @@
package v1alpha1
import corev1 "k8s.io/api/core/v1"
#ConfigMap: corev1.#ConfigMap & {
apiVersion: "v1"
kind: "ConfigMap"
...
}

View File

@@ -0,0 +1 @@
package v1alpha1

View File

@@ -0,0 +1,20 @@
package v1alpha1
// #Platform represents a platform to manage. Holos manages a platform by
// rendering platform components and applying the configuration to clusters as
// defined by the platform resource.
#Platform: {
#TypeMeta
apiVersion: #APIVersion
kind: "Platform"
metadata: #ObjectMeta
spec: {
// model represents the user defined platform model, which is produced and
// defined by the user supplied form.
model: {...}
// components represents components to manage in the platform, organized by
// the kind of cluster the rendered configuration applies to.
components: {}
}
}

View File

@@ -0,0 +1 @@
package v1alpha1

View File

@@ -0,0 +1,16 @@
package v1alpha1
// #TypeMeta is similar to kubernetes api TypeMeta, but for holos api
// objects such as the Platform config resource.
#TypeMeta: {
kind: string @go(Kind)
apiVersion: string @go(APIVersion)
}
// #ObjectMeta is similar to kubernetes api ObjectMeta, but for holos api
// objects.
#ObjectMeta: {
name: string @go(Name)
labels: {[string]: string} @go(Labels,map[string]string)
annotations: {[string]: string} @go(Annotations,map[string]string)
}

View File

@@ -1 +1 @@
73
74

View File

@@ -1 +1 @@
3
0