Compare commits

...

105 Commits

Author SHA1 Message Date
Jeff McCune
8bc7804a9c Merge pull request #190 from holos-run/jeff/189-reference-docs
(#189) v1alpha2 API for reference docs
2024-06-30 15:07:46 -07:00
Jeff McCune
a39807a858 (#189) go mod tidy 2024-06-30 15:04:39 -07:00
Jeff McCune
5170650760 (#189) Remove yaml tags for v1alpha2.
Unnecessary, json tags are sufficient for both yaml and json.
2024-06-30 14:50:50 -07:00
Jeff McCune
1d81b3c3b4 (#189) Clarify documentation of v1alpha2
Focusing on the purpose of #APIObjects
2024-06-30 14:36:04 -07:00
Jeff McCune
33970dafe8 (#189) Version 0.85.0 v1alpha2 2024-06-30 10:27:48 -07:00
Jeff McCune
faa46c54d8 (#189) Do not write empty files with gitops results
Previosly, the holos component Results for each ArgoCD Application
resource managed as part of each BuildPlan results in an empty file
being written for the empty list of k8s api objects.

This patch fixes the problem by skipping writing the accumulated output
of API objects with the Result metadata.name starts with `gitops/`.

This is kind of a hack, but it works well enough for now.
2024-06-30 10:15:28 -07:00
Jeff McCune
42509a34cf (#189) Fix the gitops Application component name
Previously components appeared to be duplicated, it was not clear to the
user one build plan results in two components: one for the k8s yaml and
one for the gitops argocd Application resource.

```
❯ holos render component --cluster-name aws1 components/login/zitadel-server
9:27AM INF result.go:195 wrote deploy file version=0.84.1 path=deploy/clusters/aws1/gitops/zitadel-server.application.gen.yaml bytes=338
9:27AM INF render.go:92 rendered zitadel-server version=0.84.1 cluster=aws1 name=zitadel-server status=ok action=rendered
9:27AM INF render.go:92 rendered zitadel-server version=0.84.1 cluster=aws1 name=zitadel-server status=ok action=rendered
```

This patch prefixes the ArgoCD Application resource, which is
implemented as a separate HolosComponent in the same BuildPlan.  The
result is more clear about what is going on:

```
❯ holos render component --cluster-name aws1 components/login/zitadel-server
9:39AM INF result.go:195 wrote deploy file version=0.84.1 path=deploy/clusters/aws1/gitops/zitadel-server.application.gen.yaml bytes=338
9:39AM INF render.go:92 rendered gitops/zitadel-server version=0.84.1 cluster=aws1 name=gitops/zitadel-server status=ok action=rendered
9:39AM INF render.go:92 rendered zitadel-server version=0.84.1 cluster=aws1 name=zitadel-server status=ok action=rendered
```
2024-06-30 09:37:14 -07:00
Jeff McCune
ef369d4860 (#189) Format cue code with make fmt
Previously the internal cue code was not formatted properly.  This patch
updates `make fmt` to automatically format the embedded internal
platforms.
2024-06-30 09:35:25 -07:00
Jeff McCune
747ed3462a (#189) Fix Helm + Kustomize post renderer for v1alpha2
Previously the `login/zitadel-server` component failed to render with
the following error.  This is a result of the kustomize config fileds
moving down one level to the `kustomize` field in v1alpha2 relative to
`v1alpha`.

```
spec.components.helmChartList.0.kustomizeFiles: field not allowed:
    ./buildplan.cue:106:9
    ./buildplan.cue:106:27
    ./buildplan.cue:118:3
    ./buildplan.cue:124:4
    ./buildplan.cue:125:4
    ./buildplan.cue:126:5
    ./buildplan.cue:162:10
    ./buildplan.cue:165:37
    ./buildplan.cue:206:13
    ./components/login/zitadel-server/zitadel.cue:9:1
    ./components/login/zitadel-server/zitadel.cue:18:9
    ./components/login/zitadel-server/zitadel.cue:19:9
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:31:8
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:36:15
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:19
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:22
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:48:18
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:12:13
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:13:2
spec.components.helmChartList.0.resourcesFile: field not allowed:
    ./buildplan.cue:106:9
    ./buildplan.cue:106:27
    ./buildplan.cue:118:3
    ./buildplan.cue:122:4
    ./buildplan.cue:125:4
    ./buildplan.cue:125:43
    ./buildplan.cue:162:10
    ./buildplan.cue:165:37
    ./buildplan.cue:206:13
    ./components/login/zitadel-server/zitadel.cue:9:1
    ./components/login/zitadel-server/zitadel.cue:18:9
    ./components/login/zitadel-server/zitadel.cue:19:9
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:31:8
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:36:15
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:19
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:22
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:48:18
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:12:13
    ./cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:13:2
_PlatformConfig: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./buildplan.cue:232:21
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./components/login/login.cue:6:18
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./components/login/zitadel.cue:8:18
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:61:17
    ./platform.cue:60:19
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:62:17
    ./platform.cue:60:19
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:79:17
    ./platform.cue:78:19
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:80:17
    ./platform.cue:78:19
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:100:25
    ./schema.cue:14:44
_PlatformConfig: invalid interpolation: invalid interpolation: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./platform.cue:102:22
    ./platform.cue:100:25
    ./schema.cue:14:44
_PlatformConfig: error in call to encoding/json.Unmarshal: json: invalid JSON:
    ./schema.cue:14:44
```

With this patch the component renders without any further modification:

```
❯ holos render component --cluster-name aws1 components/login/zitadel-server
9:24AM INF result.go:195 wrote deploy file version=0.84.1 path=deploy/clusters/aws1/gitops/zitadel-server.application.gen.yaml bytes=338
9:24AM INF render.go:92 rendered zitadel-server version=0.84.1 cluster=aws1 name=zitadel-server status=ok action=rendered
9:24AM INF render.go:92 rendered zitadel-server version=0.84.1 cluster=aws1 name=zitadel-server status=ok action=rendered
```
2024-06-30 09:23:01 -07:00
Jeff McCune
1fb1798f60 (#189) Make HolosComponent Metadata Namespace optional
Previously a metadata.namespace value was required for all holos
components.  This is a problem because not all resources require a
namespace, for example producing the ArgoCD Application resource for
each build plan does not need a namespace defined, particularly when
managing only CRDs.

With this patch we get pretty far:

```
❯ holos generate platform holos
9:14AM INF platform.go:79 wrote platform.metadata.json version=0.84.1 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc path=/Users/jeff/Holos/holos-infra/saas2/platform.metadata.json
9:14AM INF platform.go:91 generated platform holos version=0.84.1 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc path=/Users/jeff/Holos/holos-infra/saas2

❯ time holos render platform --concurrency 1 ./platform
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/eso-creds-manager cluster=management num=1 total=73 duration=212.546542ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/cert-letsencrypt cluster=management num=2 total=73 duration=110.363875ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/certificates cluster=management num=3 total=73 duration=154.642541ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/login/zitadel-certs cluster=management num=4 total=73 duration=115.132041ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/ecr-creds-manager cluster=management num=5 total=73 duration=162.559542ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/eks-pod-identity-webhook cluster=management num=6 total=73 duration=135.03ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/crossplane/crds cluster=management num=7 total=73 duration=296.536833ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/crossplane/controller cluster=management num=8 total=73 duration=146.730667ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/backstage/management/certs cluster=management num=9 total=73 duration=117.42625ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/external-secrets cluster=aws1 num=10 total=73 duration=170.574458ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/eso-creds-refresher cluster=aws1 num=11 total=73 duration=161.188625ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/secretstores cluster=aws1 num=12 total=73 duration=153.708458ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/ecr-creds-refresher cluster=aws1 num=13 total=73 duration=130.369166ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/gateway-api cluster=aws1 num=14 total=73 duration=2.078997458s
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/base cluster=aws1 num=15 total=73 duration=145.869084ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/mesh/cni cluster=aws1 num=16 total=73 duration=142.113125ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/mesh/istiod cluster=aws1 num=17 total=73 duration=155.186375ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/mesh/gateway cluster=aws1 num=18 total=73 duration=137.8775ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/mesh/httpbin/backend cluster=aws1 num=19 total=73 duration=116.537458ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/istio/mesh/httpbin/routes cluster=aws1 num=20 total=73 duration=122.709875ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/pgo/crds cluster=aws1 num=21 total=73 duration=271.561666ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/pgo/controller cluster=aws1 num=22 total=73 duration=143.880292ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/login/zitadel-secrets cluster=aws1 num=23 total=73 duration=116.962167ms
9:14AM INF platform.go:52 ok render component version=0.84.1 path=components/login/zitadel-database cluster=aws1 num=24 total=73 duration=121.315875ms
9:14AM ERR could not execute version=0.84.1 code=unknown err="could not build /Users/jeff/Holos/holos-infra/saas2/components/login/zitadel-server: spec.components.helmChartList.0.resourcesFile: field not allowed" loc=builder.go:166
spec.components.helmChartList.0.resourcesFile: field not allowed:
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:106:9
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:106:27
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:118:3
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:122:4
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:125:4
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:125:43
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:162:10
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:165:37
    /Users/jeff/Holos/holos-infra/saas2/buildplan.cue:206:13
    /Users/jeff/Holos/holos-infra/saas2/components/login/zitadel-server/zitadel.cue:9:1
    /Users/jeff/Holos/holos-infra/saas2/components/login/zitadel-server/zitadel.cue:18:9
    /Users/jeff/Holos/holos-infra/saas2/components/login/zitadel-server/zitadel.cue:19:9
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:31:8
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:36:15
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:19
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:42:22
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:48:18
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:12:13
    /Users/jeff/Holos/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:13:2
9:14AM ERR could not execute version=0.84.1 code=unknown err="could not render component: exit status 1" loc=platform.go:48
holos render platform --concurrency 1 ./platform  6.62s user 1.22s system 133% cpu 5.878 total
```
2024-06-30 09:14:52 -07:00
Jeff McCune
accf80200f (#189) Fix pod-identity-webhook Helm chart for v1alpha2
The pod identity webhook component fails to render with v1alpha2.  This
patch fixes the problem by providing concrete values for enableHooks and
the namespace of the helm chart holos component.

The namespace is mainly necessary to render the ArgoCD Application
resource along side the helm chart output.
2024-06-30 08:18:58 -07:00
Jeff McCune
4522ee1d4e (#189) Working eso-creds-manager with v1alpha2
With this patch the eso-creds-manager component renders correctly.  This
is a `#Kubernetes` type build plan which uses the
spec.components.resources map to manage resources.

The only issue was needing to provide the namespace to the nested holos
component inside the BuildPlan.

The ArgoCD Application resource moves to the DeployFiles field of a
separate holos component in the same build plan at
spec.components.resources.argocd.  For this reason a separate Result
object is no longer necessary inside of the Holos cli for the purpose of
managing Flux or ArgoCD gitops.  The CUE code can simply inline whatever
gitops resources it wants and the holos cli will write the files
relative to the cluster specific deploy directory.

Result:

```
❯ holos render component --cluster-name management components/eso-creds-manager
2:55PM INF result.go:195 wrote deploy file version=0.84.1 path=deploy/clusters/management/gitops/eso-creds-manager.application.gen.yaml bytes=350
2:55PM INF render.go:92 rendered eso-creds-manager version=0.84.1 cluster=management name=eso-creds-manager status=ok action=rendered
```
2024-06-29 14:55:53 -07:00
Jeff McCune
313ebc6817 (#189) README 2024-06-29 08:04:51 -07:00
Jeff McCune
e0f439515f (#189) Fix holos render platform for v1alpha2
Previously holos render platform failed for the holos platform.  The issue was
caused by the deployFiles field moving from the BuildPlan down to
HolosComponent.

This patch fixes the problem by placing the ArgoCD Application resource into a
separate Resources entry of the BuildPlan.  The sole purpose of this additional
entry in the Resources map is to produce the Application resource along side
any other components which are part of the build plan.
2024-06-29 07:32:57 -07:00
Jeff McCune
caa7560ab9 (#189) Fix Helm.Chart.namespace: field not allowed
Fixes:

```
4:19PM ERR could not execute version=0.84.1 code=unknown err="could not build /home/jeff/workspace/holos-run/holos-infra/saas2/platform: #Helm.Chart.namespace: field not allowed" loc=platform.go:52
    /home/jeff/workspace/holos-run/holos-infra/saas2/buildplan.cue:106:9
    /home/jeff/workspace/holos-run/holos-infra/saas2/buildplan.cue:108:3
    /home/jeff/workspace/holos-run/holos-infra/saas2/buildplan.cue:118:3
    /home/jeff/workspace/holos-run/holos-infra/saas2/buildplan.cue:118:43
    /home/jeff/workspace/holos-run/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/buildplan_go_gen.cue:48:18
    /home/jeff/workspace/holos-run/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:12:13
    /home/jeff/workspace/holos-run/holos-infra/saas2/cue.mod/gen/github.com/holos-run/holos/api/core/v1alpha2/helm_go_gen.cue:13:2
```
2024-06-28 16:21:13 -07:00
Jeff McCune
bbcf280da7 (#189) Refactor v1alpha2 API
Previously methods were defined on the API objects in the v1alpha1 API.
The API should be data structures only.  This patch refactors the
methods responsible for orchestrating the build plan to pull them into
the internal render package.

The result is the API is cleaner and has no methods.  The render package
has corresponding data structures which simply wrap around the API
structure and implement the methods to render and return the result to
the CLI.

This commit compiles, but it has not been tested at all.  It's almost
surely broken completely.
2024-06-28 16:16:12 -07:00
Jeff McCune
6d2daacb7b (#189) Split api into meta and core groups
Previously in v1alpha1, all Holos structs are located in the same
package.  This makes it difficult to focus on only the structs necessary
to transfer configuration data from CUE to the `holos` cli.

This patch splits the structs into `meta` and `core` where the core
package holds the structs end users should refer to and focus on.  Only
the Platform resource is in core now, but other BuildPlan types will be
added shortly.
2024-06-28 13:02:44 -07:00
Jeff McCune
62f96a2d6c (#189) Add Go Documentation Server
Run it with:

    godoc -http=:6060
2024-06-28 12:42:34 -07:00
Jeff McCune
50f414d520 (#189) Platform v1alpha2
This patch moves the top level Platform API resource to v1alpha2 so it's
well documented using go docs.
2024-06-28 12:33:45 -07:00
Jeff McCune
882f3894f3 (#189) Clean up unused packages 2024-06-28 10:04:38 -07:00
Jeff McCune
30ddde7b49 (maint) Add make image to make help
Previously it wasn't clear how to build the image, wasn't showing up in
make help.
2024-06-24 20:48:47 -07:00
Jeff McCune
5cced6fb51 Version 0.84.0 2024-06-24 20:40:00 -07:00
Jeff McCune
a82ebf43b6 Merge pull request #187 from holos-run/jeff/180-backstage-component
(#180) Configure GitHub Apps Discovery
2024-06-24 20:38:17 -07:00
Jeff McCune
ebb6d6205a (#180) Configure GitHub Apps Discovery
Previously Backstage was not configured to integrate with GitHub.  The
integration is necessary for Backstage to automatically discover
resources in a GitHub organization and import them into the Catalog.

This patch adds a new platform model form field and section for the
primary GitHub organization name of the platform.  Additional GitHub
organizations can be added in the future, Backstage supports them.

The result is Backstage automatically scans public and private
repositories and adds the information in `catalog-info.yaml` to the UI.
2024-06-24 20:35:20 -07:00
Jeff McCune
58950c469a (#180) Manage default-istio ServiceAccount
Previosly the gateway ArogCD Application resource is out of sync because
the `default-istio` `ServiceAccount` is not in the git repository
source.  Argo would prune the service account on sync which is a problem.

This patch manages the service account so the Application can be synced
properly.
2024-06-13 06:04:10 -07:00
Jeff McCune
0eebdaf0c7 (#180) Fix authpolicy component after generate
Previously the holos render platform command fails with the following
error when giving a demo after the generate platform step.

This patch updates the internal generated holos platform to the latest
version.

Running through the demo is successful now.

```
holos logout
holos login
holos register user
holos generate platform holos
holos pull platform config .
holos render platform ./platform
```
2024-06-13 05:51:47 -07:00
Jeff McCune
54e2f28f4c (#179) Double check if the error group is done.
I'm not sure if we should check in the loop, in the go routine, or in
both places.  Double check in both cases just to be sure we're not doing
extra unnecessary work.
2024-06-06 15:51:16 -07:00
Jeff McCune
d4d50ef12b (#179) Use errorgroup SetLimit to limit concurrency
Previously a channel was used to limit concurrency.  This is more
difficult to read and comprehend than the inbuilt errorgroup.SetLimit
functionality.

This patch uses `errgroup.`[Group.SetLimit()][1] to limit concurrency,
avoid leaking go routines, and avoid unnecessary work.

[1]: https://pkg.go.dev/golang.org/x/sync/errgroup#Group.SetLimit
2024-06-06 15:23:49 -07:00
Jeff McCune
075f2b16a4 Merge pull request #179 from holos-run:nate/concurrency
Add concurrency to 'holos render platform'
2024-06-06 15:10:50 -07:00
Nate McCurdy
6f8008a53c Add concurrency to 'holos render platform'
This adds concurrency to the 'holos render platform' command so platform
components are rendered in less time than before.

Default concurrency is set to `min(runtime.NumCPU(), 8)`, which is the
lesser of 8 or the number of CPU cores. In testing, I found that past 8,
there are diminishing or negative returns due to memory usage or
rendering each component.

In practice, this reduced rendering of the saas platform components from
~90s to ~28s on my 12-core macbook pro.

This also changes the key name of the Helm Chart's version in log lines
from `version` to `chart_version` since `version` already exists and
shows the Holos CLI version.
2024-06-06 15:04:55 -07:00
Jeff McCune
0618b52bae (#181) Add AuthorizationPolicy resources for admin interfaces
Previously, when a user registered and logged into the holos app server,
they were able to reach admin interfaces like
https://argocd.admin.example.com

This patch adds AuthorizationPolicy resources governing the whole
cluster.  Users with the prod-cluster-{admin,edit,view} roles may access
admin services like argocd.

Users without these roles are blocked with RBAC: access denied.

In ZITADEL, the Holos Platform project is granted to the CIAM
organization without granting the prod-cluster-* roles, so there's no
possible way a CIAM user account can have these roles.
2024-06-06 14:57:48 -07:00
Jeff McCune
f1951c5db3 (#178) Add holos push platform model command
Previously there wasn't a good way to populate the platform model in the
database after building a new instance of holos server.

With this patch, the process to reset clean is:

```
export HOLOS_SERVER=https://dev.app.holos.run:443
grpcurl -H "x-oidc-id-token: $(holos token)" ${HOLOS_SERVER##*/} holos.user.v1alpha1.SystemService.DropTables
grpcurl -H "x-oidc-id-token: $(holos token)" ${HOLOS_SERVER##*/} holos.system.v1alpha1.SystemService.SeedDatabase
```

Then populate the form and model:

```
holos push platform form .
holos push platform model .
```

The `platform.config.json` file stored in version control is pushed to
the holos server and stored in the database.  This makes it nice and
easy to reset entirely, or move to another service url.
2024-06-05 15:38:55 -07:00
Jeff McCune
dad12acd8d (#178) Seed the Holos Platform itself
Previously this would have needed to be created in pgAdmin.
2024-06-05 14:17:31 -07:00
Jeff McCune
a4503e076f (#178) Add make image task to push the container image
Previously there wasn't an easy way to make the container image and
publish it.  This adds a simple `make image` task to build and push the
image.
2024-06-05 14:03:31 -07:00
Jeff McCune
09ddd339b8 (#178) Update user ids for SeedDatabase rpc
Need them to match the new login issuer.
2024-06-05 13:57:52 -07:00
Jeff McCune
bc94f4b6b8 (#178) Login to https://login.holos.run
Previously the default oidc issuer was to one of the kubernetes clusters
running in my basement.  This patch changes the issuer to the production
ready issuer running in EKS.
2024-06-05 13:42:37 -07:00
Jeff McCune
564406f60f (#178) Add app.example.com HTTPRoute for holos server
Previously the holos server Service was not exposed.

This patch exposes the holos service with an HTTPRoute behind the auth
proxy.  Holos successfully authenticates the user with the
x-oidc-id-token header set by the default Gateway.

---

Add dev-holos-infra and dev-holos-app

Previously the PostgresCluster and the holos server Deployment are not
managed on the aws2 cluster.

This patch is a start, but the Deployment does not yet start.  We need
to pass an option for the oidc issuer.

---

Add namespaces and cert for prod-holos, dev-holos, jeff-holos

Previously we didn't have a place to deploy holos server.  This patch
adds a namespace, creates a Gateway listener, and binds the tls certs
for app.example.com and *.app.example.com to the listeners.

In addition, cluster specific endpoints of *.app.aws2.example.com,
*.app.aws1.example.com, etc. are created to provide dev environment
urls. For example jeff.app.aws2.example.com is my personal dev hostname.
2024-06-05 13:15:11 -07:00
Jeff McCune
7845ce62e0 (#178) Update buf with make tools
Previously go releaser was failing because buf has been updated again.
2024-06-03 11:10:42 -07:00
Jeff McCune
a1542752b7 (#178) Add ArgoCD Application resources for each build plan
Previously holos render platform ./platform did not render any GitOps
resources for Flux or ArgoCD.

This patch uses the new DeployFiles field in holos v0.83.0 to write an
Application resource for every component BuildPlan listed in the
platform.
2024-06-03 10:33:05 -07:00
Jeff McCune
7956475363 (#178) Add BuildPlan deployFiles field
Previously, each BuildPlan has no clear way to produce an ArgoCD
Application resource.  This patch provides a general solution where each
BuildPlan can provide arbitrary files as a map[string]string where the
key is the file path relative to the gitops repository `deploy/` folder.
2024-06-03 10:00:35 -07:00
Jeff McCune
004ed56591 (#178) Add ArgoCD repository credentials
Previously ArgoCD has no ssh credentials to connect to GitHub.  This
patch adds an ssh ed25519 key as a secret in the management cluster.
The secret is synced to the workload clusters using an ExternalSecret
with the proper label for ArgoCD to find and load it for use with any
application that references the Git URL.
2024-06-02 15:58:35 -07:00
Jeff McCune
d497df3c27 (#178) Add ArgoCD RBAC Policy
Previously a logged in user could not modify anything in ArgoCD.  With
this patch users who have been granted the prod-cluster-admin role in
ZITADEL are granted the admin role in ArgoCD.
2024-06-02 15:07:27 -07:00
Jeff McCune
3a8d46234f (#178) Add ArgoCD
Previously ArgoCD was present in the platform configuration, but not
functional.  This patch brings ArgoCD fully up, integrated with the
service mesh, auth proxy, and SSO at
https://argocd.admin.clustername.example.com/

The upstream [helm chart][1] is used instead of the kustomize install
method.  We had existing prior art integrating the v6 helm chart with
the holos platform identity provider, so we continue with the helm
chart.

CRDs are still managed with the kustomize version.  The CRDs need to be
kept in sync.  It's possible to generate the kustomization.yaml file
from the same version value as is used by the helm chart, but we don't
for the time being.

[1]: https://github.com/argoproj/argo-helm/tree/argo-cd-7.1.1/charts/argo-cd
2024-06-02 14:35:36 -07:00
Jeff McCune
4d24dc5149 (#178) Add authpolicy component for RequestAuthentication
Previously, no RequestAuthentication or AuthorizationPolicy resources
govern the default Gateway.  This patch adds the resources and
configures the service mesh with the authproxy as an ExtAuthZ provider
for CUSTOM AuthorizationPolicy rules.

This patch also fixes a bug in the zitadel-server component where
resources from the upstream helm chart did not specify a namespace.
Kustomize is used as a post processor to force all resources into the
zitadel namespace.

Add multiple HTTPRoutes to validate http2 connection reuse

This patch adds multiple HTTPRoute resources which match
*.admin.example.com  The purpose is to validate http2 connections are
reused properly with Chrome.

With this patch no 404 no route errors are encountered when navigating
between the various httpbin{1,2,3,4} urls.

Add note backupRestore will trigger a restore

The process of configuring ZITADEL to provision from a datasource will
cause an in-place restore from S3.  This isn't a major issue, but users
should be aware data added since the most recent backup will be lost.
2024-06-02 09:41:57 -07:00
Jeff McCune
8eb7fbf7dc (#178) Move httpbin HTTPRoute resources to namespace istio-gateways
Previously, HTTPRoute resources were in the same namespace as the
backend service, httpbin in this case.  This doesn't follow the default
behavior of a Gateway listener only allowing attachment from HTTPRoute
resources in the same namespace as the Gateway.

This also complicates intercepting the authproxy path prefix and sending
it to the authproxy.  We'd need to add a ReferenceGrant in the authproxy
namespace, which seems backwards and dangerous because it would grant
the application developer the ability to route requests to all Services
in the istio-gateways namespace.

This patch enables Cluster Operators to manage the HTTPRoute resources
and direct the auth proxy path prefix of `/holos/authproxy` to the auth
proxy Service in the same namespace.

ReferenceGrant resources are used to enable the HTTPRoute backend
references.

When an application developer needs to manage their own HTTPRoute, as is
the case for ZITADEL, a label selector may be used and will override
less specific HTTPRoute hostsnames in the istio-gateways namespace.
2024-06-01 21:18:47 -07:00
Jeff McCune
ffeeb7c553 (#178) Add authproxy Deployment
With redis.  The auth proxy authenticates correctly against zitadel
running in the same cluster.  Validated by visiting
https://httpbin.admin.clustername.example.com/holos/authproxy

Visiting
https://httpbin.admin.clustername.example.com/holos/authproxy/auth
returns the id token in the response header, visible in the Chrome
network inspector.  The ID token works as expected from multiple orgs
with project grants in ZITADEL from the Holos org to the OIS org.

This patch doesn't fully implement the auth proxy feature.
AuthorizationPolicy and RequestAuthentication resources need to be
added.

Before we do so, we need to move the HTTPRoute resources into the
gateway namespace so all of the security policies are in one place and
to simplify the process of routing requests to two backends, the
authproxy and the backend server.
2024-06-01 20:12:35 -07:00
Jeff McCune
c3c174155c (#178) Add httpbin{1,2,3,4} HTTPRoutes to validate http2 connection reuse
This patch adds multiple HTTPRoute resources which match
*.admin.example.com  The purpose is to validate http2 connections are
reused properly with Chrome.

With this patch no 404 no route errors are encountered when navigating
between the various httpbin{1,2,3,4} urls.
2024-06-01 12:44:33 -07:00
Jeff McCune
2c2d2a9fd9 (#178) Add Namespaces documentation
Describe how to manage a new namespace to build a component in.
2024-06-01 09:43:32 -07:00
Jeff McCune
d692e2a6d5 (#178) Split subdomain certs into two certs
Problem:
Istio 1.22 with Gateway API and HTTPRoute is mis-routing HTTP2 requests
when the tls certificate has two dns names, for example
login.example.com and *.login.example.com.

When the user visits login.example.com and then tries to visit
other.login.example.com with Chrome, the connection is re-used and istio
returns a 404 route not found error even though there is a valid and
accepted HTTPRoute for *.login.example.com

This patch attempts to fix the problem by ensuring certificate dns names
map exactly to Gateway listeners.  When a wildcard cert is used, the
corresponding Gateway listener host field exactly matches the wildcard
cert dns name so Istio and envoy should not get confused.
2024-06-01 09:30:47 -07:00
Jeff McCune
e4cebddd0c (#178) Make aws2 the primary cluster 2024-05-31 14:01:11 -07:00
Jeff McCune
0e48537d65 (#178) Add zitadel-server component
This patch adds the ZITADEL server component, which deploys zitadel from
a helm chart.  Kustomize is used heavily to patch the output of helm to
make the configuration fit nicely with the holos platform.

With this patch the two Jobs that initialize the database and setup
ZITADEL run successfully.  The ZITADEL deployment starts successfully.

ZITADEL is accessible at https://login.example.com/ with the default
admin username of `zitadel-admin@zitadel.login.example.com` and password
`Password1!`.

Use grant.holos.run/subdomain.admin: "true" for HTTPRoute

This patch clarifies the label that grants httproute attachment for a
subdomain Gateway listener to a namespace.

Fix istio-base holos component name

Was named `base` which is the chart name, not the holos component name.
2024-05-31 13:47:03 -07:00
Jeff McCune
a461a96b9c (#178) Add ZITADEL crunchy pgo PostgresCluster
This patch adds the postgres clusters and a few console form controls to
configure how backups are taken and if the postgres cluster is
initialized from an existing backup or not.

The pgo-s3-creds file is manually created at this time.  It looks like:

    ❯ holos get secret -n zitadel pgo-s3-creds --print-key s3.conf
    [global]
    repo2-cipher-pass=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    repo2-s3-key=KKKKKKKKKKKKKKKKKKKK
    repo2-s3-key-secret=/SSSSSSS/SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
    repo3-cipher-pass=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    repo3-s3-key=KKKKKKKKKKKKKKKKKKKK
    repo3-s3-key-secret=/SSSSSSS/SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS

The s3 key and secret are credentials to read / write to the bucket.
The cipher pass is a random string for client side encryption.  Generate
it with `tr -dc A-Za-z0-9 </dev/urandom | head -c 64`
2024-05-30 11:33:00 -07:00
Jeff McCune
9524c4f7c3 (#178) Add crunchy postgres operator
Needed for ZITADEL and Holos Server.  Intended for ephemeral dev
environments, but may also try it in staging while we wait for RDS.
2024-05-29 12:03:05 -07:00
Jeff McCune
64b04d9cfd (#178) Add Gateway listener for login.example.com
This patch is foundational work for the ZITADEL login service.

This patch adds a tls certificate with names *.login.example.com and
login.example.com, a pair of listeners attached to the certificate in
the `default` Gateway, and the ExternalSecret to sync the secret from
the management cluster.

The zitadel namespace is managed and has the label
holos.run/login.grant: "true" to grant HTTPRoute attachment from the
zitadel namespace to the default Gateway in the istio-gateways
namespace.
2024-05-29 09:27:08 -07:00
Jeff McCune
b419ad8caf (#178) Add HTTPRoute for httpbin.admin.aws1.example.com
With this change, https://httpbin.admin.aws1.example.com works as
expected.

PROXY protocol is configured on the AWS load balancer and the istio
gateway.  The istio gateway logs have the correct client source ip
address and x-forwarded-for headers.

Namespaces must have the holos.run/admin.grant: "true" label in order to
attach an HTTPRoute to the admin section of the default Gateway.

The TLS certificate is working as expected and hopefully does not suffer
from the NR route not found issued encountered with the Istio Gateway
API.
2024-05-28 21:10:18 -07:00
Jeff McCune
8036c17916 (#178) Add istiod and gateway components
This patch gets the istio-ingressgateway up and running in AWS with
minimal configuration.  No authentication or authorization policies have
been migrated from previous iterations of the platform.  These will be
handled in subsequent iterations.

Connectivity to a backend service like httpbin has not yet been tested.
This will happen in a follow up as well using /httpbin path prefixes on
existing services like argocd to conserve certificate resources.
2024-05-28 14:37:25 -07:00
Jeff McCune
220d498be0 (#178) Define a #IngressCertificate
This is the standard way to issue public facing certificates.  Be aware
of the 50 cert limit per week from LetsEncrypt.  We map names to certs
1:1 to avoid http2 connection reuse issues with istio.
2024-05-28 13:15:14 -07:00
Jeff McCune
0f5b6a2d6e (#178) Add istio 1.22.0 base component 2024-05-28 13:08:34 -07:00
Jeff McCune
36369d75c7 (#178) Add argocd.admin.aws1.holos.run cert
Manage certificates on a project basis similar to how namespaces
associated with each project are managed.

Manage the Certificate resources on the management cluster in the
istio-ingress namespace so the tls certs can be synced to the workload
clusters.
2024-05-28 11:50:31 -07:00
Jeff McCune
059b8283fd (#178) Add cert-letsencrypt component for holos management cluster
The secret needs to be manually provisioned for this to work since the
management cluster does not sync secrets from any other external
cluster.
2024-05-26 09:56:46 -07:00
Jeff McCune
386eb2452a (#178) Add cert-manager to the holos platform
This patch adds cert-manager on all clusters.  On the management cluster
cert manager is scheduled on spot instances to reduce cost.
2024-05-26 09:29:15 -07:00
Jeff McCune
38e9a97fd2 (#178) Add secretstores holos platform component
The secretstores component is critical and provides the mechanism to
securely fetch Secret resources from the Management Cluster.
The holos server and configuration code stored in version control
contains only ExternalSecret references, no actual secrets.

This component adds a `default` `SecretStore` to each management
namespace which uses the `eso-reader` service account token to
authenticate to the management cluster.  This service account is limited
to reading secrets within the namespace it resides in.

For example:

```yaml
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: default
  namespace: external-secrets
spec:
  provider:
    kubernetes:
      auth:
        token:
          bearerToken:
            key: token
            name: eso-reader
      remoteNamespace: external-secrets
      server:
        caBundle: Long Base64 encoded string
        url: https://34.121.54.174
```
2024-05-25 15:02:06 -07:00
Jeff McCune
ecca40e9d5 (#178) Add holos platform eso-creds-manager
This patch adds the `eso-creds-manager` component which needs to be
applied to the management cluster prior to the `eso-creds-refreher`
component being applied to workload clusters.

The manager component configures rbac to allow the creds-refresher job
to complete.

This patch also adjusts the behavior to only create secrets for the
eso-reader account by default.

Namespaces with the label `holos.run/eso.writer=true` will also have an
eso-writer secret provisioned in their namespace, allowing secrets to be
written back to the management cluster.  This is intended for the
PushSecret resource.
2024-05-24 15:09:59 -07:00
Jeff McCune
9d08e27e31 (#178) Add cue.mod/gen/k8s.io/api/batch/v1 2024-05-23 16:33:58 -07:00
Jeff McCune
969bf5e867 (#178) Import k8s rbac api
cue get go k8s.io/api/rbac/v1beta1
cue get go k8s.io/api/rbac/v1
2024-05-23 16:26:24 -07:00
Jeff McCune
3b5f28f4df (#178) Fix holos generate writing executable files
Adhere to the umask to allow group writable or world writable, but do
not set the execute bit.
2024-05-23 11:37:04 -07:00
Jeff McCune
df5619f988 (#178) Add ArgoCD schematic and component to holos
Add the ArgoCD component which is a good example of how to wrap a plain
kustomize kustomization.yaml file with Holos.
2024-05-23 11:18:29 -07:00
Jeff McCune
a6d8383176 (#178) Do not write flux kustomization if empty
If the holos component returns no data for the flux kustomization, don't
bother writing an useless empty file.
2024-05-23 10:56:08 -07:00
Jeff McCune
dbc7e374cd (#178) Update buf 2024-05-23 09:37:46 -07:00
Jeff McCune
d81729857b (#178) v0.81.2 for holos
Use v0.81.2 to build out the holos platform.  Once we have the
components structured fairly well we can circle back around and copy the
components to schematics.  There's a bit of friction regenerating the
platform from schematic each time.
2024-05-23 09:14:27 -07:00
Jeff McCune
d3d8a7b73c (#178) Shape _Namespaces to corev1.#Namespace
Eliminate the need for a for loop by having _Namespaces be a struct of
name to k8s.io/api/core/v1.#Namespace
2024-05-23 09:12:08 -07:00
Jeff McCune
d9e6776b95 (#178) npm upgrade 2024-05-23 06:41:10 -07:00
Jeff McCune
bde98faffa (#178) Use private fields to store data
Using CUE definitions like #Platform to hold data is confusing.  Clarify
the use of fields, definitions like #Platform define the shape (schema)
of the data while private fields like _Platform represent and hold the
data.
2024-05-23 06:38:52 -07:00
Jeff McCune
c2847554e0 (#178) Add namespaces to holos platform 2024-05-22 17:04:47 -07:00
Jeff McCune
9411a65dd8 (#178) Add namespaces component schematic
The first thing most platforms need to do is come up with a strategy for
managing namespaces across multiple clusters.

This patch defines #Namespaces in the holos platform and adds a
namespaces component which loops over all values in the #Namespaces
struct and manages a kubernetes Namespace object.

The platform resource itself loops over all clusters in the platform to
manage all namespaces across all clusters.

From a blank slate:

```
❯ holos generate platform holos
4:26PM INF platform.go:79 wrote platform.metadata.json version=0.82.0 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc path=/home/jeff/workspace/holos-run/holos-infra/saas/platform.metadata.json
4:26PM INF platform.go:91 generated platform holos version=0.82.0 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc path=/home/jeff/workspace/holos-run/holos-infra/saas

❯ holos pull platform config .
4:26PM INF pull.go:64 pulled platform model version=0.82.0 server=https://jeff.app.dev.k2.holos.run:443 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc
4:26PM INF pull.go:75 saved platform config version=0.82.0 server=https://jeff.app.dev.k2.holos.run:443 platform_id=018fa1cf-a609-7463-aa6e-fa53bfded1dc path=platform.config.json

❯ (cd components && holos generate component cue namespaces)
4:26PM INF component.go:147 generated component version=0.82.0 name=namespaces path=/home/jeff/workspace/holos-run/holos-infra/saas/components/namespaces

❯ holos render platform ./platform/
4:26PM INF platform.go:29 ok render component version=0.82.0 path=components/namespaces cluster=management num=1 total=2 duration=464.055541ms
4:26PM INF platform.go:29 ok render component version=0.82.0 path=components/namespaces cluster=aws1 num=2 total=2 duration=467.978499ms
```

The result:

```sh
cat deploy/clusters/management/components/namespaces/namespaces.gen.yaml
```

```yaml
---
metadata:
  name: holos
  labels:
    kubernetes.io/metadata.name: holos
kind: Namespace
apiVersion: v1
```
2024-05-22 16:32:59 -07:00
Jeff McCune
9c1165e77e (#178) Save platform.config.json with multiple lines 2024-05-22 14:10:28 -07:00
Jeff McCune
a02c7a4015 (#178) Fix the PlatformService CreatePlatform rpc
Without this patch the
holos.platform.v1alpha1.PlatformService.CreatePlatform doesn't work as
expected.  The Platform message is used which incorrectly requires a
client supplied id which is ignored by the server.

This patch allows the creation of a new platform by reusing the update
operation as a mutation that applies to both create and update.  Only
modifiable fields are part of the PlatformMutation message.
2024-05-22 12:39:24 -07:00
Jeff McCune
bdcde88e6f (#175) Add git describe to --version output
Much easier to track changes between releases.
2024-05-21 13:21:27 -07:00
Jeff McCune
a32b100192 (#175) Output at the end
Flip the let definitions to before their use to avoid confusing /
distracting users who are just getting started.

User feedback from Nate.
2024-05-21 13:03:22 -07:00
Jeff McCune
670d716403 (#175) Add podinfo oci example
This patch adds to more example helm chart based components.  podinfo
installs as a normal https repository based helm chart.  podinfo-oci
uses an oci image to manage the helm chart.

The way holos handls OCI images is subtle, so it's good to include an
example right out of the chute.  Github actions uses OCI images for
example.
2024-05-21 12:36:45 -07:00
Jeff McCune
bba3895f35 (#175) Add holos generate component helm command
This patch adds a schematic to generate a holos component that wraps a
helm chart.  The cert-manager chart is the current example.

Usage:

```bash
set -euo pipefail

rm -rf ~/holos/dev/bare
mkdir ~/holos/dev/bare
cd ~/holos/dev/bare

holos generate platform bare
holos pull platform config .
holos render platform ./platform/
(cd components && holos generate component helm cert-manager)
```

The chart builds:

```bash
holos build ./components/cert-manager | yq .
```

And renders:

```bash
holos render component ./components/cert-manager --cluster-name k2
find deploy -type f
```

```txt
9:41PM INF render.go:83 rendered cert-manager version=0.81.1 cluster=k2 status=ok action=rendered name=cert-manager
deploy/clusters/k2/holos/components/cert-manager-kustomization.gen.yaml
deploy/clusters/k2/components/cert-manager/cert-manager.gen.yaml
```
2024-05-21 11:05:53 -07:00
Jeff McCune
9e60ddbe85 (#175) Add holos generate component cue command
This patch adds a command to generate CUE based holos components from
examples embedded in the executable.  The examples are passed through
the go template rendering engine with values pulled from flags.

Each directory in the embedded filesystem becomes a unique command for
nice tab completion.  The `--name` flag defaults to "example" and is the
resulting component name.

A follow up patch with more flags will set the stage for a Helm
component schematic.

```
holos generate component cue minimal
```

```txt
3:07PM INF component.go:91 generated component version=0.80.2 name=example path=/home/jeff/holos/dev/bare/components/example
```
2024-05-20 15:10:54 -07:00
Jeff McCune
44334fca52 (#175) Fix lint 2024-05-20 12:39:43 -07:00
Jeff McCune
2e2ed398c6 (#175) Fix tests 2024-05-20 11:32:29 -07:00
Jeff McCune
34f2a52cb7 (#175) Add holos render platform command
Split holos render into component and platform.

This patch splits the previous `holos render` command into subcommands.
`holos render component ./path/to/component/` behaves as the previous
`holos render` command and renders an individual component.

The new `holos render platform ./path/to/platform/` subcommand makes
space to render the entire platform using the platform model pulled from
the PlatformService.

Starting with an empty directory:

```sh
holos register user
holos generate platform bare
holos pull platform config .
holos render platform ./platform/
```

```txt
10:01AM INF platform.go:29 ok render component version=0.80.2 path=components/configmap cluster=k1 num=1 total=1 duration=448.133038ms
```

The bare platform has a single component which refers to the platform
model pulled from the PlatformService:

```sh
cat deploy/clusters/mycluster/components/platform-configmap/platform-configmap.gen.yaml
```

```yaml
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: platform
  namespace: default
data:
  platform: |
    spec:
      model:
        cloud:
          providers:
            - cloudflare
        cloudflare:
          email: platform@openinfrastructure.co
        org:
          displayName: Open Infrastructure Services
          name: ois
```
2024-05-20 10:41:24 -07:00
Jeff McCune
d3888a884f (#175) go mod tidy 2024-05-20 06:32:53 -07:00
Jeff McCune
3845871368 (#175) holos pull platform config
This patch adds a subcommand to pull the data necessary to construct a
PlatformConfig DTO.  The PlatformConfig message contains all of the
fields and values necessary to build a platform and the platform
components.  This is an alternative to holos passing multiple tags to
CUE.  The PlatformConfig is marshalled and passed once.

The platform config is also stored in the local filesystem in the root
directory of the platform.  This enables repeated local building and
rendering without making an rpc call.

The build / render pipeline is expected to cache the PlatformConfig once
at the start of the pipeline using the pull subcommand.
2024-05-19 08:27:21 -07:00
Jeff McCune
a3b2d19adb (#175) Render the platform with the model
The `holos render platform` command is unimplemented.  This patch
partially implements platform rendering by fetching the platform model
from the PlatformService and providing it to CUE using a tag.

CUE returns a `kind: Platform` resource to `holos` which will eventually
process a Buildlan for each platform component listed in the Platform
spec.

For now, however, it's sufficient to have the current platform model
available to CUE.
2024-05-18 11:40:30 -07:00
Jeff McCune
e4e7cd8c47 (#175) Make holos render --cluster-name flag optional
Problem:
Rendering the whole platform doesn't need a cluster name.

Solution:
Make the flag optional, do not set the cue tag if it's empty.

Result:
Holos renders the platform resource and proceeds to the point where we
need to implement the iteration over platform components, passing the
platform model to each one and rendering the component.
2024-05-17 15:48:36 -07:00
Jeff McCune
fb22e5521b (#175) Define the Platform resource in CUE
We need to output a kind: Platform resource from cue so holos can
iterate over each build plan.  The platform resource itself should also
contain a copy of the platform model obtained from the PlatformService
so holos can easily pass the model to each BuildPlan it needs to execute
to render the full platform.

This patch lays the groundwork for the Platform resource.  A future
patch will have the holos cli obtain the platform model and inject it as
a JSON encoded string to CUE.  CUE will return the Platform resource
which is a list of references to build plans.  Holos will then iterate
over each build plan, pass the model back in, and execute the build
plan.

To illustrate where we're headed, the `cue export` step will move into
`holos` with a future patch.

```
❯ holos register user
3:34PM INF register.go:77 user version=0.80.0 email=jeff@ois.run server=https://app.dev.k2.holos.run:443 user_id=018f8839-3d74-7e39-afe9-181ad2fc8abe org_id=018f8839-3d74-7e3a-918c-b36494da0115
❯ holos generate platform bare
3:34PM INF generate.go:79 wrote platform.metadata.json version=0.80.0 platform_id=018f8839-3d74-7e3b-8cb8-77a2c124d173 path=/home/jeff/holos/dev/bare/platform.metadata.json
3:34PM INF generate.go:91 generated platform bare version=0.80.0 platform_id=018f8839-3d74-7e3b-8cb8-77a2c124d173 path=/home/jeff/holos/dev/bare
❯ holos push platform form .
3:34PM INF push.go:70 pushed: https://app.dev.k2.holos.run:443/ui/platform/018f8839-3d74-7e3b-8cb8-77a2c124d173 version=0.80.0
❯ cue export ./platform/
{
    "metadata": {
        "name": "bare",
        "labels": {},
        "annotations": {}
    },
    "spec": {
        "model": {}
    },
    "kind": "Platform",
    "apiVersion": "holos.run/v1alpha1"
}
```
2024-05-17 15:34:56 -07:00
Jeff McCune
d2ae766ae3 Merge pull request #176 from holos-run/dependabot/go_modules/github.com/docker/docker-26.0.2incompatible
Bump github.com/docker/docker from 26.0.0+incompatible to 26.0.2+incompatible
2024-05-17 11:53:44 -07:00
dependabot[bot]
c0db949729 Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.0+incompatible to 26.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.0...v26.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-17 18:52:51 +00:00
Jeff McCune
d2d4337ffd (#175) Improve url output
❯ holos push platform form .
11:49AM INF push.go:70 pushed: https://app.dev.k2.holos.run:443/ui/platform/018f87d1-7ca2-7e37-97ed-a06bcee9b442 version=0.79.0
2024-05-17 11:49:04 -07:00
Jeff McCune
b0ca04635e (#175) Update the client context when switching servers
When the holos server URL switches, we also need to update the client
context to get the correct org id.

Also improve quality of life by printing the url to the form when the
platform form is pushed to the server.

❯ holos push platform form .
11:41AM INF push.go:71 updated platform form version=0.79.0 server=https://app.dev.k2.holos.run:443 platform_id=018f87d1-7ca2-7e37-97ed-a06bcee9b442
11:41AM INF push.go:72 https://app.dev.k2.holos.run:443/ui/platform/018f87d1-7ca2-7e37-97ed-a06bcee9b442 version=0.79.0
2024-05-17 11:43:52 -07:00
Jeff McCune
198c66e6cd (#175) Fix tests
Not sure why this started failing, but it wasn't necessary.
2024-05-17 10:22:35 -07:00
Jeff McCune
24346b9a38 (#172) Deploy v0.79.0 to dev 2024-05-17 10:15:05 -07:00
Jeff McCune
0639562f1c (#175) go mod tidy 2024-05-17 10:09:40 -07:00
Jeff McCune
c1fa9cc531 (#175) Fix lint 2024-05-17 10:08:06 -07:00
Jeff McCune
18653534ad (#175) Add holos push platform form command
This sub-command renders the web app form from CUE code and updates the
form using the `holos.platform.v1alpha1.PlatformService/UpdatePlatform`
rpc method.

Example use case, starting fresh:

```
rm -rf ~/holos
mkdir ~/holos
cd ~/holos
```

Step 1: Login

```sh
holos login
```

```txt
9:53AM INF login.go:40 logged in as jeff@ois.run version=0.79.0 name="Jeff McCune" exp="2024-05-17 21:16:07 -0700 PDT" email=jeff@ois.run
```

Step 2: Register to create server side resources.

```sh
holos register user
```

```
9:52AM INF register.go:68 user version=0.79.0 email=jeff@ois.run user_id=018f826d-85a8-751d-81ee-64d0f2775b3f org_id=018f826d-85a8-751e-98dd-a6cddd9dd8f0
```

Step 3: Generate the bare platform in the local filesystem.

```sh
holos generate platform bare
```

```txt
9:52AM INF generate.go:79 wrote platform.metadata.json version=0.79.0 platform_id=018f826d-85a8-751f-96d0-0d2bf70df909 path=/home/jeff/holos/platform.metadata.json
9:52AM INF generate.go:91 generated platform bare version=0.79.0 platform_id=018f826d-85a8-751f-96d0-0d2bf70df909 path=/home/jeff/holos
```

Step 4: Push the platform form to the `holos server` web app.

```sh
holos push platform form .
```

```txt
9:52AM INF client.go:67 updated platform version=0.79.0 platform_id=018f826d-85a8-751f-96d0-0d2bf70df909 duration=73.62995ms
```

At this point the platform form is published and functions as expected
when visiting the platform web interface.
2024-05-17 09:51:36 -07:00
Jeff McCune
2b89c33067 (#175) Add holos orgid command
Makes it easier to work with grpcurl:

    grpcurl -H "x-oidc-id-token: $(holos token)" -d '{"org_id":"'$(holos orgid)'"}' ${HOLOS_SERVER##*/} holos.platform.v1alpha1.PlatformService.ListPlatforms
2024-05-16 21:11:24 -07:00
Jeff McCune
aee26d9375 (#175) Set header User-Agent: holos/0.70.0 (go1.22.2)
Previously: User-Agent: connect-go/1.16.0 (go1.22.2)
2024-05-16 20:49:06 -07:00
Jeff McCune
7b04d492ab (#175) Set http.Server ReadHeaderTimeout
Upstream connectrpc recommends it.  Refer to
https://connectrpc.com/docs/faq#stream-error
2024-05-16 20:28:31 -07:00
Jeff McCune
8abd03e165 (#175) Log x-request-id and x-b3-trace headers
This patch logs the x-request-id header which makes it straight forward
to correlate the logs with the service mesh logs.

For example, select the request id from the gateway logs by copying the
log from the holos server logs.

```sh
kubectl -n istio-ingress logs -l app=istio-ingressgateway -f \
  | grep --line-buffered '^{' \
  | jq 'select(.request_id=="'d0867115-5795-4096-942e-5ac188cdf618'")'
```

```json
{
  "upstream_local_address": "10.244.1.51:44248",
  "x_forwarded_for": "192.168.2.21",
  "authority": "jeff.app.dev.k2.holos.run:443",
  "upstream_transport_failure_reason": null,
  "connection_termination_details": null,
  "response_code": 200,
  "duration": 6,
  "response_flags": "-",
  "upstream_service_time": "5",
  "upstream_cluster": "outbound|3000||holos.jeff-holos.svc.cluster.local",
  "upstream_host": "10.244.1.249:3000",
  "user_agent": "connect-go/1.16.0 (go1.22.2)",
  "requested_server_name": "jeff.app.dev.k2.holos.run",
  "request_id": "d0867115-5795-4096-942e-5ac188cdf618",
  "start_time": "2024-05-17T03:16:37.900Z",
  "method": "POST",
  "protocol": "HTTP/2",
  "downstream_local_address": "65.102.23.41:443",
  "path": "/holos.user.v1alpha1.UserService/GetUser",
  "bytes_sent": 159,
  "downstream_remote_address": "192.168.2.21:59564",
  "response_code_details": "via_upstream",
  "bytes_received": 0,
  "route_name": "holos-api"
}
```
2024-05-16 20:14:34 -07:00
Jeff McCune
2df843bc98 (#175) Link the generated platform to holos server
When the user generates a platform, we need to know the platform ID it's
linked to in the holos server.  If there is no platform with the same
name, the `holos generate platform` command should error out.

This is necessary because the first thing we want to show is pushing an
updated form to `holos server`.  To update the web ui the CLI needs to
know the platform ID to update.

This patch modifies the generate command to obtain a list of platforms
for the org and verify the generated name matches one of the platforms
  that already exists.

A future patch could have the `generate platform` command call the
`holos.platform.v1alpha1.PlatformService.CreatePlatform` method if the
platform isn't found.

Results:

```sh
holos generate platform bare
```

```txt
4:15PM INF generate.go:77 wrote platform.metadata.json version=0.77.1 platform_id=018f826d-85a8-751f-96d0-0d2bf70df909 path=/home/jeff/holos/platform.metadata.json
4:15PM INF generate.go:89 generated platform bare version=0.77.1 platform_id=018f826d-85a8-751f-96d0-0d2bf70df909 path=/home/jeff/holos
```

```sh
cat platform.metadata.json
```

```json
{
  "id": "018f826d-85a8-751f-96d0-0d2bf70df909",
  "name": "bare",
  "display_name": "Bare Platform"
}
```
2024-05-16 16:18:38 -07:00
Jeff McCune
be4d2c29a5 (#175) Log info message when generating a platform
holos generate platform bare
    2:11PM INF generate.go:55 generated platform bare version=0.77.1 path=/home/jeff/holos
2024-05-16 14:26:51 -07:00
347 changed files with 87893 additions and 1194 deletions

8
Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM quay.io/holos-run/debian:bullseye AS final
USER root
WORKDIR /app
ADD bin bin
RUN chown -R app: /app
# Kubernetes requires the user to be numeric
USER 8192
ENTRYPOINT bin/holos server

View File

@@ -7,7 +7,7 @@ REPO_PATH=$(ORG_PATH)/$(PROJ)
VERSION := $(shell cat version/embedded/major version/embedded/minor version/embedded/patch | xargs printf "%s.%s.%s")
BIN_NAME := holos
DOCKER_REPO=quay.io/openinfrastructure/holos
DOCKER_REPO=quay.io/holos-run/holos
IMAGE_NAME=$(DOCKER_REPO)
$( shell mkdir -p bin)
@@ -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
@@ -53,6 +55,7 @@ tidy: ## Tidy go module.
.PHONY: fmt
fmt: ## Format code.
cd docs/examples && cue fmt ./...
cd internal/generate/platforms && cue fmt ./...
go fmt ./...
.PHONY: vet
@@ -61,7 +64,9 @@ 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/...
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/core/...
cd internal/generate/platforms && cue get go github.com/holos-run/holos/api/meta/...
.PHONY: rmgen
rmgen: ## Remove generated code
@@ -76,7 +81,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 +118,7 @@ snapshot: ## Go release snapshot
.PHONY: buf
buf: ## buf generate
cd service && buf mod update
cd service && buf dep update
buf generate
.PHONY: tools
@@ -121,11 +126,13 @@ tools: go-deps frontend-deps ## install tool dependencies
.PHONY: go-deps
go-deps: ## tool versions pinned in tools.go
go install cuelang.org/go/cmd/cue
go install github.com/bufbuild/buf/cmd/buf
go install github.com/fullstorydev/grpcurl/cmd/grpcurl
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install connectrpc.com/connect/cmd/protoc-gen-connect-go
go install honnef.co/go/tools/cmd/staticcheck@latest
go install honnef.co/go/tools/cmd/staticcheck
go install golang.org/x/tools/cmd/godoc
# curl https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash
.PHONY: frontend-deps
@@ -145,6 +152,11 @@ frontend: buf
cd internal/frontend/holos && ng build
touch internal/frontend/frontend.go
.PHONY: image
image: build ## Docker image build
docker build . -t ${DOCKER_REPO}:v$(shell ./bin/holos --version)
docker push ${DOCKER_REPO}:v$(shell ./bin/holos --version)
.PHONY: help
help: ## Display this help menu.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Holos

View File

@@ -0,0 +1,38 @@
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
// Label is an arbitrary unique identifier. Defined as a type for clarity and type checking.
type Label string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
type Kind string
// APIObjectMap represents the marshalled yaml representation of kubernetes api
// objects. Do not produce an APIObjectMap directly, instead use [APIObjects]
// to produce the marshalled yaml representation from CUE data, then provide the
// result to [HolosComponent].
type APIObjectMap map[Kind]map[Label]string
// APIObjects represents kubernetes api objects to apply to the api server.
// Useful to mix in resources to each HolosComponent type, for example adding an
// ExternalSecret to a HelmChart HolosComponent.
//
// Kind must be the resource kind, e.g. Deployment or Service.
//
// Label is an arbitrary internal identifier to uniquely identify the resource
// within the context of a `holos` command. Holos will never write the
// intermediate label to rendered output.
//
// Refer to [HolosComponent] which accepts an [APIObjectMap] field provided by
// [APIObjects].
type APIObjects struct {
// APIObjects represents Kubernetes API objects defined directly from CUE
// code. Useful to mix in resources, for example adding an ExternalSecret
// resource to a HelmChart HolosComponent.
APIObjects map[Kind]map[Label]structpb.Struct `json:"apiObjects"`
// APIObjectMap represents the marshalled yaml representation of APIObjects,
// useful to inspect the rendered representation of the resource which will be
// sent to the kubernetes API server.
APIObjectMap APIObjectMap `json:"apiObjectMap"`
}

View File

@@ -0,0 +1,116 @@
package v1alpha2
import (
"fmt"
"strings"
)
// FileContentMap represents a mapping of file names to file content.
type FileContentMap map[string]string
// BuildPlan represents a build plan for the holos cli to execute. A build plan
// is a set of zero or more holos components. The purpose of a BuildPlan is to
// define one or more [HolosComponent] kinds, for example a [HelmChart] or
// [KustomizeBuild].
//
// A BuildPlan usually has an additional empty [KubernetesObjects] for the
// purpose of using the [HolosComponent] DeployFiles field to deploy an ArgoCD
// or Flux gitops resource for the holos component.
type BuildPlan struct {
Kind string `json:"kind" cue:"\"BuildPlan\""`
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
Spec BuildPlanSpec `json:"spec"`
}
func (bp *BuildPlan) Validate() error {
errs := make([]string, 0, 2)
if bp.Kind != BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", BuildPlanKind, bp.Kind))
}
if bp.APIVersion != APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}
func (bp *BuildPlan) ResultCapacity() (count int) {
if bp == nil {
return 0
}
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}
type BuildPlanSpec struct {
Disabled bool `json:"disabled,omitempty"`
Components BuildPlanComponents `json:"components,omitempty"`
}
type BuildPlanComponents struct {
Resources map[string]KubernetesObjects `json:"resources,omitempty"`
KubernetesObjectsList []KubernetesObjects `json:"kubernetesObjectsList,omitempty"`
HelmChartList []HelmChart `json:"helmChartList,omitempty"`
KustomizeBuildList []KustomizeBuild `json:"kustomizeBuildList,omitempty"`
}
// HolosComponent defines the fields common to all holos component kinds. Every
// holos component kind should embed HolosComponent.
type HolosComponent struct {
// Kind is a string value representing the resource this object represents.
Kind string `json:"kind"`
// APIVersion represents the versioned schema of this representation of an object.
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
// Metadata represents data about the holos component such as the Name.
Metadata Metadata `json:"metadata"`
// APIObjectMap holds the marshalled representation of api objects. Useful to
// mix in resources to each HolosComponent type, for example adding an
// ExternalSecret to a HelmChart HolosComponent. Refer to [APIObjects].
APIObjectMap APIObjectMap `json:"apiObjectMap,omitempty"`
// DeployFiles represents file paths relative to the cluster deploy directory
// with the value representing the file content. Intended for defining the
// ArgoCD Application resource or Flux Kustomization resource from within CUE,
// but may be used to render any file related to the build plan from CUE.
DeployFiles FileContentMap `json:"deployFiles,omitempty"`
// Kustomize represents a kubectl kustomize build post-processing step.
Kustomize `json:"kustomize,omitempty"`
// Skip causes holos to take no action regarding this component.
Skip bool `json:"skip" cue:"bool | *false"`
}
// Metadata represents data about the holos component such as the Name.
type Metadata struct {
// Name represents the name of the holos component.
Name string `json:"name"`
// Namespace is the primary namespace of the holos component. A holos
// component may manage resources in multiple namespaces, in this case
// consider setting the component namespace to default.
//
// This field is optional because not all resources require a namespace,
// particularly CRD's and DeployFiles functionality.
// +optional
Namespace string `json:"namespace,omitempty"`
}
// Kustomize represents resources necessary to execute a kustomize build.
// Intended for at least two use cases:
//
// 1. Process a [KustomizeBuild] [HolosComponent] which represents raw yaml
// file resources in a holos component directory.
// 2. Post process a [HelmChart] [HolosComponent] to inject istio, patch jobs,
// add custom labels, etc...
type Kustomize struct {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
KustomizeFiles FileContentMap `json:"kustomizeFiles,omitempty"`
// ResourcesFile is the file name used for api objects in kustomization.yaml
ResourcesFile string `json:"resourcesFile,omitempty"`
}

View File

@@ -0,0 +1,11 @@
package v1alpha2
const (
APIVersion = "v1alpha2"
BuildPlanKind = "BuildPlan"
HelmChartKind = "HelmChart"
// ChartDir is the directory name created in the holos component directory to cache a chart.
ChartDir = "vendor"
// ResourcesFile is the file name used to store component output when post-processing with kustomize.
ResourcesFile = "resources.yaml"
)

49
api/core/v1alpha2/core.go Normal file
View File

@@ -0,0 +1,49 @@
// Package v1alpha2 contains the core API contract between the holos cli and cue
// configuration code. Platform designers, operators, and software developers
// use this API to write configuration in CUE which `holos` loads. The overall
// shape of the API defines imperative actions `holos` should carry out to
// render the complete yaml that represents a Platform.
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
type PlatformMetadata struct {
// Name represents the Platform name.
Name string `json:"name"`
}
// 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 {
// Kind is a string value representing the resource this object represents.
Kind string `json:"kind" cue:"\"Platform\""`
// APIVersion represents the versioned schema of this representation of an object.
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
// Metadata represents data about the object such as the Name.
Metadata PlatformMetadata `json:"metadata"`
// Spec represents the specification.
Spec PlatformSpec `json:"spec"`
}
// PlatformSpec represents the specification of a Platform. Think of a platform
// specification as a list of platform components to apply to a list of
// kubernetes clusters combined with the user-specified Platform Model.
type PlatformSpec struct {
// Model represents the platform model holos gets from from the
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
Model structpb.Struct `json:"model"`
// Components represents a list of holos components to manage.
Components []PlatformSpecComponent `json:"components"`
}
// PlatformSpecComponent represents a holos component to build or render.
type PlatformSpecComponent struct {
// Path is the path of the component relative to the platform root.
Path string `json:"path"`
// Cluster is the cluster name to provide when rendering the component.
Cluster string `json:"cluster"`
}

38
api/core/v1alpha2/helm.go Normal file
View File

@@ -0,0 +1,38 @@
package v1alpha2
// HelmChart represents a holos component which wraps around an upstream helm
// chart. Holos orchestrates helm by providing values obtained from CUE,
// renders the output using `helm template`, then post-processes the helm output
// yaml using the general functionality provided by [HolosComponent], for
// example [Kustomize] post-rendering and mixing in additional kubernetes api
// objects.
type HelmChart struct {
HolosComponent `json:",inline"`
Kind string `json:"kind" cue:"\"HelmChart\""`
// Chart represents a helm chart to manage.
Chart Chart `json:"chart"`
// ValuesContent represents the values.yaml file holos passes to the `helm
// template` command.
ValuesContent string `json:"valuesContent"`
// EnableHooks enables helm hooks when executing the `helm template` command.
EnableHooks bool `json:"enableHooks" cue:"bool | *false"`
}
// Chart represents a helm chart.
type Chart struct {
// Name represents the chart name.
Name string `json:"name"`
// Version represents the chart version.
Version string `json:"version"`
// Release represents the chart release when executing helm template.
Release string `json:"release"`
// Repository represents the repository to fetch the chart from.
Repository Repository `json:"repository,omitempty"`
}
// Repository represents a helm chart repository.
type Repository struct {
Name string `json:"name"`
URL string `json:"url"`
}

View File

@@ -0,0 +1,10 @@
package v1alpha2
const KubernetesObjectsKind = "KubernetesObjects"
// KubernetesObjects represents a [HolosComponent] composed of kubernetes api
// objects provided directly from CUE using [APIObjects].
type KubernetesObjects struct {
HolosComponent `json:",inline"`
Kind string `json:"kind" cue:"\"KubernetesObjects\""`
}

View File

@@ -0,0 +1,8 @@
package v1alpha2
// KustomizeBuild renders plain yaml files in the holos component directory
// using kubectl kustomize build.
type KustomizeBuild struct {
HolosComponent `json:",inline"`
Kind string `json:"kind" cue:"\"KustomizeBuild\""`
}

37
api/meta/v1alpha2/meta.go Normal file
View File

@@ -0,0 +1,37 @@
package v1alpha2
// TypeMeta describes an individual object in an API response or request with
// strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the resource this object represents.
Kind string `json:"kind"`
// APIVersion defines the versioned schema of this representation of an object.
APIVersion string `json:"apiVersion" cue:"string | *\"v1alpha2\""`
}
func (tm *TypeMeta) GetKind() string {
return tm.Kind
}
func (tm *TypeMeta) GetAPIVersion() string {
return tm.APIVersion
}
// Discriminator discriminates the kind of an api object.
type Discriminator interface {
// GetKind returns Kind.
GetKind() string
// GetAPIVersion returns APIVersion.
GetAPIVersion() string
}
// ObjectMeta represents metadata of a holos component object. The fields are a
// copy of upstream kubernetes api machinery but are holos objects distinct from
// kubernetes api objects.
type ObjectMeta struct {
// Name uniquely identifies the holos component instance and must be suitable as a file name.
Name string `json:"name,omitempty"`
// Namespace confines a holos component to a single namespace via kustomize if set.
Namespace string `json:"namespace,omitempty"`
}

View File

@@ -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 {

View File

@@ -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
View 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"`
}

View File

@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
"github.com/holos-run/holos"
"github.com/holos-run/holos/internal/errors"
@@ -121,6 +122,14 @@ func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePat
}
// cacheChart stores a cached copy of Chart in the chart subdirectory of path.
//
// It is assumed that the only method responsible for writing to chartDir is
// cacheChart itself.
//
// This relies on the atomicity of moving temporary directories into place on
// the same filesystem via os.Rename. If a syscall.EEXIST error occurs during
// renaming, it indicates that the cached chart already exists, which is an
// expected scenario when this function is called concurrently.
func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, chart Chart) error {
log := logger.FromContext(ctx)
@@ -156,11 +165,16 @@ func cacheChart(ctx context.Context, path holos.InstancePath, chartDir string, c
dst := filepath.Join(cachePath, item.Name())
log.DebugContext(ctx, "rename", "src", src, "dst", dst)
if err := os.Rename(src, dst); err != nil {
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
var linkErr *os.LinkError
if errors.As(err, &linkErr) && errors.Is(linkErr.Err, syscall.EEXIST) {
log.DebugContext(ctx, "cache already exists", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
} else {
return errors.Wrap(fmt.Errorf("could not rename: %w", err))
}
}
}
log.InfoContext(ctx, "cached", "chart", chart.Name, "version", chart.Version, "path", cachePath)
log.InfoContext(ctx, "cached", "chart", chart.Name, "chart_version", chart.Version, "path", cachePath)
return nil
}

View File

@@ -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"`
}

View File

@@ -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.
@@ -40,11 +44,6 @@ func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Metadata.Name+"-kustomization.gen.yaml")
}
// KustomizationContent returns the kustomization file contents to write.
func (r *Result) KustomizationContent() string {
return r.KsContent
}
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
return r.accumulatedOutput
@@ -133,6 +132,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 +155,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)

View File

@@ -10,7 +10,7 @@ func (tm *TypeMeta) GetKind() string {
}
func (tm *TypeMeta) GetAPIVersion() string {
return tm.Kind
return tm.APIVersion
}
// Discriminator is an interface to discriminate the kind api object.

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 .

View File

@@ -1 +1,2 @@
module: "github.com/holos-run/holos/docs/examples"
language: version: "v0.9.2"

View File

@@ -0,0 +1,3 @@
package holos
_platform_config: string @tag(platform_config, type=string)

View File

@@ -0,0 +1 @@
{}

View File

@@ -151,7 +151,7 @@ let OBJECTS = #APIObjects & {
loopback: #Service & {
_description: LoopbackDescription
metadata: LoopbackMetaName
spec: selector: LoopbackLabels
spec: selector: LoopbackLabels
spec: ports: [{port: 80, name: "http"}, {port: 443, name: "https"}]
}
}

View File

@@ -34,7 +34,7 @@ let OBJECTS = #APIObjects & {
containers: [
{
name: Holos
image: "271053619184.dkr.ecr.us-east-2.amazonaws.com/holos-run/holos-server/holos:v0.76.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
View 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`.

View 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 .
```

View File

@@ -0,0 +1,30 @@
# 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. Get a timestamp: `STAMP="$(date +%s)"`
7. Run the job to populate ecr creds: `kubectl create job -n holos-system --from=cronjob/ecr-creds-manager ecr-creds-manager-$STAMP`
8. Wait for the job to complete: `kubectl -n holos-system logs -l job-name=ecr-creds-manager-$STAMP -f`
9. Apply the `namespaces` component to the workload clusters
10. 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-${STAMP}`
11. Wait for the job to complete: `kubectl -n holos-system logs -l job-name=eso-creds-refresher-${STAMP}`
12. Apply the secretstores component to the workload cluster.
13. Apply any other cluster specific components which were modified by the `holos render platform ./platform` command.
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

View 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']` |

30
go.mod
View File

@@ -8,7 +8,7 @@ require (
connectrpc.com/grpcreflect v1.2.0
connectrpc.com/otelconnect v0.7.0
connectrpc.com/validate v0.1.0
cuelang.org/go v0.8.0
cuelang.org/go v0.9.2
entgo.io/ent v0.13.1
github.com/bufbuild/buf v1.30.1
github.com/choria-io/machine-room v0.0.0-20240417064836-c604da2f005e
@@ -29,8 +29,9 @@ require (
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.24.0
golang.org/x/tools v0.20.0
golang.org/x/net v0.26.0
golang.org/x/sync v0.7.0
golang.org/x/tools v0.22.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
@@ -44,9 +45,8 @@ require (
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
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 // indirect
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
@@ -86,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
@@ -215,6 +215,7 @@ require (
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/tetratelabs/wazero v1.6.0 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
@@ -225,6 +226,7 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/tablewriter v0.0.0-20160610135559-80b567a11ad5 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zclconf/go-cty v1.8.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
@@ -236,17 +238,15 @@ require (
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
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/grpc v1.62.1 // indirect

55
go.sum
View File

@@ -23,10 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -46,10 +44,10 @@ connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY
connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc=
connectrpc.com/validate v0.1.0 h1:r55jirxMK7HO/xZwVHj3w2XkVFarsUM77ZDy367NtH4=
connectrpc.com/validate v0.1.0/go.mod h1:GU47c9/x/gd+u9wRSPkrQOP46gx2rMN+Wo37EHgI3Ow=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24=
cuelang.org/go v0.8.0 h1:fO1XPe/SUGtc7dhnGnTPbpIDoQm/XxhDtoSF7jzO01c=
cuelang.org/go v0.8.0/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2 h1:BnG6pr9TTr6CYlrJznYUDj6V7xldD1W+1iXPum0wT/w=
cuelabs.dev/go/oci/ociregistry v0.0.0-20240404174027-a39bec0462d2/go.mod h1:pK23AUVXuNzzTpfMCA06sxZGeVQ/75FdVtW249de9Uo=
cuelang.org/go v0.9.2 h1:pfNiry2PdRBr02G/aKm5k2vhzmqbAOoaB4WurmEbWvs=
cuelang.org/go v0.9.2/go.mod h1:qpAYsLOf7gTM1YdEg6cxh553uZ4q9ZDWlPbtZr9q1Wk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
@@ -187,8 +185,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=
@@ -638,6 +636,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -670,6 +670,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@@ -719,8 +720,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -758,8 +759,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -793,16 +794,16 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -868,16 +869,16 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -885,12 +886,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -942,8 +943,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -970,8 +971,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

View File

@@ -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

View File

@@ -1,2 +0,0 @@
// Package v1alpha1 is the unstable version of the core api.
package v1alpha1

View File

@@ -10,25 +10,30 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/holos-run/holos/api/core/v1alpha2"
"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"
"github.com/holos-run/holos/internal/render"
)
const (
KubernetesObjects = v1alpha1.KubernetesObjectsKind
KubernetesObjects = v1alpha2.KubernetesObjectsKind
// Helm is the value of the kind field of holos build output indicating helm
// values and helm command information.
Helm = v1alpha1.HelmChartKind
Helm = v1alpha2.HelmChartKind
// Skip is the value when the instance should be skipped
Skip = "Skip"
// KustomizeBuild is the value of the kind field of cue output indicating holos should process the component using kustomize build to render output.
// KustomizeBuild is the value of the kind field of cue output indicating
// holos should process the component using kustomize build to render output.
KustomizeBuild = v1alpha1.KustomizeBuildKind
)
@@ -70,7 +75,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 +84,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,25 +121,24 @@ 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 []*render.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
}
results = make([]*v1alpha1.Result, 0, len(instances)*8)
results = make([]*render.Result, 0, len(instances)*8)
// Each CUE instance provides a BuildPlan
for idx, instance := range instances {
@@ -127,7 +153,7 @@ func (b *Builder) Run(ctx context.Context) (results []*v1alpha1.Result, err erro
return results, nil
}
func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (results []*v1alpha1.Result, err error) {
func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (results []*render.Result, err error) {
path := holos.InstancePath(instance.Dir)
log := logger.FromContext(ctx).With("dir", path)
@@ -161,37 +187,31 @@ 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 {
case "BuildPlan":
var bp v1alpha1.BuildPlan
var bp v1alpha2.BuildPlan
if err = decoder.Decode(&bp); err != nil {
err = errors.Wrap(fmt.Errorf("could not decode BuildPlan %s: %w", instance.Dir, err))
return
}
results, err = b.buildPlan(ctx, &bp, path)
case "Platform":
var pf v1alpha1.Platform
if err = decoder.Decode(&pf); err != nil {
err = errors.Wrap(fmt.Errorf("could not decode Platform %s: %w", instance.Dir, err))
return
if err != nil {
return results, err
}
results, err = b.buildPlatform(ctx, &pf)
default:
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
}
return
return results, err
}
func (b *Builder) buildPlatform(ctx context.Context, pf *v1alpha1.Platform) (results []*v1alpha1.Result, err error) {
log := logger.FromContext(ctx)
log.ErrorContext(ctx, "not implemented", "platform", pf)
return nil, errors.Wrap(fmt.Errorf("not implemeneted"))
}
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan, path holos.InstancePath) (results []*v1alpha1.Result, err error) {
func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha2.BuildPlan, path holos.InstancePath) (results []*render.Result, err error) {
log := logger.FromContext(ctx)
if err := buildPlan.Validate(); err != nil {
@@ -204,11 +224,12 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
return
}
// TODO: concurrent renders
results = make([]*v1alpha1.Result, 0, buildPlan.ResultCapacity())
results = make([]*render.Result, 0, buildPlan.ResultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", buildPlan.ResultCapacity())
for _, component := range buildPlan.Spec.Components.Resources {
if result, err := component.Render(ctx, path); err != nil {
ko := render.KubernetesObjects{Component: component}
if result, err := ko.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
@@ -216,21 +237,24 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
}
for _, component := range buildPlan.Spec.Components.KubernetesObjectsList {
if result, err := component.Render(ctx, path); err != nil {
ko := render.KubernetesObjects{Component: component}
if result, err := ko.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.HelmChartList {
if result, err := component.Render(ctx, path); err != nil {
hc := render.HelmChart{Component: component}
if result, err := hc.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)
}
}
for _, component := range buildPlan.Spec.Components.KustomizeBuildList {
if result, err := component.Render(ctx, path); err != nil {
kb := render.KustomizeBuild{Component: component}
if result, err := kb.Render(ctx, path); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not render: %w", err))
} else {
results = append(results, result)

View File

@@ -0,0 +1,92 @@
package builder
import (
"bytes"
"context"
"encoding/json"
"fmt"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"github.com/holos-run/holos"
core "github.com/holos-run/holos/api/core/v1alpha2"
meta "github.com/holos-run/holos/api/meta/v1alpha2"
"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) (*core.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) (*core.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 := &meta.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.GetKind(), "kind", tm.GetKind(), "apiVersion", tm.GetAPIVersion())
// New decoder for the full object
decoder = json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.DisallowUnknownFields()
var pf core.Platform
switch tm.GetKind() {
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.GetKind()))
}
return nil, err
}

View File

@@ -7,22 +7,26 @@ 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
}
outs := make([]string, 0, len(results))
for idx, result := range results {
if result == nil || result.Skip {
if result.Continue() {
slog.Debug("skip result", "idx", idx, "result", result)
continue
}
@@ -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
}

View File

@@ -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,
}

View File

@@ -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
}

View File

@@ -2,9 +2,12 @@ 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"
@@ -19,6 +22,7 @@ func New(cfg *holos.Config) *cobra.Command {
cmd.Args = cobra.NoArgs
cmd.AddCommand(NewPlatform(cfg))
cmd.AddCommand(NewComponent())
return cmd
}
@@ -31,11 +35,70 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
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, name); err != nil {
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
}

82
internal/cli/pull/pull.go Normal file
View File

@@ -0,0 +1,82 @@
// 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("model")
cmd.Aliases = []string{"config"}
cmd.Short = "pull platform model"
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
}

107
internal/cli/push/push.go Normal file
View File

@@ -0,0 +1,107 @@
// 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
}
func NewPlatformModel(cfg *client.Config) *cobra.Command {
cmd := command.New("model")
cmd.Short = "push platform model 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 config for the platform id.
p, err := client.LoadPlatformConfig(ctx, name)
if err != nil {
return errors.Wrap(err)
}
// Make the rpc call to update the platform form.
if err := rpc.UpdatePlatformModel(ctx, p.PlatformId, p.PlatformModel); err != nil {
return errors.Wrap(err)
}
slog.Default().InfoContext(ctx, fmt.Sprintf("pushed: %s/ui/platform/%s", cfg.Client().Server(), p.PlatformId))
}
return nil
}
return cmd
}

View File

@@ -1,7 +1,9 @@
// 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"
@@ -13,7 +15,7 @@ func New(cfg *holos.Config) *cobra.Command {
cmd.Short = "register with holos server"
cmd.Args = cobra.NoArgs
config := register.NewConfig(cfg)
config := client.NewConfig(cfg)
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
@@ -23,7 +25,7 @@ func New(cfg *holos.Config) *cobra.Command {
}
// NewUser returns a command to register a user with holos server.
func NewUser(cfg *register.Config) *cobra.Command {
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 {

View File

@@ -4,40 +4,50 @@ import (
"context"
"flag"
"fmt"
"runtime"
"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 +57,72 @@ 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)
}
// API Objects
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
return errors.Wrap(err)
if result.SkipWriteAccumulatedOutput() {
log.DebugContext(ctx, "skipped writing k8s objects for "+result.Name())
} else {
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)
}
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())
var concurrency int
cmd.Flags().IntVar(&concurrency, "concurrency", min(runtime.NumCPU(), 8), "Number of concurrent components to render")
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, concurrency, platform, cmd.ErrOrStderr())
}
return cmd
}
type Result interface {
Continue() bool
Name() string
@@ -83,5 +130,8 @@ type Result interface {
KustomizationFilename(writeTo string, cluster string) string
Save(ctx context.Context, path string, content string) error
AccumulatedOutput() string
KustomizationContent() string
SkipWriteAccumulatedOutput() bool
WriteDeployFiles(ctx context.Context, writeTo string) error
GetKind() string
GetAPIVersion() string
}

View File

@@ -1,6 +1,7 @@
package cli
import (
"fmt"
"log/slog"
"github.com/spf13/cobra"
@@ -12,6 +13,7 @@ import (
"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"
@@ -20,6 +22,8 @@ import (
"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"
@@ -32,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
@@ -70,6 +74,9 @@ func New(cfg *holos.Config) *cobra.Command {
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))
@@ -85,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
}

View File

@@ -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

143
internal/client/client.go Normal file
View File

@@ -0,0 +1,143 @@
// 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
}
func (c *Client) UpdatePlatformModel(ctx context.Context, platformID string, model *structpb.Struct) error {
start := time.Now()
req := &platform.UpdatePlatformRequest{
PlatformId: platformID,
Update: &platform.PlatformMutation{Model: model},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"model"}},
}
_, 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
View 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
}

View 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -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.32.0",
"@bufbuild/protoc-gen-es": "^1.9.0",
"@bufbuild/buf": "^1.34.0",
"@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",

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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/system/v1alpha1/system.proto (package holos.system.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck

View File

@@ -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/system/v1alpha1/system_service.proto (package holos.system.v1alpha1, syntax proto3)
/* eslint-disable */
// @ts-nocheck

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,7 @@ 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 { GetPlatformRequest, ListPlatformsRequest, UpdatePlatformOperation, UpdatePlatformRequest } from '../gen/holos/platform/v1alpha1/platform_service_pb';
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) })
)

View 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
}

View File

@@ -0,0 +1,4 @@
package holos
// Produce a kubectl kustomize build plan.
(#Kustomize & {Name: "{{ .Name }}"}).Output

View File

@@ -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"

View 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"
}

View 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

View File

@@ -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"
}

View 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

View File

@@ -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."
}

View File

@@ -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

View File

@@ -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"
}

View 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

View File

@@ -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"
}

View 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

View 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"
}

View File

@@ -1,9 +1,9 @@
package generate
import (
"bytes"
"context"
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
@@ -12,49 +12,6 @@ import (
"github.com/holos-run/holos/internal/server/middleware/logger"
)
//go:embed all:platforms
var platforms embed.FS
// root is the root path to copy platform cue code from.
const root = "platforms"
// Platforms returns a slice of embedded platforms or nil if there are none.
func Platforms() []string {
entries, err := fs.ReadDir(platforms, root)
if err != nil {
return nil
}
dirs := make([]string, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() && entry.Name() != "cue.mod" {
dirs = append(dirs, entry.Name())
}
}
return dirs
}
// GeneratePlatform writes the cue code for a platform to the local working
// directory.
func GeneratePlatform(ctx context.Context, name string) error {
// Check for a valid platform
platformPath := filepath.Join(root, name)
if !dirExists(platforms, platformPath) {
return errors.Wrap(fmt.Errorf("cannot generate: have: [%s] want: %+v", name, Platforms()))
}
// Copy the cue.mod directory
if err := copyEmbedFS(ctx, platforms, filepath.Join(root, "cue.mod"), "cue.mod"); err != nil {
return errors.Wrap(err)
}
// Copy the named platform
if err := copyEmbedFS(ctx, platforms, platformPath, "."); err != nil {
return errors.Wrap(err)
}
return nil
}
func dirExists(srcFS embed.FS, path string) bool {
entries, err := fs.ReadDir(srcFS, path)
if err != nil {
@@ -63,7 +20,9 @@ func dirExists(srcFS embed.FS, path string) bool {
return len(entries) > 0
}
func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string) error {
// 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 {
@@ -82,16 +41,39 @@ func copyEmbedFS(ctx context.Context, srcFS embed.FS, srcPath, dstPath string) e
return errors.Wrap(err)
}
log.DebugContext(ctx, "created", "directory", dstFullPath)
} else {
data, err := srcFS.ReadFile(path)
if err != nil {
return errors.Wrap(err)
}
if err := os.WriteFile(dstFullPath, data, os.ModePerm); err != nil {
return errors.Wrap(err)
}
log.DebugContext(ctx, "wrote", "file", dstFullPath)
return nil
}
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
}

View 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
}

View File

@@ -0,0 +1,34 @@
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]
}
}

View File

@@ -1,9 +1,8 @@
package holos
import "encoding/yaml"
import v1 "github.com/holos-run/holos/api/v1alpha1"
let PLATFORM = {message: "TODO: Load the platform from the API."}
import v1 "github.com/holos-run/holos/api/v1alpha1"
// Provide a BuildPlan to the holos cli to render k8s api objects.
v1.#BuildPlan & {
@@ -20,6 +19,12 @@ let OBJECTS = v1.#APIObjects & {
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
}

View File

@@ -1,6 +1,6 @@
package forms
import v1 "github.com/holos-run/holos/v1alpha1"
import v1 "github.com/holos-run/holos/api/v1alpha1"
// Provides a concrete v1.#Form
FormBuilder.Output
@@ -24,22 +24,12 @@ let FormBuilder = v1.#FormBuilder & {
required: true
}
validation: messages: {
pattern: "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
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.domain
domain: {
type: "input"
props: {
label: "Domain"
placeholder: "example.com"
minLength: 3
maxLength: 100
description: "DNS domain, e.g. 'example.com'"
required: true
}
}
// platform.spec.config.user.sections.org.fields.displayName
displayName: {
type: "input"
@@ -51,16 +41,6 @@ let FormBuilder = v1.#FormBuilder & {
required: true
}
}
// platform.spec.config.user.sections.org.fields.contactEmail
contactEmail: {
type: "input"
props: {
label: "Contact Email"
placeholder: "platform-team@example.com"
description: "Technical contact email address"
required: true
}
}
}
}
@@ -78,13 +58,13 @@ let FormBuilder = v1.#FormBuilder & {
multiple: true
selectAllOption: "Select All"
options: [
{value: "aws", label: "Amazon Web Services"},
{value: "gcp", label: "Google Cloud Platform"},
{value: "azure", label: "Microsoft Azure"},
{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},
{value: "github", label: "GitHub"},
{value: "ois", label: "Open Infrastructure Services"},
{value: "onprem", label: "On Premises", disabled: true},
]
}
}
@@ -160,7 +140,9 @@ let FormBuilder = v1.#FormBuilder & {
required: true
}
validation: messages: {
pattern: "It must be 3 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited."
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."
}
}
@@ -252,102 +234,76 @@ let FormBuilder = v1.#FormBuilder & {
}
}
}
Sections: backups: {
displayName: "Backups"
description: "Configure platform level data backup settings. Requires AWS."
fieldConfigs: {
s3bucket: {
// https://formly.dev/docs/api/ui/material/input
type: "select"
props: {
label: "S3 Bucket Region"
description: "Select the S3 Bucket Region."
multiple: true
options: AWSRegions
}
expressions: {
// Disable the control if AWS is not selected.
"props.disabled": "!" + AWSSelected
// Required if AWS is selected.
"props.required": AWSSelected
// Change the label depending on AWS
"props.description": AWSSelected + " ? '\(props.description)' : 'Enable AWS in the Cloud Provider section to configure backups.'"
}
}
}
}
}
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: "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"},
{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: "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: "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)"},
]

View 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"]
}

View File

@@ -0,0 +1,4 @@
package holos
// Output the Platform resource for holos to render the entire platform.
{} & _Platform

View File

@@ -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
}]
}

View File

@@ -0,0 +1,340 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane-contrib/provider-upjet-aws/v1.5.0/package/crds/aws.upbound.io_providerconfigs.yaml
package v1beta1
import "strings"
// A ProviderConfig configures the AWS provider.
#ProviderConfig: {
// 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: "aws.upbound.io/v1beta1"
// 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: "ProviderConfig"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// A ProviderConfigSpec defines the desired state of a
// ProviderConfig.
spec!: #ProviderConfigSpec
}
// A ProviderConfigSpec defines the desired state of a
// ProviderConfig.
#ProviderConfigSpec: {
// AssumeRoleChain defines the options for assuming an IAM role
assumeRoleChain?: [...{
// ExternalID is the external ID used when assuming role.
externalID?: string
// AssumeRoleARN to assume with provider credentials
roleARN?: string
// Tags is list of session tags that you want to pass. Each
// session tag consists of a key
// name and an associated value. For more information about
// session tags, see
// Tagging STS Sessions
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html).
tags?: [...{
// Name of the tag.
// Key is a required field
key: string
// Value of the tag.
// Value is a required field
value: string
}]
// TransitiveTagKeys is a list of keys for session tags that you
// want to set as transitive. If you set a
// tag key as transitive, the corresponding key and value passes
// to subsequent
// sessions in a role chain. For more information, see Chaining
// Roles with Session Tags
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining).
transitiveTagKeys?: [...string]
}]
// Credentials required to authenticate to this provider.
credentials: {
env?: {
// Name is the name of an environment variable.
name: string
}
fs?: {
// Path is a filesystem path.
path: string
}
// A SecretRef is a reference to a secret key that contains the
// credentials
// that must be used to connect to the provider.
secretRef?: {
// The key to select.
key: string
// Name of the secret.
name: string
// Namespace of the secret.
namespace: string
}
// Source of the provider credentials.
source: "None" | "Secret" | "IRSA" | "WebIdentity" | "Upbound"
upbound?: {
// WebIdentity defines the options for assuming an IAM role with a
// Web
// Identity.
webIdentity?: {
// AssumeRoleARN to assume with provider credentials
roleARN?: string
// RoleSessionName is the session name, if you wish to uniquely
// identify this session.
roleSessionName?: string
// TokenConfig is the Web Identity Token config to assume the
// role.
tokenConfig?: {
fs?: {
// Path is a filesystem path.
path: string
}
// A SecretRef is a reference to a secret key that contains the
// credentials
// that must be used to obtain the web identity token.
secretRef?: {
// The key to select.
key: string
// Name of the secret.
name: string
// Namespace of the secret.
namespace: string
}
// Source is the source of the web identity token.
source: "Secret" | "Filesystem"
}
}
}
// WebIdentity defines the options for assuming an IAM role with a
// Web Identity.
webIdentity?: {
// AssumeRoleARN to assume with provider credentials
roleARN?: string
// RoleSessionName is the session name, if you wish to uniquely
// identify this session.
roleSessionName?: string
// TokenConfig is the Web Identity Token config to assume the
// role.
tokenConfig?: {
fs?: {
// Path is a filesystem path.
path: string
}
// A SecretRef is a reference to a secret key that contains the
// credentials
// that must be used to obtain the web identity token.
secretRef?: {
// The key to select.
key: string
// Name of the secret.
name: string
// Namespace of the secret.
namespace: string
}
// Source is the source of the web identity token.
source: "Secret" | "Filesystem"
}
}
}
// Endpoint is where you can override the default endpoint
// configuration
// of AWS calls made by the provider.
endpoint?: {
// Specifies if the endpoint's hostname can be modified by the
// SDK's API
// client.
//
//
// If the hostname is mutable the SDK API clients may modify any
// part of
// the hostname based on the requirements of the API, (e.g.
// adding, or
// removing content in the hostname). Such as, Amazon S3 API
// client
// prefixing "bucketname" to the hostname, or changing the
// hostname service name component from "s3." to
// "s3-accesspoint.dualstack."
// for the dualstack endpoint of an S3 Accesspoint resource.
//
//
// Care should be taken when providing a custom endpoint for an
// API. If the
// endpoint hostname is mutable, and the client cannot modify the
// endpoint
// correctly, the operation call will most likely fail, or have
// undefined
// behavior.
//
//
// If hostname is immutable, the SDK API clients will not modify
// the
// hostname of the URL. This may cause the API client not to
// function
// correctly if the API requires the operation specific hostname
// values
// to be used by the client.
//
//
// This flag does not modify the API client's behavior if this
// endpoint
// will be used instead of Endpoint Discovery, or if the endpoint
// will be
// used to perform Endpoint Discovery. That behavior is configured
// via the
// API Client's Options.
// Note that this is effective only for resources that use AWS SDK
// v2.
hostnameImmutable?: bool
// The AWS partition the endpoint belongs to.
partitionId?: string
// Specifies the list of services you want endpoint to be used for
services?: [...string]
// The signing method that should be used for signing the requests
// to the
// endpoint.
signingMethod?: string
// The service name that should be used for signing the requests
// to the
// endpoint.
signingName?: string
// The region that should be used for signing the request to the
// endpoint.
// For IAM, which doesn't have any region, us-east-1 is used to
// sign the
// requests, which is the only signing region of IAM.
signingRegion?: string
// The source of the Endpoint. By default, this will be
// ServiceMetadata.
// When providing a custom endpoint, you should set the source as
// Custom.
// If source is not provided when providing a custom endpoint, the
// SDK may not
// perform required host mutations correctly. Source should be
// used along with
// HostnameImmutable property as per the usage requirement.
// Note that this is effective only for resources that use AWS SDK
// v2.
source?: "ServiceMetadata" | "Custom"
// URL lets you configure the endpoint URL to be used in SDK
// calls.
url: {
// Dynamic lets you configure the behavior of endpoint URL
// resolver.
dynamic?: {
// Host is the address of the main host that the resolver will use
// to
// prepend protocol, service and region configurations.
// For example, the final URL for EC2 in us-east-1 looks like
// https://ec2.us-east-1.amazonaws.com
// You would need to use "amazonaws.com" as Host and "https" as
// protocol
// to have the resolver construct it.
host: string
// Protocol is the HTTP protocol that will be used in the URL.
// Currently,
// only http and https are supported.
protocol: "http" | "https"
}
// Static is the full URL you'd like the AWS SDK to use.
// Recommended for using tools like localstack where a single host
// is exposed
// for all services and regions.
static?: string
// You can provide a static URL that will be used regardless of
// the service
// and region by choosing Static type. Alternatively, you can
// provide
// configuration for dynamically resolving the URL with the config
// you provide
// once you set the type as Dynamic.
type: "Static" | "Dynamic"
}
}
// Whether to enable the request to use path-style addressing,
// i.e., https://s3.amazonaws.com/BUCKET/KEY.
s3_use_path_style?: bool
// Whether to skip credentials validation via the STS API.
// This can be useful for testing and for AWS API implementations
// that do not have STS available.
skip_credentials_validation?: bool
// Whether to skip the AWS Metadata API check
// Useful for AWS API implementations that do not have a metadata
// API endpoint.
skip_metadata_api_check?: bool
// Whether to skip validation of provided region name.
// Useful for AWS-like implementations that use their own region
// names or to bypass the validation for
// regions that aren't publicly available yet.
skip_region_validation?: bool
// Whether to skip requesting the account ID.
// Useful for AWS API implementations that do not have the IAM,
// STS API, or metadata API
skip_requesting_account_id?: bool
}

View File

@@ -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"]
}

View File

@@ -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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f deploy/clusters/aws2/components/istio-base/istio-base.gen.yaml
package v1alpha1
import (
"strings"
"list"
)
#WasmPlugin: {
// Extend the functionality provided by the Istio proxy through
// WebAssembly filters. See more details at:
// https://istio.io/docs/reference/config/proxy_extensions/wasm-plugin.html
spec!: #WasmPluginSpec
apiVersion: "extensions.istio.io/v1alpha1"
kind: "WasmPlugin"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace!: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
}
// Extend the functionality provided by the Istio proxy through
// WebAssembly filters. See more details at:
// https://istio.io/docs/reference/config/proxy_extensions/wasm-plugin.html
#WasmPluginSpec: {
// Specifies the failure behavior for the plugin due to fatal
// errors.
//
// Valid Options: FAIL_CLOSE, FAIL_OPEN
failStrategy?: "FAIL_CLOSE" | "FAIL_OPEN"
// The pull behaviour to be applied when fetching Wasm module by
// either OCI image or `http/https`.
//
// Valid Options: IfNotPresent, Always
imagePullPolicy?: "UNSPECIFIED_POLICY" | "IfNotPresent" | "Always"
// Credentials to use for OCI image pulling.
imagePullSecret?: strings.MaxRunes(253) & strings.MinRunes(1)
// Specifies the criteria to determine which traffic is passed to
// WasmPlugin.
match?: [...{
// Criteria for selecting traffic by their direction.
//
// Valid Options: CLIENT, SERVER, CLIENT_AND_SERVER
mode?: "UNDEFINED" | "CLIENT" | "SERVER" | "CLIENT_AND_SERVER"
// Criteria for selecting traffic by their destination port.
ports?: [...{
number: uint16 & >=1
}]
}]
// Determines where in the filter chain this `WasmPlugin` is to be
// injected.
//
// Valid Options: AUTHN, AUTHZ, STATS
phase?: "UNSPECIFIED_PHASE" | "AUTHN" | "AUTHZ" | "STATS"
// The configuration that will be passed on to the plugin.
pluginConfig?: {
...
}
// The plugin name to be used in the Envoy configuration (used to
// be called `rootID`).
pluginName?: strings.MaxRunes(256) & strings.MinRunes(1)
// Determines ordering of `WasmPlugins` in the same `phase`.
priority?: null | int
selector?: {
// One or more labels that indicate a specific set of pods/VMs on
// which a policy should be applied.
matchLabels?: {
[string]: string
}
}
// SHA256 checksum that will be used to verify Wasm module or OCI
// container.
sha256?: =~"(^$|^[a-f0-9]{64}$)"
targetRef?: {
// group is the group of the target resource.
group?: string
// kind is kind of the target resource.
kind?: string
// name is the name of the target resource.
name?: string
// namespace is the namespace of the referent.
namespace?: string
}
// Optional.
targetRefs?: [...{
// group is the group of the target resource.
group?: string
// kind is kind of the target resource.
kind?: string
// name is the name of the target resource.
name?: string
// namespace is the namespace of the referent.
namespace?: string
}]
// Specifies the type of Wasm Extension to be used.
//
// Valid Options: HTTP, NETWORK
type?: "UNSPECIFIED_PLUGIN_TYPE" | "HTTP" | "NETWORK"
// URL of a Wasm module or OCI container.
url: strings.MinRunes(1)
verificationKey?: string
vmConfig?: {
// Specifies environment variables to be injected to this VM.
env?: list.MaxItems(256) & [...{
// Name of the environment variable.
name: strings.MaxRunes(256) & strings.MinRunes(1)
// Value for the environment variable.
value?: strings.MaxRunes(2048)
// Source for the environment variable's value.
//
// Valid Options: INLINE, HOST
valueFrom?: "INLINE" | "HOST"
}]
}
}

View File

@@ -0,0 +1,378 @@
// 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-secrets-eso/prod-secrets-eso.gen.yaml
package v1beta1
import (
"strings"
"struct"
)
// ClusterExternalSecret is the Schema for the
// clusterexternalsecrets API.
#ClusterExternalSecret: {
// 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: "external-secrets.io/v1beta1"
// 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: "ClusterExternalSecret"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// ClusterExternalSecretSpec defines the desired state of
// ClusterExternalSecret.
spec!: #ClusterExternalSecretSpec
}
// ClusterExternalSecretSpec defines the desired state of
// ClusterExternalSecret.
#ClusterExternalSecretSpec: {
// The metadata of the external secrets to be created
externalSecretMetadata?: {
annotations?: {
[string]: string
}
labels?: {
[string]: string
}
}
// The name of the external secrets to be created defaults to the
// name of the ClusterExternalSecret
externalSecretName?: string
// The spec for the ExternalSecrets to be created
externalSecretSpec: {
// Data defines the connection between the Kubernetes Secret keys
// and the Provider data
data?: [...{
// RemoteRef points to the remote secret and defines
// which secret (version/property/..) to fetch.
remoteRef: {
// Used to define a conversion Strategy
conversionStrategy?: "Default" | "Unicode" | *"Default"
// Used to define a decoding Strategy
decodingStrategy?: "Auto" | "Base64" | "Base64URL" | "None" | *"None"
// Key is the key used in the Provider, mandatory
key: string
// Policy for fetching tags/labels from provider secrets, possible
// options are Fetch, None. Defaults to None
metadataPolicy?: "None" | "Fetch" | *"None"
// Used to select a specific property of the Provider value (if a
// map), if supported
property?: string
// Used to select a specific version of the Provider value, if
// supported
version?: string
}
// SecretKey defines the key in which the controller stores
// the value. This is the key in the Kind=Secret
secretKey: string
// SourceRef allows you to override the source
// from which the value will pulled from.
sourceRef?: struct.MaxFields(1) & {
// GeneratorRef points to a generator custom resource.
//
//
// Deprecated: The generatorRef is not implemented in .data[].
// this will be removed with v1.
generatorRef?: {
// Specify the apiVersion of the generator resource
apiVersion?: string | *"generators.external-secrets.io/v1alpha1"
// Specify the Kind of the resource, e.g. Password, ACRAccessToken
// etc.
kind: string
// Specify the name of the generator resource
name: string
}
// SecretStoreRef defines which SecretStore to fetch the
// ExternalSecret data.
storeRef?: {
// Kind of the SecretStore resource (SecretStore or
// ClusterSecretStore)
// Defaults to `SecretStore`
kind?: string
// Name of the SecretStore resource
name: string
}
}
}]
// DataFrom is used to fetch all properties from a specific
// Provider data
// If multiple entries are specified, the Secret keys are merged
// in the specified order
dataFrom?: [...{
// Used to extract multiple key/value pairs from one secret
// Note: Extract does not support sourceRef.Generator or
// sourceRef.GeneratorRef.
extract?: {
// Used to define a conversion Strategy
conversionStrategy?: "Default" | "Unicode" | *"Default"
// Used to define a decoding Strategy
decodingStrategy?: "Auto" | "Base64" | "Base64URL" | "None" | *"None"
// Key is the key used in the Provider, mandatory
key: string
// Policy for fetching tags/labels from provider secrets, possible
// options are Fetch, None. Defaults to None
metadataPolicy?: "None" | "Fetch" | *"None"
// Used to select a specific property of the Provider value (if a
// map), if supported
property?: string
// Used to select a specific version of the Provider value, if
// supported
version?: string
}
// Used to find secrets based on tags or regular expressions
// Note: Find does not support sourceRef.Generator or
// sourceRef.GeneratorRef.
find?: {
// Used to define a conversion Strategy
conversionStrategy?: "Default" | "Unicode" | *"Default"
// Used to define a decoding Strategy
decodingStrategy?: "Auto" | "Base64" | "Base64URL" | "None" | *"None"
name?: {
// Finds secrets base
regexp?: string
}
// A root path to start the find operations.
path?: string
// Find secrets based on tags.
tags?: {
[string]: string
}
}
// Used to rewrite secret Keys after getting them from the secret
// Provider
// Multiple Rewrite operations can be provided. They are applied
// in a layered order (first to last)
rewrite?: [...{
// Used to rewrite with regular expressions.
// The resulting key will be the output of a regexp.ReplaceAll
// operation.
regexp?: {
// Used to define the regular expression of a re.Compiler.
source: string
// Used to define the target pattern of a ReplaceAll operation.
target: string
}
transform?: {
// Used to define the template to apply on the secret name.
// `.value ` will specify the secret name in the template.
template: string
}
}]
// SourceRef points to a store or generator
// which contains secret values ready to use.
// Use this in combination with Extract or Find pull values out of
// a specific SecretStore.
// When sourceRef points to a generator Extract or Find is not
// supported.
// The generator returns a static map of values
sourceRef?: struct.MaxFields(1) & {
// GeneratorRef points to a generator custom resource.
generatorRef?: {
// Specify the apiVersion of the generator resource
apiVersion?: string | *"generators.external-secrets.io/v1alpha1"
// Specify the Kind of the resource, e.g. Password, ACRAccessToken
// etc.
kind: string
// Specify the name of the generator resource
name: string
}
// SecretStoreRef defines which SecretStore to fetch the
// ExternalSecret data.
storeRef?: {
// Kind of the SecretStore resource (SecretStore or
// ClusterSecretStore)
// Defaults to `SecretStore`
kind?: string
// Name of the SecretStore resource
name: string
}
}
}]
// RefreshInterval is the amount of time before the values are
// read again from the SecretStore provider
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
// May be set to zero to fetch and create it once. Defaults to 1h.
refreshInterval?: string | *"1h"
// SecretStoreRef defines which SecretStore to fetch the
// ExternalSecret data.
secretStoreRef?: {
// Kind of the SecretStore resource (SecretStore or
// ClusterSecretStore)
// Defaults to `SecretStore`
kind?: string
// Name of the SecretStore resource
name: string
}
// ExternalSecretTarget defines the Kubernetes Secret to be
// created
// There can be only one target per ExternalSecret.
target?: {
// CreationPolicy defines rules on how to create the resulting
// Secret
// Defaults to 'Owner'
creationPolicy?: "Owner" | "Orphan" | "Merge" | "None" | *"Owner"
// DeletionPolicy defines rules on how to delete the resulting
// Secret
// Defaults to 'Retain'
deletionPolicy?: "Delete" | "Merge" | "Retain" | *"Retain"
// Immutable defines if the final secret will be immutable
immutable?: bool
// Name defines the name of the Secret resource to be managed
// This field is immutable
// Defaults to the .metadata.name of the ExternalSecret resource
name?: string
// Template defines a blueprint for the created Secret resource.
template?: {
data?: {
[string]: string
}
// EngineVersion specifies the template engine version
// that should be used to compile/execute the
// template specified in .data and .templateFrom[].
engineVersion?: "v1" | "v2" | *"v2"
mergePolicy?: "Replace" | "Merge" | *"Replace"
// ExternalSecretTemplateMetadata defines metadata fields for the
// Secret blueprint.
metadata?: {
annotations?: {
[string]: string
}
labels?: {
[string]: string
}
}
templateFrom?: [...{
configMap?: {
items: [...{
key: string
templateAs?: "Values" | "KeysAndValues" | *"Values"
}]
name: string
}
literal?: string
secret?: {
items: [...{
key: string
templateAs?: "Values" | "KeysAndValues" | *"Values"
}]
name: string
}
target?: "Data" | "Annotations" | "Labels" | *"Data"
}]
type?: string
}
} | *{
creationPolicy: "Owner"
deletionPolicy: "Retain"
}
}
// The labels to select by to find the Namespaces to create the
// ExternalSecrets in.
namespaceSelector?: {
// matchExpressions is a list of label selector requirements. The
// requirements are ANDed.
matchExpressions?: [...{
// key is the label key that the selector applies to.
key: string
// operator represents a key's relationship to a set of values.
// Valid operators are In, NotIn, Exists and DoesNotExist.
operator: string
// values is an array of string values. If the operator is In or
// NotIn,
// the values array must be non-empty. If the operator is Exists
// or DoesNotExist,
// the values array must be empty. This array is replaced during a
// strategic
// merge patch.
values?: [...string]
}]
// matchLabels is a map of {key,value} pairs. A single {key,value}
// in the matchLabels
// map is equivalent to an element of matchExpressions, whose key
// field is "key", the
// operator is "In", and the values array contains only "value".
// The requirements are ANDed.
matchLabels?: {
[string]: string
}
}
// Choose namespaces by name. This field is ORed with anything
// that NamespaceSelector ends up choosing.
namespaces?: [...string]
// The time in which the controller should reconcile its objects
// and recheck namespaces for labels.
refreshTime?: string
}

Some files were not shown because too many files have changed in this diff Show More