Compare commits

..

7 Commits

Author SHA1 Message Date
Jeff McCune
64a117b0c3 (#150) Add PlatformService.GetConfig and refactor ConfigValues proto
Problem:
The use of google.protobuf.Any was making it awkward to work with the
data provided by the user.  The structure of the form data is defined by
the platform engineer, so the intent of Any was to wrap the data in a
way we can pass over the network and persist in the database.

The escaped JSON encoding was problematic and error prone to decode on
the other end.

Solution:
Define the Platform values as a two level map with string keys, but with
protobuf message fields "sections" and "fields" respectively.  Use
google.protobuf.Value from the struct package to encode the actual
value.

Result:
In TypeScript, google.protobuf.Value encodes and decodes easily to a
JSON value.  On the go side, connect correctly handles the value as
well.

No more ugly error prone escaping:

```
❯ grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"platform_id":"'${platformId}'"}' $host holos.v1alpha1.PlatformService.GetConfig
{
  "sections": {
    "org": {
      "fields": {
        "contactEmail": "jeff@openinfrastructure.co",
        "displayName": "Open Infrastructure Services LLC",
        "domain": "ois.run",
        "name": "ois"
      }
    }
  }
}
```

This return value is intended to be directly usable in the CUE code, so
we may further nest the values into a platform.spec key.
2024-05-01 21:30:30 -07:00
Jeff McCune
cf006be9cf (#150) Add SystemService DropTables and SeedDatabase
Makes it easier to reset the database and give Gary and Nate access to
the same organization I'm in so they can provide feedback.
2024-05-01 14:30:13 -07:00
Jeff McCune
45ad3d8e63 (#150) Fix 500 error when config values aren't provided
AddPlatform was failing with a 500 error trying to decode a nil byte
slice when adding a platform without providing any values.
2024-05-01 11:31:25 -07:00
Jeff McCune
441c968c4f (#150) Look up user by iss sub, not email.
Also log when orgs are created.
2024-05-01 10:02:08 -07:00
Jeff McCune
99f2763fdf (#150) Store Platform Config Form and Values as JSON
This patch changes the backend to store the platform config form
definition and the config values supplied by the form as JSON in the
database.

The gRPC API does not change with this patch, but may need to depending
on how this works and how easy it is to evolve the data model and add
features.
2024-05-01 09:11:53 -07:00
Jeff McCune
1312395a11 (#150) Fix platforms page links
The links were hard to click.  This makes the links a much larger click
target following the example at https://material.angular.io/components/list/overview#navigation-lists
2024-05-01 08:51:29 -07:00
Jeff McCune
615f147bcb (#150) Add PutPlatformConfig to store the config values
This patch is a work in progress wiring up the form to put the values to
the holos server using grpc.

In an effort to simplify the platform configuration, the structure is a
two level map with the top level being configuration sections and the
second level being the fields associated with the config section.

To support multiple kinds of values and field controls, the values are
serialized to JSON for rpc over the network and for storage in the
database.  When they values are used, either by the UI or by the `holos
render` command, they're to be unmarshalled and in-lined into the
Platform Config data structure.

Pick back up ensuring the Platform rpc handler correctly encodes and
decodes the structure to the database.

Consider changing the config_form and config_values fields to JSON field
types in the database.  It will likely make working with this a lot
easier.

With this patch we're ready to wire up the holos render command to fetch
the platform configuration and create the end to end demo.

Here's essentially what the render command will fetch and lay down as a
json file for CUE:

```
❯ grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"platform_id":"018f2c4e-ecde-7bcb-8b89-27a99e6cc7a1"}' jeff.app.dev.k2.holos.run:443 holos.v1alpha1.PlatformService.GetPlatform | jq .platform.config.values
{
  "sections": {
    "org": {
      "values": {
        "contactEmail": "\"platform@openinfrastructure.co\"",
        "displayName": "\"Open Infrastructure Services  LLC\"",
        "domain": "\"ois.run\"",
        "name": "\"ois\""
      }
    }
  }
}
```
2024-04-30 20:21:15 -07:00
28 changed files with 930 additions and 304 deletions

16
hack/setup/bare Executable file
View File

@@ -0,0 +1,16 @@
#! /bin/bash
set -euo pipefail
TOPLEVEL="$(cd $(dirname "$0") && git rev-parse --show-toplevel)"
host="jeff.app.dev.k2.holos.run:443"
read -p "Reset all data in $host? " choice
case "$choice" in
y|Y) echo "proceeding...";;
*) exit 1;;
esac
grpcurl -H "x-oidc-id-token: $(holos token)" $host holos.v1alpha1.SystemService.DropTables
grpcurl -H "x-oidc-id-token: $(holos token)" $host holos.v1alpha1.SystemService.SeedDatabase

View File

@@ -38,8 +38,8 @@ var (
{Name: "updated_at", Type: field.TypeTime},
{Name: "name", Type: field.TypeString},
{Name: "display_name", Type: field.TypeString},
{Name: "config_form", Type: field.TypeBytes, Nullable: true},
{Name: "config_values", Type: field.TypeBytes, Nullable: true},
{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: "creator_id", Type: field.TypeUUID},

View File

@@ -16,6 +16,7 @@ import (
"github.com/holos-run/holos/internal/ent/platform"
"github.com/holos-run/holos/internal/ent/predicate"
"github.com/holos-run/holos/internal/ent/user"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
const (
@@ -812,8 +813,8 @@ type PlatformMutation struct {
updated_at *time.Time
name *string
display_name *string
config_form *[]byte
config_values *[]byte
config_form **holos.PlatformForm
config_values **holos.ConfigValues
config_cue *[]byte
config_definition *string
clearedFields map[string]struct{}
@@ -1147,12 +1148,12 @@ func (m *PlatformMutation) ResetCreatorID() {
}
// SetConfigForm sets the "config_form" field.
func (m *PlatformMutation) SetConfigForm(b []byte) {
m.config_form = &b
func (m *PlatformMutation) SetConfigForm(hf *holos.PlatformForm) {
m.config_form = &hf
}
// ConfigForm returns the value of the "config_form" field in the mutation.
func (m *PlatformMutation) ConfigForm() (r []byte, exists bool) {
func (m *PlatformMutation) ConfigForm() (r *holos.PlatformForm, exists bool) {
v := m.config_form
if v == nil {
return
@@ -1163,7 +1164,7 @@ func (m *PlatformMutation) ConfigForm() (r []byte, exists bool) {
// OldConfigForm returns the old "config_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 []byte, err error) {
func (m *PlatformMutation) OldConfigForm(ctx context.Context) (v *holos.PlatformForm, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigForm is only allowed on UpdateOne operations")
}
@@ -1196,12 +1197,12 @@ func (m *PlatformMutation) ResetConfigForm() {
}
// SetConfigValues sets the "config_values" field.
func (m *PlatformMutation) SetConfigValues(b []byte) {
m.config_values = &b
func (m *PlatformMutation) SetConfigValues(hv *holos.ConfigValues) {
m.config_values = &hv
}
// ConfigValues returns the value of the "config_values" field in the mutation.
func (m *PlatformMutation) ConfigValues() (r []byte, exists bool) {
func (m *PlatformMutation) ConfigValues() (r *holos.ConfigValues, exists bool) {
v := m.config_values
if v == nil {
return
@@ -1212,7 +1213,7 @@ func (m *PlatformMutation) ConfigValues() (r []byte, exists bool) {
// OldConfigValues returns the old "config_values" 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 []byte, err error) {
func (m *PlatformMutation) OldConfigValues(ctx context.Context) (v *holos.ConfigValues, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldConfigValues is only allowed on UpdateOne operations")
}
@@ -1583,14 +1584,14 @@ func (m *PlatformMutation) SetField(name string, value ent.Value) error {
m.SetCreatorID(v)
return nil
case platform.FieldConfigForm:
v, ok := value.([]byte)
v, ok := value.(*holos.PlatformForm)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetConfigForm(v)
return nil
case platform.FieldConfigValues:
v, ok := value.([]byte)
v, ok := value.(*holos.ConfigValues)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}

View File

@@ -3,6 +3,7 @@
package ent
import (
"encoding/json"
"fmt"
"strings"
"time"
@@ -13,6 +14,7 @@ import (
"github.com/holos-run/holos/internal/ent/organization"
"github.com/holos-run/holos/internal/ent/platform"
"github.com/holos-run/holos/internal/ent/user"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
// Platform is the model entity for the Platform schema.
@@ -32,10 +34,10 @@ 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"`
// Opaque JSON bytes representing the platform config form.
ConfigForm []byte `json:"config_form,omitempty"`
// Opaque JSON bytes representing the platform config values.
ConfigValues []byte `json:"config_values,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"`
// The definition name to vet config_values against config_cue e.g. '#PlatformSpec'
@@ -152,14 +154,18 @@ func (pl *Platform) assignValues(columns []string, values []any) error {
case platform.FieldConfigForm:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field config_form", values[i])
} else if value != nil {
pl.ConfigForm = *value
} 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)
}
}
case platform.FieldConfigValues:
if value, ok := values[i].(*[]byte); !ok {
return fmt.Errorf("unexpected type %T for field config_values", values[i])
} else if value != nil {
pl.ConfigValues = *value
} 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)
}
}
case platform.FieldConfigCue:
if value, ok := values[i].(*[]byte); !ok {

View File

@@ -86,16 +86,6 @@ func CreatorID(v uuid.UUID) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldCreatorID, v))
}
// ConfigForm applies equality check predicate on the "config_form" field. It's identical to ConfigFormEQ.
func ConfigForm(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigForm, v))
}
// ConfigValues applies equality check predicate on the "config_values" field. It's identical to ConfigValuesEQ.
func ConfigValues(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigValues, 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))
@@ -356,46 +346,6 @@ func CreatorIDNotIn(vs ...uuid.UUID) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldCreatorID, vs...))
}
// ConfigFormEQ applies the EQ predicate on the "config_form" field.
func ConfigFormEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigForm, v))
}
// ConfigFormNEQ applies the NEQ predicate on the "config_form" field.
func ConfigFormNEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldConfigForm, v))
}
// ConfigFormIn applies the In predicate on the "config_form" field.
func ConfigFormIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldConfigForm, vs...))
}
// ConfigFormNotIn applies the NotIn predicate on the "config_form" field.
func ConfigFormNotIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldConfigForm, vs...))
}
// ConfigFormGT applies the GT predicate on the "config_form" field.
func ConfigFormGT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldConfigForm, v))
}
// ConfigFormGTE applies the GTE predicate on the "config_form" field.
func ConfigFormGTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldConfigForm, v))
}
// ConfigFormLT applies the LT predicate on the "config_form" field.
func ConfigFormLT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldConfigForm, v))
}
// ConfigFormLTE applies the LTE predicate on the "config_form" field.
func ConfigFormLTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldConfigForm, v))
}
// ConfigFormIsNil applies the IsNil predicate on the "config_form" field.
func ConfigFormIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigForm))
@@ -406,46 +356,6 @@ func ConfigFormNotNil() predicate.Platform {
return predicate.Platform(sql.FieldNotNull(FieldConfigForm))
}
// ConfigValuesEQ applies the EQ predicate on the "config_values" field.
func ConfigValuesEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldEQ(FieldConfigValues, v))
}
// ConfigValuesNEQ applies the NEQ predicate on the "config_values" field.
func ConfigValuesNEQ(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldNEQ(FieldConfigValues, v))
}
// ConfigValuesIn applies the In predicate on the "config_values" field.
func ConfigValuesIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldIn(FieldConfigValues, vs...))
}
// ConfigValuesNotIn applies the NotIn predicate on the "config_values" field.
func ConfigValuesNotIn(vs ...[]byte) predicate.Platform {
return predicate.Platform(sql.FieldNotIn(FieldConfigValues, vs...))
}
// ConfigValuesGT applies the GT predicate on the "config_values" field.
func ConfigValuesGT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGT(FieldConfigValues, v))
}
// ConfigValuesGTE applies the GTE predicate on the "config_values" field.
func ConfigValuesGTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldGTE(FieldConfigValues, v))
}
// ConfigValuesLT applies the LT predicate on the "config_values" field.
func ConfigValuesLT(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLT(FieldConfigValues, v))
}
// ConfigValuesLTE applies the LTE predicate on the "config_values" field.
func ConfigValuesLTE(v []byte) predicate.Platform {
return predicate.Platform(sql.FieldLTE(FieldConfigValues, v))
}
// ConfigValuesIsNil applies the IsNil predicate on the "config_values" field.
func ConfigValuesIsNil() predicate.Platform {
return predicate.Platform(sql.FieldIsNull(FieldConfigValues))

View File

@@ -16,6 +16,7 @@ import (
"github.com/holos-run/holos/internal/ent/organization"
"github.com/holos-run/holos/internal/ent/platform"
"github.com/holos-run/holos/internal/ent/user"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
// PlatformCreate is the builder for creating a Platform entity.
@@ -79,14 +80,14 @@ func (pc *PlatformCreate) SetCreatorID(u uuid.UUID) *PlatformCreate {
}
// SetConfigForm sets the "config_form" field.
func (pc *PlatformCreate) SetConfigForm(b []byte) *PlatformCreate {
pc.mutation.SetConfigForm(b)
func (pc *PlatformCreate) SetConfigForm(hf *holos.PlatformForm) *PlatformCreate {
pc.mutation.SetConfigForm(hf)
return pc
}
// SetConfigValues sets the "config_values" field.
func (pc *PlatformCreate) SetConfigValues(b []byte) *PlatformCreate {
pc.mutation.SetConfigValues(b)
func (pc *PlatformCreate) SetConfigValues(hv *holos.ConfigValues) *PlatformCreate {
pc.mutation.SetConfigValues(hv)
return pc
}
@@ -273,11 +274,11 @@ func (pc *PlatformCreate) createSpec() (*Platform, *sqlgraph.CreateSpec) {
_node.DisplayName = value
}
if value, ok := pc.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
_node.ConfigForm = value
}
if value, ok := pc.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
_node.ConfigValues = value
}
if value, ok := pc.mutation.ConfigCue(); ok {
@@ -435,7 +436,7 @@ func (u *PlatformUpsert) UpdateCreatorID() *PlatformUpsert {
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsert) SetConfigForm(v []byte) *PlatformUpsert {
func (u *PlatformUpsert) SetConfigForm(v *holos.PlatformForm) *PlatformUpsert {
u.Set(platform.FieldConfigForm, v)
return u
}
@@ -453,7 +454,7 @@ func (u *PlatformUpsert) ClearConfigForm() *PlatformUpsert {
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsert) SetConfigValues(v []byte) *PlatformUpsert {
func (u *PlatformUpsert) SetConfigValues(v *holos.ConfigValues) *PlatformUpsert {
u.Set(platform.FieldConfigValues, v)
return u
}
@@ -628,7 +629,7 @@ func (u *PlatformUpsertOne) UpdateCreatorID() *PlatformUpsertOne {
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsertOne) SetConfigForm(v []byte) *PlatformUpsertOne {
func (u *PlatformUpsertOne) SetConfigForm(v *holos.PlatformForm) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigForm(v)
})
@@ -649,7 +650,7 @@ func (u *PlatformUpsertOne) ClearConfigForm() *PlatformUpsertOne {
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsertOne) SetConfigValues(v []byte) *PlatformUpsertOne {
func (u *PlatformUpsertOne) SetConfigValues(v *holos.ConfigValues) *PlatformUpsertOne {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigValues(v)
})
@@ -1000,7 +1001,7 @@ func (u *PlatformUpsertBulk) UpdateCreatorID() *PlatformUpsertBulk {
}
// SetConfigForm sets the "config_form" field.
func (u *PlatformUpsertBulk) SetConfigForm(v []byte) *PlatformUpsertBulk {
func (u *PlatformUpsertBulk) SetConfigForm(v *holos.PlatformForm) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigForm(v)
})
@@ -1021,7 +1022,7 @@ func (u *PlatformUpsertBulk) ClearConfigForm() *PlatformUpsertBulk {
}
// SetConfigValues sets the "config_values" field.
func (u *PlatformUpsertBulk) SetConfigValues(v []byte) *PlatformUpsertBulk {
func (u *PlatformUpsertBulk) SetConfigValues(v *holos.ConfigValues) *PlatformUpsertBulk {
return u.Update(func(s *PlatformUpsert) {
s.SetConfigValues(v)
})

View File

@@ -16,6 +16,7 @@ import (
"github.com/holos-run/holos/internal/ent/platform"
"github.com/holos-run/holos/internal/ent/predicate"
"github.com/holos-run/holos/internal/ent/user"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
// PlatformUpdate is the builder for updating Platform entities.
@@ -94,8 +95,8 @@ func (pu *PlatformUpdate) SetNillableCreatorID(u *uuid.UUID) *PlatformUpdate {
}
// SetConfigForm sets the "config_form" field.
func (pu *PlatformUpdate) SetConfigForm(b []byte) *PlatformUpdate {
pu.mutation.SetConfigForm(b)
func (pu *PlatformUpdate) SetConfigForm(hf *holos.PlatformForm) *PlatformUpdate {
pu.mutation.SetConfigForm(hf)
return pu
}
@@ -106,8 +107,8 @@ func (pu *PlatformUpdate) ClearConfigForm() *PlatformUpdate {
}
// SetConfigValues sets the "config_values" field.
func (pu *PlatformUpdate) SetConfigValues(b []byte) *PlatformUpdate {
pu.mutation.SetConfigValues(b)
func (pu *PlatformUpdate) SetConfigValues(hv *holos.ConfigValues) *PlatformUpdate {
pu.mutation.SetConfigValues(hv)
return pu
}
@@ -256,16 +257,16 @@ func (pu *PlatformUpdate) sqlSave(ctx context.Context) (n int, err error) {
_spec.SetField(platform.FieldDisplayName, field.TypeString, value)
}
if value, ok := pu.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
}
if pu.mutation.ConfigFormCleared() {
_spec.ClearField(platform.FieldConfigForm, field.TypeBytes)
_spec.ClearField(platform.FieldConfigForm, field.TypeJSON)
}
if value, ok := pu.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
}
if pu.mutation.ConfigValuesCleared() {
_spec.ClearField(platform.FieldConfigValues, field.TypeBytes)
_spec.ClearField(platform.FieldConfigValues, field.TypeJSON)
}
if value, ok := pu.mutation.ConfigCue(); ok {
_spec.SetField(platform.FieldConfigCue, field.TypeBytes, value)
@@ -420,8 +421,8 @@ func (puo *PlatformUpdateOne) SetNillableCreatorID(u *uuid.UUID) *PlatformUpdate
}
// SetConfigForm sets the "config_form" field.
func (puo *PlatformUpdateOne) SetConfigForm(b []byte) *PlatformUpdateOne {
puo.mutation.SetConfigForm(b)
func (puo *PlatformUpdateOne) SetConfigForm(hf *holos.PlatformForm) *PlatformUpdateOne {
puo.mutation.SetConfigForm(hf)
return puo
}
@@ -432,8 +433,8 @@ func (puo *PlatformUpdateOne) ClearConfigForm() *PlatformUpdateOne {
}
// SetConfigValues sets the "config_values" field.
func (puo *PlatformUpdateOne) SetConfigValues(b []byte) *PlatformUpdateOne {
puo.mutation.SetConfigValues(b)
func (puo *PlatformUpdateOne) SetConfigValues(hv *holos.ConfigValues) *PlatformUpdateOne {
puo.mutation.SetConfigValues(hv)
return puo
}
@@ -612,16 +613,16 @@ func (puo *PlatformUpdateOne) sqlSave(ctx context.Context) (_node *Platform, err
_spec.SetField(platform.FieldDisplayName, field.TypeString, value)
}
if value, ok := puo.mutation.ConfigForm(); ok {
_spec.SetField(platform.FieldConfigForm, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigForm, field.TypeJSON, value)
}
if puo.mutation.ConfigFormCleared() {
_spec.ClearField(platform.FieldConfigForm, field.TypeBytes)
_spec.ClearField(platform.FieldConfigForm, field.TypeJSON)
}
if value, ok := puo.mutation.ConfigValues(); ok {
_spec.SetField(platform.FieldConfigValues, field.TypeBytes, value)
_spec.SetField(platform.FieldConfigValues, field.TypeJSON, value)
}
if puo.mutation.ConfigValuesCleared() {
_spec.ClearField(platform.FieldConfigValues, field.TypeBytes)
_spec.ClearField(platform.FieldConfigValues, field.TypeJSON)
}
if value, ok := puo.mutation.ConfigCue(); ok {
_spec.SetField(platform.FieldConfigCue, field.TypeBytes, value)

View File

@@ -6,6 +6,7 @@ import (
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"github.com/gofrs/uuid"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
type Platform struct {
@@ -25,12 +26,12 @@ func (Platform) Fields() []ent.Field {
field.String("name").NotEmpty(),
field.String("display_name"),
field.UUID("creator_id", uuid.UUID{}),
field.Bytes("config_form").
field.JSON("config_form", &holos.PlatformForm{}).
Optional().
Comment("Opaque JSON bytes representing the platform config form."),
field.Bytes("config_values").
Comment("JSON holos.PlatformForm representing the platform data entry form."),
field.JSON("config_values", &holos.ConfigValues{}).
Optional().
Comment("Opaque JSON bytes representing the platform config values."),
Comment("JSON holos.ConfigValues representing the platform config values."),
field.Bytes("config_cue").
Optional().
Comment("Opaque bytes representing the CUE definition of the config struct."),

View File

@@ -4,21 +4,7 @@
// @ts-nocheck
import { MethodKind } from "@bufbuild/protobuf";
import { AddPlatformRequest, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse } from "./platform_pb.js";
/**
* @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;
import { AddPlatformRequest, ConfigValues, GetPlatformConfigRequest, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse, PutPlatformConfigRequest } from "./platform_pb.js";
/**
* @generated from rpc holos.v1alpha1.PlatformService.AddPlatform
@@ -34,6 +20,20 @@ export const addPlatform = {
}
} 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
*/
@@ -47,3 +47,33 @@ export const getPlatform = {
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, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse } from "./platform_pb.js";
import { AddPlatformRequest, ConfigValues, GetPlatformConfigRequest, GetPlatformRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse, PutPlatformConfigRequest } from "./platform_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
@@ -12,15 +12,6 @@ import { MethodKind } from "@bufbuild/protobuf";
export const PlatformService = {
typeName: "holos.v1alpha1.PlatformService",
methods: {
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatforms
*/
getPlatforms: {
name: "GetPlatforms",
I: GetPlatformsRequest,
O: GetPlatformsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.AddPlatform
*/
@@ -30,6 +21,15 @@ export const PlatformService = {
O: GetPlatformsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatforms
*/
getPlatforms: {
name: "GetPlatforms",
I: GetPlatformsRequest,
O: GetPlatformsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.GetPlatform
*/
@@ -39,6 +39,26 @@ export const PlatformService = {
O: GetPlatformResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc holos.v1alpha1.PlatformService.PutPlatformConfig
*/
putPlatformConfig: {
name: "PutPlatformConfig",
I: PutPlatformConfigRequest,
O: GetPlatformResponse,
kind: MethodKind.Unary,
},
/**
* GetConfig provides the unmarshalled config values for use with CUE
*
* @generated from rpc holos.v1alpha1.PlatformService.GetConfig
*/
getConfig: {
name: "GetConfig",
I: GetPlatformConfigRequest,
O: ConfigValues,
kind: MethodKind.Unary,
},
}
} as const;

View File

@@ -4,7 +4,7 @@
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
import { Message, proto3, Value } from "@bufbuild/protobuf";
import { Timestamps } from "./timestamps_pb.js";
import { Creator } from "./user_pb.js";
@@ -74,6 +74,13 @@ export class Config extends Message<Config> {
*/
form?: PlatformForm;
/**
* Values are the user supplied config values organized by section.
*
* @generated from field: holos.v1alpha1.ConfigValues values = 2;
*/
values?: ConfigValues;
constructor(data?: PartialMessage<Config>) {
super();
proto3.util.initPartial(data, this);
@@ -83,6 +90,7 @@ export class Config extends Message<Config> {
static readonly typeName = "holos.v1alpha1.Config";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "form", kind: "message", T: PlatformForm },
{ no: 2, name: "values", kind: "message", T: ConfigValues },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Config {
@@ -102,6 +110,82 @@ export class Config extends Message<Config> {
}
}
/**
* @generated from message holos.v1alpha1.ConfigSection
*/
export class ConfigSection extends Message<ConfigSection> {
/**
* @generated from field: map<string, google.protobuf.Value> fields = 1;
*/
fields: { [key: string]: Value } = {};
constructor(data?: PartialMessage<ConfigSection>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.ConfigSection";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "fields", kind: "map", K: 9 /* ScalarType.STRING */, V: {kind: "message", T: Value} },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ConfigSection {
return new ConfigSection().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ConfigSection {
return new ConfigSection().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ConfigSection {
return new ConfigSection().fromJsonString(jsonString, options);
}
static equals(a: ConfigSection | PlainMessage<ConfigSection> | undefined, b: ConfigSection | PlainMessage<ConfigSection> | undefined): boolean {
return proto3.util.equals(ConfigSection, a, b);
}
}
/**
* ConfigValues represents user defined configuration values.
*
* @generated from message holos.v1alpha1.ConfigValues
*/
export class ConfigValues extends Message<ConfigValues> {
/**
* @generated from field: map<string, holos.v1alpha1.ConfigSection> sections = 1;
*/
sections: { [key: string]: ConfigSection } = {};
constructor(data?: PartialMessage<ConfigValues>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.ConfigValues";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "sections", kind: "map", K: 9 /* ScalarType.STRING */, V: {kind: "message", T: ConfigSection} },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ConfigValues {
return new ConfigValues().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ConfigValues {
return new ConfigValues().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ConfigValues {
return new ConfigValues().fromJsonString(jsonString, options);
}
static equals(a: ConfigValues | PlainMessage<ConfigValues> | undefined, b: ConfigValues | PlainMessage<ConfigValues> | undefined): boolean {
return proto3.util.equals(ConfigValues, a, b);
}
}
/**
* @generated from message holos.v1alpha1.Platform
*/
@@ -296,9 +380,9 @@ export class FieldConfig extends Message<FieldConfig> {
}
/**
* @generated from message holos.v1alpha1.ConfigSection
* @generated from message holos.v1alpha1.ConfigFormSection
*/
export class ConfigSection extends Message<ConfigSection> {
export class ConfigFormSection extends Message<ConfigFormSection> {
/**
* @generated from field: string name = 1;
*/
@@ -319,13 +403,13 @@ export class ConfigSection extends Message<ConfigSection> {
*/
fieldConfigs: FieldConfig[] = [];
constructor(data?: PartialMessage<ConfigSection>) {
constructor(data?: PartialMessage<ConfigFormSection>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.ConfigSection";
static readonly typeName = "holos.v1alpha1.ConfigFormSection";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "displayName", kind: "scalar", T: 9 /* ScalarType.STRING */ },
@@ -333,20 +417,20 @@ export class ConfigSection extends Message<ConfigSection> {
{ no: 4, name: "fieldConfigs", kind: "message", T: FieldConfig, repeated: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ConfigSection {
return new ConfigSection().fromBinary(bytes, options);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ConfigFormSection {
return new ConfigFormSection().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ConfigSection {
return new ConfigSection().fromJson(jsonValue, options);
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ConfigFormSection {
return new ConfigFormSection().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ConfigSection {
return new ConfigSection().fromJsonString(jsonString, options);
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ConfigFormSection {
return new ConfigFormSection().fromJsonString(jsonString, options);
}
static equals(a: ConfigSection | PlainMessage<ConfigSection> | undefined, b: ConfigSection | PlainMessage<ConfigSection> | undefined): boolean {
return proto3.util.equals(ConfigSection, a, b);
static equals(a: ConfigFormSection | PlainMessage<ConfigFormSection> | undefined, b: ConfigFormSection | PlainMessage<ConfigFormSection> | undefined): boolean {
return proto3.util.equals(ConfigFormSection, a, b);
}
}
@@ -355,9 +439,9 @@ export class ConfigSection extends Message<ConfigSection> {
*/
export class PlatformFormSpec extends Message<PlatformFormSpec> {
/**
* @generated from field: repeated holos.v1alpha1.ConfigSection sections = 1;
* @generated from field: repeated holos.v1alpha1.ConfigFormSection sections = 1;
*/
sections: ConfigSection[] = [];
sections: ConfigFormSection[] = [];
constructor(data?: PartialMessage<PlatformFormSpec>) {
super();
@@ -367,7 +451,7 @@ export class PlatformFormSpec extends Message<PlatformFormSpec> {
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.PlatformFormSpec";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "sections", kind: "message", T: ConfigSection, repeated: true },
{ no: 1, name: "sections", kind: "message", T: ConfigFormSection, repeated: true },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PlatformFormSpec {
@@ -572,43 +656,6 @@ export class GetPlatformRequest extends Message<GetPlatformRequest> {
}
}
/**
* @generated from message holos.v1alpha1.GetPlatformFormRequest
*/
export class GetPlatformFormRequest extends Message<GetPlatformFormRequest> {
/**
* @generated from field: string platform_id = 1;
*/
platformId = "";
constructor(data?: PartialMessage<GetPlatformFormRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.GetPlatformFormRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetPlatformFormRequest {
return new GetPlatformFormRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetPlatformFormRequest {
return new GetPlatformFormRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetPlatformFormRequest {
return new GetPlatformFormRequest().fromJsonString(jsonString, options);
}
static equals(a: GetPlatformFormRequest | PlainMessage<GetPlatformFormRequest> | undefined, b: GetPlatformFormRequest | PlainMessage<GetPlatformFormRequest> | undefined): boolean {
return proto3.util.equals(GetPlatformFormRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.MetadataName
*/
@@ -701,3 +748,83 @@ export class PlatformForm extends Message<PlatformForm> {
}
}
/**
* @generated from message holos.v1alpha1.PutPlatformConfigRequest
*/
export class PutPlatformConfigRequest extends Message<PutPlatformConfigRequest> {
/**
* @generated from field: string platform_id = 1;
*/
platformId = "";
/**
* @generated from field: holos.v1alpha1.ConfigValues values = 2;
*/
values?: ConfigValues;
constructor(data?: PartialMessage<PutPlatformConfigRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.PutPlatformConfigRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "values", kind: "message", T: ConfigValues },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PutPlatformConfigRequest {
return new PutPlatformConfigRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): PutPlatformConfigRequest {
return new PutPlatformConfigRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): PutPlatformConfigRequest {
return new PutPlatformConfigRequest().fromJsonString(jsonString, options);
}
static equals(a: PutPlatformConfigRequest | PlainMessage<PutPlatformConfigRequest> | undefined, b: PutPlatformConfigRequest | PlainMessage<PutPlatformConfigRequest> | undefined): boolean {
return proto3.util.equals(PutPlatformConfigRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.GetPlatformConfigRequest
*/
export class GetPlatformConfigRequest extends Message<GetPlatformConfigRequest> {
/**
* @generated from field: string platform_id = 1;
*/
platformId = "";
constructor(data?: PartialMessage<GetPlatformConfigRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.GetPlatformConfigRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetPlatformConfigRequest {
return new GetPlatformConfigRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetPlatformConfigRequest {
return new GetPlatformConfigRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetPlatformConfigRequest {
return new GetPlatformConfigRequest().fromJsonString(jsonString, options);
}
static equals(a: GetPlatformConfigRequest | PlainMessage<GetPlatformConfigRequest> | undefined, b: GetPlatformConfigRequest | PlainMessage<GetPlatformConfigRequest> | undefined): boolean {
return proto3.util.equals(GetPlatformConfigRequest, a, b);
}
}

View File

@@ -0,0 +1,35 @@
// @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

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

View File

@@ -0,0 +1,70 @@
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
// @generated from file holos/v1alpha1/system.proto (package holos.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
/**
* @generated from message holos.v1alpha1.EmptyRequest
*/
export class EmptyRequest extends Message<EmptyRequest> {
constructor(data?: PartialMessage<EmptyRequest>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.EmptyRequest";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): EmptyRequest {
return new EmptyRequest().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): EmptyRequest {
return new EmptyRequest().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): EmptyRequest {
return new EmptyRequest().fromJsonString(jsonString, options);
}
static equals(a: EmptyRequest | PlainMessage<EmptyRequest> | undefined, b: EmptyRequest | PlainMessage<EmptyRequest> | undefined): boolean {
return proto3.util.equals(EmptyRequest, a, b);
}
}
/**
* @generated from message holos.v1alpha1.EmptyResponse
*/
export class EmptyResponse extends Message<EmptyResponse> {
constructor(data?: PartialMessage<EmptyResponse>) {
super();
proto3.util.initPartial(data, this);
}
static readonly runtime: typeof proto3 = proto3;
static readonly typeName = "holos.v1alpha1.EmptyResponse";
static readonly fields: FieldList = proto3.util.newFieldList(() => [
]);
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): EmptyResponse {
return new EmptyResponse().fromBinary(bytes, options);
}
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): EmptyResponse {
return new EmptyResponse().fromJson(jsonValue, options);
}
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): EmptyResponse {
return new EmptyResponse().fromJsonString(jsonString, options);
}
static equals(a: EmptyResponse | PlainMessage<EmptyResponse> | undefined, b: EmptyResponse | PlainMessage<EmptyResponse> | undefined): boolean {
return proto3.util.equals(EmptyResponse, a, b);
}
}

View File

@@ -1,9 +1,18 @@
import { Inject, Injectable, inject } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { PlatformService as ConnectPlatformService } from '../gen/holos/v1alpha1/platform_connect';
import { Observable, filter, of, switchMap } from 'rxjs';
import { ObservableClient } from '../../connect/observable-client';
import { GetPlatformFormRequest, GetPlatformResponse, GetPlatformsRequest, GetPlatformsResponse, Platform, PlatformForm } from '../gen/holos/v1alpha1/platform_pb';
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;
}
@Injectable({
providedIn: 'root'
@@ -20,9 +29,31 @@ export class PlatformService {
)
}
getPlatform(id: string): Observable<Platform | undefined> {
getPlatform(id: string): Observable<Platform> {
return this.client.getPlatform({ platformId: id }).pipe(
switchMap((resp) => { return of(resp.platform) }),
switchMap(resp => {
return of(resp.platform);
}),
filter((platform): platform is Platform => platform !== undefined),
)
}
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
})
})
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),
)
}

View File

@@ -6,7 +6,7 @@
@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"></formly-form>
<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>

View File

@@ -1,6 +1,6 @@
import { Component, Input, inject } from '@angular/core';
import { Observable, filter, shareReplay } from 'rxjs';
import { PlatformService } from '../../services/platform.service';
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';
@@ -29,20 +29,40 @@ import { MatDivider } from '@angular/material/divider';
})
export class PlatformDetailComponent {
private service = inject(PlatformService);
private platformId: string = "";
platform$!: Observable<Platform>;
form = new FormGroup({});
model = {};
model: Model = {};
onSubmit(model: any) {
console.log(model);
onSubmit(model: Model) {
console.log(model)
// if (this.form.valid) {
this.service.putConfig(this.platformId, model).pipe(shareReplay(1)).subscribe()
// }
}
@Input()
set id(platformId: string) {
this.platformId = platformId;
this.platform$ = this.service.getPlatform(platformId).pipe(
filter((platform): platform is Platform => platform !== undefined),
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()
})
})
}
return project
}),
shareReplay(1)
)
}

View File

@@ -1,12 +1,11 @@
<div class="grid-container">
<mat-nav-list>
<h3 mat-subheader>Platforms</h3>
@for (platform of platforms$ | async; track platform.id) {
<mat-list-item>
<a [routerLink]="['/platform', platform.id]">
{{ platform.displayName ? platform.displayName : platform.name }}
</a>
</mat-list-item>
<p>Select a platform to manage.</p>
@for (platform of (platforms$ | async); track platform.id) {
<a mat-list-item [routerLink]="['/platform', platform.id]">
{{ platform.displayName ? platform.displayName : platform.name }}
</a>
}
</mat-nav-list>
</div>

View File

@@ -9,32 +9,37 @@ package handler
import (
"context"
"fmt"
"log/slog"
"github.com/holos-run/holos/internal/ent"
"github.com/holos-run/holos/internal/server/middleware/logger"
)
// WithTx runs callbacks in a transaction as described in https://entgo.io/docs/transactions/#best-practices
func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
log := logger.FromContext(ctx)
tx, err := client.Tx(ctx)
if err != nil {
return err
}
defer func() {
if v := recover(); v != nil {
slog.ErrorContext(ctx, "panic", "v", v)
log.ErrorContext(ctx, "panic", "v", v)
_ = tx.Rollback()
panic(v)
}
}()
if err := fn(tx); err != nil {
if rerr := tx.Rollback(); rerr != nil {
err = fmt.Errorf("%w: rolling back transaction: %v", err, rerr)
log.ErrorContext(ctx, "could not roll back tx", "err", rerr)
err = fmt.Errorf("coult not roll back tx: %w: %w", rerr, err)
} else {
log.WarnContext(ctx, "rolled back failed tx", "err", err)
}
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("committing transaction: %w", err)
log.ErrorContext(ctx, "could not commit transaction", "err", err)
return fmt.Errorf("could not commit: %w", err)
}
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/holos-run/holos/internal/ent"
"github.com/holos-run/holos/internal/ent/user"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/logger"
"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"
@@ -66,12 +67,12 @@ func (h *OrganizationHandler) CreateCallerOrganization(
ctx context.Context,
req *connect.Request[holos.CreateCallerOrganizationRequest],
) (*connect.Response[holos.GetCallerOrganizationsResponse], error) {
log := logger.FromContext(ctx)
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
// todo get user by iss, sub
dbUser, err := getUser(ctx, h.db, authnID.Email())
dbUser, err := getUser(ctx, h.db, authnID.Issuer(), authnID.Subject())
if err != nil {
if ent.MaskNotFound(err) == nil {
return nil, connect.NewError(connect.CodeNotFound, errors.Wrap(err))
@@ -90,14 +91,14 @@ func (h *OrganizationHandler) CreateCallerOrganization(
if err != nil {
return err
}
dbUser, err = dbUser.Update().
AddOrganizations(org).
Save(ctx)
return err
return tx.Organization.UpdateOne(org).AddUsers(dbUser).Exec(ctx)
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err))
}
log = log.With("organization", org)
log.InfoContext(ctx, "created organization")
// TODO: prefetch organizations
dbOrgs, err := dbUser.QueryOrganizations().All(ctx)

View File

@@ -49,13 +49,27 @@ func (h *PlatformHandler) AddPlatform(
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(req.Msg.Platform.RawConfig.Form).
SetConfigValues(req.Msg.Platform.RawConfig.Values).
SetConfigForm(&hf).
SetConfigValues(&hv).
SetConfigCue(req.Msg.Platform.RawConfig.Cue).
SetConfigDefinition(req.Msg.Platform.RawConfig.Definition).
Save(ctx)
@@ -69,51 +83,59 @@ func (h *PlatformHandler) AddPlatform(
return resp, nil
}
func (h *PlatformHandler) getPlatform(ctx context.Context, id string, uid authn.Identity) (*ent.Platform, error) {
platformID, err := uuid.FromString(id)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
}
p, err := h.db.Platform.Query().
Where(platform.ID(platformID)).
Where(platform.HasOrganizationWith(
organization.HasUsersWith(
user.Iss(uid.Issuer()),
user.Sub(uid.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))
}
}
return p, nil
}
func (h *PlatformHandler) GetPlatform(ctx context.Context, req *connect.Request[holos.GetPlatformRequest]) (*connect.Response[holos.GetPlatformResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
platformID, err := uuid.FromString(req.Msg.PlatformId)
p, err := h.getPlatform(ctx, req.Msg.PlatformId, authnID)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
}
p, err := h.db.Platform.Query().
Where(platform.ID(platformID)).
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))
}
return nil, errors.Wrap(err)
}
return connect.NewResponse(&holos.GetPlatformResponse{Platform: PlatformToRPC(p)}), nil
}
// GetForm provides the FormlyFieldConfig for the platform to make the web ui form for user input.
func (h *PlatformHandler) GetForm(ctx context.Context, req *connect.Request[holos.GetPlatformFormRequest]) (*connect.Response[holos.PlatformForm], error) {
// Boilerplate to get the platform by id where the user is a member of the org.
func (h *PlatformHandler) PutPlatformConfig(ctx context.Context, req *connect.Request[holos.PutPlatformConfigRequest]) (*connect.Response[holos.GetPlatformResponse], error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
platformID, err := uuid.FromString(req.Msg.PlatformId)
id, err := uuid.FromString(req.Msg.PlatformId)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err))
}
// Get the platform so we can validate the values.
p, err := h.db.Platform.Query().
Where(platform.ID(platformID)).
Where(platform.ID(id)).
Where(platform.HasOrganizationWith(
organization.HasUsersWith(
user.Iss(authnID.Issuer()),
@@ -128,24 +150,47 @@ func (h *PlatformHandler) GetForm(ctx context.Context, req *connect.Request[holo
}
}
rpcPlatform := PlatformToRPC(p)
res := connect.NewResponse(rpcPlatform.Config.Form)
return res, nil
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).
Save(ctx)
if err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.GetPlatformResponse{Platform: PlatformToRPC(up)}), nil
}
func (h *PlatformHandler) GetConfig(ctx context.Context, req *connect.Request[holos.GetPlatformConfigRequest]) (*connect.Response[holos.ConfigValues], 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.PlatformId, authnID)
if err != nil {
return nil, errors.Wrap(err)
}
return connect.NewResponse(p.ConfigValues), nil
}
func PlatformToRPC(platform *ent.Platform) *holos.Platform {
var form holos.PlatformForm
if err := json.Unmarshal(platform.ConfigForm, &form); err != nil {
slog.Error("could not unmarshal platform config form", "platform_id", platform.ID.String(), "err", err)
return nil
}
return &holos.Platform{
Id: platform.ID.String(),
Name: platform.Name,
DisplayName: platform.DisplayName,
OrgId: platform.OrgID.String(),
Config: &holos.Config{Form: &form},
Config: &holos.Config{
Form: platform.ConfigForm,
Values: platform.ConfigValues,
},
Timestamps: &holos.Timestamps{
CreatedAt: timestamppb.New(platform.CreatedAt),
UpdatedAt: timestamppb.New(platform.UpdatedAt),

View File

@@ -0,0 +1,215 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"strings"
"connectrpc.com/connect"
"github.com/holos-run/holos/internal/ent"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/authn"
"github.com/holos-run/holos/internal/server/middleware/logger"
holos "github.com/holos-run/holos/service/gen/holos/v1alpha1"
)
const AdminEmail = "jeff@openinfrastructure.co"
// NewSystemHandler returns a new SystemService implementation.
func NewSystemHandler(db *ent.Client) *SystemHandler {
return &SystemHandler{db: db}
}
// SystemHandler implements the PlatformService interface.
type SystemHandler struct {
db *ent.Client
}
func (h *SystemHandler) checkAdmin(ctx context.Context) error {
authnID, err := authn.FromContext(ctx)
if err != nil {
return connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
if authnID.Email() != AdminEmail {
err := fmt.Errorf("not an admin:\n\thave (%+v)\n\twant (%+v)", authnID.Email(), AdminEmail)
return connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
return nil
}
func (h *SystemHandler) DropTables(ctx context.Context, req *connect.Request[holos.EmptyRequest]) (*connect.Response[holos.EmptyResponse], error) {
if err := h.checkAdmin(ctx); err != nil {
return nil, err
}
log := logger.FromContext(ctx)
if err := WithTx(ctx, h.db, func(tx *ent.Tx) (err error) {
var n int
if n, err = tx.Platform.Delete().Exec(ctx); err != nil {
return errors.Wrap(err)
}
log.WarnContext(ctx, "deleted platforms", "count", n)
if n, err = tx.Organization.Delete().Exec(ctx); err != nil {
return errors.Wrap(err)
}
log.WarnContext(ctx, "deleted organizations", "count", n)
if n, err = tx.User.Delete().Exec(ctx); err != nil {
return errors.Wrap(err)
}
log.WarnContext(ctx, "deleted users", "count", n)
return nil
}); err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.EmptyResponse{}), nil
}
func (h *SystemHandler) SeedDatabase(ctx context.Context, req *connect.Request[holos.EmptyRequest]) (*connect.Response[holos.EmptyResponse], 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().
SetEmail("jeff@openinfrastructure.co").
SetIss("https://login.ois.run").
SetSub("261773693724656988").
SetName("Jeff McCune").
Save(ctx)
if err != nil {
return errors.Wrap(err)
}
nate, err := tx.User.Create().
SetEmail("nate@openinfrastructure.co").
SetIss("https://login.ois.run").
SetSub("261775487611699776").
SetName("Nate McCurdy").
Save(ctx)
if err != nil {
return errors.Wrap(err)
}
gary, err := tx.User.Create().
SetEmail("gary@openinfrastructure.co").
SetIss("https://login.ois.run").
SetSub("261775531836441152").
SetName("Gary Larizza").
Save(ctx)
if err != nil {
return errors.Wrap(err)
}
// Create the org
org, err := tx.Organization.Create().
SetName("ois").
SetDisplayName("Open Infrastructure Services").
SetCreator(jeff).
Save(ctx)
if err != nil {
return errors.Wrap(err)
}
// Add org memebers
org, err = org.Update().AddUsers(jeff, gary, nate).Save(ctx)
if err != nil {
return errors.Wrap(err)
}
var hf holos.PlatformForm
if err := json.Unmarshal([]byte(BareForm), &hf); err != nil {
return errors.Wrap(err)
}
// Add a platform
err = tx.Platform.Create().
SetName("bare").
SetDisplayName("Bare Platform").
SetConfigForm(&hf).
SetCreator(jeff).
SetOrgID(org.ID).
Exec(ctx)
if err != nil {
return errors.Wrap(err)
}
stuff := []string{"Jeff", "Gary", "Nate"}
for _, name := range stuff {
err := tx.Platform.Create().
SetName(strings.ToLower(name)).
SetDisplayName(name + "'s Platform").
SetConfigForm(&hf).
SetCreator(jeff).
SetOrgID(org.ID).
Exec(ctx)
if err != nil {
return errors.Wrap(err)
}
}
return nil
}); err != nil {
return nil, connect.NewError(connect.CodeFailedPrecondition, errors.Wrap(err))
}
return connect.NewResponse(&holos.EmptyResponse{}), 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"
}
},
{
"key": "domain",
"type": "input",
"props": {
"label": "Domain",
"required": true,
"description": "DNS domain, e.g. 'example.com'",
"placeholder": "example.com"
}
},
{
"key": "displayName",
"type": "input",
"props": {
"label": "Display Name",
"required": true,
"description": "Display name, e.g. 'Example Organization'",
"placeholder": "Example Organization"
}
},
{
"key": "contactEmail",
"type": "input",
"props": {
"label": "Contact Email",
"required": true,
"description": "Technical contact email address",
"placeholder": "platform-team@example.com"
}
}
]
}
]
},
"metadata": {
"name": "bare"
},
"apiVersion": "forms.holos.run/v1alpha1"
}`

View File

@@ -56,7 +56,7 @@ func (h *UserHandler) GetCallerUser(
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
}
dbUser, err := getUser(ctx, h.db, authnID.Email())
dbUser, err := getUser(ctx, h.db, authnID.Issuer(), authnID.Subject())
if err != nil {
if ent.MaskNotFound(err) == nil {
return nil, connect.NewError(connect.CodeNotFound, errors.Wrap(err))
@@ -68,10 +68,7 @@ func (h *UserHandler) GetCallerUser(
return res, nil
}
func (h *UserHandler) CreateCallerUser(
ctx context.Context,
req *connect.Request[holos.CreateCallerUserRequest],
) (*connect.Response[holos.CreateCallerUserResponse], error) {
func (h *UserHandler) createCallerUser(ctx context.Context) (*ent.User, error) {
authnID, err := authn.FromContext(ctx)
if err != nil {
return nil, connect.NewError(connect.CodePermissionDenied, errors.Wrap(err))
@@ -87,6 +84,18 @@ func (h *UserHandler) CreateCallerUser(
return nil, err
}
return createdUser, nil
}
func (h *UserHandler) CreateCallerUser(
ctx context.Context,
req *connect.Request[holos.CreateCallerUserRequest],
) (*connect.Response[holos.CreateCallerUserResponse], error) {
createdUser, err := h.createCallerUser(ctx)
if err != nil {
return nil, err
}
res := connect.NewResponse(&holos.CreateCallerUserResponse{
User: UserToRPC(createdUser),
})
@@ -107,11 +116,16 @@ func UserToRPC(u *ent.User) *holos.User {
return &iamUser
}
func getUser(ctx context.Context, client *ent.Client, email string) (*ent.User, error) {
func getUser(ctx context.Context, client *ent.Client, iss string, sub string) (*ent.User, error) {
log := logger.FromContext(ctx)
user, err := client.User.Query().Where(user.Email(email)).Only(ctx)
user, err := client.User.Query().
Where(
user.Iss(iss),
user.Sub(sub),
).
Only(ctx)
if err != nil {
log.DebugContext(ctx, "could not get user", "err", err, "email", email)
log.DebugContext(ctx, "could not get user", "err", err, "iss", iss, "sub", sub)
return nil, errors.Wrap(err)
}
return user, nil

View File

@@ -115,11 +115,13 @@ func (s *Server) registerConnectRpc() error {
s.handle(holosconnect.NewUserServiceHandler(handler.NewUserHandler(s.db), opts))
s.handle(holosconnect.NewOrganizationServiceHandler(handler.NewOrganizationHandler(s.db), opts))
s.handle(holosconnect.NewPlatformServiceHandler(handler.NewPlatformHandler(s.db), opts))
s.handle(holosconnect.NewSystemServiceHandler(handler.NewSystemHandler(s.db), opts))
reflector := grpcreflect.NewStaticReflector(
holosconnect.UserServiceName,
holosconnect.OrganizationServiceName,
holosconnect.PlatformServiceName,
holosconnect.SystemServiceName,
)
s.mux.Handle(grpcreflect.NewHandlerV1(reflector))

View File

@@ -9,6 +9,8 @@ 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)
@@ -22,6 +24,17 @@ message RawConfig {
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 {
@@ -54,7 +67,7 @@ message FieldConfig {
FieldConfigProps props = 3;
}
message ConfigSection {
message ConfigFormSection {
string name = 1;
string displayName = 2;
string description = 3;
@@ -62,7 +75,7 @@ message ConfigSection {
}
message PlatformFormSpec {
repeated ConfigSection sections = 1;
repeated ConfigFormSection sections = 1;
}
message GetPlatformsRequest {
@@ -85,10 +98,6 @@ message GetPlatformRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
}
message GetPlatformFormRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
}
message MetadataName {
string name = 1;
}
@@ -100,8 +109,20 @@ message PlatformForm {
PlatformFormSpec spec = 4;
}
service PlatformService {
rpc GetPlatforms(GetPlatformsRequest) returns (GetPlatformsResponse) {}
rpc AddPlatform(AddPlatformRequest) returns (GetPlatformsResponse) {}
rpc GetPlatform(GetPlatformRequest) returns (GetPlatformResponse) {}
message PutPlatformConfigRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
ConfigValues values = 2;
}
message GetPlatformConfigRequest {
string platform_id = 1 [(buf.validate.field).string.uuid = true];
}
service PlatformService {
rpc AddPlatform(AddPlatformRequest) returns (GetPlatformsResponse) {}
rpc GetPlatforms(GetPlatformsRequest) returns (GetPlatformsResponse) {}
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) {}
}

View File

@@ -0,0 +1,20 @@
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/user.proto";
// For validation, see the [Standard constraints](https://github.com/bufbuild/protovalidate/blob/main/docs/standard-constraints.md)
message EmptyRequest {}
message EmptyResponse {}
service SystemService {
rpc SeedDatabase(EmptyRequest) returns (EmptyResponse) {}
rpc DropTables(EmptyRequest) returns (EmptyResponse) {}
}

View File

@@ -1 +1 @@
70
72

View File

@@ -1 +1 @@
4
1