mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7845ce62e0 | ||
|
|
a1542752b7 | ||
|
|
7956475363 | ||
|
|
004ed56591 | ||
|
|
d497df3c27 | ||
|
|
3a8d46234f | ||
|
|
4d24dc5149 | ||
|
|
8eb7fbf7dc | ||
|
|
ffeeb7c553 | ||
|
|
c3c174155c | ||
|
|
2c2d2a9fd9 | ||
|
|
d692e2a6d5 | ||
|
|
e4cebddd0c | ||
|
|
0e48537d65 | ||
|
|
a461a96b9c | ||
|
|
9524c4f7c3 | ||
|
|
64b04d9cfd | ||
|
|
b419ad8caf | ||
|
|
8036c17916 | ||
|
|
220d498be0 | ||
|
|
0f5b6a2d6e | ||
|
|
36369d75c7 | ||
|
|
059b8283fd | ||
|
|
386eb2452a | ||
|
|
38e9a97fd2 | ||
|
|
ecca40e9d5 | ||
|
|
9d08e27e31 | ||
|
|
969bf5e867 | ||
|
|
3b5f28f4df | ||
|
|
df5619f988 | ||
|
|
a6d8383176 | ||
|
|
dbc7e374cd | ||
|
|
d81729857b | ||
|
|
d3d8a7b73c | ||
|
|
d9e6776b95 | ||
|
|
bde98faffa | ||
|
|
c2847554e0 | ||
|
|
9411a65dd8 | ||
|
|
9c1165e77e | ||
|
|
a02c7a4015 | ||
|
|
bdcde88e6f | ||
|
|
a32b100192 | ||
|
|
670d716403 | ||
|
|
bba3895f35 | ||
|
|
9e60ddbe85 | ||
|
|
44334fca52 | ||
|
|
2e2ed398c6 | ||
|
|
34f2a52cb7 | ||
|
|
d3888a884f | ||
|
|
3845871368 | ||
|
|
a3b2d19adb | ||
|
|
e4e7cd8c47 | ||
|
|
fb22e5521b | ||
|
|
d2ae766ae3 | ||
|
|
c0db949729 | ||
|
|
d2d4337ffd | ||
|
|
b0ca04635e | ||
|
|
198c66e6cd | ||
|
|
24346b9a38 | ||
|
|
0639562f1c | ||
|
|
c1fa9cc531 | ||
|
|
18653534ad | ||
|
|
2b89c33067 | ||
|
|
aee26d9375 | ||
|
|
7b04d492ab | ||
|
|
8abd03e165 | ||
|
|
2df843bc98 | ||
|
|
be4d2c29a5 | ||
|
|
8ce88bf491 | ||
|
|
b05571a595 | ||
|
|
4edfc71d68 | ||
|
|
3049694a0a | ||
|
|
5860c5747b | ||
|
|
d3c2d55706 | ||
|
|
ac2ff47a9c |
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/
|
||||
|
||||
10
Makefile
10
Makefile
@@ -16,10 +16,12 @@ $( shell mkdir -p bin)
|
||||
export PATH := $(PWD)/internal/frontend/holos/node_modules/.bin:$(PATH)
|
||||
|
||||
GIT_COMMIT=$(shell git rev-parse HEAD)
|
||||
GIT_SUFFIX=$(shell test -n "`git status --porcelain`" && echo "-dirty" || echo "")
|
||||
GIT_DETAIL=$(shell git describe --tags HEAD)
|
||||
GIT_TREE_STATE=$(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean")
|
||||
BUILD_DATE=$(shell date -Iseconds)
|
||||
|
||||
LD_FLAGS="-w -X ${ORG_PATH}/${PROJ}/version.GitCommit=${GIT_COMMIT} -X ${ORG_PATH}/${PROJ}/version.GitTreeState=${GIT_TREE_STATE} -X ${ORG_PATH}/${PROJ}/version.BuildDate=${BUILD_DATE}"
|
||||
LD_FLAGS="-w -X ${ORG_PATH}/${PROJ}/version.GitDescribe=${GIT_DETAIL}${GIT_SUFFIX} -X ${ORG_PATH}/${PROJ}/version.GitCommit=${GIT_COMMIT} -X ${ORG_PATH}/${PROJ}/version.GitTreeState=${GIT_TREE_STATE} -X ${ORG_PATH}/${PROJ}/version.BuildDate=${BUILD_DATE}"
|
||||
|
||||
.PHONY: default
|
||||
default: test
|
||||
@@ -61,7 +63,7 @@ vet: ## Vet Go code.
|
||||
|
||||
.PHONY: gencue
|
||||
gencue: ## Generate CUE definitions
|
||||
cd docs/examples && cue get go github.com/holos-run/holos/api/...
|
||||
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/v1alpha1/...
|
||||
|
||||
.PHONY: rmgen
|
||||
rmgen: ## Remove generated code
|
||||
@@ -76,7 +78,7 @@ rmgen: ## Remove generated code
|
||||
regenerate: generate ## Re-generate code (delete and re-create)
|
||||
|
||||
.PHONY: generate
|
||||
generate: buf ## Generate code.
|
||||
generate: buf gencue ## Generate code.
|
||||
go generate ./...
|
||||
|
||||
.PHONY: build
|
||||
@@ -113,7 +115,7 @@ snapshot: ## Go release snapshot
|
||||
|
||||
.PHONY: buf
|
||||
buf: ## buf generate
|
||||
cd service && buf mod update
|
||||
cd service && buf dep update
|
||||
buf generate
|
||||
|
||||
.PHONY: tools
|
||||
|
||||
@@ -9,14 +9,17 @@ import (
|
||||
type BuildPlan struct {
|
||||
TypeMeta `json:",inline" yaml:",inline"`
|
||||
// Metadata represents the holos component name
|
||||
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
Spec BuildPlanSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
||||
Platform map[string]any `json:"platform,omitempty" yaml:"platform,omitempty"`
|
||||
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
Spec BuildPlanSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
||||
}
|
||||
|
||||
type BuildPlanSpec struct {
|
||||
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
|
||||
Components BuildPlanComponents `json:"components,omitempty" yaml:"components,omitempty"`
|
||||
// DeployFiles keys represent file paths relative to the cluster deploy
|
||||
// directory. Map values represent the string encoded file contents. Used to
|
||||
// write the argocd Application, but may be used to render any file from CUE.
|
||||
DeployFiles FileContentMap `json:"deployFiles,omitempty" yaml:"deployFiles,omitempty"`
|
||||
}
|
||||
|
||||
type BuildPlanComponents struct {
|
||||
|
||||
@@ -20,3 +20,11 @@ type HolosComponent struct {
|
||||
func (hc *HolosComponent) NewResult() *Result {
|
||||
return &Result{HolosComponent: *hc}
|
||||
}
|
||||
|
||||
func (hc *HolosComponent) GetAPIVersion() string {
|
||||
return hc.APIVersion
|
||||
}
|
||||
|
||||
func (hc *HolosComponent) GetKind() string {
|
||||
return hc.Kind
|
||||
}
|
||||
|
||||
13
api/v1alpha1/form.go
Normal file
13
api/v1alpha1/form.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package v1alpha1
|
||||
|
||||
import object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
|
||||
|
||||
// Form represents a collection of Formly json powered form.
|
||||
type Form struct {
|
||||
TypeMeta `json:",inline" yaml:",inline"`
|
||||
Spec FormSpec `json:"spec" yaml:"spec"`
|
||||
}
|
||||
|
||||
type FormSpec struct {
|
||||
Form object.Form `json:"form" yaml:"form"`
|
||||
}
|
||||
@@ -1,9 +1,32 @@
|
||||
package v1alpha1
|
||||
|
||||
// Platform represents a platform to manage. A Platform resource tells holos
|
||||
// which components to build. The primary use case is to specify the cluster
|
||||
// names, cluster types, and holos components to build.
|
||||
import "google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
// Platform represents a platform to manage. A Platform resource informs holos
|
||||
// which components to build. The platform resource also acts as a container
|
||||
// for the platform model form values provided by the PlatformService. The
|
||||
// primary use case is to collect the cluster names, cluster types, platform
|
||||
// model, and holos components to build into one resource.
|
||||
type Platform struct {
|
||||
TypeMeta `json:",inline" yaml:",inline"`
|
||||
Metadata ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||
Metadata ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
Spec PlatformSpec `json:"spec" yaml:"spec"`
|
||||
}
|
||||
|
||||
// PlatformSpec represents the platform build plan specification.
|
||||
type PlatformSpec struct {
|
||||
// Model represents the platform model holos gets from from the
|
||||
// holos.platform.v1alpha1.PlatformService.GetPlatform method and provides to
|
||||
// CUE using a tag.
|
||||
Model structpb.Struct `json:"model" yaml:"model"`
|
||||
Components []PlatformSpecComponent `json:"components" yaml:"components"`
|
||||
}
|
||||
|
||||
// PlatformSpecComponent represents a component to build or render with flags to
|
||||
// pass, for example the cluster name.
|
||||
type PlatformSpecComponent struct {
|
||||
// Path is the path of the component relative to the platform root.
|
||||
Path string `json:"path" yaml:"path"`
|
||||
// Cluster is the cluster name to use when building the component.
|
||||
Cluster string `json:"cluster" yaml:"cluster"`
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ type Result struct {
|
||||
HolosComponent
|
||||
// accumulatedOutput accumulates rendered api objects.
|
||||
accumulatedOutput string
|
||||
// DeployFiles keys represent file paths relative to the cluster deploy
|
||||
// directory. Map values represent the string encoded file contents. Used to
|
||||
// write the argocd Application, but may be used to render any file from CUE.
|
||||
DeployFiles FileContentMap `json:"deployFiles,omitempty" yaml:"deployFiles,omitempty"`
|
||||
}
|
||||
|
||||
// Continue returns true if Skip is true indicating the result is to be skipped over.
|
||||
@@ -133,6 +137,21 @@ func (r *Result) kustomize(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Result) WriteDeployFiles(ctx context.Context, path string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
if len(r.DeployFiles) == 0 {
|
||||
return nil
|
||||
}
|
||||
for k, content := range r.DeployFiles {
|
||||
path := filepath.Join(path, k)
|
||||
if err := r.Save(ctx, path, content); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.InfoContext(ctx, "wrote deploy file", "path", path, "bytes", len(content))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save writes the content to the filesystem for git ops.
|
||||
func (r *Result) Save(ctx context.Context, path string, content string) error {
|
||||
log := logger.FromContext(ctx)
|
||||
@@ -141,7 +160,7 @@ func (r *Result) Save(ctx context.Context, path string, content string) error {
|
||||
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Write the kube api objects
|
||||
// Write the file content
|
||||
if err := os.WriteFile(path, []byte(content), os.FileMode(0644)); err != nil {
|
||||
log.WarnContext(ctx, "could not write", "path", path, "err", err)
|
||||
return errors.Wrap(err)
|
||||
|
||||
3
cmd/holos/testdata/constraints.txt
vendored
3
cmd/holos/testdata/constraints.txt
vendored
@@ -2,6 +2,8 @@
|
||||
exec holos build ./foo/... --log-level debug
|
||||
stdout '^bf2bc7f9-9ba0-4f9e-9bd2-9a205627eb0b$'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- foo/constraints.cue --
|
||||
@@ -20,6 +22,7 @@ spec: components: KubernetesObjectsList: [
|
||||
package holos
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
#KubernetesObjects: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
|
||||
3
cmd/holos/testdata/issue15_cue_errors.txt
vendored
3
cmd/holos/testdata/issue15_cue_errors.txt
vendored
@@ -3,12 +3,15 @@
|
||||
stderr 'apiObjectMap.foo.bar: cannot convert incomplete value'
|
||||
stderr '/component.cue:\d+:\d+$'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
|
||||
@@ -3,6 +3,8 @@ exec holos build .
|
||||
stdout '^kind: SecretStore$'
|
||||
stdout '# Source: CUE apiObjects.SecretStore.default'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
@@ -13,6 +15,7 @@ kind: "BuildPlan"
|
||||
spec: components: KubernetesObjectsList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
|
||||
@@ -4,6 +4,8 @@ stdout '^kind: SecretStore$'
|
||||
stdout '# Source: CUE apiObjects.SecretStore.default'
|
||||
stderr 'skipping helm: no chart name specified'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
@@ -14,6 +16,7 @@ kind: "BuildPlan"
|
||||
spec: components: HelmChartList: [{apiObjectMap: #APIObjects.apiObjectMap}]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
#SecretStore: {
|
||||
kind: string
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
! exec holos build .
|
||||
stderr 'apiObjects.secretstore.default.foo: field not allowed'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
@@ -10,6 +12,7 @@ package holos
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
#SecretStore: {
|
||||
metadata: name: string
|
||||
|
||||
3
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
3
cmd/holos/testdata/issue33_helm_stderr.txt
vendored
@@ -2,6 +2,8 @@
|
||||
! exec holos build .
|
||||
stderr 'Error: execution error at \(zitadel/templates/secret_zitadel-masterkey.yaml:2:4\): Either set .Values.zitadel.masterkey xor .Values.zitadel.masterkeySecretName'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- zitadel.cue --
|
||||
@@ -12,6 +14,7 @@ kind: "BuildPlan"
|
||||
spec: components: HelmChartList: [_HelmChart]
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
_HelmChart: {
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
# Kustomize is a supported holos component kind
|
||||
exec holos render --cluster-name=mycluster . --log-level=debug
|
||||
exec holos render component --cluster-name=mycluster . --log-level=debug
|
||||
|
||||
# Want generated output
|
||||
cmp want.yaml deploy/clusters/mycluster/components/kstest/kstest.gen.yaml
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
! exec holos build .
|
||||
stderr 'unknown field \\"TypoKubernetesObjectsList\\"'
|
||||
|
||||
-- platform.config.json --
|
||||
{}
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
_cluster: string @tag(cluster, string)
|
||||
_platform_config: string @tag(platform_config, string)
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "BuildPlan"
|
||||
|
||||
2
cmd/holos/testdata/version.txt
vendored
2
cmd/holos/testdata/version.txt
vendored
@@ -1,5 +1,3 @@
|
||||
exec holos --version
|
||||
# want version with no v on stdout
|
||||
stdout -count=1 '^\d+\.\d+\.\d+$'
|
||||
# want nothing on stderr
|
||||
! stderr .
|
||||
|
||||
3
docs/examples/platform.config.cue
Normal file
3
docs/examples/platform.config.cue
Normal file
@@ -0,0 +1,3 @@
|
||||
package holos
|
||||
|
||||
_platform_config: string @tag(platform_config, type=string)
|
||||
1
docs/examples/platform.config.json
Normal file
1
docs/examples/platform.config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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.79.0"
|
||||
imagePullPolicy: "Always"
|
||||
env: [
|
||||
{
|
||||
|
||||
14
docs/runbooks/argocd.md
Normal file
14
docs/runbooks/argocd.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# ArgoCD
|
||||
|
||||
Create the deploy key secret in the management cluster.
|
||||
|
||||
```bash
|
||||
tmp="$(mktemp -d)"
|
||||
(cd $tmp && ssh-keygen -t ed25519 -f sshPrivateKey -m pem -C argocd -N '')
|
||||
echo git@github.com:holos-run/holos-infra.git > "${tmp}/url"
|
||||
holos create secret -n argocd --append-hash=false creds-holos-infra --from-file $tmp
|
||||
rm -rf "$tmp"
|
||||
```
|
||||
|
||||
When syncing the secret, the ExternalSecret needs to set the label
|
||||
`argocd.argoproj.io/secret-type: repo-creds`.
|
||||
97
docs/runbooks/login/backups.md
Normal file
97
docs/runbooks/login/backups.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# PostgresCluster Backups
|
||||
|
||||
This document describes how the S3 bucket for `PostgresCluster` backups is configured. These buckets are configured both for ZITADEL and for Holos
|
||||
Server and are applicable to any service in Holos that stores data in a pgo `PostgresCluster` resource.
|
||||
|
||||
## Create the Bucket
|
||||
Name: `holos-zitadel-backups` for `zitadel`
|
||||
Name: `holos-server-backups` for `holos server`
|
||||
> [!NOTE]
|
||||
> The settings below match the default settings recommended by AWS.
|
||||
|
||||
Object Ownership: `ACLs disabled` (recommended) Checked.
|
||||
Block Public Access settings for this bucket: **`Block all public access`** Checked.
|
||||
Bucket Versioning: `Disable`
|
||||
Default encryption: `Server-side encryption with Amazon S3 managed keys (SSE-S3)`
|
||||
Bucket Key: `Enable`
|
||||
Object Lock: `Disable`
|
||||
|
||||
## Create an IAM Policy
|
||||
Create one IAM Policy for each bucket to grant full access to the bucket. Replace the resource with each bucket name.
|
||||
Name: `holos-zitadel-backups` for `zitadel`
|
||||
Name: `holos-server-backups` for `holos server`
|
||||
Description: `Read and write access to a specific bucket for pgrest operating within a pgo PostgresCluster.`
|
||||
|
||||
Policy JSON:
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:GetBucketLocation",
|
||||
"s3:ListAllMyBuckets"
|
||||
],
|
||||
"Resource": "arn:aws:s3:::*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:*",
|
||||
"Resource": [
|
||||
"arn:aws:s3:::holos-zitadel-backups",
|
||||
"arn:aws:s3:::holos-zitadel-backups/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
## Create an IAM Group
|
||||
Create an IAM Group to attach the policy granting access to the bucket.
|
||||
Name: `holos-zitadel-backups` for `zitadel`
|
||||
Attach permission policies: `holos-zitadel-backups`
|
||||
|
||||
Name: `holos-server-backups` for `holos server`
|
||||
Attach permission policies: `holos-server-backups`
|
||||
## Create the IAM User
|
||||
Create an IAM User entity for each PostgresCluster. Do not provide user access to the AWS Management Console.
|
||||
Name: `holos-zitadel-backups` for `zitadel`
|
||||
Group: `holos-zitadel-backups`
|
||||
|
||||
Name: `holos-server-backups` for `holos server`
|
||||
Group: `holos-server-backups`
|
||||
|
||||
## Create an Access Key
|
||||
Create an access key for `pgbackrest` associated with the `PostgresCluster`.
|
||||
|
||||
Description:
|
||||
> Used by pgbackrest associated with the PostgresCluster resource. Refer to the PostgresCluster resource pgbackrest.cofiguration.secret.name for the stored location of the access key. Synced from the Management Cluster using an ExternalSecret.
|
||||
## Create the Secret
|
||||
Create a `Secret` in the holos management cluster usable by pgbackrest. This is a secret with a single key, `s3.conf` with the following format:
|
||||
```
|
||||
[global]
|
||||
repo2-cipher-pass=
|
||||
repo2-s3-key=
|
||||
repo2-s3-key-secret=
|
||||
repo3-cipher-pass=
|
||||
repo3-s3-key=
|
||||
repo3-s3-key-secret=
|
||||
```
|
||||
> [!NOTE]
|
||||
> Use the same values for repo2 and repo3. The purpose is to make space for migrating if need be in the future.
|
||||
|
||||
Generate the cipher pass using. This password is used to encrypt all backups using client side before the backup is written to the bucket.
|
||||
```
|
||||
tr -dc A-Za-z0-9 </dev/urandom | head -c 64
|
||||
```
|
||||
|
||||
Store the secret into the management cluster:
|
||||
```
|
||||
holos create secret --namespace zitadel holos-zitadel-backups \
|
||||
--append-hash=false --from-file .
|
||||
```
|
||||
|
||||
```
|
||||
holos create secret --namespace holos holos-server-backups \
|
||||
--append-hash=false --from-file .
|
||||
```
|
||||
25
docs/runbooks/namespace.md
Normal file
25
docs/runbooks/namespace.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Namespaces
|
||||
|
||||
Holos follows the [Namespace Sameness - Sig Multicluster Position][1]. A
|
||||
namespace is the same on all clusters within the scope of a platform.
|
||||
|
||||
Namespaces are also security boundaries for role based access control. As such,
|
||||
permission to read a secret in a namespace means the secret is readable on all
|
||||
clusters in the platform.
|
||||
|
||||
When adding a component to a platform, create a namespace using the following
|
||||
process. This ensures a namespace scoped `SecretStore` is created to sync
|
||||
`ExternalSecret` resources from the management cluster.
|
||||
|
||||
1. Add a new project to the `_Projects` struct in `platform.cue`.
|
||||
2. Add the namespace to the `spec.namespaces` field of the project.
|
||||
3. Render the platform
|
||||
4. Apply the `namespaces` component to the management cluster
|
||||
5. Apply the `eso-creds-manager` component to the management cluster to create the `eso-reader` ksa for the namespace `SecretStore`
|
||||
6. Apply the `namespaces` component to the workload clusters
|
||||
7. On the workload cluster, run the job to fetch the eso-reader creds: `kubectl create job -n holos-system --from=cronjob/eso-creds-refresher eso-creds-refresher-$(date +%s)`
|
||||
8. Apply the secretstores component to the workload cluster.
|
||||
|
||||
Your namespace is created and you have the ability to create secrets in the management cluster and pull them using ExternalSecret resources. (edited)
|
||||
|
||||
[1]: https://github.com/kubernetes/community/blob/dd4c8b704ef1c9c3bfd928c6fa9234276d61ad18/sig-multicluster/namespace-sameness-position-statement.md
|
||||
31
docs/runbooks/workload-identity.md
Normal file
31
docs/runbooks/workload-identity.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Workload Identity
|
||||
|
||||
When a new workload cluster is provisioned, allow it to access the Management
|
||||
Cluster using workload identity. This is necessary for the
|
||||
`eso-creds-refresher` component and `Job` that executes in each workload
|
||||
cluster, which in turn enables the `SecretStore` in each namespace to sync
|
||||
secrets.
|
||||
|
||||
Build the cluster with Cluster API.
|
||||
See https://github.com/holos-run/holos-infra/blob/main/hack/capi/eks/aws2/aws2-managedmachinepool.yaml#L81-L84
|
||||
|
||||
## Workload Identity Provider
|
||||
Add the Cluster as a workload identity provider to the `holos-ops` gcp project.
|
||||
|
||||
Pool: [holos](https://console.cloud.google.com/iam-admin/workload-identity-pools/pool/holos?organizationId=358674006047&project=holos-ops)
|
||||
Name: `k8s-aws1`, `k8s-aws2`, etc...
|
||||
### Issuer URL:
|
||||
```
|
||||
kubectl create -n default token default | cut -d. -f2 | base64 -d | jq -r .iss
|
||||
```
|
||||
|
||||
### Audience
|
||||
Use the default audience.
|
||||
### Attribute Mapping
|
||||
|
||||
| Google | OIDC |
|
||||
| -------------------------------- | ------------------------------------------------------ |
|
||||
| `google.subject` | `assertion.sub` |
|
||||
| `attribute.service_account_name` | `assertion['kubernetes.io']['serviceaccount']['name']` |
|
||||
| `attribute.uid` | `assertion['kubernetes.io']['serviceaccount']['uid']` |
|
||||
| `attribute.pod` | `assertion['kubernetes.io']['pod']['name']` |
|
||||
8
go.mod
8
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
|
||||
@@ -20,6 +21,7 @@ require (
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/mennanov/fieldmask-utils v1.1.2
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
@@ -29,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
|
||||
@@ -43,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
|
||||
@@ -84,7 +86,7 @@ require (
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v26.0.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v26.0.0+incompatible // indirect
|
||||
github.com/docker/docker v26.0.2+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.1 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
@@ -157,7 +159,6 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mennanov/fieldmask-utils v1.1.2 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // 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
|
||||
|
||||
4
go.sum
4
go.sum
@@ -187,8 +187,8 @@ github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUy
|
||||
github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU=
|
||||
github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE=
|
||||
github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
|
||||
8
holos.go
8
holos.go
@@ -1,10 +1,10 @@
|
||||
// Package holos defines types for the rest of the system.
|
||||
package holos
|
||||
|
||||
// A PathCueMod is a string representing the filesystem path of a cue module.
|
||||
// It is given a unique type so the API is clear.
|
||||
// A PathCueMod is a string representing the absolute filesystem path of a cue
|
||||
// module. It is given a unique type so the API is clear.
|
||||
type PathCueMod string
|
||||
|
||||
// A InstancePath is a string representing the filesystem path of a holos instance.
|
||||
// It is given a unique type so the API is clear.
|
||||
// A InstancePath is a string representing the absolute filesystem path of a
|
||||
// holos instance. It is given a unique type so the API is clear.
|
||||
type InstancePath string
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
)
|
||||
@@ -70,7 +72,7 @@ func (b *Builder) Cluster() string {
|
||||
}
|
||||
|
||||
// Instances returns the cue build instances being built.
|
||||
func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
|
||||
func (b *Builder) Instances(ctx context.Context, cfg *client.Config) ([]*build.Instance, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
|
||||
mod, err := b.findCueMod()
|
||||
@@ -79,7 +81,29 @@ func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
|
||||
}
|
||||
dir := string(mod)
|
||||
|
||||
cfg := load.Config{Dir: dir}
|
||||
cueConfig := load.Config{Dir: dir}
|
||||
|
||||
// Get the platform model from the PlatformConfig
|
||||
pc, err := client.LoadPlatformConfig(ctx, dir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
data, err := json.Marshal(pc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Refer to https://github.com/cue-lang/cue/blob/v0.7.0/cmd/cue/cmd/common.go#L429
|
||||
cueConfig.Tags = append(cueConfig.Tags, "platform_config="+string(data))
|
||||
if b.Cluster() != "" {
|
||||
cueConfig.Tags = append(cueConfig.Tags, "cluster="+b.Cluster())
|
||||
}
|
||||
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cueConfig.Tags))
|
||||
|
||||
prefix := []string{"cue", "export", "--out", "yaml"}
|
||||
for _, tag := range cueConfig.Tags {
|
||||
prefix = append(prefix, "-t", fmt.Sprintf("'%s'", tag))
|
||||
}
|
||||
|
||||
// Make args relative to the module directory
|
||||
args := make([]string, len(b.cfg.args))
|
||||
@@ -94,21 +118,20 @@ func (b *Builder) Instances(ctx context.Context) ([]*build.Instance, error) {
|
||||
}
|
||||
relPath = "./" + relPath
|
||||
args[idx] = relPath
|
||||
equiv := fmt.Sprintf("cue export --out yaml -t cluster=%v %v", b.Cluster(), relPath)
|
||||
log.Debug("cue: equivalent command: " + equiv)
|
||||
|
||||
equiv := make([]string, len(prefix), 1+len(prefix))
|
||||
copy(equiv, prefix)
|
||||
equiv = append(equiv, relPath)
|
||||
log.Debug(strings.Join(equiv, " "), "comment", "cue equivalent command")
|
||||
}
|
||||
|
||||
// Refer to https://github.com/cue-lang/cue/blob/v0.7.0/cmd/cue/cmd/common.go#L429
|
||||
cfg.Tags = append(cfg.Tags, "cluster="+b.Cluster())
|
||||
log.DebugContext(ctx, fmt.Sprintf("cue: tags %v", cfg.Tags))
|
||||
|
||||
return load.Instances(args, &cfg), nil
|
||||
return load.Instances(args, &cueConfig), nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err error) {
|
||||
func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*v1alpha1.Result, err error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building instances")
|
||||
instances, err := b.Instances(ctx)
|
||||
instances, err := b.Instances(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,9 +184,14 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
|
||||
|
||||
// New decoder for the full object
|
||||
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
|
||||
// TODO: When we release v1, explicitly allow unknown fields so we can add
|
||||
// fields without needing to bump the major version. Disallow until we reach
|
||||
// v1 for clear error reporting.
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
switch tm.Kind {
|
||||
// TODO(jeff) Process a v1alpha1.Result here, the result is tightly coupled to a BuildPlan.
|
||||
case "BuildPlan":
|
||||
var bp v1alpha1.BuildPlan
|
||||
if err = decoder.Decode(&bp); err != nil {
|
||||
@@ -171,24 +199,14 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
|
||||
return
|
||||
}
|
||||
results, err = b.buildPlan(ctx, &bp, path)
|
||||
case "Platform":
|
||||
var pf v1alpha1.Platform
|
||||
if err = decoder.Decode(&pf); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode Platform %s: %w", instance.Dir, err))
|
||||
return
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
results, err = b.buildPlatform(ctx, &pf)
|
||||
default:
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Builder) buildPlatform(ctx context.Context, pf *v1alpha1.Platform) (results []*v1alpha1.Result, err error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.ErrorContext(ctx, "not implemented", "platform", pf)
|
||||
return nil, errors.Wrap(fmt.Errorf("not implemeneted"))
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan, path holos.InstancePath) (results []*v1alpha1.Result, err error) {
|
||||
@@ -237,6 +255,17 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
|
||||
}
|
||||
}
|
||||
|
||||
// Add a separate Result if there are DeployFiles from the BuildPlan.
|
||||
if len(buildPlan.Spec.DeployFiles) > 0 {
|
||||
results = append(results, &v1alpha1.Result{
|
||||
HolosComponent: v1alpha1.HolosComponent{
|
||||
TypeMeta: buildPlan.TypeMeta,
|
||||
Metadata: buildPlan.Metadata,
|
||||
},
|
||||
DeployFiles: buildPlan.Spec.DeployFiles,
|
||||
})
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "returning results", "len", len(results))
|
||||
|
||||
return results, nil
|
||||
|
||||
90
internal/builder/platform.go
Normal file
90
internal/builder/platform.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"cuelang.org/go/cue/build"
|
||||
"cuelang.org/go/cue/cuecontext"
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/api/v1alpha1"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
)
|
||||
|
||||
// Platform builds a platform
|
||||
func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*v1alpha1.Platform, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "cue: building platform instance")
|
||||
instances, err := b.Instances(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
|
||||
if len(instances) != 1 {
|
||||
return nil, errors.Wrap(errors.New(fmt.Sprintf("instances length %d must be exactly 1", len(instances))))
|
||||
}
|
||||
|
||||
// We only process the first instance, assume the render platform subcommand enforces this.
|
||||
instance := instances[0]
|
||||
log.DebugContext(ctx, "cue: building instance", "dir", instance.Dir)
|
||||
p, err := b.runPlatform(ctx, instance)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not build platform: %w", err))
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1alpha1.Platform, error) {
|
||||
path := holos.InstancePath(instance.Dir)
|
||||
log := logger.FromContext(ctx).With("dir", path)
|
||||
|
||||
if err := instance.Err; err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not load: %w", err))
|
||||
}
|
||||
cueCtx := cuecontext.New()
|
||||
value := cueCtx.BuildInstance(instance)
|
||||
if err := value.Err(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not build %s: %w", instance.Dir, err))
|
||||
}
|
||||
log.DebugContext(ctx, "cue: validating instance")
|
||||
if err := value.Validate(); err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not validate: %w", err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: decoding holos platform")
|
||||
jsonBytes, err := value.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
// Discriminate the type of build plan.
|
||||
tm := &v1alpha1.TypeMeta{}
|
||||
err = decoder.Decode(tm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("invalid platform: %s: %w", instance.Dir, err))
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "cue: discriminated build kind: "+tm.Kind, "kind", tm.Kind, "apiVersion", tm.APIVersion)
|
||||
|
||||
// New decoder for the full object
|
||||
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
var pf v1alpha1.Platform
|
||||
switch tm.Kind {
|
||||
case "Platform":
|
||||
if err = decoder.Decode(&pf); err != nil {
|
||||
err = errors.Wrap(fmt.Errorf("could not decode platform %s: %w", instance.Dir, err))
|
||||
return nil, err
|
||||
}
|
||||
return &pf, nil
|
||||
default:
|
||||
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@@ -7,16 +7,20 @@ import (
|
||||
|
||||
"github.com/holos-run/holos/internal/builder"
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// makeBuildRunFunc returns the internal implementation of the build cli command
|
||||
func makeBuildRunFunc(cfg *holos.Config) command.RunFunc {
|
||||
func makeBuildRunFunc(cfg *client.Config) command.RunFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.ClusterName()))
|
||||
results, err := build.Run(cmd.Context())
|
||||
ctx := cmd.Root().Context()
|
||||
logger.FromContext(ctx).DebugContext(ctx, "RunE", "args", args)
|
||||
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.Holos().ClusterName()))
|
||||
results, err := build.Run(ctx, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,7 +46,12 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("build [directory...]")
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
cmd.Short = "build kubernetes api objects from a directory"
|
||||
cmd.RunE = makeBuildRunFunc(cfg)
|
||||
|
||||
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
cmd.RunE = makeBuildRunFunc(config)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -20,9 +17,6 @@ func New(name string) *cobra.Command {
|
||||
CompletionOptions: cobra.CompletionOptions{
|
||||
HiddenDefaultCmd: true,
|
||||
},
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
return errors.Wrap(fmt.Errorf("could not run %v: not implemented", c.Name()))
|
||||
},
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
|
||||
@@ -3,21 +3,52 @@ package create
|
||||
import (
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/cli/secret"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns the create command for the cli
|
||||
func New(hc *holos.Config) *cobra.Command {
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("create")
|
||||
cmd.Short = "create resources"
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.RunE = func(c *cobra.Command, args []string) error {
|
||||
return c.Usage()
|
||||
}
|
||||
|
||||
// api client config
|
||||
config := client.NewConfig(cfg)
|
||||
|
||||
// flags
|
||||
cmd.PersistentFlags().SortFlags = false
|
||||
// commands
|
||||
cmd.AddCommand(secret.NewCreateCmd(hc))
|
||||
cmd.AddCommand(secret.NewCreateCmd(cfg))
|
||||
cmd.AddCommand(NewPlatform(config))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("platform")
|
||||
|
||||
cmd.Short = "create a platform"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
pm := client.PlatformMutation{}
|
||||
cmd.Flags().AddGoFlagSet(pm.FlagSet())
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
client := client.New(cfg)
|
||||
pf, err := client.CreatePlatform(ctx, pm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
log.InfoContext(ctx, "created platform", "name", pf.GetName(), "id", pf.GetId(), "org", pf.GetOwner().GetOrgId())
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
106
internal/cli/generate/generate.go
Normal file
106
internal/cli/generate/generate.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"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))
|
||||
cmd.AddCommand(NewComponent())
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewComponent returns a command to generate a holos component
|
||||
func NewComponent() *cobra.Command {
|
||||
cmd := command.New("component")
|
||||
cmd.Short = "generate a component from an embedded schematic"
|
||||
|
||||
cmd.AddCommand(NewCueComponent())
|
||||
cmd.AddCommand(NewHelmComponent())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewHelmComponent() *cobra.Command {
|
||||
cmd := command.New("helm")
|
||||
cmd.Short = "generate a helm component from a schematic"
|
||||
|
||||
for _, name := range generate.HelmComponents() {
|
||||
cmd.AddCommand(makeSchematicCommand("helm", name))
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewCueComponent() *cobra.Command {
|
||||
cmd := command.New("cue")
|
||||
cmd.Short = "generate a cue component from a schematic"
|
||||
|
||||
for _, name := range generate.CueComponents() {
|
||||
cmd.AddCommand(makeSchematicCommand("cue", name))
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeSchematicCommand(kind, name string) *cobra.Command {
|
||||
cmd := command.New(name)
|
||||
cfg, err := generate.NewSchematic(filepath.Join("components", kind), name)
|
||||
if err != nil {
|
||||
slog.Error("could not get schematic", "err", err)
|
||||
return nil
|
||||
}
|
||||
cmd.Short = cfg.Short
|
||||
cmd.Long = cfg.Long
|
||||
cmd.Args = cobra.NoArgs
|
||||
cmd.Flags().AddGoFlagSet(cfg.FlagSet())
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
if err := generate.GenerateComponent(ctx, kind, name, cfg); 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
|
||||
}
|
||||
|
||||
81
internal/cli/pull/pull.go
Normal file
81
internal/cli/pull/pull.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Package pull pulls resources from the PlatformService and caches them in the
|
||||
// local filesystem.
|
||||
package pull
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("pull")
|
||||
cmd.Short = "pull resources from holos server"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
cmd.AddCommand(NewPlatform(config))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("platform")
|
||||
|
||||
cmd.Short = "pull platform resources"
|
||||
cmd.Args = cobra.NoArgs
|
||||
|
||||
cmd.AddCommand(NewPlatformConfig(cfg))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatformConfig(cfg *client.Config) *cobra.Command {
|
||||
cmd := command.New("config")
|
||||
cmd.Short = "pull platform config"
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
if ctx == nil {
|
||||
return errors.Wrap(errors.New("cannot execute: no context"))
|
||||
}
|
||||
ctx = logger.NewContext(ctx, logger.FromContext(ctx).With("server", cfg.Client().Server()))
|
||||
rpc := client.New(cfg)
|
||||
for _, name := range args {
|
||||
// Get the platform metadata for the platform id.
|
||||
pmd, err := client.LoadPlatform(ctx, name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log := logger.FromContext(ctx).With("platform_id", pmd.GetId())
|
||||
// Get the platform model
|
||||
model, err := rpc.PlatformModel(ctx, pmd.GetId())
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.Info("pulled platform model")
|
||||
// Build the PlatformConfig
|
||||
pc := &object.PlatformConfig{
|
||||
PlatformId: pmd.GetId(),
|
||||
PlatformModel: model,
|
||||
}
|
||||
// Save the PlatformConfig
|
||||
path, err := client.SavePlatformConfig(ctx, name, pc)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log.Info("saved platform config", "path", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
76
internal/cli/push/push.go
Normal file
76
internal/cli/push/push.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Package push pushes resources to the holos api server.
|
||||
package push
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"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/holos-run/holos/internal/server/middleware/logger"
|
||||
"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"))
|
||||
}
|
||||
ctx = logger.NewContext(ctx, logger.FromContext(ctx).With("server", cfg.Client().Server()))
|
||||
rpc := client.New(cfg)
|
||||
for _, name := range args {
|
||||
// Get the platform metadata for the platform id.
|
||||
p, err := client.LoadPlatform(ctx, name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Build the form from the cue code.
|
||||
form, err := push.PlatformForm(ctx, name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Make the rpc call to update the platform form.
|
||||
if err := rpc.UpdateForm(ctx, p.GetId(), form); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
slog.Default().InfoContext(ctx, fmt.Sprintf("pushed: %s/ui/platform/%s", cfg.Client().Server(), p.GetId()))
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -7,37 +7,46 @@ import (
|
||||
|
||||
"github.com/holos-run/holos/internal/builder"
|
||||
"github.com/holos-run/holos/internal/cli/command"
|
||||
"github.com/holos-run/holos/internal/client"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/logger"
|
||||
"github.com/holos-run/holos/internal/render"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns the render subcommand for the root command
|
||||
func New(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("render [directory...]")
|
||||
cmd := command.New("render")
|
||||
cmd.Args = cobra.NoArgs
|
||||
cmd.Short = "render platform configuration"
|
||||
cmd.AddCommand(NewComponent(cfg))
|
||||
cmd.AddCommand(NewPlatform(cfg))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// New returns the component subcommand for the render command
|
||||
func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("component [directory...]")
|
||||
cmd.Args = cobra.MinimumNArgs(1)
|
||||
cmd.Short = "write kubernetes api objects to the filesystem"
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().AddGoFlagSet(cfg.WriteFlagSet())
|
||||
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
var printInstances bool
|
||||
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flagSet.BoolVar(&printInstances, "print-instances", false, "expand /... paths for xargs")
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
if cfg.ClusterName() == "" {
|
||||
return errors.Wrap(fmt.Errorf("missing cluster name"))
|
||||
}
|
||||
|
||||
ctx := cmd.Context()
|
||||
log := logger.FromContext(ctx).With("cluster", cfg.ClusterName())
|
||||
ctx := cmd.Root().Context()
|
||||
build := builder.New(builder.Entrypoints(args), builder.Cluster(cfg.ClusterName()))
|
||||
|
||||
if printInstances {
|
||||
instances, err := build.Instances(ctx)
|
||||
instances, err := build.Instances(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
@@ -47,35 +56,78 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
results, err := build.Run(cmd.Context())
|
||||
results, err := build.Run(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// TODO: Avoid accidental over-writes if to holos component instances result in
|
||||
// the same file path. Write files into a blank temporary directory, error if a
|
||||
// file exists, then move the directory into place.
|
||||
// TODO: Avoid accidental over-writes if two or more holos component
|
||||
// instances result in the same file path. Write files into a blank
|
||||
// temporary directory, error if a file exists, then move the directory into
|
||||
// place.
|
||||
var result Result
|
||||
for _, result = range results {
|
||||
log := logger.FromContext(ctx).With(
|
||||
"cluster", cfg.ClusterName(),
|
||||
"name", result.Name(),
|
||||
)
|
||||
if result.Continue() {
|
||||
continue
|
||||
}
|
||||
// DeployFiles from the BuildPlan
|
||||
if err := result.WriteDeployFiles(ctx, cfg.WriteTo()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Build plans don't have anything but DeployFiles to write.
|
||||
if result.GetKind() == "BuildPlan" {
|
||||
continue
|
||||
}
|
||||
|
||||
// API Objects
|
||||
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
// Kustomization
|
||||
path = result.KustomizationFilename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.KustomizationContent()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
if result.KustomizationContent() == "" {
|
||||
log.DebugContext(ctx, "flux kustomization: skipped "+result.Name(), "status", "ok", "action", "skipped")
|
||||
} else {
|
||||
path = result.KustomizationFilename(cfg.WriteTo(), cfg.ClusterName())
|
||||
if err := result.Save(ctx, path, result.KustomizationContent()); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
log.InfoContext(ctx, "rendered "+result.Name(), "status", "ok", "action", "rendered", "name", result.Name())
|
||||
|
||||
log.InfoContext(ctx, "rendered "+result.Name(), "status", "ok", "action", "rendered")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
cmd := command.New("platform [directory]")
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Short = "render all platform components"
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
build := builder.New(builder.Entrypoints(args))
|
||||
|
||||
platform, err := build.Platform(ctx, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
return render.Platform(ctx, platform, cmd.ErrOrStderr())
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type Result interface {
|
||||
Continue() bool
|
||||
Name() string
|
||||
@@ -84,4 +136,7 @@ type Result interface {
|
||||
Save(ctx context.Context, path string, content string) error
|
||||
AccumulatedOutput() string
|
||||
KustomizationContent() string
|
||||
WriteDeployFiles(ctx context.Context, writeTo string) error
|
||||
GetKind() string
|
||||
GetAPIVersion() string
|
||||
}
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"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/command"
|
||||
"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/pull"
|
||||
"github.com/holos-run/holos/internal/cli/push"
|
||||
"github.com/holos-run/holos/internal/cli/register"
|
||||
"github.com/holos-run/holos/internal/cli/render"
|
||||
"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.
|
||||
@@ -29,7 +36,7 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "holos",
|
||||
Short: "holos manages a holistic integrated software development platform",
|
||||
Version: version.Version,
|
||||
Version: version.GetVersion(),
|
||||
Args: cobra.NoArgs,
|
||||
CompletionOptions: cobra.CompletionOptions{
|
||||
HiddenDefaultCmd: true, // Don't complete the complete subcommand itself
|
||||
@@ -41,7 +48,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 +72,11 @@ 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(pull.New(cfg))
|
||||
rootCmd.AddCommand(push.New(cfg))
|
||||
rootCmd.AddCommand(newOrgCmd())
|
||||
|
||||
// Maybe not needed?
|
||||
rootCmd.AddCommand(txtar.New(cfg))
|
||||
@@ -80,3 +92,15 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func newOrgCmd() (cmd *cobra.Command) {
|
||||
cmd = command.New("orgid")
|
||||
cmd.Short = "print the current context org id."
|
||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Root().Context()
|
||||
cc := holos.NewClientContext(ctx)
|
||||
_, err := fmt.Fprintln(cmd.OutOrStdout(), cc.OrgID)
|
||||
return err
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Want no hash appended
|
||||
holos create secret test --namespace holos-system --from-file $WORK/test --append-hash=false
|
||||
stderr ' created: test '
|
||||
stderr ' secret=test '
|
||||
|
||||
-- test --
|
||||
sekret
|
||||
127
internal/client/client.go
Normal file
127
internal/client/client.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Package client provides configuration and convenience methods for making API calls to the holos server.
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
"github.com/holos-run/holos/internal/token"
|
||||
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
|
||||
"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"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type PlatformMutation struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
flagSet *flag.FlagSet
|
||||
}
|
||||
|
||||
func (pm *PlatformMutation) FlagSet() *flag.FlagSet {
|
||||
if pm == nil {
|
||||
return nil
|
||||
}
|
||||
if pm.flagSet != nil {
|
||||
return pm.flagSet
|
||||
}
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fs.StringVar(&pm.Name, "name", "example", "platform name")
|
||||
fs.StringVar(&pm.DisplayName, "display-name", "Example Platform", "platform display name")
|
||||
pm.flagSet = fs
|
||||
return fs
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Client) UpdateForm(ctx context.Context, platformID string, form *object.Form) error {
|
||||
start := time.Now()
|
||||
req := &platform.UpdatePlatformRequest{
|
||||
PlatformId: platformID,
|
||||
Update: &platform.PlatformMutation{Form: form},
|
||||
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"form"}},
|
||||
}
|
||||
_, err := c.pltSvc.UpdatePlatform(ctx, connect.NewRequest(req))
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "updated platform", "platform_id", platformID, "duration", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
// PlatformModel gets the platform model from the PlatformService.
|
||||
func (c *Client) PlatformModel(ctx context.Context, platformID string) (*structpb.Struct, error) {
|
||||
start := time.Now()
|
||||
req := &platform.GetPlatformRequest{
|
||||
PlatformId: platformID,
|
||||
FieldMask: &fieldmaskpb.FieldMask{Paths: []string{"spec.model"}},
|
||||
}
|
||||
pf, err := c.pltSvc.GetPlatform(ctx, connect.NewRequest(req))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
log := logger.FromContext(ctx)
|
||||
log.DebugContext(ctx, "get platform", "platform_id", platformID, "duration", time.Since(start))
|
||||
return pf.Msg.GetPlatform().GetSpec().GetModel(), nil
|
||||
}
|
||||
|
||||
func (c *Client) CreatePlatform(ctx context.Context, pm PlatformMutation) (*platform.Platform, error) {
|
||||
log := logger.FromContext(ctx).With("platform", pm.Name)
|
||||
start := time.Now()
|
||||
req := &platform.CreatePlatformRequest{
|
||||
OrgId: c.cfg.context.OrgID,
|
||||
Create: &platform.PlatformMutation{
|
||||
Name: &pm.Name,
|
||||
DisplayName: &pm.DisplayName,
|
||||
},
|
||||
}
|
||||
pf, err := c.pltSvc.CreatePlatform(ctx, connect.NewRequest(req))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
log = log.With("platform_id", pf.Msg.GetPlatform().GetId())
|
||||
log.DebugContext(ctx, "create platform", "duration", time.Since(start))
|
||||
return pf.Msg.GetPlatform(), nil
|
||||
}
|
||||
71
internal/client/config.go
Normal file
71
internal/client/config.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Package client provides client configuration for the holos cli.
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"github.com/holos-run/holos/internal/holos"
|
||||
"github.com/holos-run/holos/internal/token"
|
||||
)
|
||||
|
||||
func NewConfig(cfg *holos.Config) *Config {
|
||||
return &Config{
|
||||
holos: cfg,
|
||||
client: holos.NewClientConfig(),
|
||||
context: holos.NewClientContext(context.Background()),
|
||||
token: token.NewConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
holos *holos.Config
|
||||
client *holos.ClientConfig
|
||||
context *holos.ClientContext
|
||||
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
|
||||
}
|
||||
|
||||
// Context returns the ClientContext useful to get the OrgID and UserID for rpc
|
||||
// calls.
|
||||
func (c *Config) Context() *holos.ClientContext {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.context
|
||||
}
|
||||
|
||||
// Holos returns the *holos.Config
|
||||
func (c *Config) Holos() *holos.Config {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.holos
|
||||
}
|
||||
69
internal/client/platform.go
Normal file
69
internal/client/platform.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
object "github.com/holos-run/holos/service/gen/holos/object/v1alpha1"
|
||||
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
// PlatformMetadataFile is the platform metadata json file name located in the root
|
||||
// of a platform directory.
|
||||
const PlatformMetadataFile = "platform.metadata.json"
|
||||
|
||||
// PlatformConfigFile is the marshaled json representation of the PlatformConfig
|
||||
// DTO used to cache the data holos passes from the PlatformService to CUE when
|
||||
// rendering platform components.
|
||||
const PlatformConfigFile = "platform.config.json"
|
||||
|
||||
// LoadPlatform loads the platform.metadata.json file from a named path. Useful
|
||||
// to obtain a platform id for PlatformService rpc methods.
|
||||
func LoadPlatform(ctx context.Context, name string) (*platform.Platform, error) {
|
||||
data, err := os.ReadFile(filepath.Join(name, PlatformMetadataFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load platform metadata: %w", err)
|
||||
}
|
||||
p := &platform.Platform{}
|
||||
if err := protojson.Unmarshal(data, p); err != nil {
|
||||
return nil, fmt.Errorf("could not load platform metadata: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// LoadPlatformConfig loads the PlatformConfig DTO from the platform.config.json
|
||||
// file. Useful to provide all values necessary to render cue config without an
|
||||
// rpc to the HolosService.
|
||||
func LoadPlatformConfig(ctx context.Context, name string) (*object.PlatformConfig, error) {
|
||||
data, err := os.ReadFile(filepath.Join(name, PlatformConfigFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load platform config: %w", err)
|
||||
}
|
||||
pc := &object.PlatformConfig{}
|
||||
if err := protojson.Unmarshal(data, pc); err != nil {
|
||||
return nil, fmt.Errorf("could not load platform config: %w", err)
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// SavePlatformConfig writes pc to the platform root directory path identified by name.
|
||||
func SavePlatformConfig(ctx context.Context, name string, pc *object.PlatformConfig) (string, error) {
|
||||
encoder := protojson.MarshalOptions{Multiline: true}
|
||||
data, err := encoder.Marshal(pc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(data) > 0 {
|
||||
data = append(data, '\n')
|
||||
}
|
||||
path := filepath.Join(name, PlatformConfigFile)
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return "", fmt.Errorf("could not write platform config: %w", err)
|
||||
}
|
||||
logger.FromContext(ctx).DebugContext(ctx, "wrote", "path", path)
|
||||
return path, nil
|
||||
}
|
||||
2132
internal/frontend/holos/package-lock.json
generated
2132
internal/frontend/holos/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,9 +21,9 @@
|
||||
"@angular/platform-browser": "^17.3.0",
|
||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||
"@angular/router": "^17.3.0",
|
||||
"@bufbuild/protobuf": "^1.9.0",
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@connectrpc/connect": "^1.4.0",
|
||||
"@connectrpc/connect-query": "^1.4.0",
|
||||
"@connectrpc/connect-query": "^1.4.1",
|
||||
"@connectrpc/connect-web": "^1.4.0",
|
||||
"@ngx-formly/core": "^6.3.0",
|
||||
"@ngx-formly/material": "^6.3.0",
|
||||
@@ -40,10 +40,10 @@
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.4",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@bufbuild/buf": "^1.31.0",
|
||||
"@bufbuild/protoc-gen-es": "^1.9.0",
|
||||
"@bufbuild/buf": "^1.32.2",
|
||||
"@bufbuild/protoc-gen-es": "^1.10.0",
|
||||
"@connectrpc/protoc-gen-connect-es": "^1.4.0",
|
||||
"@connectrpc/protoc-gen-connect-query": "^1.4.0",
|
||||
"@connectrpc/protoc-gen-connect-query": "^1.4.1",
|
||||
"@ngx-formly/schematics": "^6.3.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.2.0",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { UserService } from './gen/holos/user/v1alpha1/user_service_connect';
|
||||
import { OrganizationService } from './gen/holos/organization/v1alpha1/organization_service_connect';
|
||||
import { PlatformService } from './gen/holos/platform/v1alpha1/platform_service_connect';
|
||||
import { HolosPanelWrapperComponent } from '../wrappers/holos-panel-wrapper/holos-panel-wrapper.component';
|
||||
import { SystemService } from './gen/holos/system/v1alpha1/system_service_connect';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -19,6 +20,7 @@ export const appConfig: ApplicationConfig = {
|
||||
provideClient(UserService),
|
||||
provideClient(OrganizationService),
|
||||
provideClient(PlatformService),
|
||||
provideClient(SystemService),
|
||||
importProvidersFrom(
|
||||
ConnectModule.forRoot({
|
||||
baseUrl: window.location.origin
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/object/v1alpha1/object.proto (package holos.object.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { Message, proto3, Timestamp } from "@bufbuild/protobuf";
|
||||
import { Message, proto3, Struct, Timestamp } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
* @generated from message holos.object.v1alpha1.Detail
|
||||
@@ -325,3 +325,95 @@ export class ResourceOwner extends Message<ResourceOwner> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form represents a Formly json powered form.
|
||||
*
|
||||
* @generated from message holos.object.v1alpha1.Form
|
||||
*/
|
||||
export class Form extends Message<Form> {
|
||||
/**
|
||||
* fields represents FormlyFieldConfig[] encoded as an array of JSON objects
|
||||
* organized by section.
|
||||
*
|
||||
* @generated from field: repeated google.protobuf.Struct field_configs = 1;
|
||||
*/
|
||||
fieldConfigs: Struct[] = [];
|
||||
|
||||
constructor(data?: PartialMessage<Form>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.object.v1alpha1.Form";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "field_configs", kind: "message", T: Struct, repeated: true },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Form {
|
||||
return new Form().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Form {
|
||||
return new Form().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Form {
|
||||
return new Form().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: Form | PlainMessage<Form> | undefined, b: Form | PlainMessage<Form> | undefined): boolean {
|
||||
return proto3.util.equals(Form, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PlatformConfig represents the data passed from the holos cli to CUE when
|
||||
* rendering configuration.
|
||||
*
|
||||
* @generated from message holos.object.v1alpha1.PlatformConfig
|
||||
*/
|
||||
export class PlatformConfig extends Message<PlatformConfig> {
|
||||
/**
|
||||
* Platform UUID.
|
||||
*
|
||||
* @generated from field: string platform_id = 1;
|
||||
*/
|
||||
platformId = "";
|
||||
|
||||
/**
|
||||
* Platform Model.
|
||||
*
|
||||
* @generated from field: google.protobuf.Struct platform_model = 2;
|
||||
*/
|
||||
platformModel?: Struct;
|
||||
|
||||
constructor(data?: PartialMessage<PlatformConfig>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.object.v1alpha1.PlatformConfig";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "platform_model", kind: "message", T: Struct },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PlatformConfig {
|
||||
return new PlatformConfig().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): PlatformConfig {
|
||||
return new PlatformConfig().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): PlatformConfig {
|
||||
return new PlatformConfig().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: PlatformConfig | PlainMessage<PlatformConfig> | undefined, b: PlatformConfig | PlainMessage<PlatformConfig> | undefined): boolean {
|
||||
return proto3.util.equals(PlatformConfig, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/organization/v1alpha1/organization.proto (package holos.organization.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/organization/v1alpha1/organization_service.proto (package holos.organization.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/platform/v1alpha1/platform.proto (package holos.platform.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { Message, proto3, Struct } from "@bufbuild/protobuf";
|
||||
import { Detail } from "../../object/v1alpha1/object_pb.js";
|
||||
import { Detail, Form } from "../../object/v1alpha1/object_pb.js";
|
||||
|
||||
/**
|
||||
* @generated from message holos.platform.v1alpha1.Platform
|
||||
@@ -147,7 +147,7 @@ export class Spec extends Message<Spec> {
|
||||
model?: Struct;
|
||||
|
||||
/**
|
||||
* @generated from field: optional holos.platform.v1alpha1.Form form = 2;
|
||||
* @generated from field: optional holos.object.v1alpha1.Form form = 2;
|
||||
*/
|
||||
form?: Form;
|
||||
|
||||
@@ -180,45 +180,3 @@ export class Spec extends Message<Spec> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form represents the Formly input form.
|
||||
*
|
||||
* @generated from message holos.platform.v1alpha1.Form
|
||||
*/
|
||||
export class Form extends Message<Form> {
|
||||
/**
|
||||
* fields represents FormlyFieldConfig[] encoded as an array of JSON objects
|
||||
* organized by section.
|
||||
*
|
||||
* @generated from field: repeated google.protobuf.Struct field_configs = 1;
|
||||
*/
|
||||
fieldConfigs: Struct[] = [];
|
||||
|
||||
constructor(data?: PartialMessage<Form>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.platform.v1alpha1.Form";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "field_configs", kind: "message", T: Struct, repeated: true },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Form {
|
||||
return new Form().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Form {
|
||||
return new Form().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Form {
|
||||
return new Form().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: Form | PlainMessage<Form> | undefined, b: Form | PlainMessage<Form> | undefined): boolean {
|
||||
return proto3.util.equals(Form, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/platform/v1alpha1/platform_service.proto (package holos.platform.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { FieldMask, Message, proto3, Struct } from "@bufbuild/protobuf";
|
||||
import { Form, Platform } from "./platform_pb.js";
|
||||
import { Platform } from "./platform_pb.js";
|
||||
import { Form } from "../../object/v1alpha1/object_pb.js";
|
||||
|
||||
/**
|
||||
* @generated from message holos.platform.v1alpha1.CreatePlatformRequest
|
||||
*/
|
||||
export class CreatePlatformRequest extends Message<CreatePlatformRequest> {
|
||||
/**
|
||||
* @generated from field: holos.platform.v1alpha1.Platform platform = 1;
|
||||
* @generated from field: string org_id = 1;
|
||||
*/
|
||||
platform?: Platform;
|
||||
orgId = "";
|
||||
|
||||
/**
|
||||
* @generated from field: holos.platform.v1alpha1.PlatformMutation create = 2;
|
||||
*/
|
||||
create?: PlatformMutation;
|
||||
|
||||
constructor(data?: PartialMessage<CreatePlatformRequest>) {
|
||||
super();
|
||||
@@ -24,7 +30,8 @@ export class CreatePlatformRequest extends Message<CreatePlatformRequest> {
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.platform.v1alpha1.CreatePlatformRequest";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "platform", kind: "message", T: Platform },
|
||||
{ no: 1, name: "org_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "create", kind: "message", T: PlatformMutation },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreatePlatformRequest {
|
||||
@@ -167,20 +174,27 @@ export class GetPlatformResponse extends Message<GetPlatformResponse> {
|
||||
* @generated from message holos.platform.v1alpha1.UpdatePlatformRequest
|
||||
*/
|
||||
export class UpdatePlatformRequest extends Message<UpdatePlatformRequest> {
|
||||
/**
|
||||
* Platform UUID to update.
|
||||
*
|
||||
* @generated from field: string platform_id = 1;
|
||||
*/
|
||||
platformId = "";
|
||||
|
||||
/**
|
||||
* Update operations to perform. Fields are set to the provided value if
|
||||
* selected by the mask. Absent fields are cleared if they are selected by
|
||||
* the mask.
|
||||
*
|
||||
* @generated from field: holos.platform.v1alpha1.UpdatePlatformOperation update = 1;
|
||||
* @generated from field: holos.platform.v1alpha1.PlatformMutation update = 2;
|
||||
*/
|
||||
update?: UpdatePlatformOperation;
|
||||
update?: PlatformMutation;
|
||||
|
||||
/**
|
||||
* FieldMask represents the mutation operations to perform. Marked optional
|
||||
* for the nil guard check. Required.
|
||||
*
|
||||
* @generated from field: optional google.protobuf.FieldMask update_mask = 2;
|
||||
* @generated from field: optional google.protobuf.FieldMask update_mask = 3;
|
||||
*/
|
||||
updateMask?: FieldMask;
|
||||
|
||||
@@ -192,8 +206,9 @@ export class UpdatePlatformRequest extends Message<UpdatePlatformRequest> {
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.platform.v1alpha1.UpdatePlatformRequest";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "update", kind: "message", T: UpdatePlatformOperation },
|
||||
{ no: 2, name: "update_mask", kind: "message", T: FieldMask, opt: true },
|
||||
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "update", kind: "message", T: PlatformMutation },
|
||||
{ no: 3, name: "update_mask", kind: "message", T: FieldMask, opt: true },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): UpdatePlatformRequest {
|
||||
@@ -333,16 +348,11 @@ export class ListPlatformsResponse extends Message<ListPlatformsResponse> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.platform.v1alpha1.UpdatePlatformOperation
|
||||
* PlatformMutation represents the fields to create or update.
|
||||
*
|
||||
* @generated from message holos.platform.v1alpha1.PlatformMutation
|
||||
*/
|
||||
export class UpdatePlatformOperation extends Message<UpdatePlatformOperation> {
|
||||
/**
|
||||
* Platform UUID to update.
|
||||
*
|
||||
* @generated from field: string platform_id = 1;
|
||||
*/
|
||||
platformId = "";
|
||||
|
||||
export class PlatformMutation extends Message<PlatformMutation> {
|
||||
/**
|
||||
* Update the platform name.
|
||||
*
|
||||
@@ -367,39 +377,38 @@ export class UpdatePlatformOperation extends Message<UpdatePlatformOperation> {
|
||||
/**
|
||||
* Replace the form.
|
||||
*
|
||||
* @generated from field: optional holos.platform.v1alpha1.Form form = 5;
|
||||
* @generated from field: optional holos.object.v1alpha1.Form form = 5;
|
||||
*/
|
||||
form?: Form;
|
||||
|
||||
constructor(data?: PartialMessage<UpdatePlatformOperation>) {
|
||||
constructor(data?: PartialMessage<PlatformMutation>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.platform.v1alpha1.UpdatePlatformOperation";
|
||||
static readonly typeName = "holos.platform.v1alpha1.PlatformMutation";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "platform_id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
||||
{ no: 3, name: "display_name", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
|
||||
{ no: 4, name: "model", kind: "message", T: Struct, opt: true },
|
||||
{ no: 5, name: "form", kind: "message", T: Form, opt: true },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): UpdatePlatformOperation {
|
||||
return new UpdatePlatformOperation().fromBinary(bytes, options);
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PlatformMutation {
|
||||
return new PlatformMutation().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): UpdatePlatformOperation {
|
||||
return new UpdatePlatformOperation().fromJson(jsonValue, options);
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): PlatformMutation {
|
||||
return new PlatformMutation().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): UpdatePlatformOperation {
|
||||
return new UpdatePlatformOperation().fromJsonString(jsonString, options);
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): PlatformMutation {
|
||||
return new PlatformMutation().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: UpdatePlatformOperation | PlainMessage<UpdatePlatformOperation> | undefined, b: UpdatePlatformOperation | PlainMessage<UpdatePlatformOperation> | undefined): boolean {
|
||||
return proto3.util.equals(UpdatePlatformOperation, a, b);
|
||||
static equals(a: PlatformMutation | PlainMessage<PlatformMutation> | undefined, b: PlatformMutation | PlainMessage<PlatformMutation> | undefined): boolean {
|
||||
return proto3.util.equals(PlatformMutation, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/storage/v1alpha1/storage.proto (package holos.storage.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/system/v1alpha1/system.proto (package holos.system.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.system.v1alpha1.Version
|
||||
*/
|
||||
export class Version extends Message<Version> {
|
||||
/**
|
||||
* @generated from field: string version = 1;
|
||||
*/
|
||||
version = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string git_commit = 2;
|
||||
*/
|
||||
gitCommit = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string git_tree_state = 3;
|
||||
*/
|
||||
gitTreeState = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string go_version = 4;
|
||||
*/
|
||||
goVersion = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string build_date = 5;
|
||||
*/
|
||||
buildDate = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string os = 6;
|
||||
*/
|
||||
os = "";
|
||||
|
||||
/**
|
||||
* @generated from field: string arch = 7;
|
||||
*/
|
||||
arch = "";
|
||||
|
||||
constructor(data?: PartialMessage<Version>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.Version";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "version", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 2, name: "git_commit", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 3, name: "git_tree_state", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 4, name: "go_version", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 5, name: "build_date", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 6, name: "os", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
{ no: 7, name: "arch", kind: "scalar", T: 9 /* ScalarType.STRING */ },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Version {
|
||||
return new Version().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Version {
|
||||
return new Version().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Version {
|
||||
return new Version().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: Version | PlainMessage<Version> | undefined, b: Version | PlainMessage<Version> | undefined): boolean {
|
||||
return proto3.util.equals(Version, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { DropTablesRequest, DropTablesResponse, SeedDatabaseRequest, SeedDatabaseResponse } from "./system_service_pb.js";
|
||||
import { DropTablesRequest, DropTablesResponse, GetVersionRequest, GetVersionResponse, SeedDatabaseRequest, SeedDatabaseResponse } from "./system_service_pb.js";
|
||||
import { MethodKind } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
@@ -13,12 +13,12 @@ export const SystemService = {
|
||||
typeName: "holos.system.v1alpha1.SystemService",
|
||||
methods: {
|
||||
/**
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.SeedDatabase
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.GetVersion
|
||||
*/
|
||||
seedDatabase: {
|
||||
name: "SeedDatabase",
|
||||
I: SeedDatabaseRequest,
|
||||
O: SeedDatabaseResponse,
|
||||
getVersion: {
|
||||
name: "GetVersion",
|
||||
I: GetVersionRequest,
|
||||
O: GetVersionResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
/**
|
||||
@@ -30,6 +30,15 @@ export const SystemService = {
|
||||
O: DropTablesResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
/**
|
||||
* @generated from rpc holos.system.v1alpha1.SystemService.SeedDatabase
|
||||
*/
|
||||
seedDatabase: {
|
||||
name: "SeedDatabase",
|
||||
I: SeedDatabaseRequest,
|
||||
O: SeedDatabaseResponse,
|
||||
kind: MethodKind.Unary,
|
||||
},
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,10 +1,87 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/system/v1alpha1/system_service.proto (package holos.system.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
|
||||
import { Message, proto3 } from "@bufbuild/protobuf";
|
||||
import { FieldMask, Message, proto3 } from "@bufbuild/protobuf";
|
||||
import { Version } from "./system_pb.js";
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.GetVersionRequest
|
||||
*/
|
||||
export class GetVersionRequest extends Message<GetVersionRequest> {
|
||||
/**
|
||||
* FieldMask represents the fields to include in the response.
|
||||
*
|
||||
* @generated from field: google.protobuf.FieldMask field_mask = 1;
|
||||
*/
|
||||
fieldMask?: FieldMask;
|
||||
|
||||
constructor(data?: PartialMessage<GetVersionRequest>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.GetVersionRequest";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "field_mask", kind: "message", T: FieldMask },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetVersionRequest {
|
||||
return new GetVersionRequest().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: GetVersionRequest | PlainMessage<GetVersionRequest> | undefined, b: GetVersionRequest | PlainMessage<GetVersionRequest> | undefined): boolean {
|
||||
return proto3.util.equals(GetVersionRequest, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.GetVersionResponse
|
||||
*/
|
||||
export class GetVersionResponse extends Message<GetVersionResponse> {
|
||||
/**
|
||||
* @generated from field: holos.system.v1alpha1.Version version = 1;
|
||||
*/
|
||||
version?: Version;
|
||||
|
||||
constructor(data?: PartialMessage<GetVersionResponse>) {
|
||||
super();
|
||||
proto3.util.initPartial(data, this);
|
||||
}
|
||||
|
||||
static readonly runtime: typeof proto3 = proto3;
|
||||
static readonly typeName = "holos.system.v1alpha1.GetVersionResponse";
|
||||
static readonly fields: FieldList = proto3.util.newFieldList(() => [
|
||||
{ no: 1, name: "version", kind: "message", T: Version },
|
||||
]);
|
||||
|
||||
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromBinary(bytes, options);
|
||||
}
|
||||
|
||||
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromJson(jsonValue, options);
|
||||
}
|
||||
|
||||
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetVersionResponse {
|
||||
return new GetVersionResponse().fromJsonString(jsonString, options);
|
||||
}
|
||||
|
||||
static equals(a: GetVersionResponse | PlainMessage<GetVersionResponse> | undefined, b: GetVersionResponse | PlainMessage<GetVersionResponse> | undefined): boolean {
|
||||
return proto3.util.equals(GetVersionResponse, a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @generated from message holos.system.v1alpha1.SeedDatabaseRequest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/user/v1alpha1/user.proto (package holos.user.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @generated by protoc-gen-es v1.9.0 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v1.10.0 with parameter "target=ts"
|
||||
// @generated from file holos/user/v1alpha1/user_service.proto (package holos.user.v1alpha1, syntax proto3)
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</button>
|
||||
}
|
||||
</span>
|
||||
<app-version-button></app-version-button>
|
||||
<app-profile-button [user$]="user$"></app-profile-button>
|
||||
</mat-toolbar>
|
||||
<main class="main-content">
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ProfileButtonComponent } from '../profile-button/profile-button.component';
|
||||
import { User } from '../gen/holos/user/v1alpha1/user_pb';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { map, shareReplay, takeUntil } from 'rxjs/operators';
|
||||
import { Organization } from '../gen/holos/organization/v1alpha1/organization_pb';
|
||||
import { User } from '../gen/holos/user/v1alpha1/user_pb';
|
||||
import { ProfileButtonComponent } from '../profile-button/profile-button.component';
|
||||
import { OrganizationService } from '../services/organization.service';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { VersionButtonComponent } from '../version-button/version-button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav',
|
||||
@@ -34,28 +35,35 @@ import { OrganizationService } from '../services/organization.service';
|
||||
RouterOutlet,
|
||||
MatCardModule,
|
||||
ProfileButtonComponent,
|
||||
VersionButtonComponent,
|
||||
]
|
||||
})
|
||||
export class NavComponent implements OnInit {
|
||||
export class NavComponent implements OnInit, OnDestroy {
|
||||
private breakpointObserver = inject(BreakpointObserver);
|
||||
private userService = inject(UserService);
|
||||
private orgService = inject(OrganizationService);
|
||||
private destroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
user$!: Observable<User | null>;
|
||||
org$!: Observable<Organization | undefined>;
|
||||
|
||||
refreshOrg(): void {
|
||||
this.orgService.refreshOrganizations()
|
||||
}
|
||||
|
||||
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
|
||||
.pipe(
|
||||
map(result => result.matches),
|
||||
shareReplay()
|
||||
);
|
||||
|
||||
refreshOrg(): void {
|
||||
this.orgService.refreshOrganizations()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.user$ = this.userService.getUser();
|
||||
this.org$ = this.orgService.activeOrg();
|
||||
this.user$ = this.userService.getUser().pipe(takeUntil(this.destroy$));
|
||||
this.org$ = this.orgService.activeOrg().pipe(takeUntil(this.destroy$));
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { FieldMask, JsonValue, Struct } from '@bufbuild/protobuf';
|
||||
import { Observable, of, switchMap } from 'rxjs';
|
||||
import { ObservableClient } from '../../connect/observable-client';
|
||||
import { Organization } from '../gen/holos/organization/v1alpha1/organization_pb';
|
||||
import { Platform } from '../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { PlatformService as ConnectPlatformService } from '../gen/holos/platform/v1alpha1/platform_service_connect';
|
||||
import { Platform, Spec } from '../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { GetPlatformRequest, ListPlatformsRequest, UpdatePlatformOperation, UpdatePlatformRequest } from '../gen/holos/platform/v1alpha1/platform_service_pb';
|
||||
import { FieldMask, JsonValue, Struct } from '@bufbuild/protobuf';
|
||||
import { GetPlatformRequest, ListPlatformsRequest, PlatformMutation, UpdatePlatformRequest } from '../gen/holos/platform/v1alpha1/platform_service_pb';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -26,9 +26,9 @@ export class PlatformService {
|
||||
}
|
||||
|
||||
updateModel(platformId: string, model: JsonValue): Observable<Platform | undefined> {
|
||||
const update = new UpdatePlatformOperation({ platformId: platformId, model: Struct.fromJson(model) })
|
||||
const update = new PlatformMutation({ model: Struct.fromJson(model) })
|
||||
const updateMask = new FieldMask({ paths: ["model"] })
|
||||
const req = new UpdatePlatformRequest({ update: update, updateMask: updateMask })
|
||||
const req = new UpdatePlatformRequest({ platformId: platformId, update: update, updateMask: updateMask })
|
||||
return this.client.updatePlatform(req).pipe(
|
||||
switchMap(resp => { return of(resp.platform) })
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SystemService } from './system.service';
|
||||
|
||||
describe('SystemService', () => {
|
||||
let service: SystemService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(SystemService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
22
internal/frontend/holos/src/app/services/system.service.ts
Normal file
22
internal/frontend/holos/src/app/services/system.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { Observable, of, switchMap } from 'rxjs';
|
||||
import { ObservableClient } from '../../connect/observable-client';
|
||||
import { Version } from '../gen/holos/system/v1alpha1/system_pb';
|
||||
import { SystemService as ConnectSystemService } from '../gen/holos/system/v1alpha1/system_service_connect';
|
||||
import { GetVersionRequest } from '../gen/holos/system/v1alpha1/system_service_pb';
|
||||
import { FieldMask } from '@bufbuild/protobuf';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SystemService {
|
||||
getVersion(): Observable<Version | undefined> {
|
||||
const fieldMask = new FieldMask({ paths: ["version", "git_commit", "go_version", "os", "arch"] })
|
||||
const req = new GetVersionRequest({ fieldMask: fieldMask })
|
||||
return this.client.getVersion(req).pipe(
|
||||
switchMap(resp => { return of(resp.version) })
|
||||
)
|
||||
}
|
||||
|
||||
constructor(@Inject(ConnectSystemService) private client: ObservableClient<typeof ConnectSystemService>) { }
|
||||
}
|
||||
8
internal/frontend/holos/src/app/truncate.pipe.spec.ts
Normal file
8
internal/frontend/holos/src/app/truncate.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { TruncatePipe } from './truncate.pipe';
|
||||
|
||||
describe('TruncatePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new TruncatePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
internal/frontend/holos/src/app/truncate.pipe.ts
Normal file
13
internal/frontend/holos/src/app/truncate.pipe.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'truncate',
|
||||
standalone: true
|
||||
})
|
||||
export class TruncatePipe implements PipeTransform {
|
||||
|
||||
transform(value: string, limit: number = 8): string {
|
||||
if (!value) return '';
|
||||
return value.length > limit ? value.substring(0, limit) : value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@if (version$ | async; as version) {
|
||||
<button mat-button [matMenuTriggerFor]="menu">
|
||||
{{ version.version }}
|
||||
</button>
|
||||
|
||||
<mat-menu class="version-menu" #menu="matMenu">
|
||||
<mat-card class="version-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ version.version }}</mat-card-title>
|
||||
<mat-card-subtitle>Server version info</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<pre>Git: {{ version.gitCommit | truncate }}</pre>
|
||||
<pre>Go: {{ version.goVersion | truncate }}</pre>
|
||||
<pre>OS: {{ version.os | truncate }}</pre>
|
||||
<pre>Arch: {{ version.arch | truncate }}</pre>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="refreshVersion()" [disabled]="isLoading">Refresh</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</mat-menu>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VersionButtonComponent } from './version-button.component';
|
||||
|
||||
describe('VersionButtonComponent', () => {
|
||||
let component: VersionButtonComponent;
|
||||
let fixture: ComponentFixture<VersionButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VersionButtonComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VersionButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AsyncPipe, NgIf, NgStyle } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subject, of, startWith, switchMap, takeUntil } from 'rxjs';
|
||||
import { Version } from '../gen/holos/system/v1alpha1/system_pb';
|
||||
import { SystemService } from '../services/system.service';
|
||||
import { TruncatePipe } from '../truncate.pipe';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-version-button',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDivider,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
NgIf,
|
||||
NgStyle,
|
||||
TruncatePipe,
|
||||
],
|
||||
templateUrl: './version-button.component.html',
|
||||
styleUrl: './version-button.component.scss'
|
||||
})
|
||||
export class VersionButtonComponent implements OnInit, OnDestroy {
|
||||
private destroy$: Subject<boolean> = new Subject<boolean>();
|
||||
private refreshVersion$ = new Subject<boolean>();
|
||||
private systemService = inject(SystemService);
|
||||
version$!: Observable<Version | undefined>;
|
||||
isLoading = false;
|
||||
|
||||
refreshVersion(): void {
|
||||
this.refreshVersion$.next(true);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.version$ = this.refreshVersion$.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
startWith(true),
|
||||
switchMap(() => {
|
||||
this.isLoading = true;
|
||||
return this.systemService.getVersion().pipe(
|
||||
switchMap((version) => { this.isLoading = false; return of(version); })
|
||||
);
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.destroy$.next(true);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import { Component, Input, OnDestroy, inject } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTab, MatTabGroup } from '@angular/material/tabs';
|
||||
import { JsonValue } from '@bufbuild/protobuf';
|
||||
import { FormlyFieldConfig, FormlyFormOptions, FormlyModule } from '@ngx-formly/core';
|
||||
import { FormlyMaterialModule } from '@ngx-formly/material';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { PlatformService } from '../../services/platform.service';
|
||||
import { Platform } from '../../gen/holos/platform/v1alpha1/platform_pb';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { PlatformService } from '../../services/platform.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-platform-detail',
|
||||
|
||||
149
internal/generate/component.go
Normal file
149
internal/generate/component.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
)
|
||||
|
||||
//go:embed all:components
|
||||
var components embed.FS
|
||||
|
||||
// componentsRoot is the root path to copy component cue code from.
|
||||
const componentsRoot = "components"
|
||||
|
||||
func NewSchematic(root string, name string) (*Schematic, error) {
|
||||
data, err := components.ReadFile(filepath.Join(root, name, "schematic.json"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
schematic := Schematic{Name: name}
|
||||
if err := json.Unmarshal(data, &schematic); err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
return &schematic, nil
|
||||
}
|
||||
|
||||
// Schematic represents the flags and command metadata stored in the
|
||||
// schematic.yaml file along side each schematic.
|
||||
type Schematic struct {
|
||||
// Name represents the name of the resource the schematic generates.
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
|
||||
Short string `json:"short,omitempty" yaml:"short,omitempty"`
|
||||
Long string `json:"long,omitempty" yaml:"long,omitempty"`
|
||||
|
||||
Chart *string `json:"chart,omitempty" yaml:"chart,omitempty"`
|
||||
Version *string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||
Namespace *string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
|
||||
RepoName *string `json:"reponame,omitempty" yaml:"reponame,omitempty"`
|
||||
RepoURL *string `json:"repourl,omitempty" yaml:"repourl,omitempty"`
|
||||
|
||||
flagSet *flag.FlagSet
|
||||
}
|
||||
|
||||
func (s *Schematic) FlagSet() *flag.FlagSet {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if s.flagSet != nil {
|
||||
return s.flagSet
|
||||
}
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
fs.StringVar(&s.Name, "name", s.Name, "component name")
|
||||
if s.Chart != nil {
|
||||
fs.StringVar(s.Chart, "chart", *s.Chart, "chart name")
|
||||
}
|
||||
if s.Version != nil {
|
||||
fs.StringVar(s.Version, "component-version", *s.Version, "component version")
|
||||
}
|
||||
if s.Namespace != nil {
|
||||
fs.StringVar(s.Namespace, "namespace", *s.Namespace, "namespace")
|
||||
}
|
||||
if s.RepoName != nil {
|
||||
fs.StringVar(s.RepoName, "repo-name", *s.RepoName, "chart repository name")
|
||||
}
|
||||
if s.RepoURL != nil {
|
||||
fs.StringVar(s.RepoURL, "repo-url", *s.RepoURL, "chart repository url")
|
||||
}
|
||||
s.flagSet = fs
|
||||
return fs
|
||||
}
|
||||
|
||||
// CueComponents returns a slice of embedded component schematics or nil if there are none.
|
||||
func CueComponents() []string {
|
||||
entries, err := fs.ReadDir(components, filepath.Join(componentsRoot, "cue"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
dirs := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// HelmComponents returns a slice of embedded component schematics or nil if there are none.
|
||||
func HelmComponents() []string {
|
||||
entries, err := fs.ReadDir(components, filepath.Join(componentsRoot, "helm"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
dirs := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
dirs = append(dirs, entry.Name())
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// makeRenderFunc makes a template rendering function for embedded files.
|
||||
func makeRenderFunc[T any](log *slog.Logger, path string, cfg T) func([]byte) *bytes.Buffer {
|
||||
return func(content []byte) *bytes.Buffer {
|
||||
tmpl, err := template.New(filepath.Base(path)).Parse(string(content))
|
||||
if err != nil {
|
||||
log.Error("could not load template", "err", err)
|
||||
return bytes.NewBuffer(content)
|
||||
}
|
||||
|
||||
var rendered bytes.Buffer
|
||||
if err := tmpl.Execute(&rendered, cfg); err != nil {
|
||||
log.Error("could not execute template", "err", err)
|
||||
return bytes.NewBuffer(content)
|
||||
}
|
||||
|
||||
return &rendered
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateComponent writes the cue code for a component to the local working
|
||||
// directory.
|
||||
func GenerateComponent(ctx context.Context, kind string, name string, cfg *Schematic) error {
|
||||
// use name from args to build the source path
|
||||
path := filepath.Join(componentsRoot, kind, name)
|
||||
// use cfg.Name from flags to build the destination path
|
||||
dstPath := filepath.Join(getCwd(ctx), cfg.Name)
|
||||
log := logger.FromContext(ctx).With("name", cfg.Name, "path", dstPath)
|
||||
log.DebugContext(ctx, "mkdir")
|
||||
if err := os.MkdirAll(dstPath, os.ModePerm); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
mapper := makeRenderFunc(log, path, cfg)
|
||||
if err := copyEmbedFS(ctx, components, path, dstPath, mapper); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "generated component")
|
||||
return nil
|
||||
}
|
||||
4
internal/generate/components/cue/argocd/argocd.cue
Executable file
4
internal/generate/components/cue/argocd/argocd.cue
Executable file
@@ -0,0 +1,4 @@
|
||||
package holos
|
||||
|
||||
// Produce a kubectl kustomize build plan.
|
||||
(#Kustomize & {Name: "{{ .Name }}"}).Output
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: "{{ .Namespace }}"
|
||||
resources:
|
||||
- "https://raw.githubusercontent.com/argoproj/argo-cd/v{{ .Version }}/manifests/install.yaml"
|
||||
7
internal/generate/components/cue/argocd/schematic.json
Normal file
7
internal/generate/components/cue/argocd/schematic.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "argocd",
|
||||
"namespace": "argocd",
|
||||
"short": "argocd kustomize",
|
||||
"long": "Manage argocd using a kustomization.yaml build plan.",
|
||||
"version": "2.11.2"
|
||||
}
|
||||
21
internal/generate/components/cue/configmap/configmap.cue
Normal file
21
internal/generate/components/cue/configmap/configmap.cue
Normal file
@@ -0,0 +1,21 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
let Objects = {
|
||||
Name: "{{ .Name }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
|
||||
Resources: {
|
||||
ConfigMap: {
|
||||
example: {
|
||||
metadata: namespace: "{{ .Namespace }}"
|
||||
// _Platform.Model represents the web form model
|
||||
data: platform: yaml.Marshal({model: _Platform.Model})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "configmap",
|
||||
"namespace": "default",
|
||||
"short": "simple configmap example",
|
||||
"long": "End-to-end demonstration of data flowing from the web ui to a cluster resource"
|
||||
}
|
||||
11
internal/generate/components/cue/namespaces/namespaces.cue
Normal file
11
internal/generate/components/cue/namespaces/namespaces.cue
Normal file
@@ -0,0 +1,11 @@
|
||||
package holos
|
||||
|
||||
let Objects = {
|
||||
Name: "{{ .Name }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
|
||||
Resources: Namespace: _Namespaces
|
||||
}
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).Output
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "namespaces",
|
||||
"namespace": "default",
|
||||
"short": "manage namespaces on multiple clusters",
|
||||
"long": "Manage namespaces across all clusters in the platform following sig-multicluster namespace sameness position."
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package holos
|
||||
|
||||
let Chart = {
|
||||
Name: "{{ .Name }}"
|
||||
Version: "{{ .Version }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
|
||||
Repo: name: "{{ .RepoName }}"
|
||||
Repo: url: "{{ .RepoURL }}"
|
||||
|
||||
Values: {
|
||||
installCRDs: true
|
||||
startupapicheck: enabled: false
|
||||
// Must not use kube-system on gke autopilot. GKE Warden blocks access.
|
||||
global: leaderElection: namespace: Namespace
|
||||
}
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "cert-manager",
|
||||
"short": "cloud native certificate management",
|
||||
"long": "Automatically provision and manage TLS certificates in Kubernetes",
|
||||
"chart": "cert-manager",
|
||||
"version": "1.14.5",
|
||||
"namespace": "cert-manager",
|
||||
"reponame": "jetstack",
|
||||
"repourl": "https://charts.jetstack.io"
|
||||
}
|
||||
15
internal/generate/components/helm/podinfo-oci/podinfo.cue
Normal file
15
internal/generate/components/helm/podinfo-oci/podinfo.cue
Normal file
@@ -0,0 +1,15 @@
|
||||
package holos
|
||||
|
||||
let Chart = {
|
||||
Name: "{{ .Name }}"
|
||||
Version: "{{ .Version }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
|
||||
// OCI helm charts use the image url as the chart name
|
||||
Chart: chart: name: "{{ .Chart }}"
|
||||
|
||||
Values: {}
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "podinfo-oci",
|
||||
"short": "oci helm chart example",
|
||||
"long": "Podinfo is a tiny web application made with Go that showcases best practices of running microservices in Kubernetes.",
|
||||
"chart": "oci://ghcr.io/stefanprodan/charts/podinfo",
|
||||
"version": "6.6.2",
|
||||
"namespace": "default"
|
||||
}
|
||||
15
internal/generate/components/helm/podinfo/podinfo.cue
Normal file
15
internal/generate/components/helm/podinfo/podinfo.cue
Normal file
@@ -0,0 +1,15 @@
|
||||
package holos
|
||||
|
||||
let Chart = {
|
||||
Name: "{{ .Name }}"
|
||||
Version: "{{ .Version }}"
|
||||
Namespace: "{{ .Namespace }}"
|
||||
|
||||
Repo: name: "{{ .RepoName }}"
|
||||
Repo: url: "{{ .RepoURL }}"
|
||||
|
||||
Values: {}
|
||||
}
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
10
internal/generate/components/helm/podinfo/schematic.json
Normal file
10
internal/generate/components/helm/podinfo/schematic.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "podinfo",
|
||||
"short": "simple helm chart example",
|
||||
"long": "Podinfo is a tiny web application made with Go that showcases best practices of running microservices in Kubernetes.",
|
||||
"chart": "podinfo",
|
||||
"reponame": "podinfo",
|
||||
"repourl": "https://stefanprodan.github.io/podinfo",
|
||||
"version": "6.6.2",
|
||||
"namespace": "default"
|
||||
}
|
||||
79
internal/generate/generate.go
Normal file
79
internal/generate/generate.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/holos-run/holos/internal/errors"
|
||||
"github.com/holos-run/holos/internal/server/middleware/logger"
|
||||
)
|
||||
|
||||
func dirExists(srcFS embed.FS, path string) bool {
|
||||
entries, err := fs.ReadDir(srcFS, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(entries) > 0
|
||||
}
|
||||
|
||||
// copyEmbedFS copies embedded files from srcPath to dstPath passing the
|
||||
// contents through mapFunc.
|
||||
func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string, mapFunc func([]byte) *bytes.Buffer) 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)
|
||||
return nil
|
||||
}
|
||||
|
||||
if filepath.Base(path) == "schematic.json" {
|
||||
log.DebugContext(ctx, "skipped", "file", dstFullPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := srcFS.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
buf := mapFunc(data)
|
||||
if err := os.WriteFile(dstFullPath, buf.Bytes(), 0666); 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
|
||||
}
|
||||
94
internal/generate/platform.go
Normal file
94
internal/generate/platform.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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/logger"
|
||||
platform "github.com/holos-run/holos/service/gen/holos/platform/v1alpha1"
|
||||
)
|
||||
|
||||
//go:embed all:platforms
|
||||
var platforms embed.FS
|
||||
|
||||
// platformsRoot is the root path to copy platform cue code from.
|
||||
const platformsRoot = "platforms"
|
||||
|
||||
// Platforms returns a slice of embedded platforms or nil if there are none.
|
||||
func Platforms() []string {
|
||||
entries, err := fs.ReadDir(platforms, platformsRoot)
|
||||
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(platformsRoot, 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())
|
||||
if err := os.WriteFile(client.PlatformMetadataFile, data, 0644); err != nil {
|
||||
return errors.Wrap(fmt.Errorf("could not write platform metadata: %w", err))
|
||||
}
|
||||
log.InfoContext(ctx, "wrote "+client.PlatformMetadataFile, "path", filepath.Join(getCwd(ctx), client.PlatformMetadataFile))
|
||||
|
||||
// Copy the cue.mod directory
|
||||
if err := copyEmbedFS(ctx, platforms, filepath.Join(platformsRoot, "cue.mod"), "cue.mod", bytes.NewBuffer); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// Copy the named platform
|
||||
if err := copyEmbedFS(ctx, platforms, platformPath, ".", bytes.NewBuffer); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "generated platform "+name, "path", getCwd(ctx))
|
||||
|
||||
return nil
|
||||
}
|
||||
33
internal/generate/platforms/bare/buildplan.cue
Normal file
33
internal/generate/platforms/bare/buildplan.cue
Normal file
@@ -0,0 +1,33 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
// #Helm represents a holos build plan composed of one or more helm charts.
|
||||
#Helm: {
|
||||
Name: string
|
||||
Version: string
|
||||
Namespace: string
|
||||
|
||||
Repo: {
|
||||
name: string | *""
|
||||
url: string | *""
|
||||
}
|
||||
|
||||
Values: {...}
|
||||
|
||||
Chart: v1.#HelmChart & {
|
||||
metadata: name: string | *Name
|
||||
namespace: string | *Namespace
|
||||
chart: name: string | *Name
|
||||
chart: version: string | *Version
|
||||
chart: repository: Repo
|
||||
// Render the values to yaml for holos to provide to helm.
|
||||
valuesContent: yaml.Marshal(Values)
|
||||
}
|
||||
|
||||
// output represents the build plan provided to the holos cli.
|
||||
Output: v1.#BuildPlan & {
|
||||
spec: components: helmChartList: [Chart]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package holos
|
||||
|
||||
import "encoding/yaml"
|
||||
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
// 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"
|
||||
}
|
||||
// Output the platform model which is derived from the web app form the
|
||||
// platform engineer provides and the form values the end user provides.
|
||||
data: platform: yaml.Marshal(PLATFORM)
|
||||
}
|
||||
}
|
||||
|
||||
let PLATFORM = {
|
||||
spec: model: _Platform.spec.model
|
||||
}
|
||||
@@ -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\")"
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
47
internal/generate/platforms/bare/platform.cue
Normal file
47
internal/generate/platforms/bare/platform.cue
Normal file
@@ -0,0 +1,47 @@
|
||||
package holos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
import v1 "github.com/holos-run/holos/api/v1alpha1"
|
||||
|
||||
import dto "github.com/holos-run/holos/service/gen/holos/object/v1alpha1:object"
|
||||
|
||||
// _PlatformConfig represents all of the data passed from holos to cue.
|
||||
// Intended to carry the platform model and project models.
|
||||
_PlatformConfig: dto.#PlatformConfig & json.Unmarshal(_PlatformConfigJSON)
|
||||
_PlatformConfigJSON: string | *"{}" @tag(platform_config, type=string)
|
||||
|
||||
// _Platform provides a platform resource to the holos cli for rendering. The
|
||||
// field is hidden because most components need to refer to platform data,
|
||||
// specifically the platform model and the project models. The platform
|
||||
// resource itself is output once when rendering the entire platform, see the
|
||||
// platform/ subdirectory.
|
||||
_Platform: v1.#Platform & {
|
||||
metadata: {
|
||||
name: string | *"bare" @tag(platform_name, type=string)
|
||||
}
|
||||
|
||||
// spec is the platform specification
|
||||
spec: {
|
||||
// model represents the web form values provided by the user.
|
||||
model: _PlatformConfig.platform_model
|
||||
components: [for c in _components {c}]
|
||||
|
||||
_components: [string]: v1.#PlatformSpecComponent
|
||||
_components: {
|
||||
for WorkloadCluster in _Clusters.Workload {
|
||||
"\(WorkloadCluster)-configmap": {
|
||||
path: "components/configmap"
|
||||
cluster: WorkloadCluster
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// _Clusters represents the clusters in the platform. The default values are
|
||||
// intended to be provided by the user in a file which is not written over by
|
||||
// `holos generate`.
|
||||
_Clusters: {
|
||||
Workload: [...string] | *["mycluster"]
|
||||
}
|
||||
4
internal/generate/platforms/bare/platform/platform.cue
Normal file
4
internal/generate/platforms/bare/platform/platform.cue
Normal file
@@ -0,0 +1,4 @@
|
||||
package holos
|
||||
|
||||
// Output the Platform resource for holos to render the entire platform.
|
||||
{} & _Platform
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,189 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f deploy/clusters/aws2/components/argocd-crds/argocd-crds.gen.yaml
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import "strings"
|
||||
|
||||
// AppProject provides a logical grouping of applications,
|
||||
// providing controls for: * where the apps may deploy to
|
||||
// (cluster whitelist) * what may be deployed (repository
|
||||
// whitelist, resource whitelist/blacklist) * who can access
|
||||
// these applications (roles, OIDC group claims bindings) * and
|
||||
// what they can do (RBAC policies) * automation access to these
|
||||
// roles (JWT tokens)
|
||||
#AppProject: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object. Servers should convert recognized schemas to the
|
||||
// latest internal value, and may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "argoproj.io/v1alpha1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents. Servers may infer this from the endpoint
|
||||
// the client submits requests to. Cannot be updated. In
|
||||
// CamelCase. More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "AppProject"
|
||||
metadata: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// AppProjectSpec is the specification of an AppProject
|
||||
spec!: #AppProjectSpec
|
||||
}
|
||||
|
||||
// AppProjectSpec is the specification of an AppProject
|
||||
#AppProjectSpec: {
|
||||
// ClusterResourceBlacklist contains list of blacklisted cluster
|
||||
// level resources
|
||||
clusterResourceBlacklist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// ClusterResourceWhitelist contains list of whitelisted cluster
|
||||
// level resources
|
||||
clusterResourceWhitelist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// Description contains optional project description
|
||||
description?: string
|
||||
|
||||
// Destinations contains list of destinations available for
|
||||
// deployment
|
||||
destinations?: [...{
|
||||
// Name is an alternate way of specifying the target cluster by
|
||||
// its symbolic name. This must be set if Server is not set.
|
||||
name?: string
|
||||
|
||||
// Namespace specifies the target namespace for the application's
|
||||
// resources. The namespace will only be set for namespace-scoped
|
||||
// resources that have not set a value for .metadata.namespace
|
||||
namespace?: string
|
||||
|
||||
// Server specifies the URL of the target cluster's Kubernetes
|
||||
// control plane API. This must be set if Name is not set.
|
||||
server?: string
|
||||
}]
|
||||
|
||||
// NamespaceResourceBlacklist contains list of blacklisted
|
||||
// namespace level resources
|
||||
namespaceResourceBlacklist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// NamespaceResourceWhitelist contains list of whitelisted
|
||||
// namespace level resources
|
||||
namespaceResourceWhitelist?: [...{
|
||||
group: string
|
||||
kind: string
|
||||
}]
|
||||
|
||||
// OrphanedResources specifies if controller should monitor
|
||||
// orphaned resources of apps in this project
|
||||
orphanedResources?: {
|
||||
// Ignore contains a list of resources that are to be excluded
|
||||
// from orphaned resources monitoring
|
||||
ignore?: [...{
|
||||
group?: string
|
||||
kind?: string
|
||||
name?: string
|
||||
}]
|
||||
|
||||
// Warn indicates if warning condition should be created for apps
|
||||
// which have orphaned resources
|
||||
warn?: bool
|
||||
}
|
||||
|
||||
// PermitOnlyProjectScopedClusters determines whether destinations
|
||||
// can only reference clusters which are project-scoped
|
||||
permitOnlyProjectScopedClusters?: bool
|
||||
|
||||
// Roles are user defined RBAC roles associated with this project
|
||||
roles?: [...{
|
||||
// Description is a description of the role
|
||||
description?: string
|
||||
|
||||
// Groups are a list of OIDC group claims bound to this role
|
||||
groups?: [...string]
|
||||
|
||||
// JWTTokens are a list of generated JWT tokens bound to this role
|
||||
jwtTokens?: [...{
|
||||
exp?: int
|
||||
iat: int
|
||||
id?: string
|
||||
}]
|
||||
|
||||
// Name is a name for this role
|
||||
name: string
|
||||
|
||||
// Policies Stores a list of casbin formatted strings that define
|
||||
// access policies for the role in the project
|
||||
policies?: [...string]
|
||||
}]
|
||||
|
||||
// SignatureKeys contains a list of PGP key IDs that commits in
|
||||
// Git must be signed with in order to be allowed for sync
|
||||
signatureKeys?: [...{
|
||||
// The ID of the key in hexadecimal notation
|
||||
keyID: string
|
||||
}]
|
||||
|
||||
// SourceNamespaces defines the namespaces application resources
|
||||
// are allowed to be created in
|
||||
sourceNamespaces?: [...string]
|
||||
|
||||
// SourceRepos contains list of repository URLs which can be used
|
||||
// for deployment
|
||||
sourceRepos?: [...string]
|
||||
|
||||
// SyncWindows controls when syncs can be run for apps in this
|
||||
// project
|
||||
syncWindows?: [...{
|
||||
// Applications contains a list of applications that the window
|
||||
// will apply to
|
||||
applications?: [...string]
|
||||
|
||||
// Clusters contains a list of clusters that the window will apply
|
||||
// to
|
||||
clusters?: [...string]
|
||||
|
||||
// Duration is the amount of time the sync window will be open
|
||||
duration?: string
|
||||
|
||||
// Kind defines if the window allows or blocks syncs
|
||||
kind?: string
|
||||
|
||||
// ManualSync enables manual syncs when they would otherwise be
|
||||
// blocked
|
||||
manualSync?: bool
|
||||
|
||||
// Namespaces contains a list of namespaces that the window will
|
||||
// apply to
|
||||
namespaces?: [...string]
|
||||
|
||||
// Schedule is the time the window will begin, specified in cron
|
||||
// format
|
||||
schedule?: string
|
||||
|
||||
// TimeZone of the sync that will be applied to the schedule
|
||||
timeZone?: string
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f /home/jeff/workspace/holos-run/holos-infra/deploy/clusters/k2/components/prod-mesh-certmanager/prod-mesh-certmanager.gen.yaml
|
||||
|
||||
package v1
|
||||
|
||||
import "strings"
|
||||
|
||||
// A Certificate resource should be created to ensure an up to
|
||||
// date and signed X.509 certificate is stored in the Kubernetes
|
||||
// Secret resource named in `spec.secretName`.
|
||||
// The stored certificate will be renewed before it expires (as
|
||||
// configured by `spec.renewBefore`).
|
||||
#Certificate: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object. Servers should convert recognized schemas to the
|
||||
// latest internal value, and may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "cert-manager.io/v1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents. Servers may infer this from the endpoint
|
||||
// the client submits requests to. Cannot be updated. In
|
||||
// CamelCase. More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "Certificate"
|
||||
metadata!: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// Specification of the desired state of the Certificate resource.
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
spec!: #CertificateSpec
|
||||
}
|
||||
|
||||
// Specification of the desired state of the Certificate resource.
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
#CertificateSpec: {
|
||||
// Defines extra output formats of the private key and signed
|
||||
// certificate chain to be written to this Certificate's target
|
||||
// Secret.
|
||||
// This is an Alpha Feature and is only enabled with the
|
||||
// `--feature-gates=AdditionalCertificateOutputFormats=true`
|
||||
// option set on both the controller and webhook components.
|
||||
additionalOutputFormats?: [...{
|
||||
// Type is the name of the format type that should be written to
|
||||
// the Certificate's target Secret.
|
||||
type: "DER" | "CombinedPEM"
|
||||
}]
|
||||
|
||||
// Requested common name X509 certificate subject attribute. More
|
||||
// info:
|
||||
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6
|
||||
// NOTE: TLS clients will ignore this value when any subject
|
||||
// alternative name is set (see
|
||||
// https://tools.ietf.org/html/rfc6125#section-6.4.4).
|
||||
// Should have a length of 64 characters or fewer to avoid
|
||||
// generating invalid CSRs. Cannot be set if the `literalSubject`
|
||||
// field is set.
|
||||
commonName?: string
|
||||
|
||||
// Requested DNS subject alternative names.
|
||||
dnsNames?: [...string]
|
||||
|
||||
// Requested 'duration' (i.e. lifetime) of the Certificate. Note
|
||||
// that the issuer may choose to ignore the requested duration,
|
||||
// just like any other requested attribute.
|
||||
// If unset, this defaults to 90 days. Minimum accepted duration
|
||||
// is 1 hour. Value must be in units accepted by Go
|
||||
// time.ParseDuration https://golang.org/pkg/time/#ParseDuration.
|
||||
duration?: string
|
||||
|
||||
// Requested email subject alternative names.
|
||||
emailAddresses?: [...string]
|
||||
|
||||
// Whether the KeyUsage and ExtKeyUsage extensions should be set
|
||||
// in the encoded CSR.
|
||||
// This option defaults to true, and should only be disabled if
|
||||
// the target issuer does not support CSRs with these X509
|
||||
// KeyUsage/ ExtKeyUsage extensions.
|
||||
encodeUsagesInRequest?: bool
|
||||
|
||||
// Requested IP address subject alternative names.
|
||||
ipAddresses?: [...string]
|
||||
|
||||
// Requested basic constraints isCA value. The isCA value is used
|
||||
// to set the `isCA` field on the created CertificateRequest
|
||||
// resources. Note that the issuer may choose to ignore the
|
||||
// requested isCA value, just like any other requested attribute.
|
||||
// If true, this will automatically add the `cert sign` usage to
|
||||
// the list of requested `usages`.
|
||||
isCA?: bool
|
||||
|
||||
// Reference to the issuer responsible for issuing the
|
||||
// certificate. If the issuer is namespace-scoped, it must be in
|
||||
// the same namespace as the Certificate. If the issuer is
|
||||
// cluster-scoped, it can be used from any namespace.
|
||||
// The `name` field of the reference must always be specified.
|
||||
issuerRef: {
|
||||
// Group of the resource being referred to.
|
||||
group?: string
|
||||
|
||||
// Kind of the resource being referred to.
|
||||
kind?: string
|
||||
|
||||
// Name of the resource being referred to.
|
||||
name: string
|
||||
}
|
||||
|
||||
// Additional keystore output formats to be stored in the
|
||||
// Certificate's Secret.
|
||||
keystores?: {
|
||||
// JKS configures options for storing a JKS keystore in the
|
||||
// `spec.secretName` Secret resource.
|
||||
jks?: {
|
||||
// Create enables JKS keystore creation for the Certificate. If
|
||||
// true, a file named `keystore.jks` will be created in the
|
||||
// target Secret resource, encrypted using the password stored in
|
||||
// `passwordSecretRef`. The keystore file will be updated
|
||||
// immediately. If the issuer provided a CA certificate, a file
|
||||
// named `truststore.jks` will also be created in the target
|
||||
// Secret resource, encrypted using the password stored in
|
||||
// `passwordSecretRef` containing the issuing Certificate
|
||||
// Authority
|
||||
create: bool
|
||||
|
||||
// PasswordSecretRef is a reference to a key in a Secret resource
|
||||
// containing the password used to encrypt the JKS keystore.
|
||||
passwordSecretRef: {
|
||||
// The key of the entry in the Secret resource's `data` field to
|
||||
// be used. Some instances of this field may be defaulted, in
|
||||
// others it may be required.
|
||||
key?: string
|
||||
|
||||
// Name of the resource being referred to. More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
// PKCS12 configures options for storing a PKCS12 keystore in the
|
||||
// `spec.secretName` Secret resource.
|
||||
pkcs12?: {
|
||||
// Create enables PKCS12 keystore creation for the Certificate. If
|
||||
// true, a file named `keystore.p12` will be created in the
|
||||
// target Secret resource, encrypted using the password stored in
|
||||
// `passwordSecretRef`. The keystore file will be updated
|
||||
// immediately. If the issuer provided a CA certificate, a file
|
||||
// named `truststore.p12` will also be created in the target
|
||||
// Secret resource, encrypted using the password stored in
|
||||
// `passwordSecretRef` containing the issuing Certificate
|
||||
// Authority
|
||||
create: bool
|
||||
|
||||
// PasswordSecretRef is a reference to a key in a Secret resource
|
||||
// containing the password used to encrypt the PKCS12 keystore.
|
||||
passwordSecretRef: {
|
||||
// The key of the entry in the Secret resource's `data` field to
|
||||
// be used. Some instances of this field may be defaulted, in
|
||||
// others it may be required.
|
||||
key?: string
|
||||
|
||||
// Name of the resource being referred to. More info:
|
||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
name: string
|
||||
}
|
||||
|
||||
// Profile specifies the key and certificate encryption algorithms
|
||||
// and the HMAC algorithm used to create the PKCS12 keystore.
|
||||
// Default value is `LegacyRC2` for backward compatibility.
|
||||
// If provided, allowed values are: `LegacyRC2`: Deprecated. Not
|
||||
// supported by default in OpenSSL 3 or Java 20. `LegacyDES`:
|
||||
// Less secure algorithm. Use this option for maximal
|
||||
// compatibility. `Modern2023`: Secure algorithm. Use this option
|
||||
// in case you have to always use secure algorithms (eg. because
|
||||
// of company policy). Please note that the security of the
|
||||
// algorithm is not that important in reality, because the
|
||||
// unencrypted certificate and private key are also stored in the
|
||||
// Secret.
|
||||
profile?: "LegacyRC2" | "LegacyDES" | "Modern2023"
|
||||
}
|
||||
}
|
||||
|
||||
// Requested X.509 certificate subject, represented using the LDAP
|
||||
// "String Representation of a Distinguished Name" [1].
|
||||
// Important: the LDAP string format also specifies the order of
|
||||
// the attributes in the subject, this is important when issuing
|
||||
// certs for LDAP authentication. Example:
|
||||
// `CN=foo,DC=corp,DC=example,DC=com` More info [1]:
|
||||
// https://datatracker.ietf.org/doc/html/rfc4514 More info:
|
||||
// https://github.com/cert-manager/cert-manager/issues/3203 More
|
||||
// info: https://github.com/cert-manager/cert-manager/issues/4424
|
||||
// Cannot be set if the `subject` or `commonName` field is set.
|
||||
// This is an Alpha Feature and is only enabled with the
|
||||
// `--feature-gates=LiteralCertificateSubject=true` option set on
|
||||
// both the controller and webhook components.
|
||||
literalSubject?: string
|
||||
|
||||
// x.509 certificate NameConstraint extension which MUST NOT be
|
||||
// used in a non-CA certificate. More Info:
|
||||
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
|
||||
// This is an Alpha Feature and is only enabled with the
|
||||
// `--feature-gates=NameConstraints=true` option set on both the
|
||||
// controller and webhook components.
|
||||
nameConstraints?: {
|
||||
// if true then the name constraints are marked critical.
|
||||
critical?: bool
|
||||
|
||||
// Excluded contains the constraints which must be disallowed. Any
|
||||
// name matching a restriction in the excluded field is invalid
|
||||
// regardless of information appearing in the permitted
|
||||
excluded?: {
|
||||
// DNSDomains is a list of DNS domains that are permitted or
|
||||
// excluded.
|
||||
dnsDomains?: [...string]
|
||||
|
||||
// EmailAddresses is a list of Email Addresses that are permitted
|
||||
// or excluded.
|
||||
emailAddresses?: [...string]
|
||||
|
||||
// IPRanges is a list of IP Ranges that are permitted or excluded.
|
||||
// This should be a valid CIDR notation.
|
||||
ipRanges?: [...string]
|
||||
|
||||
// URIDomains is a list of URI domains that are permitted or
|
||||
// excluded.
|
||||
uriDomains?: [...string]
|
||||
}
|
||||
|
||||
// Permitted contains the constraints in which the names must be
|
||||
// located.
|
||||
permitted?: {
|
||||
// DNSDomains is a list of DNS domains that are permitted or
|
||||
// excluded.
|
||||
dnsDomains?: [...string]
|
||||
|
||||
// EmailAddresses is a list of Email Addresses that are permitted
|
||||
// or excluded.
|
||||
emailAddresses?: [...string]
|
||||
|
||||
// IPRanges is a list of IP Ranges that are permitted or excluded.
|
||||
// This should be a valid CIDR notation.
|
||||
ipRanges?: [...string]
|
||||
|
||||
// URIDomains is a list of URI domains that are permitted or
|
||||
// excluded.
|
||||
uriDomains?: [...string]
|
||||
}
|
||||
}
|
||||
|
||||
// `otherNames` is an escape hatch for SAN that allows any type.
|
||||
// We currently restrict the support to string like otherNames,
|
||||
// cf RFC 5280 p 37 Any UTF8 String valued otherName can be
|
||||
// passed with by setting the keys oid: x.x.x.x and UTF8Value:
|
||||
// somevalue for `otherName`. Most commonly this would be UPN set
|
||||
// with oid: 1.3.6.1.4.1.311.20.2.3 You should ensure that any
|
||||
// OID passed is valid for the UTF8String type as we do not
|
||||
// explicitly validate this.
|
||||
otherNames?: [...{
|
||||
// OID is the object identifier for the otherName SAN. The object
|
||||
// identifier must be expressed as a dotted string, for example,
|
||||
// "1.2.840.113556.1.4.221".
|
||||
oid?: string
|
||||
|
||||
// utf8Value is the string value of the otherName SAN. The
|
||||
// utf8Value accepts any valid UTF8 string to set as value for
|
||||
// the otherName SAN.
|
||||
utf8Value?: string
|
||||
}]
|
||||
|
||||
// Private key options. These include the key algorithm and size,
|
||||
// the used encoding and the rotation policy.
|
||||
privateKey?: {
|
||||
// Algorithm is the private key algorithm of the corresponding
|
||||
// private key for this certificate.
|
||||
// If provided, allowed values are either `RSA`, `ECDSA` or
|
||||
// `Ed25519`. If `algorithm` is specified and `size` is not
|
||||
// provided, key size of 2048 will be used for `RSA` key
|
||||
// algorithm and key size of 256 will be used for `ECDSA` key
|
||||
// algorithm. key size is ignored when using the `Ed25519` key
|
||||
// algorithm.
|
||||
algorithm?: "RSA" | "ECDSA" | "Ed25519"
|
||||
|
||||
// The private key cryptography standards (PKCS) encoding for this
|
||||
// certificate's private key to be encoded in.
|
||||
// If provided, allowed values are `PKCS1` and `PKCS8` standing
|
||||
// for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if
|
||||
// not specified.
|
||||
encoding?: "PKCS1" | "PKCS8"
|
||||
|
||||
// RotationPolicy controls how private keys should be regenerated
|
||||
// when a re-issuance is being processed.
|
||||
// If set to `Never`, a private key will only be generated if one
|
||||
// does not already exist in the target `spec.secretName`. If one
|
||||
// does exists but it does not have the correct algorithm or
|
||||
// size, a warning will be raised to await user intervention. If
|
||||
// set to `Always`, a private key matching the specified
|
||||
// requirements will be generated whenever a re-issuance occurs.
|
||||
// Default is `Never` for backward compatibility.
|
||||
rotationPolicy?: "Never" | "Always"
|
||||
|
||||
// Size is the key bit size of the corresponding private key for
|
||||
// this certificate.
|
||||
// If `algorithm` is set to `RSA`, valid values are `2048`, `4096`
|
||||
// or `8192`, and will default to `2048` if not specified. If
|
||||
// `algorithm` is set to `ECDSA`, valid values are `256`, `384`
|
||||
// or `521`, and will default to `256` if not specified. If
|
||||
// `algorithm` is set to `Ed25519`, Size is ignored. No other
|
||||
// values are allowed.
|
||||
size?: int
|
||||
}
|
||||
|
||||
// How long before the currently issued certificate's expiry
|
||||
// cert-manager should renew the certificate. For example, if a
|
||||
// certificate is valid for 60 minutes, and `renewBefore=10m`,
|
||||
// cert-manager will begin to attempt to renew the certificate 50
|
||||
// minutes after it was issued (i.e. when there are 10 minutes
|
||||
// remaining until the certificate is no longer valid).
|
||||
// NOTE: The actual lifetime of the issued certificate is used to
|
||||
// determine the renewal time. If an issuer returns a certificate
|
||||
// with a different lifetime than the one requested, cert-manager
|
||||
// will use the lifetime of the issued certificate.
|
||||
// If unset, this defaults to 1/3 of the issued certificate's
|
||||
// lifetime. Minimum accepted value is 5 minutes. Value must be
|
||||
// in units accepted by Go time.ParseDuration
|
||||
// https://golang.org/pkg/time/#ParseDuration.
|
||||
renewBefore?: string
|
||||
|
||||
// The maximum number of CertificateRequest revisions that are
|
||||
// maintained in the Certificate's history. Each revision
|
||||
// represents a single `CertificateRequest` created by this
|
||||
// Certificate, either when it was created, renewed, or Spec was
|
||||
// changed. Revisions will be removed by oldest first if the
|
||||
// number of revisions exceeds this number.
|
||||
// If set, revisionHistoryLimit must be a value of `1` or greater.
|
||||
// If unset (`nil`), revisions will not be garbage collected.
|
||||
// Default value is `nil`.
|
||||
revisionHistoryLimit?: int
|
||||
|
||||
// Name of the Secret resource that will be automatically created
|
||||
// and managed by this Certificate resource. It will be populated
|
||||
// with a private key and certificate, signed by the denoted
|
||||
// issuer. The Secret resource lives in the same namespace as the
|
||||
// Certificate resource.
|
||||
secretName: string
|
||||
|
||||
// Defines annotations and labels to be copied to the
|
||||
// Certificate's Secret. Labels and annotations on the Secret
|
||||
// will be changed as they appear on the SecretTemplate when
|
||||
// added or removed. SecretTemplate annotations are added in
|
||||
// conjunction with, and cannot overwrite, the base set of
|
||||
// annotations cert-manager sets on the Certificate's Secret.
|
||||
secretTemplate?: {
|
||||
// Annotations is a key value map to be copied to the target
|
||||
// Kubernetes Secret.
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
|
||||
// Labels is a key value map to be copied to the target Kubernetes
|
||||
// Secret.
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// Requested set of X509 certificate subject attributes. More
|
||||
// info:
|
||||
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6
|
||||
// The common name attribute is specified separately in the
|
||||
// `commonName` field. Cannot be set if the `literalSubject`
|
||||
// field is set.
|
||||
subject?: {
|
||||
// Countries to be used on the Certificate.
|
||||
countries?: [...string]
|
||||
|
||||
// Cities to be used on the Certificate.
|
||||
localities?: [...string]
|
||||
|
||||
// Organizational Units to be used on the Certificate.
|
||||
organizationalUnits?: [...string]
|
||||
|
||||
// Organizations to be used on the Certificate.
|
||||
organizations?: [...string]
|
||||
|
||||
// Postal codes to be used on the Certificate.
|
||||
postalCodes?: [...string]
|
||||
|
||||
// State/Provinces to be used on the Certificate.
|
||||
provinces?: [...string]
|
||||
|
||||
// Serial number to be used on the Certificate.
|
||||
serialNumber?: string
|
||||
|
||||
// Street addresses to be used on the Certificate.
|
||||
streetAddresses?: [...string]
|
||||
}
|
||||
|
||||
// Requested URI subject alternative names.
|
||||
uris?: [...string]
|
||||
|
||||
// Requested key usages and extended key usages. These usages are
|
||||
// used to set the `usages` field on the created
|
||||
// CertificateRequest resources. If `encodeUsagesInRequest` is
|
||||
// unset or set to `true`, the usages will additionally be
|
||||
// encoded in the `request` field which contains the CSR blob.
|
||||
// If unset, defaults to `digital signature` and `key
|
||||
// encipherment`.
|
||||
usages?: [..."signing" | "digital signature" | "content commitment" | "key encipherment" | "key agreement" | "data encipherment" | "cert sign" | "crl sign" | "encipher only" | "decipher only" | "any" | "server auth" | "client auth" | "code signing" | "email protection" | "s/mime" | "ipsec end system" | "ipsec tunnel" | "ipsec user" | "timestamping" | "ocsp signing" | "microsoft sgc" | "netscape sgc"]
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Code generated by timoni. DO NOT EDIT.
|
||||
|
||||
//timoni:generate timoni vendor crd -f /home/jeff/workspace/holos-run/holos-infra/deploy/clusters/k2/components/prod-mesh-certmanager/prod-mesh-certmanager.gen.yaml
|
||||
|
||||
package v1
|
||||
|
||||
import "strings"
|
||||
|
||||
// A CertificateRequest is used to request a signed certificate
|
||||
// from one of the configured issuers.
|
||||
// All fields within the CertificateRequest's `spec` are immutable
|
||||
// after creation. A CertificateRequest will either succeed or
|
||||
// fail, as denoted by its `Ready` status condition and its
|
||||
// `status.failureTime` field.
|
||||
// A CertificateRequest is a one-shot resource, meaning it
|
||||
// represents a single point in time request for a certificate
|
||||
// and cannot be re-used.
|
||||
#CertificateRequest: {
|
||||
// APIVersion defines the versioned schema of this representation
|
||||
// of an object. Servers should convert recognized schemas to the
|
||||
// latest internal value, and may reject unrecognized values.
|
||||
// More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
apiVersion: "cert-manager.io/v1"
|
||||
|
||||
// Kind is a string value representing the REST resource this
|
||||
// object represents. Servers may infer this from the endpoint
|
||||
// the client submits requests to. Cannot be updated. In
|
||||
// CamelCase. More info:
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
kind: "CertificateRequest"
|
||||
metadata!: {
|
||||
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
|
||||
string
|
||||
}
|
||||
labels?: {
|
||||
[string]: string
|
||||
}
|
||||
annotations?: {
|
||||
[string]: string
|
||||
}
|
||||
}
|
||||
|
||||
// Specification of the desired state of the CertificateRequest
|
||||
// resource.
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
spec!: #CertificateRequestSpec
|
||||
}
|
||||
|
||||
// Specification of the desired state of the CertificateRequest
|
||||
// resource.
|
||||
// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
#CertificateRequestSpec: {
|
||||
// Requested 'duration' (i.e. lifetime) of the Certificate. Note
|
||||
// that the issuer may choose to ignore the requested duration,
|
||||
// just like any other requested attribute.
|
||||
duration?: string
|
||||
|
||||
// Extra contains extra attributes of the user that created the
|
||||
// CertificateRequest. Populated by the cert-manager webhook on
|
||||
// creation and immutable.
|
||||
extra?: {
|
||||
[string]: [...string]
|
||||
}
|
||||
|
||||
// Groups contains group membership of the user that created the
|
||||
// CertificateRequest. Populated by the cert-manager webhook on
|
||||
// creation and immutable.
|
||||
groups?: [...string]
|
||||
|
||||
// Requested basic constraints isCA value. Note that the issuer
|
||||
// may choose to ignore the requested isCA value, just like any
|
||||
// other requested attribute.
|
||||
// NOTE: If the CSR in the `Request` field has a BasicConstraints
|
||||
// extension, it must have the same isCA value as specified here.
|
||||
// If true, this will automatically add the `cert sign` usage to
|
||||
// the list of requested `usages`.
|
||||
isCA?: bool
|
||||
|
||||
// Reference to the issuer responsible for issuing the
|
||||
// certificate. If the issuer is namespace-scoped, it must be in
|
||||
// the same namespace as the Certificate. If the issuer is
|
||||
// cluster-scoped, it can be used from any namespace.
|
||||
// The `name` field of the reference must always be specified.
|
||||
issuerRef: {
|
||||
// Group of the resource being referred to.
|
||||
group?: string
|
||||
|
||||
// Kind of the resource being referred to.
|
||||
kind?: string
|
||||
|
||||
// Name of the resource being referred to.
|
||||
name: string
|
||||
}
|
||||
|
||||
// The PEM-encoded X.509 certificate signing request to be
|
||||
// submitted to the issuer for signing.
|
||||
// If the CSR has a BasicConstraints extension, its isCA attribute
|
||||
// must match the `isCA` value of this CertificateRequest. If the
|
||||
// CSR has a KeyUsage extension, its key usages must match the
|
||||
// key usages in the `usages` field of this CertificateRequest.
|
||||
// If the CSR has a ExtKeyUsage extension, its extended key
|
||||
// usages must match the extended key usages in the `usages`
|
||||
// field of this CertificateRequest.
|
||||
request: string
|
||||
|
||||
// UID contains the uid of the user that created the
|
||||
// CertificateRequest. Populated by the cert-manager webhook on
|
||||
// creation and immutable.
|
||||
uid?: string
|
||||
|
||||
// Requested key usages and extended key usages.
|
||||
// NOTE: If the CSR in the `Request` field has uses the KeyUsage
|
||||
// or ExtKeyUsage extension, these extensions must have the same
|
||||
// values as specified here without any additional values.
|
||||
// If unset, defaults to `digital signature` and `key
|
||||
// encipherment`.
|
||||
usages?: [..."signing" | "digital signature" | "content commitment" | "key encipherment" | "key agreement" | "data encipherment" | "cert sign" | "crl sign" | "encipher only" | "decipher only" | "any" | "server auth" | "client auth" | "code signing" | "email protection" | "s/mime" | "ipsec end system" | "ipsec tunnel" | "ipsec user" | "timestamping" | "ocsp signing" | "microsoft sgc" | "netscape sgc"]
|
||||
|
||||
// Username contains the name of the user that created the
|
||||
// CertificateRequest. Populated by the cert-manager webhook on
|
||||
// creation and immutable.
|
||||
username?: string
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user