Compare commits

..

12 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
Jeff McCune
47a5e237e0 (#162) Lint go, typescript, and proto3 files
This patch adds lint coverage for proto3 and typescript to keep our code
reasonably clean.  The go linter was already enabled.
2024-05-06 14:17:08 -07:00
Jeff McCune
1279e2351a (#162) Move Platform back to holos.v1alpha1
No need to have a separate package for the PlatformService and related
protobuf messages.
2024-05-06 13:47:37 -07:00
Jeff McCune
adb8177026 Merge pull request #166 from holos-run/jeff/165-deploy-holos
(#165) Deploy Holos to Dev
2024-05-06 11:23:48 -07:00
Jeff McCune
4e8fa5abda (#165) Bump dev deployment to 0.73.1 2024-05-06 11:22:24 -07:00
Jeff McCune
6894f45b6c (#165) Deploy Holos to Dev
This patch deploys holos to the dev environment on the k2 cluster.  It's
accessible at https://app.dev.k2.holos.run/ behind the auth proxy by
default.
2024-05-06 11:10:29 -07:00
Jeff McCune
89d25be837 (#161) Wrap main router outlet in <main></main> 2024-05-06 09:16:15 -07:00
Jeff McCune
5b33e48552 (#161) Reasonably advanced form modeling the reference platform
This form goes a good way toward capturing what we need to configure the
entire reference platform.  Elements and sections are responsive to
which cloud providers are selected, which achieves my goal of modeling a
reasonably advanced form using only JSON data produced by CUE.

To write the form via the API:

    cue export ./forms/platform/ --out json \
      | jq '{platform_id: "'${platformId}'", fields: .spec.fields}' \
      | grpcurl -H "x-oidc-id-token: $(holos token)" -d @ ${host}:443 \
      holos.platform.v1alpha1.PlatformService.PutForm
2024-05-04 20:16:09 -07:00
Jeff McCune
79e8ab639a (#161) Fix the FormGroup & Refactor API
The way we were organizing fields into section broke Formly validation.
This patch fixes the problem by using the recommended approach of
[Nested Forms][1].

This patch also refactors the PlatformService API to clean it up.
GetForm / PutForm are separated from the Platform methods.  Similarly
GetModel / PutModel are separated out and are specific to get and put
the model data.

NOTE: I'm not sure we should have separated out the platform service
into it's own protobuf package.  Seems maybe unnecessary.

❯ grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"platform_id":"018f36fb-e3ff-7f7f-a5d1-7ca2bf499e94"}' jeff.app.dev.k2.holos.run:443 holos.platform.v1alpha1.PlatformService.GetModel
{
  "model": {
    "org": {
      "contactEmail": "platform@openinfrastructure.co",
      "displayName": "Open Infrastructure Services LLC",
      "domain": "ois.run",
      "name": "ois"
    },
    "privacy": {
      "country": "earth",
      "regions": [
        "us-east-2",
        "us-west-2"
      ]
    },
    "terms": {
      "didAgree": true
    }
  }
}

[1]: https://formly.dev/docs/examples/other/nested-formly-forms
2024-05-04 10:14:37 -07:00
Jeff McCune
a0cc673736 (#150) Wire up select and multi select boxes
This patch wires up a Select and a Multi Select box.  This patch also
establishes a decision as it relates to Formly TypeScript / gRPC Proto3
/ CUE definitions of the form data structure.  The decision is to use
gRPC as a transport for any JSON to avoid friction trying to fit Formly
types into Proto3 messages.

Note when using google.protobuf.Value messages with bufbuild/connect-es,
we need to round trip them one last time through JSON to get the
original JSON on the other side.  This is because connect-es preserves
the type discriminators in the case and value fields of the message.

Refer to: [Accessing oneof
groups](https://github.com/bufbuild/protobuf-es/blob/main/docs/runtime_api.md#accessing-oneof-groups)

NOTE: On the wire, carry any JSON as field configs for expedience.  I
attempted to reflect FormlyFieldConfig in protobuf, but it was too time
consuming.  The loosely defined Formly json data API creates significant
friction when joined with a well defined protobuf API.  Therefore, we do
not specify anything about the Forms API, convey any valid JSON, and
leave it up to CUE and Formly on the sending and receiving side of the
API.

We use CUE to define our own holos form elements as a subset of the loose
Formly definitions.  We further hope Formly will move toward a better JSON
data API, but it's unlikely.  Consider replacing Formly entirely and
building on top of the strongly typed Angular Dyanmic Forms API.

Refer to: https://github.com/ngx-formly/ngx-formly/blob/v6.3.0/src/core/src/lib/models/fieldconfig.ts#L15
Consider: https://angular.io/guide/dynamic-form

Usage:

Generate the form from CUE

    cue export ./forms/platform/ --out json | jq -cM | pbcopy

Store the form JSON in the config_values column of the platforms table.

View the form, and submit some data. Then get the data back out for use rendering the platform:

    grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"platform_id":"'${platformId}'"}' $holos holos.v1alpha1.PlatformService.GetConfig

```json
{
  "platform": {
    "spec": {
      "config": {
        "user": {
          "sections": {
            "org": {
              "fields": {
                "contactEmail": "jeff@openinfrastructure.co",
                "displayName": "Open Infrastructure Services LLC",
                "domain": "ois.run",
                "name": "ois"
              }
            },
            "privacy": {
              "fields": {
                "country": "earth",
                "regions": [
                  "us-east-2",
                  "us-west-2"
                ]
              }
            },
            "terms": {
              "fields": {
                "didAgree": true
              }
            }
          }
        }
      }
    }
  }
}
```
2024-05-03 10:42:03 -07:00
Jeff McCune
d06ecfadc8 (#150) Refactor PlatformService.GetConfig for use with CUE
Problem:
The GetConfig response value isn't directly usable with CUE without some
gymnastics.

Solution:
Refactor the protobuf definition and response output to make the user
defined and supplied config values provided by the API directly usable
in the CUE code that defines the platform.

Result:

The top level platform config is directly usable in the
`internal/platforms/bare` directory:

    grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"platform_id":"'${platformID}'"}' $host \
      holos.v1alpha1.PlatformService.GetConfig \
      > platform.holos.json

Vet the user supplied data:

    cue vet ./ -d '#PlatformConfig' platform.holos.json

Build the holos component.  The ConfigMap consumes the user supplied
data:

    cue export --out yaml -t cluster=k2 ./components/configmap platform.holos.json \
      | yq .spec.components

Note the data provided by the input form is embedded into the
ConfigMap managed by Holos:

```yaml
KubernetesObjectsList:
  - metadata:
      name: platform-configmap
    apiObjectMap:
      ConfigMap:
        platform: |
          metadata:
            name: platform
            namespace: default
            labels:
              app.holos.run/managed: "true"
          data:
            platform: |
              kind: Platform
              spec:
                config:
                  user:
                    sections:
                      org:
                        fields:
                          contactEmail: jeff@openinfrastructure.co
                          displayName: Open Infrastructure Services LLC
                          domain: ois.run
                          name: ois
              apiVersion: app.holos.run/v1alpha1
              metadata:
                name: bare
                labels: {}
                annotations: {}
              holos:
                flags:
                  cluster: k2
          kind: ConfigMap
          apiVersion: v1
    Skip: false
```
2024-05-02 06:39:33 -07:00
79 changed files with 5534 additions and 2086 deletions

View File

@@ -87,6 +87,8 @@ test: ## Run tests.
.PHONY: lint
lint: ## Run linters.
buf lint
cd internal/frontend/holos && ng lint
golangci-lint run
.PHONY: coverage

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

@@ -18,7 +18,3 @@ plugins:
out: internal/frontend/holos/src/app/gen
opt:
- target=ts
- plugin: connect-query
out: internal/frontend/holos/src/app/gen
opt:
- target=ts

View File

@@ -18,6 +18,7 @@ import "encoding/yaml"
Issuer?: [Name=_]: #Issuer & {metadata: name: Name}
Gateway?: [Name=_]: #Gateway & {metadata: name: Name}
ConfigMap?: [Name=_]: #ConfigMap & {metadata: name: Name}
ServiceAccount?: [Name=_]: #ServiceAccount & {metadata: name: Name}
Deployment?: [_]: #Deployment
StatefulSet?: [_]: #StatefulSet

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: [{

View File

@@ -0,0 +1,73 @@
package holos
let Namespace = "dev-holos"
let Holos = "holos"
// spec represents the output provided to holos
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "dev-holos-app"
apiObjectMap: OBJECTS.apiObjectMap
},
]
// OBJECTS represents the kubernetes api objects to manage.
let OBJECTS = #APIObjects & {
apiObjects: Deployment: holos: {
metadata: {
name: Holos
namespace: Namespace
labels: app: Holos
}
spec: {
selector: matchLabels: app: Holos
template: metadata: labels: {
app: Holos
"sidecar.istio.io/inject": "true"
}
strategy: rollingUpdate: maxSurge: 1
strategy: rollingUpdate: maxUnavailable: 0
template: {
spec: {
serviceAccountName: Holos
securityContext: seccompProfile: type: "RuntimeDefault"
containers: [
{
name: Holos
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:0.73.1"
imagePullPolicy: "Always"
env: [
{
name: "TZ"
value: "America/Los_Angeles"
},
{
name: "DATABASE_URL"
valueFrom: secretKeyRef: {
key: "uri"
name: "holos-pguser-holos"
}
},
]
ports: [
{
containerPort: 3000
name: "http"
protocol: "TCP"
},
]
securityContext: capabilities: drop: ["ALL"]
securityContext: allowPrivilegeEscalation: false
securityContext: runAsNonRoot: true
resources: limits: {
cpu: "0.25"
memory: "256Mi"
}
resources: requests: resources.limits
},
]
}
}
}
}
}

View File

@@ -0,0 +1,129 @@
package holos
let Namespace = "dev-holos"
let Holos = "holos"
// spec represents the output provided to holos
spec: components: KubernetesObjectsList: [
#KubernetesObjects & {
metadata: name: "dev-holos-infra"
apiObjectMap: OBJECTS.apiObjectMap
},
]
let Metadata = {
name: Holos
namespace: Namespace
labels: app: Holos
}
// OBJECTS represents the kubernetes api objects to manage.
let OBJECTS = #APIObjects & {
// Postgres
// Deployment
// VirtualService
apiObjects: ServiceAccount: holos: {
metadata: Metadata
imagePullSecrets: [{name: "kube-system-ecr-image-pull-creds"}]
}
apiObjects: PostgresCluster: holos: {
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
metadata: Metadata
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.1-0"
instances: [{
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
podAffinityTerm: {
labelSelector: matchLabels: "postgres-operator.crunchydata.com/cluster": "holos"
topologyKey: "kubernetes.io/hostname"
}
weight: 1
}]
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "1Gi"
}
name: "db"
replicas: 1
}]
port: 5432
postgresVersion: 16
users: [{
databases: ["holos"]
name: "holos"
options: "SUPERUSER"
}]
backups: pgbackrest: {
global: {
"archive-async": "y"
"archive-push-queue-max": "100MiB"
"spool-path": "/pgdata/backups"
}
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.47-2"
repos: [{
name: "repo1"
volume: volumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "1Gi"
}
}]
}
}
}
apiObjects: Service: holos: {
apiVersion: "v1"
metadata: Metadata
spec: {
type: "ClusterIP"
selector: app: "holos"
ports: [{
appProtocol: "http2"
name: "http"
port: 3000
protocol: "TCP"
targetPort: 3000
}, {
appProtocol: "http"
name: "metrics"
port: 9090
protocol: "TCP"
targetPort: 9090
}]
}
}
apiObjects: VirtualService: holos: {
apiVersion: "networking.istio.io/v1beta1"
metadata: Metadata
spec: {
gateways: ["istio-ingress/default"]
hosts: [
"app.dev.holos.run",
"app.dev.\(#ClusterName).holos.run",
]
http: [{
match: [{
uri: prefix: "/ui"
}]
name: "ui"
route: [{
destination: {
host: "holos"
port: number: 3000
}
}]
}, {
name: "api"
route: [{
destination: {
host: "holos"
port: number: 3000
}
}]
}]
}
}
}

View File

@@ -3,5 +3,6 @@ USER root
WORKDIR /app
ADD bin bin
RUN chown -R app: /app
USER app
# Kubernetes requires the user to be numeric
USER 8192
ENTRYPOINT bin/holos server

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

@@ -38,10 +38,10 @@ var (
{Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString},
{Name: "display_name", Type: field.TypeString},
{Name: "config_form", Type: field.TypeJSON, Nullable: true},
{Name: "config_values", Type: field.TypeJSON, Nullable: true},
{Name: "config_cue", Type: field.TypeBytes, Nullable: true},
{Name: "config_definition", Type: field.TypeString, Nullable: true},
{Name: "form", Type: field.TypeJSON, Nullable: true},
{Name: "model", Type: field.TypeJSON, Nullable: true},
{Name: "cue", Type: field.TypeBytes, Nullable: true},
{Name: "cue_definition", Type: field.TypeString, Nullable: true},
{Name: "creator_id", Type: field.TypeUUID},
{Name: "org_id", Type: field.TypeUUID},
}

View File

@@ -813,10 +813,10 @@ type PlatformMutation struct {
updated_at *time.Time
name *string
display_name *string
config_form **holos.PlatformForm
config_values **holos.ConfigValues
config_cue *[]byte
config_definition *string
form **holos.Form
model **holos.Model
cue *[]byte
cue_definition *string
clearedFields map[string]struct{}
creator *uuid.UUID
clearedcreator bool
@@ -1147,200 +1147,200 @@ func (m *PlatformMutation) ResetCreatorID() {
m.creator = nil
}
// SetConfigForm sets the "config_form" field.
func (m *PlatformMutation) SetConfigForm(hf *holos.PlatformForm) {
m.config_form = &hf
// SetForm sets the "form" field.
func (m *PlatformMutation) SetForm(h *holos.Form) {
m.form = &h
}
// ConfigForm returns the value of the "config_form" field in the mutation.
func (m *PlatformMutation) ConfigForm() (r *holos.PlatformForm, exists bool) {
v := m.config_form
// Form returns the value of the "form" field in the mutation.
func (m *PlatformMutation) Form() (r *holos.Form, exists bool) {
v := m.form
if v == nil {
return
}
return *v, true
}
// OldConfigForm returns the old "config_form" field's value of the Platform entity.
// OldForm returns the old "form" field's value of the Platform entity.
// If the Platform object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PlatformMutation) OldConfigForm(ctx context.Context) (v *holos.PlatformForm, err error) {
func (m *PlatformMutation) OldForm(ctx context.Context) (v *holos.Form, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigForm is only allowed on UpdateOne operations")
return v, errors.New("OldForm is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldConfigForm requires an ID field in the mutation")
return v, errors.New("OldForm requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldConfigForm: %w", err)
return v, fmt.Errorf("querying old value for OldForm: %w", err)
}
return oldValue.ConfigForm, nil
return oldValue.Form, nil
}
// ClearConfigForm clears the value of the "config_form" field.
func (m *PlatformMutation) ClearConfigForm() {
m.config_form = nil
m.clearedFields[platform.FieldConfigForm] = struct{}{}
// ClearForm clears the value of the "form" field.
func (m *PlatformMutation) ClearForm() {
m.form = nil
m.clearedFields[platform.FieldForm] = struct{}{}
}
// ConfigFormCleared returns if the "config_form" field was cleared in this mutation.
func (m *PlatformMutation) ConfigFormCleared() bool {
_, ok := m.clearedFields[platform.FieldConfigForm]
// FormCleared returns if the "form" field was cleared in this mutation.
func (m *PlatformMutation) FormCleared() bool {
_, ok := m.clearedFields[platform.FieldForm]
return ok
}
// ResetConfigForm resets all changes to the "config_form" field.
func (m *PlatformMutation) ResetConfigForm() {
m.config_form = nil
delete(m.clearedFields, platform.FieldConfigForm)
// ResetForm resets all changes to the "form" field.
func (m *PlatformMutation) ResetForm() {
m.form = nil
delete(m.clearedFields, platform.FieldForm)
}
// SetConfigValues sets the "config_values" field.
func (m *PlatformMutation) SetConfigValues(hv *holos.ConfigValues) {
m.config_values = &hv
// SetModel sets the "model" field.
func (m *PlatformMutation) SetModel(h *holos.Model) {
m.model = &h
}
// ConfigValues returns the value of the "config_values" field in the mutation.
func (m *PlatformMutation) ConfigValues() (r *holos.ConfigValues, exists bool) {
v := m.config_values
// Model returns the value of the "model" field in the mutation.
func (m *PlatformMutation) Model() (r *holos.Model, exists bool) {
v := m.model
if v == nil {
return
}
return *v, true
}
// OldConfigValues returns the old "config_values" field's value of the Platform entity.
// OldModel returns the old "model" field's value of the Platform entity.
// If the Platform object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PlatformMutation) OldConfigValues(ctx context.Context) (v *holos.ConfigValues, err error) {
func (m *PlatformMutation) OldModel(ctx context.Context) (v *holos.Model, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigValues is only allowed on UpdateOne operations")
return v, errors.New("OldModel is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldConfigValues requires an ID field in the mutation")
return v, errors.New("OldModel requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldConfigValues: %w", err)
return v, fmt.Errorf("querying old value for OldModel: %w", err)
}
return oldValue.ConfigValues, nil
return oldValue.Model, nil
}
// ClearConfigValues clears the value of the "config_values" field.
func (m *PlatformMutation) ClearConfigValues() {
m.config_values = nil
m.clearedFields[platform.FieldConfigValues] = struct{}{}
// ClearModel clears the value of the "model" field.
func (m *PlatformMutation) ClearModel() {
m.model = nil
m.clearedFields[platform.FieldModel] = struct{}{}
}
// ConfigValuesCleared returns if the "config_values" field was cleared in this mutation.
func (m *PlatformMutation) ConfigValuesCleared() bool {
_, ok := m.clearedFields[platform.FieldConfigValues]
// ModelCleared returns if the "model" field was cleared in this mutation.
func (m *PlatformMutation) ModelCleared() bool {
_, ok := m.clearedFields[platform.FieldModel]
return ok
}
// ResetConfigValues resets all changes to the "config_values" field.
func (m *PlatformMutation) ResetConfigValues() {
m.config_values = nil
delete(m.clearedFields, platform.FieldConfigValues)
// ResetModel resets all changes to the "model" field.
func (m *PlatformMutation) ResetModel() {
m.model = nil
delete(m.clearedFields, platform.FieldModel)
}
// SetConfigCue sets the "config_cue" field.
func (m *PlatformMutation) SetConfigCue(b []byte) {
m.config_cue = &b
// SetCue sets the "cue" field.
func (m *PlatformMutation) SetCue(b []byte) {
m.cue = &b
}
// ConfigCue returns the value of the "config_cue" field in the mutation.
func (m *PlatformMutation) ConfigCue() (r []byte, exists bool) {
v := m.config_cue
// Cue returns the value of the "cue" field in the mutation.
func (m *PlatformMutation) Cue() (r []byte, exists bool) {
v := m.cue
if v == nil {
return
}
return *v, true
}
// OldConfigCue returns the old "config_cue" field's value of the Platform entity.
// OldCue returns the old "cue" field's value of the Platform entity.
// If the Platform object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PlatformMutation) OldConfigCue(ctx context.Context) (v []byte, err error) {
func (m *PlatformMutation) OldCue(ctx context.Context) (v []byte, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigCue is only allowed on UpdateOne operations")
return v, errors.New("OldCue is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldConfigCue requires an ID field in the mutation")
return v, errors.New("OldCue requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldConfigCue: %w", err)
return v, fmt.Errorf("querying old value for OldCue: %w", err)
}
return oldValue.ConfigCue, nil
return oldValue.Cue, nil
}
// ClearConfigCue clears the value of the "config_cue" field.
func (m *PlatformMutation) ClearConfigCue() {
m.config_cue = nil
m.clearedFields[platform.FieldConfigCue] = struct{}{}
// ClearCue clears the value of the "cue" field.
func (m *PlatformMutation) ClearCue() {
m.cue = nil
m.clearedFields[platform.FieldCue] = struct{}{}
}
// ConfigCueCleared returns if the "config_cue" field was cleared in this mutation.
func (m *PlatformMutation) ConfigCueCleared() bool {
_, ok := m.clearedFields[platform.FieldConfigCue]
// CueCleared returns if the "cue" field was cleared in this mutation.
func (m *PlatformMutation) CueCleared() bool {
_, ok := m.clearedFields[platform.FieldCue]
return ok
}
// ResetConfigCue resets all changes to the "config_cue" field.
func (m *PlatformMutation) ResetConfigCue() {
m.config_cue = nil
delete(m.clearedFields, platform.FieldConfigCue)
// ResetCue resets all changes to the "cue" field.
func (m *PlatformMutation) ResetCue() {
m.cue = nil
delete(m.clearedFields, platform.FieldCue)
}
// SetConfigDefinition sets the "config_definition" field.
func (m *PlatformMutation) SetConfigDefinition(s string) {
m.config_definition = &s
// SetCueDefinition sets the "cue_definition" field.
func (m *PlatformMutation) SetCueDefinition(s string) {
m.cue_definition = &s
}
// ConfigDefinition returns the value of the "config_definition" field in the mutation.
func (m *PlatformMutation) ConfigDefinition() (r string, exists bool) {
v := m.config_definition
// CueDefinition returns the value of the "cue_definition" field in the mutation.
func (m *PlatformMutation) CueDefinition() (r string, exists bool) {
v := m.cue_definition
if v == nil {
return
}
return *v, true
}
// OldConfigDefinition returns the old "config_definition" field's value of the Platform entity.
// OldCueDefinition returns the old "cue_definition" field's value of the Platform entity.
// If the Platform object wasn't provided to the builder, the object is fetched from the database.
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
func (m *PlatformMutation) OldConfigDefinition(ctx context.Context) (v string, err error) {
func (m *PlatformMutation) OldCueDefinition(ctx context.Context) (v string, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigDefinition is only allowed on UpdateOne operations")
return v, errors.New("OldCueDefinition is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldConfigDefinition requires an ID field in the mutation")
return v, errors.New("OldCueDefinition requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldConfigDefinition: %w", err)
return v, fmt.Errorf("querying old value for OldCueDefinition: %w", err)
}
return oldValue.ConfigDefinition, nil
return oldValue.CueDefinition, nil
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (m *PlatformMutation) ClearConfigDefinition() {
m.config_definition = nil
m.clearedFields[platform.FieldConfigDefinition] = struct{}{}
// ClearCueDefinition clears the value of the "cue_definition" field.
func (m *PlatformMutation) ClearCueDefinition() {
m.cue_definition = nil
m.clearedFields[platform.FieldCueDefinition] = struct{}{}
}
// ConfigDefinitionCleared returns if the "config_definition" field was cleared in this mutation.
func (m *PlatformMutation) ConfigDefinitionCleared() bool {
_, ok := m.clearedFields[platform.FieldConfigDefinition]
// CueDefinitionCleared returns if the "cue_definition" field was cleared in this mutation.
func (m *PlatformMutation) CueDefinitionCleared() bool {
_, ok := m.clearedFields[platform.FieldCueDefinition]
return ok
}
// ResetConfigDefinition resets all changes to the "config_definition" field.
func (m *PlatformMutation) ResetConfigDefinition() {
m.config_definition = nil
delete(m.clearedFields, platform.FieldConfigDefinition)
// ResetCueDefinition resets all changes to the "cue_definition" field.
func (m *PlatformMutation) ResetCueDefinition() {
m.cue_definition = nil
delete(m.clearedFields, platform.FieldCueDefinition)
}
// ClearCreator clears the "creator" edge to the User entity.
@@ -1463,17 +1463,17 @@ func (m *PlatformMutation) Fields() []string {
if m.creator != nil {
fields = append(fields, platform.FieldCreatorID)
}
if m.config_form != nil {
fields = append(fields, platform.FieldConfigForm)
if m.form != nil {
fields = append(fields, platform.FieldForm)
}
if m.config_values != nil {
fields = append(fields, platform.FieldConfigValues)
if m.model != nil {
fields = append(fields, platform.FieldModel)
}
if m.config_cue != nil {
fields = append(fields, platform.FieldConfigCue)
if m.cue != nil {
fields = append(fields, platform.FieldCue)
}
if m.config_definition != nil {
fields = append(fields, platform.FieldConfigDefinition)
if m.cue_definition != nil {
fields = append(fields, platform.FieldCueDefinition)
}
return fields
}
@@ -1495,14 +1495,14 @@ func (m *PlatformMutation) Field(name string) (ent.Value, bool) {
return m.DisplayName()
case platform.FieldCreatorID:
return m.CreatorID()
case platform.FieldConfigForm:
return m.ConfigForm()
case platform.FieldConfigValues:
return m.ConfigValues()
case platform.FieldConfigCue:
return m.ConfigCue()
case platform.FieldConfigDefinition:
return m.ConfigDefinition()
case platform.FieldForm:
return m.Form()
case platform.FieldModel:
return m.Model()
case platform.FieldCue:
return m.Cue()
case platform.FieldCueDefinition:
return m.CueDefinition()
}
return nil, false
}
@@ -1524,14 +1524,14 @@ func (m *PlatformMutation) OldField(ctx context.Context, name string) (ent.Value
return m.OldDisplayName(ctx)
case platform.FieldCreatorID:
return m.OldCreatorID(ctx)
case platform.FieldConfigForm:
return m.OldConfigForm(ctx)
case platform.FieldConfigValues:
return m.OldConfigValues(ctx)
case platform.FieldConfigCue:
return m.OldConfigCue(ctx)
case platform.FieldConfigDefinition:
return m.OldConfigDefinition(ctx)
case platform.FieldForm:
return m.OldForm(ctx)
case platform.FieldModel:
return m.OldModel(ctx)
case platform.FieldCue:
return m.OldCue(ctx)
case platform.FieldCueDefinition:
return m.OldCueDefinition(ctx)
}
return nil, fmt.Errorf("unknown Platform field %s", name)
}
@@ -1583,33 +1583,33 @@ func (m *PlatformMutation) SetField(name string, value ent.Value) error {
}
m.SetCreatorID(v)
return nil
case platform.FieldConfigForm:
v, ok := value.(*holos.PlatformForm)
case platform.FieldForm:
v, ok := value.(*holos.Form)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetConfigForm(v)
m.SetForm(v)
return nil
case platform.FieldConfigValues:
v, ok := value.(*holos.ConfigValues)
case platform.FieldModel:
v, ok := value.(*holos.Model)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetConfigValues(v)
m.SetModel(v)
return nil
case platform.FieldConfigCue:
case platform.FieldCue:
v, ok := value.([]byte)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetConfigCue(v)
m.SetCue(v)
return nil
case platform.FieldConfigDefinition:
case platform.FieldCueDefinition:
v, ok := value.(string)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetConfigDefinition(v)
m.SetCueDefinition(v)
return nil
}
return fmt.Errorf("unknown Platform field %s", name)
@@ -1641,17 +1641,17 @@ func (m *PlatformMutation) AddField(name string, value ent.Value) error {
// mutation.
func (m *PlatformMutation) ClearedFields() []string {
var fields []string
if m.FieldCleared(platform.FieldConfigForm) {
fields = append(fields, platform.FieldConfigForm)
if m.FieldCleared(platform.FieldForm) {
fields = append(fields, platform.FieldForm)
}
if m.FieldCleared(platform.FieldConfigValues) {
fields = append(fields, platform.FieldConfigValues)
if m.FieldCleared(platform.FieldModel) {
fields = append(fields, platform.FieldModel)
}
if m.FieldCleared(platform.FieldConfigCue) {
fields = append(fields, platform.FieldConfigCue)
if m.FieldCleared(platform.FieldCue) {
fields = append(fields, platform.FieldCue)
}
if m.FieldCleared(platform.FieldConfigDefinition) {
fields = append(fields, platform.FieldConfigDefinition)
if m.FieldCleared(platform.FieldCueDefinition) {
fields = append(fields, platform.FieldCueDefinition)
}
return fields
}
@@ -1667,17 +1667,17 @@ func (m *PlatformMutation) FieldCleared(name string) bool {
// error if the field is not defined in the schema.
func (m *PlatformMutation) ClearField(name string) error {
switch name {
case platform.FieldConfigForm:
m.ClearConfigForm()
case platform.FieldForm:
m.ClearForm()
return nil
case platform.FieldConfigValues:
m.ClearConfigValues()
case platform.FieldModel:
m.ClearModel()
return nil
case platform.FieldConfigCue:
m.ClearConfigCue()
case platform.FieldCue:
m.ClearCue()
return nil
case platform.FieldConfigDefinition:
m.ClearConfigDefinition()
case platform.FieldCueDefinition:
m.ClearCueDefinition()
return nil
}
return fmt.Errorf("unknown Platform nullable field %s", name)
@@ -1705,17 +1705,17 @@ func (m *PlatformMutation) ResetField(name string) error {
case platform.FieldCreatorID:
m.ResetCreatorID()
return nil
case platform.FieldConfigForm:
m.ResetConfigForm()
case platform.FieldForm:
m.ResetForm()
return nil
case platform.FieldConfigValues:
m.ResetConfigValues()
case platform.FieldModel:
m.ResetModel()
return nil
case platform.FieldConfigCue:
m.ResetConfigCue()
case platform.FieldCue:
m.ResetCue()
return nil
case platform.FieldConfigDefinition:
m.ResetConfigDefinition()
case platform.FieldCueDefinition:
m.ResetCueDefinition()
return nil
}
return fmt.Errorf("unknown Platform field %s", name)

View File

@@ -34,14 +34,14 @@ type Platform struct {
DisplayName string `json:"display_name,omitempty"`
// CreatorID holds the value of the "creator_id" field.
CreatorID uuid.UUID `json:"creator_id,omitempty"`
// JSON holos.PlatformForm representing the platform data entry form.
ConfigForm *holos.PlatformForm `json:"config_form,omitempty"`
// JSON holos.ConfigValues representing the platform config values.
ConfigValues *holos.ConfigValues `json:"config_values,omitempty"`
// Opaque bytes representing the CUE definition of the config struct.
ConfigCue []byte `json:"config_cue,omitempty"`
// JSON representation of FormlyFormConfig[] refer to https://github.com/holos-run/holos/issues/161
Form *holos.Form `json:"form,omitempty"`
// JSON representation of the form model which holds user input values refer to https://github.com/holos-run/holos/issues/161
Model *holos.Model `json:"model,omitempty"`
// CUE definition to vet the model against e.g. #PlatformConfig
Cue []byte `json:"cue,omitempty"`
// The definition name to vet config_values against config_cue e.g. '#PlatformSpec'
ConfigDefinition string `json:"config_definition,omitempty"`
CueDefinition string `json:"cue_definition,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the PlatformQuery when eager-loading is set.
Edges PlatformEdges `json:"edges"`
@@ -86,9 +86,9 @@ func (*Platform) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case platform.FieldConfigForm, platform.FieldConfigValues, platform.FieldConfigCue:
case platform.FieldForm, platform.FieldModel, platform.FieldCue:
values[i] = new([]byte)
case platform.FieldName, platform.FieldDisplayName, platform.FieldConfigDefinition:
case platform.FieldName, platform.FieldDisplayName, platform.FieldCueDefinition:
values[i] = new(sql.NullString)
case platform.FieldCreatedAt, platform.FieldUpdatedAt:
values[i] = new(sql.NullTime)
@@ -151,33 +151,33 @@ func (pl *Platform) assignValues(columns []string, values []any) error {
} else if value != nil {
pl.CreatorID = *value
}
case platform.FieldConfigForm:
case platform.FieldForm:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field config_form", values[i])
return fmt.Errorf("unexpected type %T for field form", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &pl.ConfigForm); err != nil {
return fmt.Errorf("unmarshal field config_form: %w", err)
if err := json.Unmarshal(*value, &pl.Form); err != nil {
return fmt.Errorf("unmarshal field form: %w", err)
}
}
case platform.FieldConfigValues:
case platform.FieldModel:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field config_values", values[i])
return fmt.Errorf("unexpected type %T for field model", values[i])
} else if value != nil && len(*value) > 0 {
if err := json.Unmarshal(*value, &pl.ConfigValues); err != nil {
return fmt.Errorf("unmarshal field config_values: %w", err)
if err := json.Unmarshal(*value, &pl.Model); err != nil {
return fmt.Errorf("unmarshal field model: %w", err)
}
}
case platform.FieldConfigCue:
case platform.FieldCue:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field config_cue", values[i])
return fmt.Errorf("unexpected type %T for field cue", values[i])
} else if value != nil {
pl.ConfigCue = *value
pl.Cue = *value
}
case platform.FieldConfigDefinition:
case platform.FieldCueDefinition:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field config_definition", values[i])
return fmt.Errorf("unexpected type %T for field cue_definition", values[i])
} else if value.Valid {
pl.ConfigDefinition = value.String
pl.CueDefinition = value.String
}
default:
pl.selectValues.Set(columns[i], values[i])
@@ -243,17 +243,17 @@ func (pl *Platform) String() string {
builder.WriteString("creator_id=")
builder.WriteString(fmt.Sprintf("%v", pl.CreatorID))
builder.WriteString(", ")
builder.WriteString("config_form=")
builder.WriteString(fmt.Sprintf("%v", pl.ConfigForm))
builder.WriteString("form=")
builder.WriteString(fmt.Sprintf("%v", pl.Form))
builder.WriteString(", ")
builder.WriteString("config_values=")
builder.WriteString(fmt.Sprintf("%v", pl.ConfigValues))
builder.WriteString("model=")
builder.WriteString(fmt.Sprintf("%v", pl.Model))
builder.WriteString(", ")
builder.WriteString("config_cue=")
builder.WriteString(fmt.Sprintf("%v", pl.ConfigCue))
builder.WriteString("cue=")
builder.WriteString(fmt.Sprintf("%v", pl.Cue))
builder.WriteString(", ")
builder.WriteString("config_definition=")
builder.WriteString(pl.ConfigDefinition)
builder.WriteString("cue_definition=")
builder.WriteString(pl.CueDefinition)
builder.WriteByte(')')
return builder.String()
}

View File

@@ -27,14 +27,14 @@ const (
FieldDisplayName = "display_name"
// FieldCreatorID holds the string denoting the creator_id field in the database.
FieldCreatorID = "creator_id"
// FieldConfigForm holds the string denoting the config_form field in the database.
FieldConfigForm = "config_form"
// FieldConfigValues holds the string denoting the config_values field in the database.
FieldConfigValues = "config_values"
// FieldConfigCue holds the string denoting the config_cue field in the database.
FieldConfigCue = "config_cue"
// FieldConfigDefinition holds the string denoting the config_definition field in the database.
FieldConfigDefinition = "config_definition"
// FieldForm holds the string denoting the form field in the database.
FieldForm = "form"
// FieldModel holds the string denoting the model field in the database.
FieldModel = "model"
// FieldCue holds the string denoting the cue field in the database.
FieldCue = "cue"
// FieldCueDefinition holds the string denoting the cue_definition field in the database.
FieldCueDefinition = "cue_definition"
// EdgeCreator holds the string denoting the creator edge name in mutations.
EdgeCreator = "creator"
// EdgeOrganization holds the string denoting the organization edge name in mutations.
@@ -66,10 +66,10 @@ var Columns = []string{
FieldName,
FieldDisplayName,
FieldCreatorID,
FieldConfigForm,
FieldConfigValues,
FieldConfigCue,
FieldConfigDefinition,
FieldForm,
FieldModel,
FieldCue,
FieldCueDefinition,
}
// ValidColumn reports if the column name is valid (part of the table columns).
@@ -133,9 +133,9 @@ func ByCreatorID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatorID, opts...).ToFunc()
}
// ByConfigDefinition orders the results by the config_definition field.
func ByConfigDefinition(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConfigDefinition, opts...).ToFunc()
// ByCueDefinition orders the results by the cue_definition field.
func ByCueDefinition(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCueDefinition, opts...).ToFunc()
}
// ByCreatorField orders the results by creator field.

View File

@@ -86,14 +86,14 @@ func CreatorID(v uuid.UUID) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCreatorID, v))
}
// ConfigCue applies equality check predicate on the "config_cue" field. It's identical to ConfigCueEQ.
func ConfigCue(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigCue, v))
// Cue applies equality check predicate on the "cue" field. It's identical to CueEQ.
func Cue(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCue, v))
}
// ConfigDefinition applies equality check predicate on the "config_definition" field. It's identical to ConfigDefinitionEQ.
func ConfigDefinition(v string) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigDefinition, v))
// CueDefinition applies equality check predicate on the "cue_definition" field. It's identical to CueDefinitionEQ.
func CueDefinition(v string) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCueDefinition, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
@@ -346,149 +346,149 @@ func CreatorIDNotIn(vs ...uuid.UUID) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldCreatorID, vs...))
}
// ConfigFormIsNil applies the IsNil predicate on the "config_form" field.
func ConfigFormIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigForm))
// FormIsNil applies the IsNil predicate on the "form" field.
func FormIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldForm))
}
// ConfigFormNotNil applies the NotNil predicate on the "config_form" field.
func ConfigFormNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldConfigForm))
// FormNotNil applies the NotNil predicate on the "form" field.
func FormNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldForm))
}
// ConfigValuesIsNil applies the IsNil predicate on the "config_values" field.
func ConfigValuesIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigValues))
// ModelIsNil applies the IsNil predicate on the "model" field.
func ModelIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldModel))
}
// ConfigValuesNotNil applies the NotNil predicate on the "config_values" field.
func ConfigValuesNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldConfigValues))
// ModelNotNil applies the NotNil predicate on the "model" field.
func ModelNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldModel))
}
// ConfigCueEQ applies the EQ predicate on the "config_cue" field.
func ConfigCueEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigCue, v))
// CueEQ applies the EQ predicate on the "cue" field.
func CueEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCue, v))
}
// ConfigCueNEQ applies the NEQ predicate on the "config_cue" field.
func ConfigCueNEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldConfigCue, v))
// CueNEQ applies the NEQ predicate on the "cue" field.
func CueNEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldCue, v))
}
// ConfigCueIn applies the In predicate on the "config_cue" field.
func ConfigCueIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldConfigCue, vs...))
// CueIn applies the In predicate on the "cue" field.
func CueIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldCue, vs...))
}
// ConfigCueNotIn applies the NotIn predicate on the "config_cue" field.
func ConfigCueNotIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldConfigCue, vs...))
// CueNotIn applies the NotIn predicate on the "cue" field.
func CueNotIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldCue, vs...))
}
// ConfigCueGT applies the GT predicate on the "config_cue" field.
func ConfigCueGT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldConfigCue, v))
// CueGT applies the GT predicate on the "cue" field.
func CueGT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldCue, v))
}
// ConfigCueGTE applies the GTE predicate on the "config_cue" field.
func ConfigCueGTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldConfigCue, v))
// CueGTE applies the GTE predicate on the "cue" field.
func CueGTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldCue, v))
}
// ConfigCueLT applies the LT predicate on the "config_cue" field.
func ConfigCueLT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldConfigCue, v))
// CueLT applies the LT predicate on the "cue" field.
func CueLT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldCue, v))
}
// ConfigCueLTE applies the LTE predicate on the "config_cue" field.
func ConfigCueLTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldConfigCue, v))
// CueLTE applies the LTE predicate on the "cue" field.
func CueLTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldCue, v))
}
// ConfigCueIsNil applies the IsNil predicate on the "config_cue" field.
func ConfigCueIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigCue))
// CueIsNil applies the IsNil predicate on the "cue" field.
func CueIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldCue))
}
// ConfigCueNotNil applies the NotNil predicate on the "config_cue" field.
func ConfigCueNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldConfigCue))
// CueNotNil applies the NotNil predicate on the "cue" field.
func CueNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldCue))
}
// ConfigDefinitionEQ applies the EQ predicate on the "config_definition" field.
func ConfigDefinitionEQ(v string) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigDefinition, v))
// CueDefinitionEQ applies the EQ predicate on the "cue_definition" field.
func CueDefinitionEQ(v string) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCueDefinition, v))
}
// ConfigDefinitionNEQ applies the NEQ predicate on the "config_definition" field.
func ConfigDefinitionNEQ(v string) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldConfigDefinition, v))
// CueDefinitionNEQ applies the NEQ predicate on the "cue_definition" field.
func CueDefinitionNEQ(v string) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldCueDefinition, v))
}
// ConfigDefinitionIn applies the In predicate on the "config_definition" field.
func ConfigDefinitionIn(vs ...string) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldConfigDefinition, vs...))
// CueDefinitionIn applies the In predicate on the "cue_definition" field.
func CueDefinitionIn(vs ...string) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldCueDefinition, vs...))
}
// ConfigDefinitionNotIn applies the NotIn predicate on the "config_definition" field.
func ConfigDefinitionNotIn(vs ...string) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldConfigDefinition, vs...))
// CueDefinitionNotIn applies the NotIn predicate on the "cue_definition" field.
func CueDefinitionNotIn(vs ...string) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldCueDefinition, vs...))
}
// ConfigDefinitionGT applies the GT predicate on the "config_definition" field.
func ConfigDefinitionGT(v string) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldConfigDefinition, v))
// CueDefinitionGT applies the GT predicate on the "cue_definition" field.
func CueDefinitionGT(v string) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldCueDefinition, v))
}
// ConfigDefinitionGTE applies the GTE predicate on the "config_definition" field.
func ConfigDefinitionGTE(v string) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldConfigDefinition, v))
// CueDefinitionGTE applies the GTE predicate on the "cue_definition" field.
func CueDefinitionGTE(v string) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldCueDefinition, v))
}
// ConfigDefinitionLT applies the LT predicate on the "config_definition" field.
func ConfigDefinitionLT(v string) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldConfigDefinition, v))
// CueDefinitionLT applies the LT predicate on the "cue_definition" field.
func CueDefinitionLT(v string) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldCueDefinition, v))
}
// ConfigDefinitionLTE applies the LTE predicate on the "config_definition" field.
func ConfigDefinitionLTE(v string) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldConfigDefinition, v))
// CueDefinitionLTE applies the LTE predicate on the "cue_definition" field.
func CueDefinitionLTE(v string) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldCueDefinition, v))
}
// ConfigDefinitionContains applies the Contains predicate on the "config_definition" field.
func ConfigDefinitionContains(v string) predicate.Platform {
return predicate.Platform(sql.FieldContains(FieldConfigDefinition, v))
// CueDefinitionContains applies the Contains predicate on the "cue_definition" field.
func CueDefinitionContains(v string) predicate.Platform {
return predicate.Platform(sql.FieldContains(FieldCueDefinition, v))
}
// ConfigDefinitionHasPrefix applies the HasPrefix predicate on the "config_definition" field.
func ConfigDefinitionHasPrefix(v string) predicate.Platform {
return predicate.Platform(sql.FieldHasPrefix(FieldConfigDefinition, v))
// CueDefinitionHasPrefix applies the HasPrefix predicate on the "cue_definition" field.
func CueDefinitionHasPrefix(v string) predicate.Platform {
return predicate.Platform(sql.FieldHasPrefix(FieldCueDefinition, v))
}
// ConfigDefinitionHasSuffix applies the HasSuffix predicate on the "config_definition" field.
func ConfigDefinitionHasSuffix(v string) predicate.Platform {
return predicate.Platform(sql.FieldHasSuffix(FieldConfigDefinition, v))
// CueDefinitionHasSuffix applies the HasSuffix predicate on the "cue_definition" field.
func CueDefinitionHasSuffix(v string) predicate.Platform {
return predicate.Platform(sql.FieldHasSuffix(FieldCueDefinition, v))
}
// ConfigDefinitionIsNil applies the IsNil predicate on the "config_definition" field.
func ConfigDefinitionIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigDefinition))
// CueDefinitionIsNil applies the IsNil predicate on the "cue_definition" field.
func CueDefinitionIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldCueDefinition))
}
// ConfigDefinitionNotNil applies the NotNil predicate on the "config_definition" field.
func ConfigDefinitionNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldConfigDefinition))
// CueDefinitionNotNil applies the NotNil predicate on the "cue_definition" field.
func CueDefinitionNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldCueDefinition))
}
// ConfigDefinitionEqualFold applies the EqualFold predicate on the "config_definition" field.
func ConfigDefinitionEqualFold(v string) predicate.Platform {
return predicate.Platform(sql.FieldEqualFold(FieldConfigDefinition, v))
// CueDefinitionEqualFold applies the EqualFold predicate on the "cue_definition" field.
func CueDefinitionEqualFold(v string) predicate.Platform {
return predicate.Platform(sql.FieldEqualFold(FieldCueDefinition, v))
}
// ConfigDefinitionContainsFold applies the ContainsFold predicate on the "config_definition" field.
func ConfigDefinitionContainsFold(v string) predicate.Platform {
return predicate.Platform(sql.FieldContainsFold(FieldConfigDefinition, v))
// CueDefinitionContainsFold applies the ContainsFold predicate on the "cue_definition" field.
func CueDefinitionContainsFold(v string) predicate.Platform {
return predicate.Platform(sql.FieldContainsFold(FieldCueDefinition, v))
}
// HasCreator applies the HasEdge predicate on the "creator" edge.

View File

@@ -79,34 +79,34 @@ func (pc *PlatformCreate) SetCreatorID(u uuid.UUID) *PlatformCreate {
return pc
}
// SetConfigForm sets the "config_form" field.
func (pc *PlatformCreate) SetConfigForm(hf *holos.PlatformForm) *PlatformCreate {
pc.mutation.SetConfigForm(hf)
// SetForm sets the "form" field.
func (pc *PlatformCreate) SetForm(h *holos.Form) *PlatformCreate {
pc.mutation.SetForm(h)
return pc
}
// SetConfigValues sets the "config_values" field.
func (pc *PlatformCreate) SetConfigValues(hv *holos.ConfigValues) *PlatformCreate {
pc.mutation.SetConfigValues(hv)
// SetModel sets the "model" field.
func (pc *PlatformCreate) SetModel(h *holos.Model) *PlatformCreate {
pc.mutation.SetModel(h)
return pc
}
// SetConfigCue sets the "config_cue" field.
func (pc *PlatformCreate) SetConfigCue(b []byte) *PlatformCreate {
pc.mutation.SetConfigCue(b)
// SetCue sets the "cue" field.
func (pc *PlatformCreate) SetCue(b []byte) *PlatformCreate {
pc.mutation.SetCue(b)
return pc
}
// SetConfigDefinition sets the "config_definition" field.
func (pc *PlatformCreate) SetConfigDefinition(s string) *PlatformCreate {
pc.mutation.SetConfigDefinition(s)
// SetCueDefinition sets the "cue_definition" field.
func (pc *PlatformCreate) SetCueDefinition(s string) *PlatformCreate {
pc.mutation.SetCueDefinition(s)
return pc
}
// SetNillableConfigDefinition sets the "config_definition" field if the given value is not nil.
func (pc *PlatformCreate) SetNillableConfigDefinition(s *string) *PlatformCreate {
// SetNillableCueDefinition sets the "cue_definition" field if the given value is not nil.
func (pc *PlatformCreate) SetNillableCueDefinition(s *string) *PlatformCreate {
if s != nil {
pc.SetConfigDefinition(*s)
pc.SetCueDefinition(*s)
}
return pc
}
@@ -273,21 +273,21 @@ func (pc *PlatformCreate) createSpec() (*Platform, *sqlgraph.CreateSpec) {
_spec.SetField(platform.FieldDisplayName, field.TypeString, value)
_node.DisplayName = value
}
if value, ok := pc.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
_node.ConfigForm = value
if value, ok := pc.mutation.Form(); ok {
_spec.SetField(platform.FieldForm, field.TypeJSON, value)
_node.Form = value
}
if value, ok := pc.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
_node.ConfigValues = value
if value, ok := pc.mutation.Model(); ok {
_spec.SetField(platform.FieldModel, field.TypeJSON, value)
_node.Model = value
}
if value, ok := pc.mutation.ConfigCue(); ok {
_spec.SetField(platform.FieldConfigCue, field.TypeBytes, value)
_node.ConfigCue = value
if value, ok := pc.mutation.Cue(); ok {
_spec.SetField(platform.FieldCue, field.TypeBytes, value)
_node.Cue = value
}
if value, ok := pc.mutation.ConfigDefinition(); ok {
_spec.SetField(platform.FieldConfigDefinition, field.TypeString, value)
_node.ConfigDefinition = value
if value, ok := pc.mutation.CueDefinition(); ok {
_spec.SetField(platform.FieldCueDefinition, field.TypeString, value)
_node.CueDefinition = value
}
if nodes := pc.mutation.CreatorIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
@@ -435,75 +435,75 @@ func (u *PlatformUpsert) UpdateCreatorID() *PlatformUpsert {
return u
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsert) SetConfigForm(v *holos.PlatformForm) *PlatformUpsert {
u.Set(platform.FieldConfigForm, v)
// SetForm sets the "form" field.
func (u *PlatformUpsert) SetForm(v *holos.Form) *PlatformUpsert {
u.Set(platform.FieldForm, v)
return u
}
// UpdateConfigForm sets the "config_form" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateConfigForm() *PlatformUpsert {
u.SetExcluded(platform.FieldConfigForm)
// UpdateForm sets the "form" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateForm() *PlatformUpsert {
u.SetExcluded(platform.FieldForm)
return u
}
// ClearConfigForm clears the value of the "config_form" field.
func (u *PlatformUpsert) ClearConfigForm() *PlatformUpsert {
u.SetNull(platform.FieldConfigForm)
// ClearForm clears the value of the "form" field.
func (u *PlatformUpsert) ClearForm() *PlatformUpsert {
u.SetNull(platform.FieldForm)
return u
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsert) SetConfigValues(v *holos.ConfigValues) *PlatformUpsert {
u.Set(platform.FieldConfigValues, v)
// SetModel sets the "model" field.
func (u *PlatformUpsert) SetModel(v *holos.Model) *PlatformUpsert {
u.Set(platform.FieldModel, v)
return u
}
// UpdateConfigValues sets the "config_values" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateConfigValues() *PlatformUpsert {
u.SetExcluded(platform.FieldConfigValues)
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateModel() *PlatformUpsert {
u.SetExcluded(platform.FieldModel)
return u
}
// ClearConfigValues clears the value of the "config_values" field.
func (u *PlatformUpsert) ClearConfigValues() *PlatformUpsert {
u.SetNull(platform.FieldConfigValues)
// ClearModel clears the value of the "model" field.
func (u *PlatformUpsert) ClearModel() *PlatformUpsert {
u.SetNull(platform.FieldModel)
return u
}
// SetConfigCue sets the "config_cue" field.
func (u *PlatformUpsert) SetConfigCue(v []byte) *PlatformUpsert {
u.Set(platform.FieldConfigCue, v)
// SetCue sets the "cue" field.
func (u *PlatformUpsert) SetCue(v []byte) *PlatformUpsert {
u.Set(platform.FieldCue, v)
return u
}
// UpdateConfigCue sets the "config_cue" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateConfigCue() *PlatformUpsert {
u.SetExcluded(platform.FieldConfigCue)
// UpdateCue sets the "cue" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateCue() *PlatformUpsert {
u.SetExcluded(platform.FieldCue)
return u
}
// ClearConfigCue clears the value of the "config_cue" field.
func (u *PlatformUpsert) ClearConfigCue() *PlatformUpsert {
u.SetNull(platform.FieldConfigCue)
// ClearCue clears the value of the "cue" field.
func (u *PlatformUpsert) ClearCue() *PlatformUpsert {
u.SetNull(platform.FieldCue)
return u
}
// SetConfigDefinition sets the "config_definition" field.
func (u *PlatformUpsert) SetConfigDefinition(v string) *PlatformUpsert {
u.Set(platform.FieldConfigDefinition, v)
// SetCueDefinition sets the "cue_definition" field.
func (u *PlatformUpsert) SetCueDefinition(v string) *PlatformUpsert {
u.Set(platform.FieldCueDefinition, v)
return u
}
// UpdateConfigDefinition sets the "config_definition" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateConfigDefinition() *PlatformUpsert {
u.SetExcluded(platform.FieldConfigDefinition)
// UpdateCueDefinition sets the "cue_definition" field to the value that was provided on create.
func (u *PlatformUpsert) UpdateCueDefinition() *PlatformUpsert {
u.SetExcluded(platform.FieldCueDefinition)
return u
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (u *PlatformUpsert) ClearConfigDefinition() *PlatformUpsert {
u.SetNull(platform.FieldConfigDefinition)
// ClearCueDefinition clears the value of the "cue_definition" field.
func (u *PlatformUpsert) ClearCueDefinition() *PlatformUpsert {
u.SetNull(platform.FieldCueDefinition)
return u
}
@@ -628,87 +628,87 @@ func (u *PlatformUpsertOne) UpdateCreatorID() *PlatformUpsertOne {
})
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsertOne) SetConfigForm(v *holos.PlatformForm) *PlatformUpsertOne {
// SetForm sets the "form" field.
func (u *PlatformUpsertOne) SetForm(v *holos.Form) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigForm(v)
s.SetForm(v)
})
}
// UpdateConfigForm sets the "config_form" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateConfigForm() *PlatformUpsertOne {
// UpdateForm sets the "form" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateForm() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigForm()
s.UpdateForm()
})
}
// ClearConfigForm clears the value of the "config_form" field.
func (u *PlatformUpsertOne) ClearConfigForm() *PlatformUpsertOne {
// ClearForm clears the value of the "form" field.
func (u *PlatformUpsertOne) ClearForm() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigForm()
s.ClearForm()
})
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsertOne) SetConfigValues(v *holos.ConfigValues) *PlatformUpsertOne {
// SetModel sets the "model" field.
func (u *PlatformUpsertOne) SetModel(v *holos.Model) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigValues(v)
s.SetModel(v)
})
}
// UpdateConfigValues sets the "config_values" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateConfigValues() *PlatformUpsertOne {
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateModel() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigValues()
s.UpdateModel()
})
}
// ClearConfigValues clears the value of the "config_values" field.
func (u *PlatformUpsertOne) ClearConfigValues() *PlatformUpsertOne {
// ClearModel clears the value of the "model" field.
func (u *PlatformUpsertOne) ClearModel() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigValues()
s.ClearModel()
})
}
// SetConfigCue sets the "config_cue" field.
func (u *PlatformUpsertOne) SetConfigCue(v []byte) *PlatformUpsertOne {
// SetCue sets the "cue" field.
func (u *PlatformUpsertOne) SetCue(v []byte) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigCue(v)
s.SetCue(v)
})
}
// UpdateConfigCue sets the "config_cue" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateConfigCue() *PlatformUpsertOne {
// UpdateCue sets the "cue" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateCue() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigCue()
s.UpdateCue()
})
}
// ClearConfigCue clears the value of the "config_cue" field.
func (u *PlatformUpsertOne) ClearConfigCue() *PlatformUpsertOne {
// ClearCue clears the value of the "cue" field.
func (u *PlatformUpsertOne) ClearCue() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigCue()
s.ClearCue()
})
}
// SetConfigDefinition sets the "config_definition" field.
func (u *PlatformUpsertOne) SetConfigDefinition(v string) *PlatformUpsertOne {
// SetCueDefinition sets the "cue_definition" field.
func (u *PlatformUpsertOne) SetCueDefinition(v string) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigDefinition(v)
s.SetCueDefinition(v)
})
}
// UpdateConfigDefinition sets the "config_definition" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateConfigDefinition() *PlatformUpsertOne {
// UpdateCueDefinition sets the "cue_definition" field to the value that was provided on create.
func (u *PlatformUpsertOne) UpdateCueDefinition() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigDefinition()
s.UpdateCueDefinition()
})
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (u *PlatformUpsertOne) ClearConfigDefinition() *PlatformUpsertOne {
// ClearCueDefinition clears the value of the "cue_definition" field.
func (u *PlatformUpsertOne) ClearCueDefinition() *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigDefinition()
s.ClearCueDefinition()
})
}
@@ -1000,87 +1000,87 @@ func (u *PlatformUpsertBulk) UpdateCreatorID() *PlatformUpsertBulk {
})
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsertBulk) SetConfigForm(v *holos.PlatformForm) *PlatformUpsertBulk {
// SetForm sets the "form" field.
func (u *PlatformUpsertBulk) SetForm(v *holos.Form) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigForm(v)
s.SetForm(v)
})
}
// UpdateConfigForm sets the "config_form" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateConfigForm() *PlatformUpsertBulk {
// UpdateForm sets the "form" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateForm() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigForm()
s.UpdateForm()
})
}
// ClearConfigForm clears the value of the "config_form" field.
func (u *PlatformUpsertBulk) ClearConfigForm() *PlatformUpsertBulk {
// ClearForm clears the value of the "form" field.
func (u *PlatformUpsertBulk) ClearForm() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigForm()
s.ClearForm()
})
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsertBulk) SetConfigValues(v *holos.ConfigValues) *PlatformUpsertBulk {
// SetModel sets the "model" field.
func (u *PlatformUpsertBulk) SetModel(v *holos.Model) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigValues(v)
s.SetModel(v)
})
}
// UpdateConfigValues sets the "config_values" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateConfigValues() *PlatformUpsertBulk {
// UpdateModel sets the "model" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateModel() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigValues()
s.UpdateModel()
})
}
// ClearConfigValues clears the value of the "config_values" field.
func (u *PlatformUpsertBulk) ClearConfigValues() *PlatformUpsertBulk {
// ClearModel clears the value of the "model" field.
func (u *PlatformUpsertBulk) ClearModel() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigValues()
s.ClearModel()
})
}
// SetConfigCue sets the "config_cue" field.
func (u *PlatformUpsertBulk) SetConfigCue(v []byte) *PlatformUpsertBulk {
// SetCue sets the "cue" field.
func (u *PlatformUpsertBulk) SetCue(v []byte) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigCue(v)
s.SetCue(v)
})
}
// UpdateConfigCue sets the "config_cue" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateConfigCue() *PlatformUpsertBulk {
// UpdateCue sets the "cue" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateCue() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigCue()
s.UpdateCue()
})
}
// ClearConfigCue clears the value of the "config_cue" field.
func (u *PlatformUpsertBulk) ClearConfigCue() *PlatformUpsertBulk {
// ClearCue clears the value of the "cue" field.
func (u *PlatformUpsertBulk) ClearCue() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigCue()
s.ClearCue()
})
}
// SetConfigDefinition sets the "config_definition" field.
func (u *PlatformUpsertBulk) SetConfigDefinition(v string) *PlatformUpsertBulk {
// SetCueDefinition sets the "cue_definition" field.
func (u *PlatformUpsertBulk) SetCueDefinition(v string) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigDefinition(v)
s.SetCueDefinition(v)
})
}
// UpdateConfigDefinition sets the "config_definition" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateConfigDefinition() *PlatformUpsertBulk {
// UpdateCueDefinition sets the "cue_definition" field to the value that was provided on create.
func (u *PlatformUpsertBulk) UpdateCueDefinition() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.UpdateConfigDefinition()
s.UpdateCueDefinition()
})
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (u *PlatformUpsertBulk) ClearConfigDefinition() *PlatformUpsertBulk {
// ClearCueDefinition clears the value of the "cue_definition" field.
func (u *PlatformUpsertBulk) ClearCueDefinition() *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.ClearConfigDefinition()
s.ClearCueDefinition()
})
}

View File

@@ -94,59 +94,59 @@ func (pu *PlatformUpdate) SetNillableCreatorID(u *uuid.UUID) *PlatformUpdate {
return pu
}
// SetConfigForm sets the "config_form" field.
func (pu *PlatformUpdate) SetConfigForm(hf *holos.PlatformForm) *PlatformUpdate {
pu.mutation.SetConfigForm(hf)
// SetForm sets the "form" field.
func (pu *PlatformUpdate) SetForm(h *holos.Form) *PlatformUpdate {
pu.mutation.SetForm(h)
return pu
}
// ClearConfigForm clears the value of the "config_form" field.
func (pu *PlatformUpdate) ClearConfigForm() *PlatformUpdate {
pu.mutation.ClearConfigForm()
// ClearForm clears the value of the "form" field.
func (pu *PlatformUpdate) ClearForm() *PlatformUpdate {
pu.mutation.ClearForm()
return pu
}
// SetConfigValues sets the "config_values" field.
func (pu *PlatformUpdate) SetConfigValues(hv *holos.ConfigValues) *PlatformUpdate {
pu.mutation.SetConfigValues(hv)
// SetModel sets the "model" field.
func (pu *PlatformUpdate) SetModel(h *holos.Model) *PlatformUpdate {
pu.mutation.SetModel(h)
return pu
}
// ClearConfigValues clears the value of the "config_values" field.
func (pu *PlatformUpdate) ClearConfigValues() *PlatformUpdate {
pu.mutation.ClearConfigValues()
// ClearModel clears the value of the "model" field.
func (pu *PlatformUpdate) ClearModel() *PlatformUpdate {
pu.mutation.ClearModel()
return pu
}
// SetConfigCue sets the "config_cue" field.
func (pu *PlatformUpdate) SetConfigCue(b []byte) *PlatformUpdate {
pu.mutation.SetConfigCue(b)
// SetCue sets the "cue" field.
func (pu *PlatformUpdate) SetCue(b []byte) *PlatformUpdate {
pu.mutation.SetCue(b)
return pu
}
// ClearConfigCue clears the value of the "config_cue" field.
func (pu *PlatformUpdate) ClearConfigCue() *PlatformUpdate {
pu.mutation.ClearConfigCue()
// ClearCue clears the value of the "cue" field.
func (pu *PlatformUpdate) ClearCue() *PlatformUpdate {
pu.mutation.ClearCue()
return pu
}
// SetConfigDefinition sets the "config_definition" field.
func (pu *PlatformUpdate) SetConfigDefinition(s string) *PlatformUpdate {
pu.mutation.SetConfigDefinition(s)
// SetCueDefinition sets the "cue_definition" field.
func (pu *PlatformUpdate) SetCueDefinition(s string) *PlatformUpdate {
pu.mutation.SetCueDefinition(s)
return pu
}
// SetNillableConfigDefinition sets the "config_definition" field if the given value is not nil.
func (pu *PlatformUpdate) SetNillableConfigDefinition(s *string) *PlatformUpdate {
// SetNillableCueDefinition sets the "cue_definition" field if the given value is not nil.
func (pu *PlatformUpdate) SetNillableCueDefinition(s *string) *PlatformUpdate {
if s != nil {
pu.SetConfigDefinition(*s)
pu.SetCueDefinition(*s)
}
return pu
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (pu *PlatformUpdate) ClearConfigDefinition() *PlatformUpdate {
pu.mutation.ClearConfigDefinition()
// ClearCueDefinition clears the value of the "cue_definition" field.
func (pu *PlatformUpdate) ClearCueDefinition() *PlatformUpdate {
pu.mutation.ClearCueDefinition()
return pu
}
@@ -256,29 +256,29 @@ func (pu *PlatformUpdate) sqlSave(ctx context.Context) (n int, err error) {
if value, ok := pu.mutation.DisplayName(); ok {
_spec.SetField(platform.FieldDisplayName, field.TypeString, value)
}
if value, ok := pu.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
if value, ok := pu.mutation.Form(); ok {
_spec.SetField(platform.FieldForm, field.TypeJSON, value)
}
if pu.mutation.ConfigFormCleared() {
_spec.ClearField(platform.FieldConfigForm, field.TypeJSON)
if pu.mutation.FormCleared() {
_spec.ClearField(platform.FieldForm, field.TypeJSON)
}
if value, ok := pu.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
if value, ok := pu.mutation.Model(); ok {
_spec.SetField(platform.FieldModel, field.TypeJSON, value)
}
if pu.mutation.ConfigValuesCleared() {
_spec.ClearField(platform.FieldConfigValues, field.TypeJSON)
if pu.mutation.ModelCleared() {
_spec.ClearField(platform.FieldModel, field.TypeJSON)
}
if value, ok := pu.mutation.ConfigCue(); ok {
_spec.SetField(platform.FieldConfigCue, field.TypeBytes, value)
if value, ok := pu.mutation.Cue(); ok {
_spec.SetField(platform.FieldCue, field.TypeBytes, value)
}
if pu.mutation.ConfigCueCleared() {
_spec.ClearField(platform.FieldConfigCue, field.TypeBytes)
if pu.mutation.CueCleared() {
_spec.ClearField(platform.FieldCue, field.TypeBytes)
}
if value, ok := pu.mutation.ConfigDefinition(); ok {
_spec.SetField(platform.FieldConfigDefinition, field.TypeString, value)
if value, ok := pu.mutation.CueDefinition(); ok {
_spec.SetField(platform.FieldCueDefinition, field.TypeString, value)
}
if pu.mutation.ConfigDefinitionCleared() {
_spec.ClearField(platform.FieldConfigDefinition, field.TypeString)
if pu.mutation.CueDefinitionCleared() {
_spec.ClearField(platform.FieldCueDefinition, field.TypeString)
}
if pu.mutation.CreatorCleared() {
edge := &sqlgraph.EdgeSpec{
@@ -420,59 +420,59 @@ func (puo *PlatformUpdateOne) SetNillableCreatorID(u *uuid.UUID) *PlatformUpdate
return puo
}
// SetConfigForm sets the "config_form" field.
func (puo *PlatformUpdateOne) SetConfigForm(hf *holos.PlatformForm) *PlatformUpdateOne {
puo.mutation.SetConfigForm(hf)
// SetForm sets the "form" field.
func (puo *PlatformUpdateOne) SetForm(h *holos.Form) *PlatformUpdateOne {
puo.mutation.SetForm(h)
return puo
}
// ClearConfigForm clears the value of the "config_form" field.
func (puo *PlatformUpdateOne) ClearConfigForm() *PlatformUpdateOne {
puo.mutation.ClearConfigForm()
// ClearForm clears the value of the "form" field.
func (puo *PlatformUpdateOne) ClearForm() *PlatformUpdateOne {
puo.mutation.ClearForm()
return puo
}
// SetConfigValues sets the "config_values" field.
func (puo *PlatformUpdateOne) SetConfigValues(hv *holos.ConfigValues) *PlatformUpdateOne {
puo.mutation.SetConfigValues(hv)
// SetModel sets the "model" field.
func (puo *PlatformUpdateOne) SetModel(h *holos.Model) *PlatformUpdateOne {
puo.mutation.SetModel(h)
return puo
}
// ClearConfigValues clears the value of the "config_values" field.
func (puo *PlatformUpdateOne) ClearConfigValues() *PlatformUpdateOne {
puo.mutation.ClearConfigValues()
// ClearModel clears the value of the "model" field.
func (puo *PlatformUpdateOne) ClearModel() *PlatformUpdateOne {
puo.mutation.ClearModel()
return puo
}
// SetConfigCue sets the "config_cue" field.
func (puo *PlatformUpdateOne) SetConfigCue(b []byte) *PlatformUpdateOne {
puo.mutation.SetConfigCue(b)
// SetCue sets the "cue" field.
func (puo *PlatformUpdateOne) SetCue(b []byte) *PlatformUpdateOne {
puo.mutation.SetCue(b)
return puo
}
// ClearConfigCue clears the value of the "config_cue" field.
func (puo *PlatformUpdateOne) ClearConfigCue() *PlatformUpdateOne {
puo.mutation.ClearConfigCue()
// ClearCue clears the value of the "cue" field.
func (puo *PlatformUpdateOne) ClearCue() *PlatformUpdateOne {
puo.mutation.ClearCue()
return puo
}
// SetConfigDefinition sets the "config_definition" field.
func (puo *PlatformUpdateOne) SetConfigDefinition(s string) *PlatformUpdateOne {
puo.mutation.SetConfigDefinition(s)
// SetCueDefinition sets the "cue_definition" field.
func (puo *PlatformUpdateOne) SetCueDefinition(s string) *PlatformUpdateOne {
puo.mutation.SetCueDefinition(s)
return puo
}
// SetNillableConfigDefinition sets the "config_definition" field if the given value is not nil.
func (puo *PlatformUpdateOne) SetNillableConfigDefinition(s *string) *PlatformUpdateOne {
// SetNillableCueDefinition sets the "cue_definition" field if the given value is not nil.
func (puo *PlatformUpdateOne) SetNillableCueDefinition(s *string) *PlatformUpdateOne {
if s != nil {
puo.SetConfigDefinition(*s)
puo.SetCueDefinition(*s)
}
return puo
}
// ClearConfigDefinition clears the value of the "config_definition" field.
func (puo *PlatformUpdateOne) ClearConfigDefinition() *PlatformUpdateOne {
puo.mutation.ClearConfigDefinition()
// ClearCueDefinition clears the value of the "cue_definition" field.
func (puo *PlatformUpdateOne) ClearCueDefinition() *PlatformUpdateOne {
puo.mutation.ClearCueDefinition()
return puo
}
@@ -612,29 +612,29 @@ func (puo *PlatformUpdateOne) sqlSave(ctx context.Context) (_node *Platform, err
if value, ok := puo.mutation.DisplayName(); ok {
_spec.SetField(platform.FieldDisplayName, field.TypeString, value)
}
if value, ok := puo.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
if value, ok := puo.mutation.Form(); ok {
_spec.SetField(platform.FieldForm, field.TypeJSON, value)
}
if puo.mutation.ConfigFormCleared() {
_spec.ClearField(platform.FieldConfigForm, field.TypeJSON)
if puo.mutation.FormCleared() {
_spec.ClearField(platform.FieldForm, field.TypeJSON)
}
if value, ok := puo.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
if value, ok := puo.mutation.Model(); ok {
_spec.SetField(platform.FieldModel, field.TypeJSON, value)
}
if puo.mutation.ConfigValuesCleared() {
_spec.ClearField(platform.FieldConfigValues, field.TypeJSON)
if puo.mutation.ModelCleared() {
_spec.ClearField(platform.FieldModel, field.TypeJSON)
}
if value, ok := puo.mutation.ConfigCue(); ok {
_spec.SetField(platform.FieldConfigCue, field.TypeBytes, value)
if value, ok := puo.mutation.Cue(); ok {
_spec.SetField(platform.FieldCue, field.TypeBytes, value)
}
if puo.mutation.ConfigCueCleared() {
_spec.ClearField(platform.FieldConfigCue, field.TypeBytes)
if puo.mutation.CueCleared() {
_spec.ClearField(platform.FieldCue, field.TypeBytes)
}
if value, ok := puo.mutation.ConfigDefinition(); ok {
_spec.SetField(platform.FieldConfigDefinition, field.TypeString, value)
if value, ok := puo.mutation.CueDefinition(); ok {
_spec.SetField(platform.FieldCueDefinition, field.TypeString, value)
}
if puo.mutation.ConfigDefinitionCleared() {
_spec.ClearField(platform.FieldConfigDefinition, field.TypeString)
if puo.mutation.CueDefinitionCleared() {
_spec.ClearField(platform.FieldCueDefinition, field.TypeString)
}
if puo.mutation.CreatorCleared() {
edge := &sqlgraph.EdgeSpec{

View File

@@ -26,16 +26,16 @@ func (Platform) Fields() []ent.Field {
field.String("name").NotEmpty(),
field.String("display_name"),
field.UUID("creator_id", uuid.UUID{}),
field.JSON("config_form", &holos.PlatformForm{}).
field.JSON("form", &holos.Form{}).
Optional().
Comment("JSON holos.PlatformForm representing the platform data entry form."),
field.JSON("config_values", &holos.ConfigValues{}).
Comment("JSON representation of FormlyFormConfig[] refer to https://github.com/holos-run/holos/issues/161"),
field.JSON("model", &holos.Model{}).
Optional().
Comment("JSON holos.ConfigValues representing the platform config values."),
field.Bytes("config_cue").
Comment("JSON representation of the form model which holds user input values refer to https://github.com/holos-run/holos/issues/161"),
field.Bytes("cue").
Optional().
Comment("Opaque bytes representing the CUE definition of the config struct."),
field.String("config_definition").
Comment("CUE definition to vet the model against e.g. #PlatformConfig"),
field.String("cue_definition").
Optional().
Comment("The definition name to vet config_values against config_cue e.g. '#PlatformSpec'"),
}

View File

@@ -0,0 +1,47 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/accessibility"
],
"rules": {}
}
]
}

View File

@@ -40,3 +40,6 @@ testem.log
# System files
.DS_Store
Thumbs.db
# NX?
/.nx/

View File

@@ -100,8 +100,22 @@
],
"scripts": []
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
"test": "ng test",
"lint": "ng lint"
},
"private": true,
"dependencies": {
@@ -32,6 +33,11 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.4",
"@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.4",
"@angular/compiler-cli": "^17.3.0",
"@bufbuild/buf": "^1.31.0",
@@ -40,6 +46,9 @@
"@connectrpc/protoc-gen-connect-query": "^1.3.1",
"@ngx-formly/schematics": "^6.3.0",
"@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.2.0",
"eslint": "^8.57.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
@@ -48,4 +57,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.4.2"
}
}
}

View File

@@ -9,6 +9,7 @@ import { provideClient } from "../connect/client.provider";
import { UserService } from './gen/holos/v1alpha1/user_connect';
import { OrganizationService } from './gen/holos/v1alpha1/organization_connect';
import { PlatformService } from './gen/holos/v1alpha1/platform_connect';
import { HolosPanelWrapperComponent } from '../wrappers/holos-panel-wrapper/holos-panel-wrapper.component';
export const appConfig: ApplicationConfig = {
providers: [
@@ -22,7 +23,9 @@ export const appConfig: ApplicationConfig = {
ConnectModule.forRoot({
baseUrl: window.location.origin
}),
FormlyModule.forRoot(),
FormlyModule.forRoot({
wrappers: [{ name: 'holos-panel', component: HolosPanelWrapperComponent }],
}),
),
]
};

View File

@@ -1,35 +0,0 @@
// @generated by protoc-gen-connect-query v1.3.1 with parameter "target=ts"
// @generated from file holos/v1alpha1/organization.proto (package holos.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { MethodKind } from "@bufbuild/protobuf";
import { CreateCallerOrganizationRequest, GetCallerOrganizationsRequest, GetCallerOrganizationsResponse } from "./organization_pb.js";
/**
* @generated from rpc holos.v1alpha1.OrganizationService.GetCallerOrganizations
*/
export const getCallerOrganizations = {
localName: "getCallerOrganizations",
name: "GetCallerOrganizations",
kind: MethodKind.Unary,
I: GetCallerOrganizationsRequest,
O: GetCallerOrganizationsResponse,
service: {
typeName: "holos.v1alpha1.OrganizationService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.OrganizationService.CreateCallerOrganization
*/
export const createCallerOrganization = {
localName: "createCallerOrganization",
name: "CreateCallerOrganization",
kind: MethodKind.Unary,
I: CreateCallerOrganizationRequest,
O: GetCallerOrganizationsResponse,
service: {
typeName: "holos.v1alpha1.OrganizationService"
}
} as const;

View File

@@ -3,7 +3,7 @@
/* eslint-disable */
// @ts-nocheck
import { CreateCallerOrganizationRequest, GetCallerOrganizationsRequest, GetCallerOrganizationsResponse } from "./organization_pb.js";
import { CreateCallerOrganizationRequest, CreateCallerOrganizationResponse, ListCallerOrganizationsRequest, ListCallerOrganizationsResponse } from "./organization_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
@@ -13,12 +13,12 @@ export const OrganizationService = {
typeName: "holos.v1alpha1.OrganizationService",
methods: {
/**
* @generated from rpc holos.v1alpha1.OrganizationService.GetCallerOrganizations
* @generated from rpc holos.v1alpha1.OrganizationService.ListCallerOrganizations
*/
getCallerOrganizations: {
name: "GetCallerOrganizations",
I: GetCallerOrganizationsRequest,
O: GetCallerOrganizationsResponse,
listCallerOrganizations: {
name: "ListCallerOrganizations",
I: ListCallerOrganizationsRequest,
O: ListCallerOrganizationsResponse,
kind: MethodKind.Unary,
},
/**
@@ -27,7 +27,7 @@ export const OrganizationService = {
createCallerOrganization: {
name: "CreateCallerOrganization",
I: CreateCallerOrganizationRequest,
O: GetCallerOrganizationsResponse,
O: CreateCallerOrganizationResponse,
kind: MethodKind.Unary,
},
}

View File

@@ -72,40 +72,40 @@ export class Organization extends Message<Organization> {
}
/**
* @generated from message holos.v1alpha1.GetCallerOrganizationsRequest
* @generated from message holos.v1alpha1.ListCallerOrganizationsRequest
*/
export class GetCallerOrganizationsRequest extends Message<GetCallerOrganizationsRequest> {
constructor(data?: PartialMessage<GetCallerOrganizationsRequest>) {
export class ListCallerOrganizationsRequest extends Message<ListCallerOrganizationsRequest> {
constructor(data?: PartialMessage<ListCallerOrganizationsRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.GetCallerOrganizationsRequest";
static readonly typeName = "holos.v1alpha1.ListCallerOrganizationsRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetCallerOrganizationsRequest {
return new GetCallerOrganizationsRequest().fromBinary(bytes, options);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListCallerOrganizationsRequest {
return new ListCallerOrganizationsRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetCallerOrganizationsRequest {
return new GetCallerOrganizationsRequest().fromJson(jsonValue, options);
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListCallerOrganizationsRequest {
return new ListCallerOrganizationsRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetCallerOrganizationsRequest {
return new GetCallerOrganizationsRequest().fromJsonString(jsonString, options);
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListCallerOrganizationsRequest {
return new ListCallerOrganizationsRequest().fromJsonString(jsonString, options);
}
static equals(a: GetCallerOrganizationsRequest | PlainMessage<GetCallerOrganizationsRequest> | undefined, b: GetCallerOrganizationsRequest | PlainMessage<GetCallerOrganizationsRequest> | undefined): boolean {
return proto3.util.equals(GetCallerOrganizationsRequest, a, b);
static equals(a: ListCallerOrganizationsRequest | PlainMessage<ListCallerOrganizationsRequest> | undefined, b: ListCallerOrganizationsRequest | PlainMessage<ListCallerOrganizationsRequest> | undefined): boolean {
return proto3.util.equals(ListCallerOrganizationsRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.GetCallerOrganizationsResponse
* @generated from message holos.v1alpha1.ListCallerOrganizationsResponse
*/
export class GetCallerOrganizationsResponse extends Message<GetCallerOrganizationsResponse> {
export class ListCallerOrganizationsResponse extends Message<ListCallerOrganizationsResponse> {
/**
* @generated from field: holos.v1alpha1.User user = 1;
*/
@@ -116,32 +116,32 @@ export class GetCallerOrganizationsResponse extends Message<GetCallerOrganizatio
*/
organizations: Organization[] = [];
constructor(data?: PartialMessage<GetCallerOrganizationsResponse>) {
constructor(data?: PartialMessage<ListCallerOrganizationsResponse>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.GetCallerOrganizationsResponse";
static readonly typeName = "holos.v1alpha1.ListCallerOrganizationsResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "user", kind: "message", T: User },
{ no: 2, name: "organizations", kind: "message", T: Organization, repeated: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetCallerOrganizationsResponse {
return new GetCallerOrganizationsResponse().fromBinary(bytes, options);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListCallerOrganizationsResponse {
return new ListCallerOrganizationsResponse().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetCallerOrganizationsResponse {
return new GetCallerOrganizationsResponse().fromJson(jsonValue, options);
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListCallerOrganizationsResponse {
return new ListCallerOrganizationsResponse().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetCallerOrganizationsResponse {
return new GetCallerOrganizationsResponse().fromJsonString(jsonString, options);
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListCallerOrganizationsResponse {
return new ListCallerOrganizationsResponse().fromJsonString(jsonString, options);
}
static equals(a: GetCallerOrganizationsResponse | PlainMessage<GetCallerOrganizationsResponse> | undefined, b: GetCallerOrganizationsResponse | PlainMessage<GetCallerOrganizationsResponse> | undefined): boolean {
return proto3.util.equals(GetCallerOrganizationsResponse, a, b);
static equals(a: ListCallerOrganizationsResponse | PlainMessage<ListCallerOrganizationsResponse> | undefined, b: ListCallerOrganizationsResponse | PlainMessage<ListCallerOrganizationsResponse> | undefined): boolean {
return proto3.util.equals(ListCallerOrganizationsResponse, a, b);
}
}
@@ -176,3 +176,46 @@ export class CreateCallerOrganizationRequest extends Message<CreateCallerOrganiz
}
}
/**
* @generated from message holos.v1alpha1.CreateCallerOrganizationResponse
*/
export class CreateCallerOrganizationResponse extends Message<CreateCallerOrganizationResponse> {
/**
* @generated from field: holos.v1alpha1.User user = 1;
*/
user?: User;
/**
* @generated from field: repeated holos.v1alpha1.Organization organizations = 2;
*/
organizations: Organization[] = [];
constructor(data?: PartialMessage<CreateCallerOrganizationResponse>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.CreateCallerOrganizationResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "user", kind: "message", T: User },
{ no: 2, name: "organizations", kind: "message", T: Organization, repeated: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateCallerOrganizationResponse {
return new CreateCallerOrganizationResponse().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateCallerOrganizationResponse {
return new CreateCallerOrganizationResponse().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateCallerOrganizationResponse {
return new CreateCallerOrganizationResponse().fromJsonString(jsonString, options);
}
static equals(a: CreateCallerOrganizationResponse | PlainMessage<CreateCallerOrganizationResponse> | undefined, b: CreateCallerOrganizationResponse | PlainMessage<CreateCallerOrganizationResponse> | undefined): boolean {
return proto3.util.equals(CreateCallerOrganizationResponse, a, b);
}
}

View File

@@ -1,79 +0,0 @@
// @generated by protoc-gen-connect-query v1.3.1 with parameter "target=ts"
// @generated from file holos/v1alpha1/platform.proto (package holos.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { MethodKind } from "@bufbuild/protobuf";
import { AddPlatformRequest, ConfigValues, GetPlatformConfigRequest, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse, PutPlatformConfigRequest } from "./platform_pb.js";
/**
* @generated from rpc holos.v1alpha1.PlatformService.AddPlatform
*/
export const addPlatform = {
localName: "addPlatform",
name: "AddPlatform",
kind: MethodKind.Unary,
I: AddPlatformRequest,
O: GetPlatformsResponse,
service: {
typeName: "holos.v1alpha1.PlatformService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatforms
*/
export const getPlatforms = {
localName: "getPlatforms",
name: "GetPlatforms",
kind: MethodKind.Unary,
I: GetPlatformsRequest,
O: GetPlatformsResponse,
service: {
typeName: "holos.v1alpha1.PlatformService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatform
*/
export const getPlatform = {
localName: "getPlatform",
name: "GetPlatform",
kind: MethodKind.Unary,
I: GetPlatformRequest,
O: GetPlatformResponse,
service: {
typeName: "holos.v1alpha1.PlatformService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.PlatformService.PutPlatformConfig
*/
export const putPlatformConfig = {
localName: "putPlatformConfig",
name: "PutPlatformConfig",
kind: MethodKind.Unary,
I: PutPlatformConfigRequest,
O: GetPlatformResponse,
service: {
typeName: "holos.v1alpha1.PlatformService"
}
} as const;
/**
* GetConfig provides the unmarshalled config values for use with CUE
*
* @generated from rpc holos.v1alpha1.PlatformService.GetConfig
*/
export const getConfig = {
localName: "getConfig",
name: "GetConfig",
kind: MethodKind.Unary,
I: GetPlatformConfigRequest,
O: ConfigValues,
service: {
typeName: "holos.v1alpha1.PlatformService"
}
} as const;

View File

@@ -3,7 +3,7 @@
/* eslint-disable */
// @ts-nocheck
import { AddPlatformRequest, ConfigValues, GetPlatformConfigRequest, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse, PutPlatformConfigRequest } from "./platform_pb.js";
import { AddPlatformRequest, AddPlatformResponse, GetFormRequest, GetFormResponse, GetModelRequest, GetModelResponse, GetPlatformRequest, GetPlatformResponse, ListPlatformsRequest, ListPlatformsResponse, PutFormRequest, PutFormResponse, PutModelRequest, PutModelResponse } from "./platform_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
@@ -18,16 +18,7 @@ export const PlatformService = {
addPlatform: {
name: "AddPlatform",
I: AddPlatformRequest,
O: GetPlatformsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatforms
*/
getPlatforms: {
name: "GetPlatforms",
I: GetPlatformsRequest,
O: GetPlatformsResponse,
O: AddPlatformResponse,
kind: MethodKind.Unary,
},
/**
@@ -40,23 +31,48 @@ export const PlatformService = {
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.PutPlatformConfig
* @generated from rpc holos.v1alpha1.PlatformService.ListPlatforms
*/
putPlatformConfig: {
name: "PutPlatformConfig",
I: PutPlatformConfigRequest,
O: GetPlatformResponse,
listPlatforms: {
name: "ListPlatforms",
I: ListPlatformsRequest,
O: ListPlatformsResponse,
kind: MethodKind.Unary,
},
/**
* GetConfig provides the unmarshalled config values for use with CUE
*
* @generated from rpc holos.v1alpha1.PlatformService.GetConfig
* @generated from rpc holos.v1alpha1.PlatformService.GetForm
*/
getConfig: {
name: "GetConfig",
I: GetPlatformConfigRequest,
O: ConfigValues,
getForm: {
name: "GetForm",
I: GetFormRequest,
O: GetFormResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.PutForm
*/
putForm: {
name: "PutForm",
I: PutFormRequest,
O: PutFormResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetModel
*/
getModel: {
name: "GetModel",
I: GetModelRequest,
O: GetModelResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.PutModel
*/
putModel: {
name: "PutModel",
I: PutModelRequest,
O: PutModelResponse,
kind: MethodKind.Unary,
},
}

View File

@@ -1,35 +0,0 @@
// @generated by protoc-gen-connect-query v1.3.1 with parameter "target=ts"
// @generated from file holos/v1alpha1/system.proto (package holos.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { MethodKind } from "@bufbuild/protobuf";
import { EmptyRequest, EmptyResponse } from "./system_pb.js";
/**
* @generated from rpc holos.v1alpha1.SystemService.SeedDatabase
*/
export const seedDatabase = {
localName: "seedDatabase",
name: "SeedDatabase",
kind: MethodKind.Unary,
I: EmptyRequest,
O: EmptyResponse,
service: {
typeName: "holos.v1alpha1.SystemService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.SystemService.DropTables
*/
export const dropTables = {
localName: "dropTables",
name: "DropTables",
kind: MethodKind.Unary,
I: EmptyRequest,
O: EmptyResponse,
service: {
typeName: "holos.v1alpha1.SystemService"
}
} as const;

View File

@@ -3,7 +3,7 @@
/* eslint-disable */
// @ts-nocheck
import { EmptyRequest, EmptyResponse } from "./system_pb.js";
import { DropTablesRequest, DropTablesResponse, SeedDatabaseRequest, SeedDatabaseResponse } from "./system_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
@@ -17,8 +17,8 @@ export const SystemService = {
*/
seedDatabase: {
name: "SeedDatabase",
I: EmptyRequest,
O: EmptyResponse,
I: SeedDatabaseRequest,
O: SeedDatabaseResponse,
kind: MethodKind.Unary,
},
/**
@@ -26,8 +26,8 @@ export const SystemService = {
*/
dropTables: {
name: "DropTables",
I: EmptyRequest,
O: EmptyResponse,
I: DropTablesRequest,
O: DropTablesResponse,
kind: MethodKind.Unary,
},
}

View File

@@ -7,64 +7,126 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM
import { Message, proto3 } from "@bufbuild/protobuf";
/**
* @generated from message holos.v1alpha1.EmptyRequest
* @generated from message holos.v1alpha1.SeedDatabaseRequest
*/
export class EmptyRequest extends Message<EmptyRequest> {
constructor(data?: PartialMessage<EmptyRequest>) {
export class SeedDatabaseRequest extends Message<SeedDatabaseRequest> {
constructor(data?: PartialMessage<SeedDatabaseRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.EmptyRequest";
static readonly typeName = "holos.v1alpha1.SeedDatabaseRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): EmptyRequest {
return new EmptyRequest().fromBinary(bytes, options);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): SeedDatabaseRequest {
return new SeedDatabaseRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): EmptyRequest {
return new EmptyRequest().fromJson(jsonValue, options);
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): SeedDatabaseRequest {
return new SeedDatabaseRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): EmptyRequest {
return new EmptyRequest().fromJsonString(jsonString, options);
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): SeedDatabaseRequest {
return new SeedDatabaseRequest().fromJsonString(jsonString, options);
}
static equals(a: EmptyRequest | PlainMessage<EmptyRequest> | undefined, b: EmptyRequest | PlainMessage<EmptyRequest> | undefined): boolean {
return proto3.util.equals(EmptyRequest, a, b);
static equals(a: SeedDatabaseRequest | PlainMessage<SeedDatabaseRequest> | undefined, b: SeedDatabaseRequest | PlainMessage<SeedDatabaseRequest> | undefined): boolean {
return proto3.util.equals(SeedDatabaseRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.EmptyResponse
* @generated from message holos.v1alpha1.SeedDatabaseResponse
*/
export class EmptyResponse extends Message<EmptyResponse> {
constructor(data?: PartialMessage<EmptyResponse>) {
export class SeedDatabaseResponse extends Message<SeedDatabaseResponse> {
constructor(data?: PartialMessage<SeedDatabaseResponse>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.EmptyResponse";
static readonly typeName = "holos.v1alpha1.SeedDatabaseResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): EmptyResponse {
return new EmptyResponse().fromBinary(bytes, options);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): SeedDatabaseResponse {
return new SeedDatabaseResponse().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): EmptyResponse {
return new EmptyResponse().fromJson(jsonValue, options);
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): SeedDatabaseResponse {
return new SeedDatabaseResponse().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): EmptyResponse {
return new EmptyResponse().fromJsonString(jsonString, options);
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): SeedDatabaseResponse {
return new SeedDatabaseResponse().fromJsonString(jsonString, options);
}
static equals(a: EmptyResponse | PlainMessage<EmptyResponse> | undefined, b: EmptyResponse | PlainMessage<EmptyResponse> | undefined): boolean {
return proto3.util.equals(EmptyResponse, a, b);
static equals(a: SeedDatabaseResponse | PlainMessage<SeedDatabaseResponse> | undefined, b: SeedDatabaseResponse | PlainMessage<SeedDatabaseResponse> | undefined): boolean {
return proto3.util.equals(SeedDatabaseResponse, a, b);
}
}
/**
* @generated from message holos.v1alpha1.DropTablesRequest
*/
export class DropTablesRequest extends Message<DropTablesRequest> {
constructor(data?: PartialMessage<DropTablesRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.DropTablesRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DropTablesRequest {
return new DropTablesRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DropTablesRequest {
return new DropTablesRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DropTablesRequest {
return new DropTablesRequest().fromJsonString(jsonString, options);
}
static equals(a: DropTablesRequest | PlainMessage<DropTablesRequest> | undefined, b: DropTablesRequest | PlainMessage<DropTablesRequest> | undefined): boolean {
return proto3.util.equals(DropTablesRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.DropTablesResponse
*/
export class DropTablesResponse extends Message<DropTablesResponse> {
constructor(data?: PartialMessage<DropTablesResponse>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.DropTablesResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DropTablesResponse {
return new DropTablesResponse().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DropTablesResponse {
return new DropTablesResponse().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DropTablesResponse {
return new DropTablesResponse().fromJsonString(jsonString, options);
}
static equals(a: DropTablesResponse | PlainMessage<DropTablesResponse> | undefined, b: DropTablesResponse | PlainMessage<DropTablesResponse> | undefined): boolean {
return proto3.util.equals(DropTablesResponse, a, b);
}
}

View File

@@ -1,49 +0,0 @@
// @generated by protoc-gen-connect-query v1.3.1 with parameter "target=ts"
// @generated from file holos/v1alpha1/user.proto (package holos.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { MethodKind } from "@bufbuild/protobuf";
import { CreateCallerUserRequest, CreateCallerUserResponse, GetCallerClaimsRequest, GetCallerClaimsResponse, GetCallerUserRequest, GetCallerUserResponse } from "./user_pb.js";
/**
* @generated from rpc holos.v1alpha1.UserService.GetCallerClaims
*/
export const getCallerClaims = {
localName: "getCallerClaims",
name: "GetCallerClaims",
kind: MethodKind.Unary,
I: GetCallerClaimsRequest,
O: GetCallerClaimsResponse,
service: {
typeName: "holos.v1alpha1.UserService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.UserService.GetCallerUser
*/
export const getCallerUser = {
localName: "getCallerUser",
name: "GetCallerUser",
kind: MethodKind.Unary,
I: GetCallerUserRequest,
O: GetCallerUserResponse,
service: {
typeName: "holos.v1alpha1.UserService"
}
} as const;
/**
* @generated from rpc holos.v1alpha1.UserService.CreateCallerUser
*/
export const createCallerUser = {
localName: "createCallerUser",
name: "CreateCallerUser",
kind: MethodKind.Unary,
I: CreateCallerUserRequest,
O: CreateCallerUserResponse,
service: {
typeName: "holos.v1alpha1.UserService"
}
} as const;

View File

@@ -33,7 +33,8 @@
</span>
<app-profile-button [claims$]="claims$"></app-profile-button>
</mat-toolbar>
<!-- Add Content Here -->
<router-outlet></router-outlet>
<main class="main-content">
<router-outlet></router-outlet>
</main>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@@ -13,7 +13,7 @@
.mat-toolbar.mat-primary {
position: sticky;
top: 0;
z-index: 1;
z-index: 1000;
}
.toolbar-spacer {

View File

@@ -1,20 +1,20 @@
import { Inject, Injectable } from '@angular/core';
import { OrganizationService as ConnectOrganizationService } from '../gen/holos/v1alpha1/organization_connect';
import { ObservableClient } from '../../connect/observable-client';
import { Observable, switchMap, of, shareReplay, catchError, BehaviorSubject } from 'rxjs';
import { GetCallerOrganizationsResponse, Organization } from '../gen/holos/v1alpha1/organization_pb';
import { UserService } from './user.service';
import { Code, ConnectError } from '@connectrpc/connect';
import { BehaviorSubject, Observable, catchError, of, shareReplay, switchMap } from 'rxjs';
import { ObservableClient } from '../../connect/observable-client';
import { OrganizationService as ConnectOrganizationService } from '../gen/holos/v1alpha1/organization_connect';
import { ListCallerOrganizationsResponse, Organization } from '../gen/holos/v1alpha1/organization_pb';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class OrganizationService {
private callerOrganizationsTrigger$ = new BehaviorSubject<void>(undefined);
private callerOrganizations$: Observable<GetCallerOrganizationsResponse>;
private callerOrganizations$: Observable<ListCallerOrganizationsResponse>;
private fetchCallerOrganizations(): Observable<GetCallerOrganizationsResponse> {
return this.client.getCallerOrganizations({ request: {} }).pipe(
private fetchCallerOrganizations(): Observable<ListCallerOrganizationsResponse> {
return this.client.listCallerOrganizations({ request: {} }).pipe(
switchMap(resp => {
if (resp && resp.organizations.length > 0) {
return of(resp)
@@ -25,7 +25,7 @@ export class OrganizationService {
if (err instanceof ConnectError) {
if (err.code == Code.NotFound) {
return this.userService.createUser().pipe(
switchMap(user => this.client.createCallerOrganization({ request: {} }))
switchMap(() => this.client.createCallerOrganization({ request: {} }))
)
}
}

View File

@@ -1,61 +1,40 @@
import { Inject, Injectable } from '@angular/core';
import { PlatformService as ConnectPlatformService } from '../gen/holos/v1alpha1/platform_connect';
import { Observable, filter, of, switchMap } from 'rxjs';
import { JsonValue, Struct, } from '@bufbuild/protobuf';
import { Observable, of, switchMap } from 'rxjs';
import { ObservableClient } from '../../connect/observable-client';
import { Config, ConfigSection, ConfigValues, GetPlatformsRequest, Platform, PutPlatformConfigRequest } from '../gen/holos/v1alpha1/platform_pb';
import { Organization } from '../gen/holos/v1alpha1/organization_pb';
import { Struct, Value } from '@bufbuild/protobuf';
export interface Section {
[field: string]: any;
}
export interface Model {
[section: string]: Section;
}
import { PlatformService as ConnectPlatformService } from '../gen/holos/v1alpha1/platform_connect';
import { GetFormResponse, ListPlatformsRequest, Platform, PutModelRequest, PutModelResponse } from '../gen/holos/v1alpha1/platform_pb';
@Injectable({
providedIn: 'root'
})
export class PlatformService {
getPlatforms(org: Observable<Organization>): Observable<Platform[]> {
listPlatforms(org: Observable<Organization>): Observable<Platform[]> {
return org.pipe(
switchMap(org => {
const req = new GetPlatformsRequest({ orgId: org.id })
return this.client.getPlatforms(req).pipe(
const req = new ListPlatformsRequest({ orgId: org.id })
return this.client.listPlatforms(req).pipe(
switchMap(resp => { return of(resp.platforms) })
)
})
)
}
getPlatform(id: string): Observable<Platform> {
return this.client.getPlatform({ platformId: id }).pipe(
switchMap(resp => {
return of(resp.platform);
}),
filter((platform): platform is Platform => platform !== undefined),
)
getForm(id: string): Observable<GetFormResponse> {
return this.client.getForm({ platformId: id })
}
putConfig(id: string, model: Model): Observable<Platform> {
const values = new ConfigValues
// Set string values from the model
Object.keys(model).forEach(sectionName => {
values.sections[sectionName] = new ConfigSection
Object.keys(model[sectionName]).forEach(fieldName => {
const val = new Value
val.fromJson(model[sectionName][fieldName])
values.sections[sectionName].fields[fieldName] = val
})
putModel(id: string, model: JsonValue): Observable<PutModelResponse> {
const req = new PutModelRequest({
platformId: id,
// "We recommend to use fromJson() to construct Struct literals" refer to
// https://github.com/bufbuild/protobuf-es/blob/main/docs/runtime_api.md#struct
model: Struct.fromJson(model),
})
const req = new PutPlatformConfigRequest({ platformId: id, values: values })
return this.client.putPlatformConfig(req).pipe(
switchMap(resp => { return of(resp.platform) }),
filter((platform): platform is Platform => platform !== undefined),
)
return this.client.putModel(req)
}
constructor(@Inject(ConnectPlatformService) private client: ObservableClient<typeof ConnectPlatformService>) { }
}

View File

@@ -1,25 +1,28 @@
<mat-tab-group>
<mat-tab label="Detail">
<div class="grid-container">
@if (platform$ | async; as platform) {
<form [formGroup]="form" (ngSubmit)="onSubmit(model)">
@for (section of platform.config?.form?.spec?.sections; track section.name) {
<h2>{{section.displayName ? section.displayName : section.name }}</h2>
<p>{{ section.description }}</p>
<formly-form [form]="form" [fields]="section.fieldConfigs" [model]="model[section.name]"></formly-form>
}
<p></p>
<button type="submit" mat-flat-button color="primary">Submit</button>
</form>
}
</div>
</mat-tab>
<div class="content-container">
<mat-tab-group>
<mat-tab label="Detail">
<div class="grid-container">
<form [formGroup]="form" (ngSubmit)="onSubmit(model)">
<formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>
<button type="submit" mat-flat-button color="primary" [disabled]="!form.valid">Submit</button>
</form>
</div>
</mat-tab>
<mat-tab label="Raw">
<div class="grid-container">
@if (platform$ | async; as platform) {
<pre>{{ platform | json }}</pre>
}
</div>
</mat-tab>
</mat-tab-group>
<mat-tab label="Model">
<div class="grid-container">
<pre>{{ model | json }}</pre>
</div>
</mat-tab>
<mat-tab label="Form State">
<div class="grid-container">
<pre>{{ options.formState | json }}</pre>
</div>
</mat-tab>
<mat-tab label="Fields">
<div class="grid-container">
<pre>{{ fields | json }}</pre>
</div>
</mat-tab>
</mat-tab-group>
</div>

View File

@@ -1,69 +1,90 @@
import { Component, Input, inject } from '@angular/core';
import { Observable, map, shareReplay } from 'rxjs';
import { Model, PlatformService } from '../../services/platform.service';
import { Platform } from '../../gen/holos/v1alpha1/platform_pb';
import { MatTab, MatTabGroup } from '@angular/material/tabs';
import { AsyncPipe, CommonModule } from '@angular/common';
import { Component, Input, OnDestroy, inject } from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { FormlyMaterialModule } from '@ngx-formly/material';
import { FormlyModule } from '@ngx-formly/core';
import { MatButton } from '@angular/material/button';
import { MatDivider } from '@angular/material/divider';
import { MatTab, MatTabGroup } from '@angular/material/tabs';
import { JsonValue } from '@bufbuild/protobuf';
import { FormlyFieldConfig, FormlyFormOptions, FormlyModule } from '@ngx-formly/core';
import { FormlyMaterialModule } from '@ngx-formly/material';
import { Subject, takeUntil } from 'rxjs';
import { PlatformService } from '../../services/platform.service';
@Component({
selector: 'app-platform-detail',
standalone: true,
imports: [
MatTabGroup,
MatTab,
AsyncPipe,
CommonModule,
FormlyModule,
ReactiveFormsModule,
FormlyMaterialModule,
FormlyModule,
MatButton,
MatDivider,
MatTab,
MatTabGroup,
ReactiveFormsModule,
],
templateUrl: './platform-detail.component.html',
styleUrl: './platform-detail.component.scss'
})
export class PlatformDetailComponent {
private service = inject(PlatformService);
export class PlatformDetailComponent implements OnDestroy {
private platformService = inject(PlatformService);
private platformId: string = "";
platform$!: Observable<Platform>;
private destroy$: Subject<boolean> = new Subject<boolean>();
form = new FormGroup({});
model: Model = {};
fields: FormlyFieldConfig[] = [];
model: JsonValue = {};
// Use form state to store the model for nested forms
// Refer to https://formly.dev/docs/examples/form-options/form-state/
options: FormlyFormOptions = {
formState: {
model: this.model,
},
};
onSubmit(model: Model) {
console.log(model)
// if (this.form.valid) {
this.service.putConfig(this.platformId, model).pipe(shareReplay(1)).subscribe()
// }
private setModel(model: JsonValue) {
if (model) {
this.model = model
this.options.formState.model = model
}
}
onSubmit(model: JsonValue) {
if (this.form.valid) {
console.log(model)
this.platformService
.putModel(this.platformId, model)
.pipe(takeUntil(this.destroy$))
.subscribe(resp => {
if (resp.model !== undefined) {
this.setModel(resp.model.toJson())
}
})
}
}
@Input()
set id(platformId: string) {
this.platformId = platformId;
this.platform$ = this.service.getPlatform(platformId).pipe(
map(project => {
// Initialize the model container for each section of the form config
project.config?.form?.spec?.sections.forEach(section => {
this.model[section.name] = {}
})
// Load existing values into the form
const sections = project.config?.values?.sections
if (sections !== undefined) {
Object.keys(sections).forEach(sectionName => {
Object.keys(sections[sectionName].fields).forEach(fieldName => {
this.model[sectionName][fieldName] = sections[sectionName].fields[fieldName].toJson()
})
})
this.platformService
.getForm(platformId)
.pipe(takeUntil(this.destroy$))
.subscribe(resp => {
if (resp.model !== undefined) {
this.setModel(resp.model.toJson())
}
return project
}),
shareReplay(1)
)
if (resp.fields !== undefined) {
// NOTE: We could mix functions into the json data via mapped fields,
// but consider carefully before doing so. Refer to
// https://formly.dev/docs/examples/other/json-powered
this.fields = resp.fields.map(field => field.toJson() as FormlyFieldConfig)
}
})
}
public ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}

View File

@@ -1,5 +1,5 @@
import { Platform } from '../../gen/holos/v1alpha1/platform_pb';
import { Component, inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { MatListItem, MatNavList } from '@angular/material/list';
import { Observable, filter } from 'rxjs';
import { Organization } from '../../gen/holos/v1alpha1/organization_pb';
@@ -21,16 +21,16 @@ import { RouterLink } from '@angular/router';
templateUrl: './platforms.component.html',
styleUrl: './platforms.component.scss'
})
export class PlatformsComponent {
export class PlatformsComponent implements OnInit {
private orgSvc = inject(OrganizationService);
private platformSvc = inject(PlatformService);
org$!: Observable<Organization | undefined>;
platforms$!: Observable<Platform[]>;
ngOnInit(): void {
ngOnInit() {
this.org$ = this.orgSvc.activeOrg();
this.platforms$ = this.platformSvc.getPlatforms(this.org$.pipe(
this.platforms$ = this.platformSvc.listPlatforms(this.org$.pipe(
filter((org): org is Organization => org !== undefined)
))
}

View File

@@ -0,0 +1,7 @@
<div class="card">
<h2 class="card-header">{{ props.label }}</h2>
<div class="card-body">
<p>{{ props.description }}</p>
<ng-container #fieldComponent></ng-container>
</div>
</div>

View File

@@ -0,0 +1,3 @@
.card {
margin-bottom: 20px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HolosPanelWrapperComponent } from './holos-panel-wrapper.component';
describe('HolosPanelWrapperComponent', () => {
let component: HolosPanelWrapperComponent;
let fixture: ComponentFixture<HolosPanelWrapperComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HolosPanelWrapperComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HolosPanelWrapperComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';
@Component({
selector: 'app-holos-panel-wrapper',
standalone: true,
imports: [],
templateUrl: './holos-panel-wrapper.component.html',
styleUrl: './holos-panel-wrapper.component.scss'
})
export class HolosPanelWrapperComponent extends FieldWrapper { }

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,43 +1,358 @@
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."
fieldConfigs: {
// platform.org.name
name: props: {
label: "Name"
placeholder: "example"
description: "DNS label, e.g. 'example'"
// platform.spec.config.user.sections.org.fields.name
name: {
type: "input"
props: {
label: "Name"
// placeholder: "example" placeholder cannot be used with validation?
description: "DNS label, e.g. 'example'"
pattern: "^[a-z]([0-9a-z]|-){1,28}[0-9a-z]$"
minLength: 3
maxLength: 30
required: true
}
validation: messages: {
pattern: "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
}
}
// platform.org.domain
domain: props: {
label: "Domain"
placeholder: "example.com"
description: "DNS domain, e.g. 'example.com'"
// platform.spec.config.user.sections.org.fields.domain
domain: {
type: "input"
props: {
label: "Domain"
placeholder: "example.com"
minLength: 3
maxLength: 100
description: "DNS domain, e.g. 'example.com'"
required: true
}
}
// platform.org.displayName
displayName: props: {
label: "Display Name"
placeholder: "Example Organization"
description: "Display name, e.g. 'Example Organization'"
// platform.spec.config.user.sections.org.fields.displayName
displayName: {
type: "input"
props: {
label: "Display Name"
placeholder: "Example Organization"
description: "Display name, e.g. 'Example Organization'"
maxLength: 100
required: true
}
}
// platform.org.contactEmail
contactEmail: props: {
label: "Contact Email"
placeholder: "platform-team@example.com"
description: "Technical contact email address"
// platform.spec.config.user.sections.org.fields.contactEmail
contactEmail: {
type: "input"
props: {
label: "Contact Email"
placeholder: "platform-team@example.com"
description: "Technical contact email address"
required: true
}
}
}
}
Sections: cloud: {
displayName: "Cloud Providers"
description: "Select the services that provide resources for the platform."
fieldConfigs: {
providers: {
// https://formly.dev/docs/api/ui/material/select/
type: "select"
props: {
label: "Select Providers"
description: "Select the cloud providers the platform builds upon."
multiple: true
selectAllOption: "Select All"
options: [
{value: "aws", label: "Amazon Web Services"},
{value: "gcp", label: "Google Cloud Platform"},
{value: "azure", label: "Microsoft Azure"},
{value: "cloudflare", label: "Cloudflare"},
{value: "github", label: "GitHub"},
{value: "ois", label: "Open Infrastructure Services"},
{value: "onprem", label: "On Premises", disabled: true},
]
}
}
}
}
Sections: aws: {
displayName: "Amazon Web Services"
description: "Provide the information necessary for Holos to manage AWS resources to provide the platform."
expressions: hide: "!\(AWSSelected)"
fieldConfigs: {
primaryRoleARN: {
// https://formly.dev/docs/api/ui/material/input
type: "input"
props: {
label: "Holos Admin Role ARN"
description: "Enter the AWS Role ARN Holos will use to bootstrap resources. For example, arn:aws:iam::123456789012:role/HolosAdminAccess"
pattern: "^arn:.*"
minLength: 4
required: true
}
validation: messages: {
pattern: "Must be a valid ARN. Refer to https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html"
}
}
regions: {
// https://formly.dev/docs/api/ui/material/select/
type: "select"
props: {
label: "Select Regions"
description: "Select the AWS regions this platform operates in."
multiple: true
required: true
selectAllOption: "Select All"
options: AWSRegions
}
}
}
}
Sections: gcp: {
displayName: "Google Cloud Platform"
description: "Use this form to configure platform level GCP settings."
expressions: hide: "!\(GCPSelected)"
fieldConfigs: {
regions: {
// https://formly.dev/docs/api/ui/material/select/
type: "select"
props: {
label: "Select Regions"
description: "Select the GCP regions this platform operates in."
multiple: true
selectAllOption: "Select All"
// gcloud compute regions list --format=json | jq '.[] | {value: .name, label: .description}' regions.json | jq -s | cue export --out cue
options: GCPRegions
}
}
gcpProjectID: {
// https://formly.dev/docs/api/ui/material/input
type: "input"
props: {
label: "Project ID"
description: "Enter the project id where the provisioner cluster resides."
pattern: "^[a-z]([0-9a-z]|-){1,28}[0-9a-z]$"
minLength: 6
maxLength: 30
required: true
}
validation: messages: {
pattern: "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
}
}
gcpProjectNumber: {
// https://formly.dev/docs/api/ui/material/input
type: "input"
props: {
label: "Project Number"
// note type number here
type: "number"
description: "Enter the project number where the provisioner cluster resides."
pattern: "^[0-9]+$"
required: true
}
validation: messages: {
pattern: "Must be a valid project number."
}
}
provisionerCABundle: {
type: "input"
props: {
label: "Provisioner CA Bundle"
description: "Enter the provisioner cluster ca bundle. kubectl config view --minify --flatten -ojsonpath='{.clusters[0].cluster.certificate-authority-data}'"
pattern: "^[0-9a-zA-Z]+=*$"
required: true
}
validation: messages: {
pattern: "Must be a base64 encoded pem encoded certificate bundle."
}
}
provisionerURL: {
type: "input"
props: {
label: "Provisioner URL"
description: "Enter the URL of the provisioner cluster API endpoint. kubectl config view --minify --flatten -ojsonpath='{.clusters[0].cluster.server}'"
pattern: "^https://.*$"
required: true
}
validation: messages: {
pattern: "Must be a https:// URL."
}
}
}
}
Sections: cloudflare: {
displayName: "Cloudflare"
description: "Cloudflare is primarily used for DNS automation."
expressions: hide: "!" + CloudflareSelected
fieldConfigs: {
email: {
// https://formly.dev/docs/api/ui/material/input
type: "input"
props: {
label: "Account Email"
description: "Enter the Cloudflare email address to manage DNS"
minLength: 3
required: true
}
}
}
}
Sections: github: {
displayName: "GitHub"
description: "GitHub is primarily used to host Git repositories and execute Actions workflows."
expressions: hide: "!\(GitHubSelected)"
fieldConfigs: {
primaryOrg: {
// https://formly.dev/docs/api/ui/material/input
type: "input"
props: {
label: "Organization"
description: "Enter the primary GitHub organization associed with the platform."
pattern: "^(?!-)(?!.*--)([a-zA-Z0-9]|-){1,39}$"
minLength: 1
maxLength: 39
required: true
}
validation: messages: {
pattern: "All characters must be either a hyphen or alphanumeric. Cannot start with a hyphen. Cannot include consecutive hyphens."
}
}
}
}
Sections: backups: {
displayName: "Backups"
description: "Configure platform level data backup settings. Requires AWS."
fieldConfigs: {
s3bucket: {
// https://formly.dev/docs/api/ui/material/input
type: "select"
props: {
label: "S3 Bucket Region"
description: "Select the S3 Bucket Region."
multiple: true
options: AWSRegions
}
expressions: {
// Disable the control if AWS is not selected.
"props.disabled": "!" + AWSSelected
// Required if AWS is selected.
"props.required": AWSSelected
// Change the label depending on AWS
"props.description": AWSSelected + " ? '\(props.description)' : 'Enable AWS in the Cloud Provider section to configure backups.'"
}
}
}
}
}
// Provide the output form fields
Platform.Form
let GCPRegions = [
{value: "africa-south1", label: "africa-south1"},
{value: "asia-east1", label: "asia-east1"},
{value: "asia-east2", label: "asia-east2"},
{value: "asia-northeast1", label: "asia-northeast1"},
{value: "asia-northeast2", label: "asia-northeast2"},
{value: "asia-northeast3", label: "asia-northeast3"},
{value: "asia-south1", label: "asia-south1"},
{value: "asia-south2", label: "asia-south2"},
{value: "asia-southeast1", label: "asia-southeast1"},
{value: "asia-southeast2", label: "asia-southeast2"},
{value: "australia-southeast1", label: "australia-southeast1"},
{value: "australia-southeast2", label: "australia-southeast2"},
{value: "europe-central2", label: "europe-central2"},
{value: "europe-north1", label: "europe-north1"},
{value: "europe-southwest1", label: "europe-southwest1"},
{value: "europe-west1", label: "europe-west1"},
{value: "europe-west10", label: "europe-west10"},
{value: "europe-west12", label: "europe-west12"},
{value: "europe-west2", label: "europe-west2"},
{value: "europe-west3", label: "europe-west3"},
{value: "europe-west4", label: "europe-west4"},
{value: "europe-west6", label: "europe-west6"},
{value: "europe-west8", label: "europe-west8"},
{value: "europe-west9", label: "europe-west9"},
{value: "me-central1", label: "me-central1"},
{value: "me-central2", label: "me-central2"},
{value: "me-west1", label: "me-west1"},
{value: "northamerica-northeast1", label: "northamerica-northeast1"},
{value: "northamerica-northeast2", label: "northamerica-northeast2"},
{value: "southamerica-east1", label: "southamerica-east1"},
{value: "southamerica-west1", label: "southamerica-west1"},
{value: "us-central1", label: "us-central1"},
{value: "us-east1", label: "us-east1"},
{value: "us-east4", label: "us-east4"},
{value: "us-east5", label: "us-east5"},
{value: "us-south1", label: "us-south1"},
{value: "us-west1", label: "us-west1"},
{value: "us-west2", label: "us-west2"},
{value: "us-west3", label: "us-west3"},
{value: "us-west4", label: "us-west4"},
]
let AWSRegions = [
{value: "us-east-1", label: "N. Virginia (us-east-1)"},
{value: "us-east-2", label: "Ohio (us-east-2)"},
{value: "us-west-1", label: "N. California (us-west-1)"},
{value: "us-west-2", label: "Oregon (us-west-2)"},
{value: "us-gov-west1", label: "US GovCloud West (us-gov-west1)"},
{value: "us-gov-east1", label: "US GovCloud East (us-gov-east1)"},
{value: "ca-central-1", label: "Canada (ca-central-1)"},
{value: "eu-north-1", label: "Stockholm (eu-north-1)"},
{value: "eu-west-1", label: "Ireland (eu-west-1)"},
{value: "eu-west-2", label: "London (eu-west-2)"},
{value: "eu-west-3", label: "Paris (eu-west-3)"},
{value: "eu-central-1", label: "Frankfurt (eu-central-1)"},
{value: "eu-south-1", label: "Milan (eu-south-1)"},
{value: "af-south-1", label: "Cape Town (af-south-1)"},
{value: "ap-northeast-1", label: "Tokyo (ap-northeast-1)"},
{value: "ap-northeast-2", label: "Seoul (ap-northeast-2)"},
{value: "ap-northeast-3", label: "Osaka (ap-northeast-3)"},
{value: "ap-southeast-1", label: "Singapore (ap-southeast-1)"},
{value: "ap-southeast-2", label: "Sydney (ap-southeast-2)"},
{value: "ap-east-1", label: "Hong Kong (ap-east-1)"},
{value: "ap-south-1", label: "Mumbai (ap-south-1)"},
{value: "me-south-1", label: "Bahrain (me-south-1)"},
{value: "sa-east-1", label: "São Paulo (sa-east-1)"},
{value: "cn-north-1", label: "Bejing (cn-north-1)"},
{value: "cn-northwest-1", label: "Ningxia (cn-northwest-1)"},
{value: "ap-southeast-3", label: "Jakarta (ap-southeast-3)"},
]
let AWSSelected = "formState.model.cloud?.providers?.includes(\"aws\")"
let GCPSelected = "formState.model.cloud?.providers?.includes(\"gcp\")"
let GitHubSelected = "formState.model.cloud?.providers?.includes(\"github\")"
let CloudflareSelected = "formState.model.cloud?.providers?.includes(\"cloudflare\")"

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,25 +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: {[string]: {[string]: string | bool | [...string]}}

View File

@@ -1 +0,0 @@
{"platform":{"spec":{"org":{"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

@@ -1,65 +0,0 @@
package v1alpha1
#Platform: {
name: string // short dns label name
displayName: string // Display name
description: string // Plaform description
sections: {[NAME=string]: #ConfigSection & {name: NAME}}
Form: {
let Name = name
apiVersion: "forms.holos.run/v1alpha1"
kind: "PlatformForm"
metadata: name: Name
spec: #PlatformFormSpec
}
let Sections = sections
Form: spec: sections: [for s in Sections {s.output}]
}
#PlatformFormSpec: {
sections: [...#ConfigSectionOutput]
}
// #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: {
name: string // e.g. "org"
displayName: string // e.g. "Organization"
description: string
fieldConfigs: {[NAME=string]: #FieldConfig & {key: NAME}}
let Name = name
let DisplayName = displayName
let Description = description
let FieldConfigs = fieldConfigs
output: #ConfigSectionOutput & {
name: Name
displayName: DisplayName
description: Description
fieldConfigs: [for fc in FieldConfigs {fc}]
}
}
#ConfigSectionOutput: {
name: string
displayName: string
description: string
fieldConfigs: [...#FieldConfig]
}
// Refer to https://formly.dev/docs/api/core#formlyfieldconfig
#FieldConfig: {
key: string
type: "input"
props: {
label: string
placeholder: string
description: string
required: *true | false
}
}

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

@@ -0,0 +1,123 @@
package v1alpha1
// #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"
spec: fields: [...#FieldConfig]
}
// #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}]
}
}
// #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
expressions: {[string]: string}
fieldConfigs: {[NAME=string]: #FieldConfig & {key: NAME}}
let Description = description
let Expressions = expressions
// Wrap the fields of the section into one FormlyFieldConfig
wrapper: #FieldConfig & {
key: name
// See our custom wrappers registered in app.config.ts
wrappers: ["holos-panel"]
props: label: displayName
props: description: Description
for k, v in Expressions {
expressions: "\(k)": v
}
// Might need to initialize the default value for a fieldGroup
// https://github.com/ngx-formly/ngx-formly/issues/3667
fieldGroup: [for fc in fieldConfigs {fc}]
}
}
// #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: {
key: string
// type is optional, may be a nested form which has no type field
type?: string | "input" | "select" | "checkbox"
// For nested forms, refer: to https://formly.dev/docs/examples/other/nested-formly-forms
wrappers?: [...string]
// Refer to: https://formly.dev/docs/api/ui/material/select#formlyselectprops
// and other input field select props.
props: {
#FormlySelectProps
label: string
type?: string
placeholder?: string
description: string
required?: *true | false
pattern?: string
minLength?: number
maxLength?: number
}
// Refer to: https://github.com/ngx-formly/ngx-formly/blob/v6.3.0/src/core/src/lib/models/fieldconfig.ts#L49-L64
// We support only the string form.
validation?: {
// Note, you can set messages for pattern, minLength, maxLength here.
messages?: [string]: string
}
// Refer to: https://github.com/ngx-formly/ngx-formly/blob/v6.3.0/src/core/src/lib/models/fieldconfig.ts#L66-L71
// We do not support validators because they must be javascript functions, not data.
validators?: "not supported"
// Refer to: https://github.com/ngx-formly/ngx-formly/blob/v6.3.0/src/core/src/lib/models/fieldconfig.ts#L115-L120
expressions?: [string]: string
hide?: true | false
// Required to populate protobuf value.
resetOnHide: *true | false
defaultValue?: _
className?: string
fieldGroup?: [...#FieldConfig]
focus?: true | *false
modelOptions?: {
debounce?: {
default: number
}
updateOn?: "change" | "blur" | "submit"
}
}
// Refer to https://formly.dev/docs/api/ui/material/select#formlyselectprops
#FormlySelectProps: {
disableOptionCentering?: true | false
multiple?: true | false
panelClass?: string
selectAllOption?: string
typeaheadDebounceInterval?: number
options?: [...{value: string | number | bool, label: string, disabled?: true | *false}]
// These could be used to set different keys for value and label in the
// options list, but we don't support that level of customization.
// They're here for documentation purposes only.
labelProp?: "label"
valueProp?: "value"
}

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

@@ -27,10 +27,10 @@ type OrganizationHandler struct {
db *ent.Client
}
func (h *OrganizationHandler) GetCallerOrganizations(
func (h *OrganizationHandler) ListCallerOrganizations(
ctx context.Context,
req *connect.Request[holos.GetCallerOrganizationsRequest],
) (*connect.Response[holos.GetCallerOrganizationsResponse], error) {
req *connect.Request[holos.ListCallerOrganizationsRequest],
) (*connect.Response[holos.ListCallerOrganizationsResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
@@ -56,7 +56,7 @@ func (h *OrganizationHandler) GetCallerOrganizations(
rpcOrgs = append(rpcOrgs, OrganizationToRPC(dbOrg))
}
res := connect.NewResponse(&holos.GetCallerOrganizationsResponse{
res := connect.NewResponse(&holos.ListCallerOrganizationsResponse{
User: UserToRPC(dbUser),
Organizations: rpcOrgs,
})
@@ -66,7 +66,7 @@ func (h *OrganizationHandler) GetCallerOrganizations(
func (h *OrganizationHandler) CreateCallerOrganization(
ctx context.Context,
req *connect.Request[holos.CreateCallerOrganizationRequest],
) (*connect.Response[holos.GetCallerOrganizationsResponse], error) {
) (*connect.Response[holos.CreateCallerOrganizationResponse], error) {
log := logger.FromContext(ctx)
authnID, err := authn.FromContext(ctx)
if err != nil {
@@ -110,7 +110,7 @@ func (h *OrganizationHandler) CreateCallerOrganization(
rpcOrgs = append(rpcOrgs, OrganizationToRPC(dbOrg))
}
res := connect.NewResponse(&holos.GetCallerOrganizationsResponse{
res := connect.NewResponse(&holos.CreateCallerOrganizationResponse{
User: UserToRPC(dbUser),
Organizations: rpcOrgs,
})

View File

@@ -2,7 +2,6 @@ package handler
import (
"context"
"encoding/json"
"fmt"
"log/slog"
@@ -10,12 +9,11 @@ import (
"github.com/gofrs/uuid"
"github.com/holos-run/holos/internal/ent"
"github.com/holos-run/holos/internal/ent/organization"
"github.com/holos-run/holos/internal/ent/platform"
entplatform "github.com/holos-run/holos/internal/ent/platform"
"github.com/holos-run/holos/internal/ent/user"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/authn"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
"google.golang.org/protobuf/types/known/timestamppb"
)
// NewPlatformHandler returns a new PlatformService implementation.
@@ -28,59 +26,42 @@ type PlatformHandler struct {
db *ent.Client
}
func (h *PlatformHandler) GetPlatforms(
func (h *PlatformHandler) ListPlatforms(
ctx context.Context,
req *connect.Request[holos.GetPlatformsRequest],
) (*connect.Response[holos.GetPlatformsResponse], error) {
req *connect.Request[holos.ListPlatformsRequest],
) (*connect.Response[holos.ListPlatformsResponse], error) {
_, reqDBOrg, err := getAuthnUsersOrg(ctx, req.Msg.OrgId, h.db)
if err != nil {
return nil, errors.Wrap(err)
}
return getPlatformsResponse(reqDBOrg), nil
resp := &holos.ListPlatformsResponse{Platforms: rpcPlatforms(reqDBOrg)}
return connect.NewResponse(resp), nil
}
func (h *PlatformHandler) AddPlatform(
ctx context.Context,
req *connect.Request[holos.AddPlatformRequest],
) (*connect.Response[holos.GetPlatformsResponse], error) {
) (*connect.Response[holos.AddPlatformResponse], error) {
dbUser, dbOrg, err := getAuthnUsersOrg(ctx, req.Msg.Platform.OrgId, h.db)
if err != nil {
return nil, errors.Wrap(err)
}
var hf holos.PlatformForm
if len(req.Msg.Platform.RawConfig.Form) > 0 {
if err := json.Unmarshal(req.Msg.Platform.RawConfig.Form, &hf); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
}
}
var hv holos.ConfigValues
if len(req.Msg.Platform.RawConfig.Values) > 0 {
if err := json.Unmarshal(req.Msg.Platform.RawConfig.Values, &hv); err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
}
}
platform, err := h.db.Platform.Create().
SetOrgID(dbOrg.ID).
SetCreatorID(dbUser.ID).
SetName(req.Msg.Platform.Name).
SetDisplayName(req.Msg.Platform.DisplayName).
SetConfigForm(&hf).
SetConfigValues(&hv).
SetConfigCue(req.Msg.Platform.RawConfig.Cue).
SetConfigDefinition(req.Msg.Platform.RawConfig.Definition).
Save(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
resp := getPlatformsResponse(dbOrg)
resp.Msg.Platforms = append(resp.Msg.Platforms, PlatformToRPC(platform))
resp := &holos.AddPlatformResponse{Platforms: rpcPlatforms(dbOrg)}
resp.Platforms = append(resp.Platforms, PlatformToRPC(platform))
return resp, nil
return connect.NewResponse(resp), nil
}
func (h *PlatformHandler) getPlatform(ctx context.Context, id string, uid authn.Identity) (*ent.Platform, error) {
@@ -90,8 +71,8 @@ func (h *PlatformHandler) getPlatform(ctx context.Context, id string, uid authn.
}
p, err := h.db.Platform.Query().
Where(platform.ID(platformID)).
Where(platform.HasOrganizationWith(
Where(entplatform.ID(platformID)).
Where(entplatform.HasOrganizationWith(
organization.HasUsersWith(
user.Iss(uid.Issuer()),
user.Sub(uid.Subject()),
@@ -122,52 +103,30 @@ func (h *PlatformHandler) GetPlatform(ctx context.Context, req *connect.Request[
return connect.NewResponse(&holos.GetPlatformResponse{Platform: PlatformToRPC(p)}), nil
}
func (h *PlatformHandler) PutPlatformConfig(ctx context.Context, req *connect.Request[holos.PutPlatformConfigRequest]) (*connect.Response[holos.GetPlatformResponse], error) {
func (h *PlatformHandler) PutModel(ctx context.Context, req *connect.Request[holos.PutModelRequest]) (*connect.Response[holos.PutModelResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
id, err := uuid.FromString(req.Msg.PlatformId)
p, err := h.getPlatform(ctx, req.Msg.GetPlatformId(), authnID)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
return nil, errors.Wrap(err)
}
// Get the platform so we can validate the values.
p, err := h.db.Platform.Query().
Where(platform.ID(id)).
Where(platform.HasOrganizationWith(
organization.HasUsersWith(
user.Iss(authnID.Issuer()),
user.Sub(authnID.Subject()),
))).
Only(ctx)
if err != nil {
if ent.MaskNotFound(err) == nil {
return nil, connect.NewError(connect.CodeNotFound, errors.Wrap(err))
} else {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
}
slog.WarnContext(ctx, "todo: validate the platform config against cue definitions", "action", "todo", "cue", len(p.Cue))
slog.WarnContext(ctx, "todo: validate the platform config against cue definitions", "action", "todo", "cue", len(p.ConfigCue))
up, err := h.db.Platform.UpdateOneID(id).
Where(platform.HasOrganizationWith(
organization.HasUsersWith(
user.Iss(authnID.Issuer()),
user.Sub(authnID.Subject()),
))).
SetConfigValues(req.Msg.Values).
_, err = p.Update().
SetModel(&holos.Model{Model: req.Msg.GetModel()}).
Save(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.GetPlatformResponse{Platform: PlatformToRPC(up)}), nil
return connect.NewResponse(&holos.PutModelResponse{Model: req.Msg.Model}), nil
}
func (h *PlatformHandler) GetConfig(ctx context.Context, req *connect.Request[holos.GetPlatformConfigRequest]) (*connect.Response[holos.ConfigValues], error) {
func (h *PlatformHandler) GetModel(ctx context.Context, req *connect.Request[holos.GetModelRequest]) (*connect.Response[holos.GetModelResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
@@ -178,7 +137,43 @@ func (h *PlatformHandler) GetConfig(ctx context.Context, req *connect.Request[ho
return nil, errors.Wrap(err)
}
return connect.NewResponse(p.ConfigValues), nil
return connect.NewResponse(&holos.GetModelResponse{Model: p.Model.Model}), nil
}
func (h *PlatformHandler) GetForm(ctx context.Context, req *connect.Request[holos.GetFormRequest]) (*connect.Response[holos.GetFormResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
p, err := h.getPlatform(ctx, req.Msg.GetPlatformId(), authnID)
if err != nil {
return nil, errors.Wrap(err)
}
return connect.NewResponse(&holos.GetFormResponse{Fields: p.Form.GetFields(), Model: p.Model.GetModel()}), nil
}
func (h *PlatformHandler) PutForm(ctx context.Context, req *connect.Request[holos.PutFormRequest]) (*connect.Response[holos.PutFormResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
p, err := h.getPlatform(ctx, req.Msg.GetPlatformId(), authnID)
if err != nil {
return nil, errors.Wrap(err)
}
_, err = p.Update().
SetForm(&holos.Form{Fields: req.Msg.GetFields()}).
Save(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
resp := &holos.PutFormResponse{Fields: req.Msg.GetFields()}
return connect.NewResponse(resp), nil
}
func PlatformToRPC(platform *ent.Platform) *holos.Platform {
@@ -187,17 +182,7 @@ func PlatformToRPC(platform *ent.Platform) *holos.Platform {
Name: platform.Name,
DisplayName: platform.DisplayName,
OrgId: platform.OrgID.String(),
Config: &holos.Config{
Form: platform.ConfigForm,
Values: platform.ConfigValues,
},
Timestamps: &holos.Timestamps{
CreatedAt: timestamppb.New(platform.CreatedAt),
UpdatedAt: timestamppb.New(platform.UpdatedAt),
},
Creator: &holos.Creator{
Id: platform.CreatorID.String(),
},
Spec: &holos.PlatformSpec{Model: platform.Model.GetModel()},
}
}
@@ -249,14 +234,14 @@ func getAuthnUsersOrg(ctx context.Context, orgID string, db *ent.Client) (*ent.U
return dbUser, reqDBOrg, nil
}
func getPlatformsResponse(reqDBOrg *ent.Organization) *connect.Response[holos.GetPlatformsResponse] {
// one extra in case a new platform is appended.
rpcPlatforms := make([]*holos.Platform, 0, 1+len(reqDBOrg.Edges.Platforms))
for _, platform := range reqDBOrg.Edges.Platforms {
rpcPlatforms = append(rpcPlatforms, PlatformToRPC(platform))
func rpcPlatforms(reqDBOrg *ent.Organization) []*holos.Platform {
if reqDBOrg == nil {
return nil
}
return connect.NewResponse(&holos.GetPlatformsResponse{
Platforms: rpcPlatforms,
})
// one extra in case a new platform is appended.
platforms := make([]*holos.Platform, 0, 1+len(reqDBOrg.Edges.Platforms))
for _, platform := range reqDBOrg.Edges.Platforms {
platforms = append(platforms, PlatformToRPC(platform))
}
return platforms
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"connectrpc.com/connect"
"github.com/gofrs/uuid"
"github.com/holos-run/holos/internal/ent"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/authn"
@@ -38,7 +39,7 @@ func (h *SystemHandler) checkAdmin(ctx context.Context) error {
return nil
}
func (h *SystemHandler) DropTables(ctx context.Context, req *connect.Request[holos.EmptyRequest]) (*connect.Response[holos.EmptyResponse], error) {
func (h *SystemHandler) DropTables(ctx context.Context, req *connect.Request[holos.DropTablesRequest]) (*connect.Response[holos.DropTablesResponse], error) {
if err := h.checkAdmin(ctx); err != nil {
return nil, err
}
@@ -63,16 +64,17 @@ func (h *SystemHandler) DropTables(ctx context.Context, req *connect.Request[hol
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.EmptyResponse{}), nil
return connect.NewResponse(&holos.DropTablesResponse{}), nil
}
func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[holos.EmptyRequest]) (*connect.Response[holos.EmptyResponse], error) {
func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[holos.SeedDatabaseRequest]) (*connect.Response[holos.SeedDatabaseResponse], error) {
if err := h.checkAdmin(ctx); err != nil {
return nil, err
}
if err := WithTx(ctx, h.db, func(tx *ent.Tx) (err error) {
jeff, err := tx.User.Create().
SetID(uuid.FromStringOrNil("018f36fb-e3f2-7f7f-a72f-ce48eb16c82d")).
SetEmail("jeff@openinfrastructure.co").
SetIss("https://login.ois.run").
SetSub("261773693724656988").
@@ -102,6 +104,7 @@ func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[h
// Create the org
org, err := tx.Organization.Create().
SetID(uuid.FromStringOrNil("018f36fb-e3f7-7f7f-a1c5-c85fb735d215")).
SetName("ois").
SetDisplayName("Open Infrastructure Services").
SetCreator(jeff).
@@ -116,16 +119,23 @@ func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[h
return errors.Wrap(err)
}
var hf holos.PlatformForm
if err := json.Unmarshal([]byte(BareForm), &hf); err != nil {
var form holos.Form
if err := json.Unmarshal([]byte(BareForm), &form); err != nil {
return errors.Wrap(err)
}
var model holos.Model
if err := json.Unmarshal([]byte(Model), &model); err != nil {
return errors.Wrap(err)
}
// Add a platform
err = tx.Platform.Create().
SetID(uuid.FromStringOrNil("018f36fb-e3ff-7f7f-a5d1-7ca2bf499e94")).
SetName("bare").
SetDisplayName("Bare Platform").
SetConfigForm(&hf).
SetForm(&form).
SetModel(&model).
SetCreator(jeff).
SetOrgID(org.ID).
Exec(ctx)
@@ -138,7 +148,8 @@ func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[h
err := tx.Platform.Create().
SetName(strings.ToLower(name)).
SetDisplayName(name + "'s Platform").
SetConfigForm(&hf).
SetForm(&form).
SetModel(&model).
SetCreator(jeff).
SetOrgID(org.ID).
Exec(ctx)
@@ -152,64 +163,738 @@ func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[h
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.EmptyResponse{}), nil
return connect.NewResponse(&holos.SeedDatabaseResponse{}), nil
}
const BareForm = `{
"kind": "PlatformForm",
"spec": {
"sections": [
{
"name": "org",
"description": "Organization config values are used to derive more specific configuration values throughout the platform.",
"displayName": "Organization",
"fieldConfigs": [
{
"key": "name",
"type": "input",
"props": {
"label": "Name",
"required": true,
"description": "DNS label, e.g. 'example'",
"placeholder": "example"
const Model = `{"model":{}}`
const BareForm = `
{
"fields": [
{
"key": "org",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Organization",
"description": "Organization config values are used to derive more specific configuration values throughout the platform."
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "name",
"type": "input",
"props": {
"label": "Name",
"description": "DNS label, e.g. 'example'",
"pattern": "^[a-z]([0-9a-z]|-){1,28}[0-9a-z]$",
"minLength": 3,
"maxLength": 30,
"required": true
},
"validation": {
"messages": {
"pattern": "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
}
},
{
"key": "domain",
"type": "input",
"props": {
"label": "Domain",
"required": true,
"description": "DNS domain, e.g. 'example.com'",
"placeholder": "example.com"
"resetOnHide": true
},
{
"key": "domain",
"type": "input",
"props": {
"label": "Domain",
"placeholder": "example.com",
"minLength": 3,
"maxLength": 100,
"description": "DNS domain, e.g. 'example.com'",
"required": true
},
"resetOnHide": true
},
{
"key": "displayName",
"type": "input",
"props": {
"label": "Display Name",
"placeholder": "Example Organization",
"description": "Display name, e.g. 'Example Organization'",
"maxLength": 100,
"required": true
},
"resetOnHide": true
},
{
"key": "contactEmail",
"type": "input",
"props": {
"label": "Contact Email",
"placeholder": "platform-team@example.com",
"description": "Technical contact email address",
"required": true
},
"resetOnHide": true
}
]
},
{
"key": "cloud",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Cloud Providers",
"description": "Select the services that provide resources for the platform."
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "providers",
"type": "select",
"props": {
"label": "Select Providers",
"description": "Select the cloud providers the platform builds upon.",
"multiple": true,
"selectAllOption": "Select All",
"options": [
{
"value": "aws",
"label": "Amazon Web Services"
},
{
"value": "gcp",
"label": "Google Cloud Platform"
},
{
"value": "azure",
"label": "Microsoft Azure"
},
{
"value": "cloudflare",
"label": "Cloudflare"
},
{
"value": "github",
"label": "GitHub"
},
{
"value": "ois",
"label": "Open Infrastructure Services"
},
{
"value": "onprem",
"label": "On Premises",
"disabled": true
}
]
},
"resetOnHide": true
}
]
},
{
"key": "aws",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Amazon Web Services",
"description": "Provide the information necessary for Holos to manage AWS resources to provide the platform."
},
"expressions": {
"hide": "!formState.model.cloud?.providers?.includes(\"aws\")"
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "primaryRoleARN",
"type": "input",
"props": {
"label": "Holos Admin Role ARN",
"description": "Enter the AWS Role ARN Holos will use to bootstrap resources. For example, arn:aws:iam::123456789012:role/HolosAdminAccess",
"pattern": "^arn:.*",
"minLength": 4,
"required": true
},
"validation": {
"messages": {
"pattern": "Must be a valid ARN. Refer to https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html"
}
},
{
"key": "displayName",
"type": "input",
"props": {
"label": "Display Name",
"required": true,
"description": "Display name, e.g. 'Example Organization'",
"placeholder": "Example Organization"
"resetOnHide": true
},
{
"key": "regions",
"type": "select",
"props": {
"label": "Select Regions",
"description": "Select the AWS regions this platform operates in.",
"multiple": true,
"required": true,
"selectAllOption": "Select All",
"options": [
{
"value": "us-east-1",
"label": "N. Virginia (us-east-1)"
},
{
"value": "us-east-2",
"label": "Ohio (us-east-2)"
},
{
"value": "us-west-1",
"label": "N. California (us-west-1)"
},
{
"value": "us-west-2",
"label": "Oregon (us-west-2)"
},
{
"value": "us-gov-west1",
"label": "US GovCloud West (us-gov-west1)"
},
{
"value": "us-gov-east1",
"label": "US GovCloud East (us-gov-east1)"
},
{
"value": "ca-central-1",
"label": "Canada (ca-central-1)"
},
{
"value": "eu-north-1",
"label": "Stockholm (eu-north-1)"
},
{
"value": "eu-west-1",
"label": "Ireland (eu-west-1)"
},
{
"value": "eu-west-2",
"label": "London (eu-west-2)"
},
{
"value": "eu-west-3",
"label": "Paris (eu-west-3)"
},
{
"value": "eu-central-1",
"label": "Frankfurt (eu-central-1)"
},
{
"value": "eu-south-1",
"label": "Milan (eu-south-1)"
},
{
"value": "af-south-1",
"label": "Cape Town (af-south-1)"
},
{
"value": "ap-northeast-1",
"label": "Tokyo (ap-northeast-1)"
},
{
"value": "ap-northeast-2",
"label": "Seoul (ap-northeast-2)"
},
{
"value": "ap-northeast-3",
"label": "Osaka (ap-northeast-3)"
},
{
"value": "ap-southeast-1",
"label": "Singapore (ap-southeast-1)"
},
{
"value": "ap-southeast-2",
"label": "Sydney (ap-southeast-2)"
},
{
"value": "ap-east-1",
"label": "Hong Kong (ap-east-1)"
},
{
"value": "ap-south-1",
"label": "Mumbai (ap-south-1)"
},
{
"value": "me-south-1",
"label": "Bahrain (me-south-1)"
},
{
"value": "sa-east-1",
"label": "São Paulo (sa-east-1)"
},
{
"value": "cn-north-1",
"label": "Bejing (cn-north-1)"
},
{
"value": "cn-northwest-1",
"label": "Ningxia (cn-northwest-1)"
},
{
"value": "ap-southeast-3",
"label": "Jakarta (ap-southeast-3)"
}
]
},
"resetOnHide": true
}
]
},
{
"key": "gcp",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Google Cloud Platform",
"description": "Use this form to configure platform level GCP settings."
},
"expressions": {
"hide": "!formState.model.cloud?.providers?.includes(\"gcp\")"
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "regions",
"type": "select",
"props": {
"label": "Select Regions",
"description": "Select the GCP regions this platform operates in.",
"multiple": true,
"selectAllOption": "Select All",
"options": [
{
"value": "africa-south1",
"label": "africa-south1"
},
{
"value": "asia-east1",
"label": "asia-east1"
},
{
"value": "asia-east2",
"label": "asia-east2"
},
{
"value": "asia-northeast1",
"label": "asia-northeast1"
},
{
"value": "asia-northeast2",
"label": "asia-northeast2"
},
{
"value": "asia-northeast3",
"label": "asia-northeast3"
},
{
"value": "asia-south1",
"label": "asia-south1"
},
{
"value": "asia-south2",
"label": "asia-south2"
},
{
"value": "asia-southeast1",
"label": "asia-southeast1"
},
{
"value": "asia-southeast2",
"label": "asia-southeast2"
},
{
"value": "australia-southeast1",
"label": "australia-southeast1"
},
{
"value": "australia-southeast2",
"label": "australia-southeast2"
},
{
"value": "europe-central2",
"label": "europe-central2"
},
{
"value": "europe-north1",
"label": "europe-north1"
},
{
"value": "europe-southwest1",
"label": "europe-southwest1"
},
{
"value": "europe-west1",
"label": "europe-west1"
},
{
"value": "europe-west10",
"label": "europe-west10"
},
{
"value": "europe-west12",
"label": "europe-west12"
},
{
"value": "europe-west2",
"label": "europe-west2"
},
{
"value": "europe-west3",
"label": "europe-west3"
},
{
"value": "europe-west4",
"label": "europe-west4"
},
{
"value": "europe-west6",
"label": "europe-west6"
},
{
"value": "europe-west8",
"label": "europe-west8"
},
{
"value": "europe-west9",
"label": "europe-west9"
},
{
"value": "me-central1",
"label": "me-central1"
},
{
"value": "me-central2",
"label": "me-central2"
},
{
"value": "me-west1",
"label": "me-west1"
},
{
"value": "northamerica-northeast1",
"label": "northamerica-northeast1"
},
{
"value": "northamerica-northeast2",
"label": "northamerica-northeast2"
},
{
"value": "southamerica-east1",
"label": "southamerica-east1"
},
{
"value": "southamerica-west1",
"label": "southamerica-west1"
},
{
"value": "us-central1",
"label": "us-central1"
},
{
"value": "us-east1",
"label": "us-east1"
},
{
"value": "us-east4",
"label": "us-east4"
},
{
"value": "us-east5",
"label": "us-east5"
},
{
"value": "us-south1",
"label": "us-south1"
},
{
"value": "us-west1",
"label": "us-west1"
},
{
"value": "us-west2",
"label": "us-west2"
},
{
"value": "us-west3",
"label": "us-west3"
},
{
"value": "us-west4",
"label": "us-west4"
}
]
},
"resetOnHide": true
},
{
"key": "gcpProjectID",
"type": "input",
"props": {
"label": "Project ID",
"description": "Enter the project id where the provisioner cluster resides.",
"pattern": "^[a-z]([0-9a-z]|-){1,28}[0-9a-z]$",
"minLength": 6,
"maxLength": 30,
"required": true
},
"validation": {
"messages": {
"pattern": "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
}
},
{
"key": "contactEmail",
"type": "input",
"props": {
"label": "Contact Email",
"required": true,
"description": "Technical contact email address",
"placeholder": "platform-team@example.com"
"resetOnHide": true
},
{
"key": "gcpProjectNumber",
"type": "input",
"props": {
"label": "Project Number",
"type": "number",
"description": "Enter the project number where the provisioner cluster resides.",
"pattern": "^[0-9]+$",
"required": true
},
"validation": {
"messages": {
"pattern": "Must be a valid project number."
}
}
]
}
]
},
"metadata": {
"name": "bare"
},
"apiVersion": "forms.holos.run/v1alpha1"
}`
},
"resetOnHide": true
},
{
"key": "provisionerCABundle",
"type": "input",
"props": {
"label": "Provisioner CA Bundle",
"description": "Enter the provisioner cluster ca bundle. kubectl config view --minify --flatten -ojsonpath='{.clusters[0].cluster.certificate-authority-data}'",
"pattern": "^[0-9a-zA-Z]+=*$",
"required": true
},
"validation": {
"messages": {
"pattern": "Must be a base64 encoded pem encoded certificate bundle."
}
},
"resetOnHide": true
},
{
"key": "provisionerURL",
"type": "input",
"props": {
"label": "Provisioner URL",
"description": "Enter the URL of the provisioner cluster API endpoint. kubectl config view --minify --flatten -ojsonpath='{.clusters[0].cluster.server}'",
"pattern": "^https://.*$",
"required": true
},
"validation": {
"messages": {
"pattern": "Must be a https:// URL."
}
},
"resetOnHide": true
}
]
},
{
"key": "cloudflare",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Cloudflare",
"description": "Cloudflare is primarily used for DNS automation."
},
"expressions": {
"hide": "!formState.model.cloud?.providers?.includes(\"cloudflare\")"
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "email",
"type": "input",
"props": {
"label": "Account Email",
"description": "Enter the Cloudflare email address to manage DNS",
"minLength": 3,
"required": true
},
"resetOnHide": true
}
]
},
{
"key": "github",
"wrappers": [
"holos-panel"
],
"props": {
"label": "GitHub",
"description": "GitHub is primarily used to host Git repositories and execute Actions workflows."
},
"expressions": {
"hide": "!formState.model.cloud?.providers?.includes(\"github\")"
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "primaryOrg",
"type": "input",
"props": {
"label": "Organization",
"description": "Enter the primary GitHub organization associed with the platform.",
"pattern": "^(?!-)(?!.*--)([a-zA-Z0-9]|-){1,39}$",
"minLength": 1,
"maxLength": 39,
"required": true
},
"validation": {
"messages": {
"pattern": "All characters must be either a hyphen or alphanumeric. Cannot start with a hyphen. Cannot include consecutive hyphens."
}
},
"resetOnHide": true
}
]
},
{
"key": "backups",
"wrappers": [
"holos-panel"
],
"props": {
"label": "Backups",
"description": "Configure platform level data backup settings. Requires AWS."
},
"resetOnHide": true,
"fieldGroup": [
{
"key": "s3bucket",
"type": "select",
"props": {
"label": "S3 Bucket Region",
"description": "Select the S3 Bucket Region.",
"multiple": true,
"options": [
{
"value": "us-east-1",
"label": "N. Virginia (us-east-1)"
},
{
"value": "us-east-2",
"label": "Ohio (us-east-2)"
},
{
"value": "us-west-1",
"label": "N. California (us-west-1)"
},
{
"value": "us-west-2",
"label": "Oregon (us-west-2)"
},
{
"value": "us-gov-west1",
"label": "US GovCloud West (us-gov-west1)"
},
{
"value": "us-gov-east1",
"label": "US GovCloud East (us-gov-east1)"
},
{
"value": "ca-central-1",
"label": "Canada (ca-central-1)"
},
{
"value": "eu-north-1",
"label": "Stockholm (eu-north-1)"
},
{
"value": "eu-west-1",
"label": "Ireland (eu-west-1)"
},
{
"value": "eu-west-2",
"label": "London (eu-west-2)"
},
{
"value": "eu-west-3",
"label": "Paris (eu-west-3)"
},
{
"value": "eu-central-1",
"label": "Frankfurt (eu-central-1)"
},
{
"value": "eu-south-1",
"label": "Milan (eu-south-1)"
},
{
"value": "af-south-1",
"label": "Cape Town (af-south-1)"
},
{
"value": "ap-northeast-1",
"label": "Tokyo (ap-northeast-1)"
},
{
"value": "ap-northeast-2",
"label": "Seoul (ap-northeast-2)"
},
{
"value": "ap-northeast-3",
"label": "Osaka (ap-northeast-3)"
},
{
"value": "ap-southeast-1",
"label": "Singapore (ap-southeast-1)"
},
{
"value": "ap-southeast-2",
"label": "Sydney (ap-southeast-2)"
},
{
"value": "ap-east-1",
"label": "Hong Kong (ap-east-1)"
},
{
"value": "ap-south-1",
"label": "Mumbai (ap-south-1)"
},
{
"value": "me-south-1",
"label": "Bahrain (me-south-1)"
},
{
"value": "sa-east-1",
"label": "São Paulo (sa-east-1)"
},
{
"value": "cn-north-1",
"label": "Bejing (cn-north-1)"
},
{
"value": "cn-northwest-1",
"label": "Ningxia (cn-northwest-1)"
},
{
"value": "ap-southeast-3",
"label": "Jakarta (ap-southeast-3)"
}
]
},
"expressions": {
"props.disabled": "!formState.model.cloud?.providers?.includes(\"aws\")",
"props.required": "formState.model.cloud?.providers?.includes(\"aws\")",
"props.description": "formState.model.cloud?.providers?.includes(\"aws\") ? 'Select the S3 Bucket Region.' : 'Enable AWS in the Cloud Provider section to configure backups.'"
},
"resetOnHide": true
}
]
}
]
}
`

View File

@@ -20,16 +20,21 @@ message Organization {
Creator creator = 5;
}
message GetCallerOrganizationsRequest {}
message ListCallerOrganizationsRequest {}
message GetCallerOrganizationsResponse {
message ListCallerOrganizationsResponse {
User user = 1;
repeated Organization organizations = 2;
}
message CreateCallerOrganizationRequest {}
service OrganizationService {
rpc GetCallerOrganizations(GetCallerOrganizationsRequest) returns (GetCallerOrganizationsResponse) {}
rpc CreateCallerOrganization(CreateCallerOrganizationRequest) returns (GetCallerOrganizationsResponse) {}
message CreateCallerOrganizationResponse {
User user = 1;
repeated Organization organizations = 2;
}
service OrganizationService {
rpc ListCallerOrganizations(ListCallerOrganizationsRequest) returns (ListCallerOrganizationsResponse) {}
rpc CreateCallerOrganization(CreateCallerOrganizationRequest) returns (CreateCallerOrganizationResponse) {}
}

View File

@@ -1,128 +1,111 @@
syntax = "proto3";
package holos.v1alpha1;
option go_package = "github.com/holos-run/holos/service/gen/holos/v1alpha1;holos";
// git clone https://github.com/bufbuild/protovalidate then add <parent>/protovalidate/proto/protovalidate to your editor proto search path
import "buf/validate/validate.proto";
import "holos/v1alpha1/timestamps.proto";
import "holos/v1alpha1/organization.proto";
import "holos/v1alpha1/user.proto";
import "google/protobuf/any.proto";
import "google/protobuf/struct.proto";
// For validation, see the [Standard constraints](https://github.com/bufbuild/protovalidate/blob/main/docs/standard-constraints.md)
// RawConfig represents the raw form configuration as opaque bytes. Used for input.
message RawConfig {
bytes form = 1;
bytes values = 2;
bytes cue = 3;
string definition = 4;
}
message Config {
PlatformForm form = 1;
// Values are the user supplied config values organized by section.
ConfigValues values = 2;
}
message ConfigSection {
map<string, google.protobuf.Value> fields = 1;
}
// ConfigValues represents user defined configuration values.
message ConfigValues {
map<string, ConfigSection> sections = 1;
}
message Platform {
// Unique id assigned by the server.
string id = 1;
Timestamps timestamps = 2;
// Organization ID resource owner.
string org_id = 3 [(buf.validate.field).string.uuid = true];
string org_id = 2 [(buf.validate.field).string.uuid = true];
// name is the platform short name as a dns label.
string name = 4 [(buf.validate.field).string.max_len = 100];
string display_name = 5 [(buf.validate.field).string.max_len = 100];
Creator creator = 6;
// config represents the platform config form and values. Read only.
Config config = 7;
// raw_config represents the platform config form and values. Write only.
RawConfig raw_config = 8;
string name = 3 [(buf.validate.field).string.max_len = 100];
string display_name = 4 [(buf.validate.field).string.max_len = 100];
PlatformSpec spec = 5;
}
message FieldConfigProps {
string label = 1;
string placeholder = 2;
string description = 3;
bool required = 4;
message PlatformSpec {
// model represents the user-defined and user-supplied form field values.
google.protobuf.Struct model = 1;
}
message FieldConfig {
string key = 1;
string type = 2;
FieldConfigProps props = 3;
// Form represents the Formly input form.
message Form {
// fields represents FormlyFieldConfig[] encoded as a JSON array.
repeated google.protobuf.Struct fields = 1;
}
message ConfigFormSection {
string name = 1;
string displayName = 2;
string description = 3;
repeated FieldConfig fieldConfigs = 4;
// Model represents the values entered into the form, stored in the form's model
// in the web app, and persisted into the backend database. The model is
// ultimately intended as the input to platform rendering.
message Model {
google.protobuf.Struct model = 1;
}
message PlatformFormSpec {
repeated ConfigFormSection sections = 1;
}
message GetPlatformsRequest {
message ListPlatformsRequest {
string org_id = 1 [(buf.validate.field).string.uuid = true];
}
message GetPlatformsResponse {
message ListPlatformsResponse {
repeated Platform platforms = 1;
}
message GetPlatformResponse {
Platform platform = 1;
}
message AddPlatformRequest {
Platform platform = 1;
}
message AddPlatformResponse {
repeated Platform platforms = 1;
}
message GetPlatformRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
}
message MetadataName {
string name = 1;
message GetPlatformResponse {
Platform platform = 1;
}
message PlatformForm {
string apiVersion = 1;
string kind = 2;
MetadataName metadata = 3;
PlatformFormSpec spec = 4;
}
message PutPlatformConfigRequest {
message GetFormRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
ConfigValues values = 2;
}
message GetPlatformConfigRequest {
message GetFormResponse {
repeated google.protobuf.Struct fields = 1;
google.protobuf.Struct model = 2;
}
message GetModelRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
}
message GetModelResponse {
google.protobuf.Struct model = 1;
}
message PutModelRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
google.protobuf.Struct model = 2;
}
message PutModelResponse {
google.protobuf.Struct model = 1;
}
message PutFormRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
repeated google.protobuf.Struct fields = 2;
}
message PutFormResponse {
repeated google.protobuf.Struct fields = 1;
}
service PlatformService {
rpc AddPlatform(AddPlatformRequest) returns (GetPlatformsResponse) {}
rpc GetPlatforms(GetPlatformsRequest) returns (GetPlatformsResponse) {}
rpc AddPlatform(AddPlatformRequest) returns (AddPlatformResponse) {}
rpc GetPlatform(GetPlatformRequest) returns (GetPlatformResponse) {}
rpc PutPlatformConfig(PutPlatformConfigRequest) returns (GetPlatformResponse) {}
// GetConfig provides the unmarshalled config values for use with CUE
rpc GetConfig(GetPlatformConfigRequest) returns (ConfigValues) {}
rpc ListPlatforms(ListPlatformsRequest) returns (ListPlatformsResponse) {}
rpc GetForm(GetFormRequest) returns (GetFormResponse) {}
rpc PutForm(PutFormRequest) returns (PutFormResponse) {}
rpc GetModel(GetModelRequest) returns (GetModelResponse) {}
rpc PutModel(PutModelRequest) returns (PutModelResponse) {}
}

View File

@@ -5,16 +5,16 @@ package holos.v1alpha1;
option go_package = "github.com/holos-run/holos/service/gen/holos/v1alpha1;holos";
// git clone https://github.com/bufbuild/protovalidate then add <parent>/protovalidate/proto/protovalidate to your editor proto search path
import "buf/validate/validate.proto";
import "holos/v1alpha1/timestamps.proto";
import "holos/v1alpha1/user.proto";
// For validation, see the [Standard constraints](https://github.com/bufbuild/protovalidate/blob/main/docs/standard-constraints.md)
message EmptyRequest {}
message EmptyResponse {}
message SeedDatabaseRequest {}
message SeedDatabaseResponse {}
message DropTablesRequest {}
message DropTablesResponse {}
service SystemService {
rpc SeedDatabase(EmptyRequest) returns (EmptyResponse) {}
rpc DropTables(EmptyRequest) returns (EmptyResponse) {}
rpc SeedDatabase(SeedDatabaseRequest) returns (SeedDatabaseResponse) {}
rpc DropTables(DropTablesRequest) returns (DropTablesResponse) {}
}

View File

@@ -4,7 +4,6 @@ package holos.v1alpha1;
option go_package = "github.com/holos-run/holos/service/gen/holos/v1alpha1;holos";
import "google/protobuf/timestamp.proto";
// git clone https://github.com/bufbuild/protovalidate then add <parent>/protovalidate/proto/protovalidate to your editor proto search path
import "buf/validate/validate.proto";
import "holos/v1alpha1/timestamps.proto";

View File

@@ -1 +1 @@
72
74

View File

@@ -1 +1 @@
1
0