mirror of
https://github.com/holos-run/holos.git
synced 2026-03-20 17:25:01 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df843bc98 | ||
|
|
be4d2c29a5 | ||
|
|
8ce88bf491 | ||
|
|
b05571a595 | ||
|
|
4edfc71d68 | ||
|
|
3049694a0a | ||
|
|
5860c5747b | ||
|
|
d3c2d55706 |
3
.github/workflows/release.yaml
vendored
3
.github/workflows/release.yaml
vendored
@@ -54,6 +54,9 @@ jobs:
|
||||
- name: List keys
|
||||
run: gpg -K
|
||||
|
||||
- name: Git diff
|
||||
run: git diff
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ coverage.out
|
||||
*.hold/
|
||||
/deploy/
|
||||
.vscode/
|
||||
tmp/
|
||||
|
||||
2
Makefile
2
Makefile
@@ -113,7 +113,7 @@ snapshot: ## Go release snapshot
|
||||
|
||||
.PHONY: buf
|
||||
buf: ## buf generate
|
||||
cd service && buf mod update
|
||||
cd service && buf dep update
|
||||
buf generate
|
||||
|
||||
.PHONY: tools
|
||||
|
||||
@@ -34,7 +34,7 @@ let OBJECTS = #APIObjects & {
|
||||
containers: [
|
||||
{
|
||||
name: Holos
|
||||
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:0.74.0"
|
||||
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:v0.76.0"
|
||||
imagePullPolicy: "Always"
|
||||
env: [
|
||||
{
|
||||
|
||||
4
go.mod
4
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240401165935-b983156c5e99.1
|
||||
connectrpc.com/connect v1.16.0
|
||||
connectrpc.com/grpcreflect v1.2.0
|
||||
connectrpc.com/otelconnect v0.7.0
|
||||
connectrpc.com/validate v0.1.0
|
||||
cuelang.org/go v0.8.0
|
||||
entgo.io/ent v0.13.1
|
||||
@@ -30,6 +31,7 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa
|
||||
google.golang.org/protobuf v1.33.1-0.20240408130810-98873a205002
|
||||
honnef.co/go/tools v0.4.7
|
||||
k8s.io/api v0.29.2
|
||||
@@ -44,7 +46,6 @@ require (
|
||||
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
connectrpc.com/otelconnect v0.7.0 // indirect
|
||||
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
@@ -248,7 +249,6 @@ require (
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
47
internal/cli/generate/generate.go
Normal file
47
internal/cli/generate/generate.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/generate"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns a new generate command.
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("generate")
|
||||
cmd.Aliases = []string{"gen"}
|
||||
cmd.Short = "generate local resources"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
cmd.AddCommand(NewPlatform(cfg))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("platform")
|
||||
cmd.Use = "platform [flags] PLATFORM"
|
||||
cmd.Short = "generate a platform from an embedded schematic"
|
||||
cmd.Long = fmt.Sprintf("Embedded platforms available to generate:\n\n %s", strings.Join(generate.Platforms(), "\n "))
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
clientContext := holos.NewClientContext(ctx)
|
||||
client := client.New(client.NewConfig(cfg))
|
||||
|
||||
for _, name := range args {
|
||||
if err := generate.GeneratePlatform(ctx, client, clientContext.OrgID, name); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
cue "cuelang.org/go/cue/errors"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
)
|
||||
|
||||
// MakeMain makes a main function for the cli or tests.
|
||||
@@ -25,7 +27,8 @@ func MakeMain(options ...holos.Option) func() int {
|
||||
|
||||
// HandleError is the top level error handler that unwraps and logs errors.
|
||||
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
|
||||
log := hc.NewTopLevelLogger()
|
||||
// Connect errors have codes, log them.
|
||||
log := hc.NewTopLevelLogger().With("code", connect.CodeOf(err))
|
||||
var cueErr cue.Error
|
||||
var errAt *errors.ErrorAt
|
||||
const msg = "could not execute"
|
||||
@@ -39,5 +42,24 @@ func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int
|
||||
msg := cue.Details(cueErr, nil)
|
||||
_, _ = fmt.Fprint(hc.Stderr(), msg)
|
||||
}
|
||||
// connect errors have details and codes.
|
||||
// Refer to https://connectrpc.com/docs/go/errors
|
||||
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
|
||||
for _, detail := range connectErr.Details() {
|
||||
msg, valueErr := detail.Value()
|
||||
if valueErr != nil {
|
||||
log.WarnContext(ctx, "could not decode error detail", "err", err, "type", detail.Type(), "note", "this usually means we don't have the schema for the protobuf message type")
|
||||
continue
|
||||
}
|
||||
if info, ok := msg.(*errdetails.ErrorInfo); ok {
|
||||
logDetail := log.With("reason", info.GetReason(), "domain", info.GetDomain())
|
||||
for k, v := range info.GetMetadata() {
|
||||
logDetail = logDetail.With(k, v)
|
||||
}
|
||||
logDetail.ErrorContext(ctx, info.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
58
internal/cli/push/push.go
Normal file
58
internal/cli/push/push.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Package push pushes resources to the holos api server.
|
||||
package push
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/push"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("push")
|
||||
cmd.Short = "push resources to holos server"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
cmd.AddCommand(NewPlatform(config))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("platform")
|
||||
|
||||
cmd.Short = "push platform resources to holos server"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
cmd.AddCommand(NewPlatformForm(cfg))
|
||||
// cmd.AddCommand(NewPlatformModel(cfg))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatformForm(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("form")
|
||||
cmd.Short = "push platform form to holos server"
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
if ctx == nil {
|
||||
return errors.Wrap(errors.New("cannot execute: no context"))
|
||||
}
|
||||
for _, name := range args {
|
||||
if err := push.PlatformForm(ctx, name); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
36
internal/cli/register/register.go
Normal file
36
internal/cli/register/register.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Package register provides user registration via the command line.
|
||||
package register
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/register"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns a new register command.
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("register")
|
||||
cmd.Short = "register with holos server"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
cmd.AddCommand(NewUser(config))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewUser returns a command to register a user with holos server.
|
||||
func NewUser(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("user")
|
||||
cmd.Short = "user registration workflow"
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
return register.User(ctx, cfg)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -5,23 +5,27 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/holos-run/holos/version"
|
||||
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/internal/server"
|
||||
|
||||
"github.com/holos-run/holos/internal/cli/build"
|
||||
"github.com/holos-run/holos/internal/cli/controller"
|
||||
"github.com/holos-run/holos/internal/cli/create"
|
||||
"github.com/holos-run/holos/internal/cli/generate"
|
||||
"github.com/holos-run/holos/internal/cli/get"
|
||||
"github.com/holos-run/holos/internal/cli/kv"
|
||||
"github.com/holos-run/holos/internal/cli/login"
|
||||
"github.com/holos-run/holos/internal/cli/logout"
|
||||
"github.com/holos-run/holos/internal/cli/preflight"
|
||||
"github.com/holos-run/holos/internal/cli/push"
|
||||
"github.com/holos-run/holos/internal/cli/register"
|
||||
"github.com/holos-run/holos/internal/cli/render"
|
||||
"github.com/holos-run/holos/internal/cli/rpc"
|
||||
"github.com/holos-run/holos/internal/cli/token"
|
||||
"github.com/holos-run/holos/internal/cli/txtar"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/version"
|
||||
)
|
||||
|
||||
// New returns a new root *cobra.Command for command line execution.
|
||||
@@ -41,7 +45,7 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
return err
|
||||
}
|
||||
log := cfg.Logger()
|
||||
c.SetContext(logger.NewContext(c.Context(), log))
|
||||
c.Root().SetContext(logger.NewContext(c.Context(), log))
|
||||
// Set the default logger after flag parsing.
|
||||
slog.SetDefault(log)
|
||||
return nil
|
||||
@@ -65,6 +69,9 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
rootCmd.AddCommand(logout.New(cfg))
|
||||
rootCmd.AddCommand(token.New(cfg))
|
||||
rootCmd.AddCommand(rpc.New(cfg))
|
||||
rootCmd.AddCommand(generate.New(cfg))
|
||||
rootCmd.AddCommand(register.New(cfg))
|
||||
rootCmd.AddCommand(push.New(cfg))
|
||||
|
||||
// Maybe not needed?
|
||||
rootCmd.AddCommand(txtar.New(cfg))
|
||||
|
||||
@@ -45,7 +45,7 @@ func NewPlatformModel(cfg *Config) *cobra.Command {
|
||||
cmd := command.New("platform-model")
|
||||
cmd.Short = "get the platform model"
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
ctx := cmd.Root().Context()
|
||||
log := logger.FromContext(ctx)
|
||||
// client := platformconnect.NewPlatformServiceClient(token.NewClient(cfg.token), cfg.client.Server())
|
||||
client := platformconnect.NewPlatformServiceClient(token.NewClient(cfg.token), cfg.client.Server())
|
||||
|
||||
51
internal/client/client.go
Normal file
51
internal/client/client.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Package client provides configuration and convenience methods for making API calls to the holos server.
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/holos-run/holos/internal/token"
|
||||
"github.com/holos-run/holos/service/gen/holos/organization/v1alpha1/organizationconnect"
|
||||
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
|
||||
"github.com/holos-run/holos/service/gen/holos/platform/v1alpha1/platformconnect"
|
||||
"github.com/holos-run/holos/service/gen/holos/user/v1alpha1/userconnect"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
func New(cfg *Config) *Client {
|
||||
t := token.NewClient(cfg.Token())
|
||||
s := cfg.Client().Server()
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
usrSvc: userconnect.NewUserServiceClient(t, s),
|
||||
orgSvc: organizationconnect.NewOrganizationServiceClient(t, s),
|
||||
pltSvc: platformconnect.NewPlatformServiceClient(t, s),
|
||||
}
|
||||
}
|
||||
|
||||
// Client provides convenience methods for making API calls to the holos server.
|
||||
type Client struct {
|
||||
cfg *Config
|
||||
usrSvc userconnect.UserServiceClient
|
||||
pltSvc platformconnect.PlatformServiceClient
|
||||
orgSvc organizationconnect.OrganizationServiceClient
|
||||
}
|
||||
|
||||
func (c *Client) Platforms(ctx context.Context, orgID string) ([]*platform.Platform, error) {
|
||||
if c == nil {
|
||||
return nil, errors.New("no service client")
|
||||
}
|
||||
req := &platform.ListPlatformsRequest{
|
||||
OrgId: orgID,
|
||||
FieldMask: &fieldmaskpb.FieldMask{
|
||||
Paths: []string{"id", "name", "displayName"},
|
||||
},
|
||||
}
|
||||
resp, err := c.pltSvc.ListPlatforms(ctx, connect.NewRequest(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Msg.GetPlatforms(), nil
|
||||
}
|
||||
51
internal/client/config.go
Normal file
51
internal/client/config.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Package client provides client configuration for the holos cli.
|
||||
package client
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/token"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
holos *holos.Config
|
||||
client *holos.ClientConfig
|
||||
token *token.Config
|
||||
}
|
||||
|
||||
func (c *Config) ClientFlagSet() *flag.FlagSet {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.client.FlagSet()
|
||||
}
|
||||
|
||||
func (c *Config) TokenFlagSet() *flag.FlagSet {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.token.FlagSet()
|
||||
}
|
||||
|
||||
func (c *Config) Token() *token.Config {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.token
|
||||
}
|
||||
|
||||
func (c *Config) Client() *holos.ClientConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewConfig(cfg *holos.Config) *Config {
|
||||
return &Config{
|
||||
holos: cfg,
|
||||
client: holos.NewClientConfig(),
|
||||
token: token.NewConfig(),
|
||||
}
|
||||
}
|
||||
2027
internal/frontend/holos/package-lock.json
generated
2027
internal/frontend/holos/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.4",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@bufbuild/buf": "^1.31.0",
|
||||
"@bufbuild/buf": "^1.32.0",
|
||||
"@bufbuild/protoc-gen-es": "^1.9.0",
|
||||
"@connectrpc/protoc-gen-connect-es": "^1.4.0",
|
||||
"@connectrpc/protoc-gen-connect-query": "^1.4.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { CreateUserRequest, CreateUserResponse, GetUserRequest, GetUserResponse } from "./user_service_pb.js";
|
||||
import { CreateUserRequest, CreateUserResponse, GetUserRequest, GetUserResponse, RegisterUserRequest, RegisterUserResponse } from "./user_service_pb.js";
|
||||
import { MethodKind } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
@@ -36,6 +36,17 @@ export const UserService = {
|
||||
O: GetUserResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
/**
|
||||
* Register an user and initialize an organization, bare platform, and reference platform.
|
||||
*
|
||||
* @generated from rpc holos.user.v1alpha1.UserService.RegisterUser
|
||||
*/
|
||||
registerUser: {
|
||||
name: "RegisterUser",
|
||||
I: RegisterUserRequest,
|
||||
O: RegisterUserResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM
|
||||
import { FieldMask, Message, proto3 } from "@bufbuild/protobuf";
|
||||
import { User } from "./user_pb.js";
|
||||
import { UserRef } from "../../object/v1alpha1/object_pb.js";
|
||||
import { Organization } from "../../organization/v1alpha1/organization_pb.js";
|
||||
|
||||
/**
|
||||
* Create a User from the oidc id token claims or the provided user. Each one
|
||||
@@ -172,3 +173,118 @@ export class GetUserResponse extends Message<GetUserResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a User from the oidc id token claims or the provided user. Each one
|
||||
* of subject, email, and user id must be globally unique.
|
||||
*
|
||||
* @generated from message holos.user.v1alpha1.RegisterUserRequest
|
||||
*/
|
||||
export class RegisterUserRequest extends Message<RegisterUserRequest> {
|
||||
/**
|
||||
* User resource to create. If absent, the server populates User fields with
|
||||
* the oidc id token claims of the authenticated request.
|
||||
* NOTE: The server may ignore this request field and register the user solely
|
||||
* from authenticated identity claims.
|
||||
*
|
||||
* @generated from field: optional holos.user.v1alpha1.User user = 1;
|
||||
*/
|
||||
user?: User;
|
||||
|
||||
/**
|
||||
* Mask of the user fields to include in the response.
|
||||
*
|
||||
* @generated from field: optional google.protobuf.FieldMask user_mask = 2;
|
||||
*/
|
||||
userMask?: FieldMask;
|
||||
|
||||
/**
|
||||
* Organization resource to create. If absent, the server generates an
|
||||
* organization based on the user fields.
|
||||
* NOTE: The server may ignore this request field and register the
|
||||
* organization solely from authenticated identity claims.
|
||||
*
|
||||
* @generated from field: optional holos.organization.v1alpha1.Organization organization = 3;
|
||||
*/
|
||||
organization?: Organization;
|
||||
|
||||
/**
|
||||
* Mask of the organization fields to include in the response.
|
||||
*
|
||||
* @generated from field: optional google.protobuf.FieldMask organization_mask = 4;
|
||||
*/
|
||||
organizationMask?: FieldMask;
|
||||
|
||||
constructor(data?: PartialMessage<RegisterUserRequest>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.user.v1alpha1.RegisterUserRequest";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "user", kind: "message", T: User, opt: true },
|
||||
{ no: 2, name: "user_mask", kind: "message", T: FieldMask, opt: true },
|
||||
{ no: 3, name: "organization", kind: "message", T: Organization, opt: true },
|
||||
{ no: 4, name: "organization_mask", kind: "message", T: FieldMask, opt: true },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): RegisterUserRequest {
|
||||
return new RegisterUserRequest().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): RegisterUserRequest {
|
||||
return new RegisterUserRequest().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): RegisterUserRequest {
|
||||
return new RegisterUserRequest().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: RegisterUserRequest | PlainMessage<RegisterUserRequest> | undefined, b: RegisterUserRequest | PlainMessage<RegisterUserRequest> | undefined): boolean {
|
||||
return proto3.util.equals(RegisterUserRequest, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.user.v1alpha1.RegisterUserResponse
|
||||
*/
|
||||
export class RegisterUserResponse extends Message<RegisterUserResponse> {
|
||||
/**
|
||||
* @generated from field: holos.user.v1alpha1.User user = 1;
|
||||
*/
|
||||
user?: User;
|
||||
|
||||
/**
|
||||
* @generated from field: holos.organization.v1alpha1.Organization organization = 2;
|
||||
*/
|
||||
organization?: Organization;
|
||||
|
||||
constructor(data?: PartialMessage<RegisterUserResponse>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.user.v1alpha1.RegisterUserResponse";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "user", kind: "message", T: User },
|
||||
{ no: 2, name: "organization", kind: "message", T: Organization },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): RegisterUserResponse {
|
||||
return new RegisterUserResponse().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): RegisterUserResponse {
|
||||
return new RegisterUserResponse().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): RegisterUserResponse {
|
||||
return new RegisterUserResponse().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: RegisterUserResponse | PlainMessage<RegisterUserResponse> | undefined, b: RegisterUserResponse | PlainMessage<RegisterUserResponse> | undefined): boolean {
|
||||
return proto3.util.equals(RegisterUserResponse, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
149
internal/generate/generate.go
Normal file
149
internal/generate/generate.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
|
||||
)
|
||||
|
||||
//go:embed all:platforms
|
||||
var platforms embed.FS
|
||||
|
||||
// root is the root path to copy platform cue code from.
|
||||
const root = "platforms"
|
||||
|
||||
// Platforms returns a slice of embedded platforms or nil if there are none.
|
||||
func Platforms() []string {
|
||||
entries, err := fs.ReadDir(platforms, root)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
dirs := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() && entry.Name() != "cue.mod" {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// GeneratePlatform writes the cue code for a platform to the local working
|
||||
// directory.
|
||||
func GeneratePlatform(ctx context.Context, rpc *client.Client, orgID string, name string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
// Check for a valid platform
|
||||
platformPath := filepath.Join(root, name)
|
||||
if !dirExists(platforms, platformPath) {
|
||||
return errors.Wrap(fmt.Errorf("cannot generate: have: [%s] want: %+v", name, Platforms()))
|
||||
}
|
||||
|
||||
// Link the local platform the SaaS platform ID.
|
||||
rpcPlatforms, err := rpc.Platforms(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
var rpcPlatform *platform.Platform
|
||||
for _, p := range rpcPlatforms {
|
||||
if p.GetName() == name {
|
||||
rpcPlatform = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpcPlatform == nil {
|
||||
return errors.Wrap(errors.New("cannot generate: platform not found in the holos server"))
|
||||
}
|
||||
|
||||
// Write the platform data.
|
||||
data, err := json.MarshalIndent(rpcPlatform, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
data = append(data, '\n')
|
||||
}
|
||||
log = log.With("platform_id", rpcPlatform.GetId())
|
||||
path := "platform.metadata.json"
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not write platform metadata: %w", err))
|
||||
}
|
||||
log.InfoContext(ctx, "wrote "+path, "path", filepath.Join(getCwd(ctx), path))
|
||||
|
||||
// Copy the cue.mod directory
|
||||
if err := copyEmbedFS(ctx, platforms, filepath.Join(root, "cue.mod"), "cue.mod"); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Copy the named platform
|
||||
if err := copyEmbedFS(ctx, platforms, platformPath, "."); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "generated platform "+name, "path", getCwd(ctx))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dirExists(srcFS embed.FS, path string) bool {
|
||||
entries, err := fs.ReadDir(srcFS, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(entries) > 0
|
||||
}
|
||||
|
||||
func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
return fs.WalkDir(srcFS, srcPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(srcPath, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
dstFullPath := filepath.Join(dstPath, relPath)
|
||||
|
||||
if d.IsDir() {
|
||||
if err := os.MkdirAll(dstFullPath, os.ModePerm); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "created", "directory", dstFullPath)
|
||||
} else {
|
||||
data, err := srcFS.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
if err := os.WriteFile(dstFullPath, data, os.ModePerm); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.DebugContext(ctx, "wrote", "file", dstFullPath)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getCwd(ctx context.Context) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WarnContext(ctx, "could not get working directory", "err", err)
|
||||
return "."
|
||||
}
|
||||
abs, err := filepath.Abs(cwd)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WarnContext(ctx, "could not get absolute path", "err", err)
|
||||
return cwd
|
||||
}
|
||||
return abs
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package forms
|
||||
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
// Provides a concrete v1.#Form
|
||||
FormBuilder.Output
|
||||
|
||||
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.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 \(props.minLength) to \(props.maxLength) lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
|
||||
minLength: "Must be at least \(props.minLength) characters"
|
||||
maxLength: "Must be at most \(props.maxLength) characters"
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 \(props.minLength) to \(props.maxLength) lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
|
||||
minLength: "Must be at least \(props.minLength) characters."
|
||||
maxLength: "Must be at most \(props.maxLength) characters."
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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\")"
|
||||
1
internal/generate/platforms/cue.mod/module.cue
Normal file
1
internal/generate/platforms/cue.mod/module.cue
Normal file
@@ -0,0 +1 @@
|
||||
module: "user.holos.run/platform"
|
||||
@@ -0,0 +1,4 @@
|
||||
package holos
|
||||
|
||||
// #ClusterName is the --cluster-name flag value provided by the holos cli.
|
||||
#ClusterName: string @tag(cluster, type=string)
|
||||
@@ -0,0 +1,25 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
let PLATFORM = {message: "TODO: Load the platform from the API."}
|
||||
|
||||
// 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 = v1.#APIObjects & {
|
||||
apiObjects: ConfigMap: platform: {
|
||||
metadata: {
|
||||
name: "platform"
|
||||
namespace: "default"
|
||||
}
|
||||
data: platform: yaml.Marshal(PLATFORM)
|
||||
}
|
||||
}
|
||||
7
internal/generate/platforms/example/readme.md
Normal file
7
internal/generate/platforms/example/readme.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Example 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 |
|
||||
81
internal/holos/clientcontext.go
Normal file
81
internal/holos/clientcontext.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package holos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
func NewClientContext(ctx context.Context) *ClientContext {
|
||||
cc := &ClientContext{}
|
||||
if cc.Exists() {
|
||||
if err := cc.Load(ctx); err != nil {
|
||||
logger.FromContext(ctx).WarnContext(ctx, "could not load client context", "err", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
// ClientContext represents the context the holos api is working in. Used to
|
||||
// store and recall values from the filesystem.
|
||||
type ClientContext struct {
|
||||
// OrgID is the organization id of the current context.
|
||||
OrgID string `json:"org_id"`
|
||||
// UserID is the user id of the current context.
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
func (cc *ClientContext) Save(ctx context.Context) error {
|
||||
log := logger.FromContext(ctx)
|
||||
config := cc.configFile()
|
||||
data, err := json.MarshalIndent(cc, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(config, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "saved", "path", config, "bytes", len(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ClientContext) Load(ctx context.Context) error {
|
||||
log := logger.FromContext(ctx)
|
||||
config := cc.configFile()
|
||||
data, err := os.ReadFile(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(data, cc); err != nil {
|
||||
return err
|
||||
}
|
||||
log.DebugContext(ctx, "loaded", "path", config, "bytes", len(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists returns true if the client context file exists.
|
||||
func (cc *ClientContext) Exists() bool {
|
||||
_, err := os.Stat(cc.configFile())
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (cc *ClientContext) configFile() string {
|
||||
config := "client-context.json"
|
||||
if home := homedir.HomeDir(); home != "" {
|
||||
dir := filepath.Join(home, ".holos")
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
slog.Warn("could not mkdir", "path", dir, "err", err)
|
||||
}
|
||||
config = filepath.Join(home, ".holos", config)
|
||||
}
|
||||
return config
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user