Compare commits

..

41 Commits

Author SHA1 Message Date
Jeff McCune
53cb9ba7fb (#189) Make the v1alpha2 API data only
Previously a couple of methods were defined on the Result struct.

This patch moves the methods to an internal wrapper struct to remove
them from the API documentation.

With this patch the API between holos and CUE is entirely a data API.
2024-06-30 17:19:35 -07:00
Jeff McCune
4cc139b372 (#189) v1alpha2 Reference Docs 2024-06-30 16:13:12 -07:00
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
105 changed files with 11267 additions and 469 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)
@@ -55,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
@@ -64,6 +65,8 @@ vet: ## Vet Go code.
.PHONY: gencue
gencue: ## Generate CUE definitions
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
@@ -123,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
@@ -147,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,44 @@
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
// Label is an arbitrary unique identifier internal to holos itself. The holos
// cli is expected to never write a Label value to rendered output files,
// therefore use a [Label] then the identifier must be unique and internal.
// Defined as a type for clarity and type checking.
//
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
// Label keys is guaranteed to not lose data when rendering output because a
// Label is expected to never be written to the final output.
type Label string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
type Kind string
// APIObject represents the most basic generic form of a single kubernetes api
// object. Represented as a JSON object internally for compatibility between
// tools, for example loading from CUE.
type APIObject structpb.Struct
// 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 defined directly from CUE code.
// Useful to mix in resources to any kind of [HolosComponent], for example
// adding an ExternalSecret resource to a [HelmChart].
//
// [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 map[Kind]map[Label]APIObject `json:"apiObjects"`
APIObjectMap APIObjectMap `json:"apiObjectMap"`
}

View File

@@ -0,0 +1,96 @@
package v1alpha2
// FilePath represents a file path.
type FilePath string
// FileContent represents file contents.
type FileContent string
// FileContentMap represents a mapping of file paths to file contents. Paths
// are relative to the `holos` output "deploy" directory, and may contain
// sub-directories.
type FileContentMap map[FilePath]FileContent
// BuildPlan represents a build plan for the holos cli to execute. The purpose
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
//
// 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"`
}
// BuildPlanSpec represents the specification of the build plan.
type BuildPlanSpec struct {
// Disabled causes the holos cli to take no action over the [BuildPlan].
Disabled bool `json:"disabled,omitempty"`
// Components represents multiple [HolosComponent] kinds to manage.
Components BuildPlanComponents `json:"components,omitempty"`
}
type BuildPlanComponents struct {
Resources map[Label]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"
)

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

@@ -0,0 +1,44 @@
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"`
}

24
api/core/v1alpha2/doc.go Normal file
View File

@@ -0,0 +1,24 @@
// 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.
//
// [Platform] defines the complete configuration of a platform. With the holos
// reference platform this takes the shape of one management cluster and at
// least two workload cluster. Each cluster has multiple [HolosComponent]
// resources applied to it.
//
// Each holos component path, e.g. `components/namespaces` produces exactly one
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
//
// The primary kinds of [HolosComponent] are:
//
// 1. [HelmChart] to render config from a helm chart.
// 2. [KustomizeBuild] to render config from [Kustomize]
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
// configuration.
//
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
// may be provided to [Kustomize] for post-processing.
package v1alpha2

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 represents a [HolosComponent] that 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

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

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

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

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

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

@@ -16,9 +16,14 @@ process. This ensures a namespace scoped `SecretStore` is created to sync
3. Render the platform
4. Apply the `namespaces` component to the management cluster
5. Apply the `eso-creds-manager` component to the management cluster to create the `eso-reader` ksa for the namespace `SecretStore`
6. Apply the `namespaces` component to the workload clusters
7. On the workload cluster, run the job to fetch the eso-reader creds: `kubectl create job -n holos-system --from=cronjob/eso-creds-refresher eso-creds-refresher-$(date +%s)`
8. Apply the secretstores component to the workload cluster.
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)

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

51
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=
@@ -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,2 +0,0 @@
// Package v1alpha1 is the unstable version of the core api.
package v1alpha1

View File

@@ -15,22 +15,25 @@ import (
"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
)
@@ -46,6 +49,43 @@ type Builder struct {
cfg config
}
type buildPlanWrapper struct {
buildPlan *v1alpha2.BuildPlan
}
func (b *buildPlanWrapper) validate() error {
if b == nil {
return fmt.Errorf("invalid BuildPlan: is nil")
}
bp := b.buildPlan
if bp == nil {
return fmt.Errorf("invalid BuildPlan: is nil")
}
errs := make([]string, 0, 2)
if bp.Kind != v1alpha2.BuildPlanKind {
errs = append(errs, fmt.Sprintf("kind invalid: want: %s have: %s", v1alpha1.BuildPlanKind, bp.Kind))
}
if bp.APIVersion != v1alpha2.APIVersion {
errs = append(errs, fmt.Sprintf("apiVersion invalid: want: %s have: %s", v1alpha2.APIVersion, bp.APIVersion))
}
if len(errs) > 0 {
return fmt.Errorf("invalid BuildPlan: " + strings.Join(errs, ", "))
}
return nil
}
func (b *buildPlanWrapper) resultCapacity() (count int) {
if b == nil {
return 0
}
bp := b.buildPlan
count = len(bp.Spec.Components.HelmChartList) +
len(bp.Spec.Components.KubernetesObjectsList) +
len(bp.Spec.Components.KustomizeBuildList) +
len(bp.Spec.Components.Resources)
return count
}
// New returns a new *Builder configured by opts Option.
func New(opts ...Option) *Builder {
var cfg config
@@ -128,14 +168,14 @@ func (b *Builder) Instances(ctx context.Context, cfg *client.Config) ([]*build.I
return load.Instances(args, &cueConfig), nil
}
func (b *Builder) Run(ctx context.Context, cfg *client.Config) (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, 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 {
@@ -150,7 +190,7 @@ func (b *Builder) Run(ctx context.Context, cfg *client.Config) (results []*v1alp
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)
@@ -191,9 +231,8 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
decoder.DisallowUnknownFields()
switch tm.Kind {
// TODO(jeff) Process a v1alpha1.Result here, the result is tightly coupled to a BuildPlan.
case "BuildPlan":
var bp v1alpha1.BuildPlan
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
@@ -209,10 +248,12 @@ func (b Builder) runInstance(ctx context.Context, instance *build.Instance) (res
return results, err
}
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 {
bpw := buildPlanWrapper{buildPlan: buildPlan}
if err := bpw.validate(); err != nil {
log.WarnContext(ctx, "could not validate", "skipped", true, "err", err)
return nil, errors.Wrap(fmt.Errorf("could not validate %w", err))
}
@@ -222,11 +263,12 @@ func (b *Builder) buildPlan(ctx context.Context, buildPlan *v1alpha1.BuildPlan,
return
}
// TODO: concurrent renders
results = make([]*v1alpha1.Result, 0, buildPlan.ResultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", buildPlan.ResultCapacity())
results = make([]*render.Result, 0, bpw.resultCapacity())
log.DebugContext(ctx, "allocated results slice", "cap", bpw.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)
@@ -234,38 +276,30 @@ 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)
}
}
// Add a separate Result if there are DeployFiles from the BuildPlan.
if len(buildPlan.Spec.DeployFiles) > 0 {
results = append(results, &v1alpha1.Result{
HolosComponent: v1alpha1.HolosComponent{
TypeMeta: buildPlan.TypeMeta,
Metadata: buildPlan.Metadata,
},
DeployFiles: buildPlan.Spec.DeployFiles,
})
}
log.DebugContext(ctx, "returning results", "len", len(results))
return results, nil

View File

@@ -9,14 +9,15 @@ import (
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/cuecontext"
"github.com/holos-run/holos"
"github.com/holos-run/holos/api/v1alpha1"
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) (*v1alpha1.Platform, error) {
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)
@@ -38,7 +39,7 @@ func (b *Builder) Platform(ctx context.Context, cfg *client.Config) (*v1alpha1.P
return p, nil
}
func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1alpha1.Platform, error) {
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)
@@ -60,22 +61,23 @@ func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1
if err != nil {
return nil, errors.Wrap(fmt.Errorf("could not marshal cue instance %s: %w", instance.Dir, err))
}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
// Discriminate the type of build plan.
tm := &v1alpha1.TypeMeta{}
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.Kind, "kind", tm.Kind, "apiVersion", tm.APIVersion)
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 v1alpha1.Platform
switch tm.Kind {
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))
@@ -83,7 +85,7 @@ func (b Builder) runPlatform(ctx context.Context, instance *build.Instance) (*v1
}
return &pf, nil
default:
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.Kind))
err = errors.Wrap(fmt.Errorf("unknown kind: %v", tm.GetKind()))
}
return nil, err

View File

@@ -26,7 +26,7 @@ func makeBuildRunFunc(cfg *client.Config) command.RunFunc {
}
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
}

View File

@@ -38,8 +38,9 @@ func NewPlatform(cfg *client.Config) *cobra.Command {
}
func NewPlatformConfig(cfg *client.Config) *cobra.Command {
cmd := command.New("config")
cmd.Short = "pull platform config"
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 {

View File

@@ -35,7 +35,7 @@ func NewPlatform(cfg *client.Config) *cobra.Command {
cmd.Args = cobra.NoArgs
cmd.AddCommand(NewPlatformForm(cfg))
// cmd.AddCommand(NewPlatformModel(cfg))
cmd.AddCommand(NewPlatformModel(cfg))
return cmd
}
@@ -74,3 +74,34 @@ func NewPlatformForm(cfg *client.Config) *cobra.Command {
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

@@ -4,6 +4,7 @@ import (
"context"
"flag"
"fmt"
"runtime"
"github.com/holos-run/holos/internal/builder"
"github.com/holos-run/holos/internal/cli/command"
@@ -77,22 +78,13 @@ func NewComponent(cfg *holos.Config) *cobra.Command {
if err := result.WriteDeployFiles(ctx, cfg.WriteTo()); err != nil {
return errors.Wrap(err)
}
// Build plans don't have anything but DeployFiles to write.
if result.GetKind() == "BuildPlan" {
continue
}
// API Objects
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
return errors.Wrap(err)
}
// Kustomization
if result.KustomizationContent() == "" {
log.DebugContext(ctx, "flux kustomization: skipped "+result.Name(), "status", "ok", "action", "skipped")
if result.SkipWriteAccumulatedOutput() {
log.DebugContext(ctx, "skipped writing k8s objects for "+result.Name())
} else {
path = result.KustomizationFilename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.KustomizationContent()); err != nil {
path := result.Filename(cfg.WriteTo(), cfg.ClusterName())
if err := result.Save(ctx, path, result.AccumulatedOutput()); err != nil {
return errors.Wrap(err)
}
}
@@ -113,6 +105,9 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
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))
@@ -122,7 +117,7 @@ func NewPlatform(cfg *holos.Config) *cobra.Command {
return errors.Wrap(err)
}
return render.Platform(ctx, platform, cmd.ErrOrStderr())
return render.Platform(ctx, concurrency, platform, cmd.ErrOrStderr())
}
return cmd
@@ -135,7 +130,7 @@ 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

@@ -91,6 +91,22 @@ func (c *Client) UpdateForm(ctx context.Context, platformID string, form *object
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()

View File

@@ -18,7 +18,7 @@
"@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.1",
"@connectrpc/connect-web": "^1.4.0",
@@ -37,8 +37,8 @@
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.4",
"@angular/compiler-cli": "^17.3.0",
"@bufbuild/buf": "^1.32.1",
"@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.1",
"@ngx-formly/schematics": "^6.3.0",
@@ -2494,9 +2494,9 @@
}
},
"node_modules/@bufbuild/buf": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.32.1.tgz",
"integrity": "sha512-uPVhqDzYtz9Q7WTodCschf9xXKL5/TQHtU1fKOUmain/dGe66YtSU4LQ0SWmxAQEJIUSmkH4UOPgKEzNMKdWeg==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.34.0.tgz",
"integrity": "sha512-DR0P746bYiY7ziQTui0bKAvPa7ihCNxONWLtW54HQXvTkGnTc6C1keVaSz4UhNdSsBu/Xsj69GO9SizodfjUtQ==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -2508,18 +2508,18 @@
"node": ">=12"
},
"optionalDependencies": {
"@bufbuild/buf-darwin-arm64": "1.32.1",
"@bufbuild/buf-darwin-x64": "1.32.1",
"@bufbuild/buf-linux-aarch64": "1.32.1",
"@bufbuild/buf-linux-x64": "1.32.1",
"@bufbuild/buf-win32-arm64": "1.32.1",
"@bufbuild/buf-win32-x64": "1.32.1"
"@bufbuild/buf-darwin-arm64": "1.34.0",
"@bufbuild/buf-darwin-x64": "1.34.0",
"@bufbuild/buf-linux-aarch64": "1.34.0",
"@bufbuild/buf-linux-x64": "1.34.0",
"@bufbuild/buf-win32-arm64": "1.34.0",
"@bufbuild/buf-win32-x64": "1.34.0"
}
},
"node_modules/@bufbuild/buf-darwin-arm64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.32.1.tgz",
"integrity": "sha512-Duw4StB5sth8s4cQfOa7Be6+OAXfGuuo3ZOkUzJTxWOVH0sWq0nTkO90kXMJOjOkmB/JMnqRQcVAdKuu9u1pcw==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.34.0.tgz",
"integrity": "sha512-3+h/jSAr7H+KT8MWWRMbN/gQ87KlGLkTGwm4/mpry1ap9Thw/UdOrk5MfmbK3CRM/rlw4mAn1Egu/Q7R5eO98g==",
"cpu": [
"arm64"
],
@@ -2533,9 +2533,9 @@
}
},
"node_modules/@bufbuild/buf-darwin-x64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.32.1.tgz",
"integrity": "sha512-3ANVbOoSmfdFxhOvjMDLTr2u35+mdEQcF9Tx39ZEA+Las0WucV6n/bGPwucpH04a9UsW59npNt3IzA4VvUDcyw==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.34.0.tgz",
"integrity": "sha512-Jdm0COuA2CMKoef2H8rBsRnc16mJUmCQ2KvJH5otvFrMhzPmr1MUyicCybY26HXFD/6DcnbWZvf6W8LfDMMyGQ==",
"cpu": [
"x64"
],
@@ -2549,9 +2549,9 @@
}
},
"node_modules/@bufbuild/buf-linux-aarch64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.32.1.tgz",
"integrity": "sha512-QdGirTSFU/WzI/lBo9ph4ThQJS9S8Zm3l/7hg+07GrF57VqB1pUZvnh2298R10/kLKP6lpMtqeVrjMhIcHtxTw==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.34.0.tgz",
"integrity": "sha512-utSspJlPmVPh4Ugvn9k7MEEMHDZMI13jvwHkBE6wNSkYxxYTRR5zLHtmysaYQo51Fx+3ar6mL4HnhTqLrgO5GA==",
"cpu": [
"arm64"
],
@@ -2565,9 +2565,9 @@
}
},
"node_modules/@bufbuild/buf-linux-x64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.32.1.tgz",
"integrity": "sha512-6R8whslj+6WQi9nUjVkNx6AW64czFOFD22dLmrB4i3bY/WDku+/5CNHBU/On738pmgujQrEVT4ztB6fVmVtKOg==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.34.0.tgz",
"integrity": "sha512-INCGsPLBL4aK2jHBMdZzEJUPv7f6f8skIUMMip7YdJl1nsIh27C/Dl7Q6A6/sv9IhYibWKAoxP7SuiOv2iTdEw==",
"cpu": [
"x64"
],
@@ -2581,9 +2581,9 @@
}
},
"node_modules/@bufbuild/buf-win32-arm64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.32.1.tgz",
"integrity": "sha512-QPDxdLRxJpiCTEx7/5bIN3V3EPGvZ1+dyEco6d1qIydDrH9BbCWNy9YLPJOaDxAbewW4lrAX73FmMTTM4tNtbw==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.34.0.tgz",
"integrity": "sha512-g1EogebjJ93bzmyn/fEi47tTz57M+7WYZ7/vX+DFXgLLYIxTWHDK4YN+3Hs+K7Sbx7KaVdsdEqof8xZ4WoVFnQ==",
"cpu": [
"arm64"
],
@@ -2597,9 +2597,9 @@
}
},
"node_modules/@bufbuild/buf-win32-x64": {
"version": "1.32.1",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.32.1.tgz",
"integrity": "sha512-rZSM5id3zko+YQICZB3ypj+AVL0rcN7gra8SN4Ep4aOWAH6gib3RgH51cFcq9VgI1N1xTBy8wZvQMnMLPBn2zg==",
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.34.0.tgz",
"integrity": "sha512-0rPXP7pV7+2twhcpN8hDdgV68UCiazLRcMBjWKubwcSJhAP8jRLqSJv3VGnXmpdYPbYGDQ0htfcgLNUvzllRhQ==",
"cpu": [
"x64"
],
@@ -2613,18 +2613,18 @@
}
},
"node_modules/@bufbuild/protobuf": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.9.0.tgz",
"integrity": "sha512-W7gp8Q/v1NlCZLsv8pQ3Y0uCu/SHgXOVFK+eUluUKWXmsb6VHkpNx0apdOWWcDbB9sJoKeP8uPrjmehJz6xETQ=="
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="
},
"node_modules/@bufbuild/protoc-gen-es": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-1.9.0.tgz",
"integrity": "sha512-LJy1nC3Jsfdhs9v48P7qF6YXIqh+usFhXSVzJDTmw0yKjxQ3CKBNISRtaMql/g9hb1MLRU6unHCcFfdz4HSO/Q==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-1.10.0.tgz",
"integrity": "sha512-zBYBsVT/ul4uZb6F+kD7/k4sWNHVVbEPfJwKi0FDr+9VJo8MKIofI6pkr5ksBLr4fi/74r+e/75Xi/0clL5dXg==",
"dev": true,
"dependencies": {
"@bufbuild/protobuf": "^1.9.0",
"@bufbuild/protoplugin": "1.9.0"
"@bufbuild/protobuf": "^1.10.0",
"@bufbuild/protoplugin": "1.10.0"
},
"bin": {
"protoc-gen-es": "bin/protoc-gen-es"
@@ -2633,7 +2633,7 @@
"node": ">=14"
},
"peerDependencies": {
"@bufbuild/protobuf": "1.9.0"
"@bufbuild/protobuf": "1.10.0"
},
"peerDependenciesMeta": {
"@bufbuild/protobuf": {
@@ -2642,12 +2642,12 @@
}
},
"node_modules/@bufbuild/protoplugin": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-1.9.0.tgz",
"integrity": "sha512-/mxMiGs5h78RUHT7v4+mv0Wt0gyRf/SOS5PLzKEg2sclEAlFPbXfZ8HjlvxJpXZP/YpP3HvsW/mil3E69G0mXg==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-1.10.0.tgz",
"integrity": "sha512-u6NE4vL0lw1+EK4/PiE/SQB7fKO4LRJNTEScIXVOi2x88K/c8WKc/k0KyEaA0asVBMpwekJQZGnRyj04ZtN5Gg==",
"dev": true,
"dependencies": {
"@bufbuild/protobuf": "1.9.0",
"@bufbuild/protobuf": "1.10.0",
"@typescript/vfs": "^1.4.0",
"typescript": "4.5.2"
}

View File

@@ -21,7 +21,7 @@
"@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.1",
"@connectrpc/connect-web": "^1.4.0",
@@ -40,8 +40,8 @@
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.4",
"@angular/compiler-cli": "^17.3.0",
"@bufbuild/buf": "^1.32.1",
"@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.1",
"@ngx-formly/schematics": "^6.3.0",

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/object/v1alpha1/object.proto (package holos.object.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.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,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/platform/v1alpha1/platform.proto (package holos.platform.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/platform/v1alpha1/platform_service.proto (package holos.platform.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/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

@@ -1,33 +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
Name: string
Version: string
Namespace: string
Repo: {
name: string | *""
url: string | *""
}
Repo: {
name: string | *""
url: string | *""
}
Values: {...}
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)
}
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 represents the build plan provided to the holos cli.
Output: v1.#BuildPlan & {
spec: components: helmChartList: [Chart]
}
spec: components: helmChartList: [Chart]
}
}

View File

@@ -58,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},
]
}
}
@@ -237,73 +237,73 @@ let FormBuilder = v1.#FormBuilder & {
}
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,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,48 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
// Label is an arbitrary unique identifier internal to holos itself. The holos
// cli is expected to never write a Label value to rendered output files,
// therefore use a [Label] then the identifier must be unique and internal.
// Defined as a type for clarity and type checking.
//
// A Label is useful to convert a CUE struct to a list, for example producing a list of [APIObject] resources from an [APIObjectMap]. A CUE struct using
// Label keys is guaranteed to not lose data when rendering output because a
// Label is expected to never be written to the final output.
#Label: string
// Kind is a kubernetes api object kind. Defined as a type for clarity and type checking.
#Kind: string
// APIObject represents the most basic generic form of a single kubernetes api
// object. Represented as a JSON object internally for compatibility between
// tools, for example loading from CUE.
#APIObject: structpb.#Struct
// 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].
#APIObjectMap: {[string]: [string]: string}
// APIObjects represents Kubernetes API objects defined directly from CUE code.
// Useful to mix in resources to any kind of [HolosComponent], for example
// adding an ExternalSecret resource to a [HelmChart].
//
// [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].
#APIObjects: {
apiObjects: {[string]: [string]: #APIObject} @go(APIObjects,map[Kind]map[Label]APIObject)
apiObjectMap: #APIObjectMap @go(APIObjectMap)
}

View File

@@ -0,0 +1,105 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
// FilePath represents a file path.
#FilePath: string
// FileContent represents file contents.
#FileContent: string
// FileContentMap represents a mapping of file paths to file contents. Paths
// are relative to the `holos` output "deploy" directory, and may contain
// sub-directories.
#FileContentMap: {[string]: #FileContent}
// BuildPlan represents a build plan for the holos cli to execute. The purpose
// of a BuildPlan is to define one or more [HolosComponent] kinds. For example a
// [HelmChart], [KustomizeBuild], or [KubernetesObjects].
//
// 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.
#BuildPlan: {
kind: string & "BuildPlan" @go(Kind)
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
spec: #BuildPlanSpec @go(Spec)
}
// BuildPlanSpec represents the specification of the build plan.
#BuildPlanSpec: {
// Disabled causes the holos cli to take no action over the [BuildPlan].
disabled?: bool @go(Disabled)
// Components represents multiple [HolosComponent] kinds to manage.
components?: #BuildPlanComponents @go(Components)
}
#BuildPlanComponents: {
resources?: {[string]: #KubernetesObjects} @go(Resources,map[Label]KubernetesObjects)
kubernetesObjectsList?: [...#KubernetesObjects] @go(KubernetesObjectsList,[]KubernetesObjects)
helmChartList?: [...#HelmChart] @go(HelmChartList,[]HelmChart)
kustomizeBuildList?: [...#KustomizeBuild] @go(KustomizeBuildList,[]KustomizeBuild)
}
// HolosComponent defines the fields common to all holos component kinds. Every
// holos component kind should embed HolosComponent.
#HolosComponent: {
// Kind is a string value representing the resource this object represents.
kind: string @go(Kind)
// APIVersion represents the versioned schema of this representation of an object.
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
// Metadata represents data about the holos component such as the Name.
metadata: #Metadata @go(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 @go(APIObjectMap)
// 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 @go(DeployFiles)
// Kustomize represents a kubectl kustomize build post-processing step.
kustomize?: #Kustomize @go(Kustomize)
// Skip causes holos to take no action regarding this component.
skip: bool & (bool | *false) @go(Skip)
}
// Metadata represents data about the holos component such as the Name.
#Metadata: {
// Name represents the name of the holos component.
name: string @go(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 @go(Namespace)
}
// 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...
#Kustomize: {
// KustomizeFiles holds file contents for kustomize, e.g. patch files.
kustomizeFiles?: #FileContentMap @go(KustomizeFiles)
// ResourcesFile is the file name used for api objects in kustomization.yaml
resourcesFile?: string @go(ResourcesFile)
}

View File

@@ -0,0 +1,15 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
#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"

View File

@@ -0,0 +1,52 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
import "google.golang.org/protobuf/types/known/structpb"
#PlatformMetadata: {
// Name represents the Platform name.
name: string @go(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.
#Platform: {
// Kind is a string value representing the resource this object represents.
kind: string & "Platform" @go(Kind)
// APIVersion represents the versioned schema of this representation of an object.
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
// Metadata represents data about the object such as the Name.
metadata: #PlatformMetadata @go(Metadata)
// Spec represents the specification.
spec: #PlatformSpec @go(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.
#PlatformSpec: {
// Model represents the platform model holos gets from from the
// PlatformService.GetPlatform rpc method and provides to CUE using a tag.
model: structpb.#Struct @go(Model)
// Components represents a list of holos components to manage.
components: [...#PlatformSpecComponent] @go(Components,[]PlatformSpecComponent)
}
// PlatformSpecComponent represents a holos component to build or render.
#PlatformSpecComponent: {
// Path is the path of the component relative to the platform root.
path: string @go(Path)
// Cluster is the cluster name to provide when rendering the component.
cluster: string @go(Cluster)
}

View File

@@ -0,0 +1,28 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
// 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.
//
// [Platform] defines the complete configuration of a platform. With the holos
// reference platform this takes the shape of one management cluster and at
// least two workload cluster. Each cluster has multiple [HolosComponent]
// resources applied to it.
//
// Each holos component path, e.g. `components/namespaces` produces exactly one
// [BuildPlan] which in turn contains a set of [HolosComponent] kinds.
//
// The primary kinds of [HolosComponent] are:
//
// 1. [HelmChart] to render config from a helm chart.
// 2. [KustomizeBuild] to render config from [Kustomize]
// 3. [KubernetesObjects] to render [APIObjects] defined directly in CUE
// configuration.
//
// Note that Holos operates as a data pipeline, so the output of a [HelmChart]
// may be provided to [Kustomize] for post-processing.
package v1alpha2

View File

@@ -0,0 +1,47 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
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.
#HelmChart: {
#HolosComponent
kind: string & "HelmChart" @go(Kind)
// Chart represents a helm chart to manage.
chart: #Chart @go(Chart)
// ValuesContent represents the values.yaml file holos passes to the `helm
// template` command.
valuesContent: string @go(ValuesContent)
// EnableHooks enables helm hooks when executing the `helm template` command.
enableHooks: bool & (bool | *false) @go(EnableHooks)
}
// Chart represents a helm chart.
#Chart: {
// Name represents the chart name.
name: string @go(Name)
// Version represents the chart version.
version: string @go(Version)
// Release represents the chart release when executing helm template.
release: string @go(Release)
// Repository represents the repository to fetch the chart from.
repository?: #Repository @go(Repository)
}
// Repository represents a helm chart repository.
#Repository: {
name: string @go(Name)
url: string @go(URL)
}

View File

@@ -0,0 +1,14 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
#KubernetesObjectsKind: "KubernetesObjects"
// KubernetesObjects represents a [HolosComponent] composed of Kubernetes API
// objects provided directly from CUE using [APIObjects].
#KubernetesObjects: {
#HolosComponent
kind: string & "KubernetesObjects" @go(Kind)
}

View File

@@ -0,0 +1,12 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/core/v1alpha2
package v1alpha2
// KustomizeBuild represents a [HolosComponent] that renders plain yaml files in
// the holos component directory using `kubectl kustomize build`.
#KustomizeBuild: {
#HolosComponent
kind: string & "KustomizeBuild" @go(Kind)
}

View File

@@ -0,0 +1,30 @@
// Code generated by cue get go. DO NOT EDIT.
//cue:generate cue get go github.com/holos-run/holos/api/meta/v1alpha2
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.
#TypeMeta: {
// Kind is a string value representing the resource this object represents.
kind: string @go(Kind)
// APIVersion defines the versioned schema of this representation of an object.
apiVersion: string & (string | *"v1alpha2") @go(APIVersion)
}
// Discriminator discriminates the kind of an api object.
#Discriminator: _
// 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.
#ObjectMeta: {
// Name uniquely identifies the holos component instance and must be suitable as a file name.
name?: string @go(Name)
// Namespace confines a holos component to a single namespace via kustomize if set.
namespace?: string @go(Namespace)
}

View File

@@ -0,0 +1,132 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_functions.yaml
package v1beta1
import "strings"
// A Function installs an OCI compatible Crossplane package,
// extending
// Crossplane with support for a new kind of composition function.
//
//
// Read the Crossplane documentation for
// [more information about
// Functions](https://docs.crossplane.io/latest/concepts/composition-functions).
#Function: {
// 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: "pkg.crossplane.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: "Function"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// FunctionSpec specifies the configuration of a Function.
spec!: #FunctionSpec
}
// FunctionSpec specifies the configuration of a Function.
#FunctionSpec: {
// Map of string keys and values that can be used to organize and
// categorize
// (scope and select) objects. May match selectors of replication
// controllers
// and services.
// More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
commonLabels?: {
[string]: string
}
controllerConfigRef?: {
// Name of the ControllerConfig.
name: string
}
// IgnoreCrossplaneConstraints indicates to the package manager
// whether to
// honor Crossplane version constrains specified by the package.
// Default is false.
ignoreCrossplaneConstraints?: bool | *false
// Package is the name of the package that is being requested.
package: string
// PackagePullPolicy defines the pull policy for the package.
// Default is IfNotPresent.
packagePullPolicy?: string | *"IfNotPresent"
// PackagePullSecrets are named secrets in the same namespace that
// can be used
// to fetch packages from private registries.
packagePullSecrets?: [...{
// Name of the referent.
// More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
// TODO: Add other useful fields. apiVersion, kind, uid?
name?: string
}]
// RevisionActivationPolicy specifies how the package controller
// should
// update from one revision to the next. Options are Automatic or
// Manual.
// Default is Automatic.
revisionActivationPolicy?: string | *"Automatic"
// RevisionHistoryLimit dictates how the package controller cleans
// up old
// inactive package revisions.
// Defaults to 1. Can be disabled by explicitly setting to 0.
revisionHistoryLimit?: int | *1
// RuntimeConfigRef references a RuntimeConfig resource that will
// be used
// to configure the package runtime.
runtimeConfigRef?: {
// API version of the referent.
apiVersion?: string | *"pkg.crossplane.io/v1beta1"
// Kind of the referent.
kind?: string | *"DeploymentRuntimeConfig"
// Name of the RuntimeConfig.
name: string
} | *{
name: "default"
}
// SkipDependencyResolution indicates to the package manager
// whether to skip
// resolving dependencies for a package. Setting this value to
// true may have
// unintended consequences.
// Default is false.
skipDependencyResolution?: bool | *false
}

View File

@@ -0,0 +1,136 @@
// Code generated by timoni. DO NOT EDIT.
//timoni:generate timoni vendor crd -f https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_providers.yaml
package v1
import "strings"
// A Provider installs an OCI compatible Crossplane package,
// extending
// Crossplane with support for new kinds of managed resources.
//
//
// Read the Crossplane documentation for
// [more information about
// Providers](https://docs.crossplane.io/latest/concepts/providers).
#Provider: {
// 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: "pkg.crossplane.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: "Provider"
metadata!: {
name!: strings.MaxRunes(253) & strings.MinRunes(1) & {
string
}
namespace?: strings.MaxRunes(63) & strings.MinRunes(1) & {
string
}
labels?: {
[string]: string
}
annotations?: {
[string]: string
}
}
// ProviderSpec specifies details about a request to install a
// provider to
// Crossplane.
spec!: #ProviderSpec
}
// ProviderSpec specifies details about a request to install a
// provider to
// Crossplane.
#ProviderSpec: {
// Map of string keys and values that can be used to organize and
// categorize
// (scope and select) objects. May match selectors of replication
// controllers
// and services.
// More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
commonLabels?: {
[string]: string
}
controllerConfigRef?: {
// Name of the ControllerConfig.
name: string
}
// IgnoreCrossplaneConstraints indicates to the package manager
// whether to
// honor Crossplane version constrains specified by the package.
// Default is false.
ignoreCrossplaneConstraints?: bool | *false
// Package is the name of the package that is being requested.
package: string
// PackagePullPolicy defines the pull policy for the package.
// Default is IfNotPresent.
packagePullPolicy?: string | *"IfNotPresent"
// PackagePullSecrets are named secrets in the same namespace that
// can be used
// to fetch packages from private registries.
packagePullSecrets?: [...{
// Name of the referent.
// More info:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
// TODO: Add other useful fields. apiVersion, kind, uid?
name?: string
}]
// RevisionActivationPolicy specifies how the package controller
// should
// update from one revision to the next. Options are Automatic or
// Manual.
// Default is Automatic.
revisionActivationPolicy?: string | *"Automatic"
// RevisionHistoryLimit dictates how the package controller cleans
// up old
// inactive package revisions.
// Defaults to 1. Can be disabled by explicitly setting to 0.
revisionHistoryLimit?: int | *1
// RuntimeConfigRef references a RuntimeConfig resource that will
// be used
// to configure the package runtime.
runtimeConfigRef?: {
// API version of the referent.
apiVersion?: string | *"pkg.crossplane.io/v1beta1"
// Kind of the referent.
kind?: string | *"DeploymentRuntimeConfig"
// Name of the RuntimeConfig.
name: string
} | *{
name: "default"
}
// SkipDependencyResolution indicates to the package manager
// whether to skip
// resolving dependencies for a package. Setting this value to
// true may have
// unintended consequences.
// Default is false.
skipDependencyResolution?: bool | *false
}

View File

@@ -32,9 +32,7 @@ import "strings"
// Configuration for access control on workloads. See more details
// at:
// https://istio.io/docs/reference/config/security/authorization-policy.html
#AuthorizationPolicySpec: ({} | {
provider: _
}) & {
#AuthorizationPolicySpec: {
// Optional.
//
// Valid Options: ALLOW, DENY, AUDIT, CUSTOM

View File

@@ -1 +1,2 @@
module: "user.holos.run/platform"
language: version: "v0.9.2"

View File

@@ -78,13 +78,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},
]
}
}
@@ -281,73 +281,73 @@ let FormBuilder = v1.#FormBuilder & {
}
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,67 @@
package holos
import "strings"
// #AppInfo represents the data structure for an application deployed onto the
// platform. This definition constraints the sechema defined at the root.
_AppInfo: #AppInfo & {
metadata: name: string
metadata: namespace: "\(spec.env)-\(metadata.name)"
spec: env: string
spec: region: hostname: spec.dns.segments._region
spec: global: hostname: spec.dns.segments._global
spec: dns: segments: {
env: [] | *[spec.env]
name: [] | [string | *metadata.name]
cluster: [] | *[_ClusterName]
domain: [] | *[_Platform.Model.org.domain]
_region: strings.Join(env+name+cluster+domain, ".")
_global: strings.Join(env+name+domain, ".")
}
}
// #AppRoute represents the HTTPRoute resources in the namespace of the Gateway.
#AppRoute: {
AppInfo: #AppInfo
Resources: {
HTTPRoute: (HTTPRouteApp & {Hostname: AppInfo.spec.region.hostname}).HTTPRoute
HTTPRoute: (HTTPRouteApp & {Hostname: AppInfo.spec.global.hostname}).HTTPRoute
}
let HTTPRouteApp = {
Hostname: string
HTTPRoute: (Hostname): {
metadata: namespace: #IstioGatewaysNamespace
metadata: labels: AppInfo.metadata.labels
metadata: annotations: AppInfo.metadata.annotations
spec: hostnames: [Hostname]
spec: parentRefs: [{
name: "default"
namespace: #IstioGatewaysNamespace
}]
spec: rules: [
{
matches: [{path: {type: "PathPrefix", value: "/"}}]
backendRefs: [{
name: AppInfo.metadata.name
namespace: AppInfo.metadata.namespace
port: AppInfo.spec.port
}]
},
{
// match the authproxy path prefix
matches: [{path: {type: "PathPrefix", value: _AuthProxy.pathPrefix}}]
backendRefs: [{
name: _AuthProxy.metadata.name
namespace: _AuthProxy.metadata.namespace
port: _AuthProxy.servicePort
}]
},
]
}
}
}

View File

@@ -0,0 +1,3 @@
package holos
_AppInfo: #AppInfo & {spec: env: "dev"}

View File

@@ -0,0 +1,108 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let Image = "quay.io/holos-run/holos:v0.83.1-7-gd9fe32b"
_AppInfo: spec: component: "app"
let Objects = {
Name: _AppInfo.status.component
Namespace: _AppInfo.metadata.namespace
Resources: [_]: [_]: metadata: namespace: Namespace
let Metadata = _AppInfo.metadata
Resources: {
let MatchLabels = {"app.kubernetes.io/component": "server"}
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
Service: holos: {
apiVersion: "v1"
metadata: Metadata
spec: {
type: "ClusterIP"
selector: MatchLabels
ports: [{
appProtocol: "http2"
name: "http"
port: _AppInfo.spec.port
protocol: "TCP"
targetPort: _AppInfo.spec.port
}, {
appProtocol: "http"
name: "metrics"
port: 9090
protocol: "TCP"
targetPort: 9090
}]
}
}
Deployment: holos: {
metadata: Metadata
metadata: labels: MatchLabels
spec: {
selector: matchLabels: MatchLabels
template: metadata: labels: Metadata.labels
template: metadata: labels: MatchLabels
template: metadata: labels: "sidecar.istio.io/inject": "true"
strategy: rollingUpdate: maxSurge: 1
strategy: rollingUpdate: maxUnavailable: 0
template: {
spec: {
serviceAccountName: Metadata.name
securityContext: seccompProfile: type: "RuntimeDefault"
containers: [
{
name: Metadata.name
image: Image
imagePullPolicy: "IfNotPresent"
command: [
"/app/bin/holos",
"server",
"--log-format=json",
"--oidc-issuer=\(_AuthProxy.issuerURL)",
"--oidc-audience=\(_AuthProxy.projectID)",
]
env: [
{
name: "TZ"
value: "America/Los_Angeles"
},
{
name: "DATABASE_URL"
valueFrom: secretKeyRef: {
key: "uri"
name: "holos-pguser-holos"
}
},
]
ports: [
{
containerPort: 3000
name: "http"
protocol: "TCP"
},
]
securityContext: capabilities: drop: ["ALL"]
securityContext: allowPrivilegeEscalation: false
securityContext: runAsNonRoot: true
resources: limits: {
cpu: "0.5"
memory: "512Mi"
}
resources: requests: resources.limits
},
]
}
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
package holos
_AppInfo: #AppInfo & {
metadata: name: "holos"
metadata: labels: {
"app.holos.run/environment": spec.env
"app.holos.run/name": metadata.name
"app.holos.run/component": spec.component
"render.holos.run/component": status.component
}
spec: env: string
spec: port: 3000
spec: component: "app" | "infra" | "routes"
spec: dns: segments: name: ["app"]
status: component: spec.env + "-" + metadata.name + "-" + spec.component
}

View File

@@ -0,0 +1,72 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
_AppInfo: spec: component: "infra"
let Objects = {
Name: _AppInfo.status.component
Namespace: _AppInfo.metadata.namespace
Resources: [_]: [_]: metadata: namespace: Namespace
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
let Metadata = _AppInfo.metadata
Resources: {
ServiceAccount: holos: {
metadata: Metadata
// TODO(jeff): The ecr-creds-refresher name should be refactored to a root
// level private var so we can update it in one place.
// Refer to ecr-creds-refresher.cue
imagePullSecrets: [{name: "ecr-creds-\(AWS_ACCOUNT)"}]
}
PostgresCluster: holos: {
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
metadata: name: "holos"
metadata: Metadata
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.1-0"
instances: [{
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
podAffinityTerm: {
labelSelector: matchLabels: "postgres-operator.crunchydata.com/cluster": metadata.name
topologyKey: "topology.kubernetes.io/zone"
}
weight: 1
}]
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "1Gi"
}
name: "db"
replicas: 1
}]
port: 5432
postgresVersion: 16
users: [{
databases: ["holos"]
name: "holos"
options: "SUPERUSER"
}]
backups: pgbackrest: {
global: {
"archive-async": "y"
"archive-push-queue-max": "100MiB"
"spool-path": "/pgdata/backups"
}
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.47-2"
repos: [{
name: "repo1"
volume: volumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "1Gi"
}
}]
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
_AppInfo: spec: component: "routes"
let Objects = {
Name: _AppInfo.status.component
Namespace: #IstioGatewaysNamespace
Resources: [_]: [_]: metadata: namespace: Namespace
// HTTPRoute resources
Resources: (#AppRoute & {AppInfo: _AppInfo}).Resources
}

View File

@@ -2,7 +2,7 @@ package holos
import (
"encoding/yaml"
v1 "github.com/holos-run/holos/api/v1alpha1"
core "github.com/holos-run/holos/api/core/v1alpha2"
kc "sigs.k8s.io/kustomize/api/types"
@@ -26,6 +26,13 @@ import (
es "external-secrets.io/externalsecret/v1beta1"
pc "postgres-operator.crunchydata.com/postgrescluster/v1beta1"
app "argoproj.io/application/v1alpha1"
cpv1 "pkg.crossplane.io/provider/v1"
cpdrcv1beta1 "pkg.crossplane.io/deploymentruntimeconfig/v1beta1"
cpfuncv1beta1 "pkg.crossplane.io/function/v1beta1"
cpawspcv1beta1 "aws.upbound.io/providerconfig/v1beta1"
)
// #Resources represents kubernetes api objects output along side a build plan.
@@ -61,6 +68,12 @@ import (
Gateway: [string]: gwv1.#Gateway & {
spec: gatewayClassName: string | *"istio"
}
// Crossplane resources
DeploymentRuntimeConfig: [string]: cpdrcv1beta1.#DeploymentRuntimeConfig
Provider: [string]: cpv1.#Provider
Function: [string]: cpfuncv1beta1.#Function
ProviderConfig: [string]: cpawspcv1beta1.#ProviderConfig
}
#ReferenceGrant: rgv1.#ReferenceGrant & {
@@ -90,13 +103,13 @@ import (
Values: {...}
Chart: v1.#HelmChart & {
metadata: name: string | *Name
namespace: string | *Namespace
chart: name: string | *Name
chart: release: chart.name
chart: version: string | *Version
chart: repository: Repo
Chart: core.#HelmChart & {
metadata: name: string | *Name
metadata: namespace: string | *Namespace
chart: name: string | *Name
chart: release: chart.name
chart: version: string | *Version
chart: repository: Repo
// Render the values to yaml for holos to provide to helm.
valuesContent: yaml.Marshal(Values)
@@ -106,15 +119,15 @@ import (
// resourcesFile represents the file helm output is written two and
// kustomize reads from. Typically "resources.yaml" but referenced as a
// constant to ensure the holos cli uses the same file.
resourcesFile: v1.#ResourcesFile
kustomize: resourcesFile: core.#ResourcesFile
// kustomizeFiles represents the files in a kustomize directory tree.
kustomizeFiles: v1.#FileContentMap
kustomize: kustomizeFiles: core.#FileContentMap
for FileName, Object in KustomizeFiles {
kustomizeFiles: "\(FileName)": yaml.Marshal(Object)
kustomize: kustomizeFiles: "\(FileName)": yaml.Marshal(Object)
}
}
apiObjectMap: (v1.#APIObjects & {apiObjects: Resources}).apiObjectMap
apiObjectMap: (#APIObjects & {apiObjects: Resources}).apiObjectMap
}
// EnableKustomizePostProcessor processes helm output with kustomize if true.
@@ -134,7 +147,7 @@ import (
"kustomization.yaml": kc.#Kustomization & {
apiVersion: "kustomize.config.k8s.io/v1beta1"
kind: "Kustomization"
resources: [v1.#ResourcesFile, for FileName, _ in KustomizeResources {FileName}]
resources: [core.#ResourcesFile, for FileName, _ in KustomizeResources {FileName}]
patches: [for x in KustomizePatches {x}]
}
}
@@ -146,7 +159,9 @@ import (
KustomizeResources: [FileName=string]: {...}
// output represents the build plan provided to the holos cli.
Output: v1.#BuildPlan & {
Output: #BuildPlan & {
_Name: Name
_Namespace: Namespace
spec: components: helmChartList: [Chart]
}
}
@@ -156,12 +171,13 @@ import (
// Name represents the holos component name
Name: string
Kustomization: v1.#KustomizeBuild & {
Kustomization: core.#KustomizeBuild & {
metadata: name: string | *Name
}
// output represents the build plan provided to the holos cli.
Output: v1.#BuildPlan & {
Output: #BuildPlan & {
_Name: Name
spec: components: kustomizeBuildList: [Kustomization]
}
}
@@ -175,11 +191,81 @@ import (
Resources: #Resources
// output represents the build plan provided to the holos cli.
Output: v1.#BuildPlan & {
Output: #BuildPlan & {
_Name: Name
_Namespace: Namespace
// resources is a map unlike other build plans which use a list.
spec: components: resources: "\(Name)": {
metadata: name: Name
apiObjectMap: (v1.#APIObjects & {apiObjects: Resources}).apiObjectMap
metadata: name: Name
metadata: namespace: Namespace
apiObjectMap: (#APIObjects & {apiObjects: Resources}).apiObjectMap
}
}
}
#BuildPlan: core.#BuildPlan & {
_Name: string
_Namespace?: string
let NAME = "gitops/\(_Name)"
// Render the ArgoCD Application for GitOps.
spec: components: resources: (NAME): {
metadata: name: NAME
if _Namespace != _|_ {
metadata: namespace: _Namespace
}
deployFiles: (#Argo & {ComponentName: _Name}).deployFiles
}
}
// #ArgoApplication represents an argocd Application resource for each
// component, written using the #HolosComponent.deployFiles field.
#Argo: {
ComponentName: string
Application: app.#Application & {
metadata: name: ComponentName
metadata: namespace: "argocd"
spec: {
destination: server: "https://kubernetes.default.svc"
project: "default"
source: {
path: "\(_Platform.Model.argocd.deployRoot)/deploy/clusters/\(_ClusterName)/components/\(ComponentName)"
repoURL: _Platform.Model.argocd.repoURL
targetRevision: _Platform.Model.argocd.targetRevision
}
}
}
// deployFiles represents the output files to write along side the component.
deployFiles: "clusters/\(_ClusterName)/gitops/\(ComponentName).application.gen.yaml": yaml.Marshal(Application)
}
// #APIObjects defines the output format for kubernetes api objects. The holos
// cli expects the yaml representation of each api object in the apiObjectMap
// field.
#APIObjects: core.#APIObjects & {
// apiObjects represents the un-marshalled form of each kubernetes api object
// managed by a holos component.
apiObjects: {
[Kind=string]: {
[string]: {
kind: Kind
...
}
}
ConfigMap: [string]: corev1.#ConfigMap & {apiVersion: "v1"}
}
// apiObjectMap holds the marshalled representation of apiObjects
apiObjectMap: {
for kind, v in apiObjects {
"\(kind)": {
for name, obj in v {
"\(name)": yaml.Marshal(obj)
}
}
}
}
}

View File

@@ -60,6 +60,7 @@ let Chart = {
// Refer to https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/
let Policy = [
"g, argocd-view, role:readonly",
"g, prod-cluster-view, role:readonly",
"g, prod-cluster-edit, role:readonly",
"g, prod-cluster-admin, role:admin",

View File

@@ -0,0 +1,11 @@
package holos
// _DBName is the database name used across multiple holos components in this project
_DBName: "backstage"
_Component: {
metadata: name: "backstage"
metadata: namespace: "backstage"
spec: hostname: "backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)"
spec: port: 7007
}

View File

@@ -0,0 +1,111 @@
package holos
import (
is "cert-manager.io/issuer/v1"
crt "cert-manager.io/certificate/v1"
)
// Manage an Issuer for the database.
// Both cockroach and postgres handle tls database connections with cert manager
// PGO: https://github.com/CrunchyData/postgres-operator-examples/tree/main/kustomize/certmanager/certman
// CRDB: https://github.com/cockroachdb/helm-charts/blob/3dcf96726ebcfe3784afb526ddcf4095a1684aea/README.md?plain=1#L196-L201
// Refer to [Using Cert Manager to Deploy TLS for Postgres on Kubernetes](https://www.crunchydata.com/blog/using-cert-manager-to-deploy-tls-for-postgres-on-kubernetes)
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let SelfSigned = "\(_DBName)-selfsigned"
let RootCA = "\(_DBName)-root-ca"
let Orgs = ["Database"]
let Objects = {
Name: "backstage-certs"
Namespace: "backstage"
Resources: {
// Put everything in the same namespace.
[_]: {
[NAME=_]: {
metadata: name: NAME
metadata: namespace: Namespace
}
}
Issuer: {
"\(SelfSigned)": is.#Issuer & {
_description: "Self signed issuer to issue ca certs"
metadata: name: SelfSigned
spec: selfSigned: {}
}
"\(RootCA)": is.#Issuer & {
_description: "Root signed intermediate ca to issue mtls database certs"
metadata: name: RootCA
spec: ca: secretName: RootCA
}
}
Certificate: {
"\(RootCA)": crt.#Certificate & {
_description: "Root CA cert for database"
metadata: name: RootCA
spec: {
commonName: RootCA
isCA: true
issuerRef: group: "cert-manager.io"
issuerRef: kind: "Issuer"
issuerRef: name: SelfSigned
privateKey: algorithm: "ECDSA"
privateKey: size: 256
secretName: RootCA
subject: organizations: Orgs
}
}
"\(_DBName)-primary-tls": #DatabaseCert & {
// PGO managed name is "<cluster name>-cluster-cert" e.g. zitadel-cluster-cert
spec: {
commonName: "\(_DBName)-primary"
dnsNames: [
commonName,
"\(commonName).\(Namespace)",
"\(commonName).\(Namespace).svc",
"\(commonName).\(Namespace).svc.cluster.local",
"localhost",
"127.0.0.1",
]
usages: ["digital signature", "key encipherment"]
}
}
"\(_DBName)-repl-tls": #DatabaseCert & {
spec: {
commonName: "_crunchyrepl"
dnsNames: [commonName]
usages: ["digital signature", "key encipherment"]
}
}
"\(_DBName)-client-tls": #DatabaseCert & {
spec: {
commonName: "\(_DBName)-client"
dnsNames: [commonName]
usages: ["digital signature", "key encipherment"]
}
}
}
}
}
#DatabaseCert: crt.#Certificate & {
metadata: name: string
metadata: namespace: string
spec: {
duration: "2160h" // 90d
renewBefore: "360h" // 15d
issuerRef: group: "cert-manager.io"
issuerRef: kind: "Issuer"
issuerRef: name: RootCA
privateKey: algorithm: "ECDSA"
privateKey: size: 256
secretName: metadata.name
subject: organizations: Orgs
}
}

View File

@@ -0,0 +1,7 @@
# Database Certs
This component issues postgres certificates using certmanager. Apply this component to the management cluster.
The purpose is to define customTLSSecret and customReplicationTLSSecret to provide certs that allow the standby to authenticate to the primary. For this type of standby, you must use custom TLS.
Refer to the PGO [Streaming Standby](https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby) tutorial.

View File

@@ -0,0 +1,112 @@
package holos
// TODO This entire config should be removed for clarity. Only the production
// config should be referenced by the Deployment.
_BackstageAppConfig: {
app: {
title: "Holos Portal"
baseUrl: "${BASE_URL}"
}
organization: name: "My Company"
backend: {
// Used for enabling authentication, secret is shared by all backend plugins
// See https://backstage.io/docs/auth/service-to-service-auth for
// information on the format
// auth:
// keys:
// - secret: ${BACKEND_SECRET}
baseUrl: "${BASE_URL}"
listen: port: 7007
// Uncomment the following host directive to bind to specific interfaces
// host: 127.0.0.1
csp: {
"connect-src": ["'self'", "http:", "https:"]
}
// Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
// Default Helmet Content-Security-Policy values can be removed by setting the key to false
cors: {
origin: "${BASE_URL}"
methods: ["GET", "HEAD", "PATCH", "POST", "PUT", "DELETE"]
credentials: true
}
}
// workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
integrations: {
github: [{
host: "github.com"
// This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
// about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration
token: "${GITHUB_TOKEN}"
}]
}
//## Example for how to add your GitHub Enterprise instance using the API:
// - host: ghe.example.net
// apiBaseUrl: https://ghe.example.net/api/v3
// token: ${GHE_TOKEN}
proxy: null
//## Example for how to add a proxy endpoint for the frontend.
//## A typical reason to do this is to handle HTTPS and CORS for internal services.
// endpoints:
// '/test':
// target: 'https://example.com'
// changeOrigin: true
// Reference documentation http://backstage.io/docs/features/techdocs/configuration
// Note: After experimenting with basic setup, use CI/CD to generate docs
// and an external cloud storage when deploying TechDocs for production use-case.
// https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
techdocs: {
builder: "local" // Alternatives - 'external'
generator: {
runIn: "docker"
} // Alternatives - 'local'
publisher: {
type: "local"
}
} // Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
auth: {
environment: "development"
// see https://backstage.io/docs/auth/ to learn about auth providers
providers: {
// See https://backstage.io/docs/auth/guest/provider
guest: {}
}
}
scaffolder: null
// see https://backstage.io/docs/features/software-templates/configuration for software template options
catalog: {
import: {
entityFilename: "catalog-info.yaml"
pullRequestBranchName: "backstage-integration"
}
rules: [{allow: ["Component", "System", "API", "Resource", "Location"]}]
locations: [
{
// Local example data, file locations are relative to the backend process, typically `packages/backend`
type: "file"
target: "../../examples/entities.yaml"
},
{
// Local example template
type: "file"
target: "../../examples/template/template.yaml"
rules: [{
allow: ["Template"]}]
},
{
// Local organizational data
type: "file"
target: "../../org.yaml"
rules: [{
allow: ["User", "Group"]}]
},
]
}
}

View File

@@ -0,0 +1,108 @@
package holos
// Imported from https://github.com/holos-run/portal/blob/d5127715fb4710b9b272768e6a1ce2ff122e693e/app-config.production.yaml
_BackstageProductionConfig: {
app: {
// Should be the same as backend.baseUrl when using the `app-backend` plugin.
baseUrl: "${BASE_URL}"
}
backend: {
// Note that the baseUrl should be the URL that the browser and other clients
// should use when communicating with the backend, i.e. it needs to be
// reachable not just from within the backend host, but from all of your
// callers. When its value is "http://localhost:7007", it's strictly private
// and can't be reached by others.
baseUrl: "${BASE_URL}"
// The listener can also be expressed as a single <host>:<port> string. In this case we bind to
// all interfaces, the most permissive setting. The right value depends on your specific deployment.
listen: ":7007"
// config options: https://node-postgres.com/api/client
database: {
client: "pg"
connection: {
host: "${POSTGRES_HOST}"
port: "${POSTGRES_PORT}"
user: "${POSTGRES_USER}"
password: "${POSTGRES_PASSWORD}"
ssl: ca: "${PGBOUNCER_CA_ROOT}"
}
}
reading: allow: [{
host: "holos.run"
}, {
host: "*.holos.run"
}, {
host: "openinfrastructure.co"
}, {
host: "*.openinfrastructure.co"
}]
}
auth: {
environment: "production"
providers: {
guest: null
holosProxy: {
issuer: "https://login.holos.run"
audience: "269746002573969304"
oidcIdTokenHeader: "x-oidc-id-token"
signIn: resolvers: [{
resolver: "emailMatchingUserEntityProfileEmail"
}, {
resolver: "signInWithoutCatalogUser"
}]
}
}
}
catalog: {
// Overrides the default list locations from app-config.yaml
// Refer to https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog
//
// NOTE: In production, CWD is /app This is different than development where
// CWD is ./packages/backend, As a result, entries cannot be copied verbatim
// from app-config.yaml otherwise they will not resolve to the correct location.
locations: [{
// Initial iam User and Group data is expected to be provided in the Secret.
// Local example data, file locations are relative to the backend process, typically `packages/backend`
type: "file"
target: "/config/iam.yaml"
rules: [{
allow: ["User", "Group"]}]
}]
// GitHub Discovery
// Refer to https://backstage.io/docs/integrations/github/discovery/#configuration
providers: {
github: {
primaryOrg: {
organization: string & _Platform.Model.github.primaryOrg
catalogPath: "/catalog-info.yaml"
filters: {
branch: "main"
repository: ".*" // Regex
}
// same options as in TaskScheduleDefinition
schedule: {
// supports cron, ISO duration, "human duration" as used in code
frequency: minutes: 30
// supports ISO duration, "human duration" as used in code
timeout: minutes: 3
}
}
}
}
}
// Refers to ExternalSecret github-app-credentials. See the readme.md for how this secret is produced.
integrations: {
github: [{
host: "github.com"
apps: [{"$include": "/secrets/github-app-credentials/github-app-credentials.yaml"}]
}]
}
}

View File

@@ -0,0 +1,173 @@
package holos
import "encoding/yaml"
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let ContainerPort = _Component.spec.port
let Objects = {
Name: "\(_Component.metadata.name)-backend"
Namespace: _Component.metadata.namespace
Resources: [_]: [_]: metadata: namespace: Namespace
let MatchLabels = {
"app.kubernetes.io/name": _Component.metadata.name
"app.kubernetes.io/instance": _Component.metadata.name
"app.kubernetes.io/component": Name
}
Resources: {
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
// For the Github integration.
ExternalSecret: [_]: #ExternalSecret & {metadata: namespace: Namespace}
ExternalSecret: githubAppCredentials: metadata: name: "github-app-credentials"
// Primary configuration for backstage to pull unified config data.
ConfigMap: config: {
metadata: namespace: Namespace
metadata: name: Name
data: {
"app-config.yaml": yaml.Marshal(_BackstageAppConfig)
"app-config.production.yaml": yaml.Marshal(_BackstageProductionConfig)
"iam.yaml": yaml.MarshalStream([for x in _BackstageIAMConfig {x}])
}
}
Deployment: backstage: {
metadata: labels: MatchLabels
spec: {
selector: matchLabels: MatchLabels
template: {
metadata: labels: "sidecar.istio.io/inject": "true"
metadata: labels: MatchLabels
spec: {
securityContext: seccompProfile: type: "RuntimeDefault"
serviceAccountName: "default"
containers: [{
name: "backstage-backend"
image: "quay.io/holos-run/portal:latest"
imagePullPolicy: "Always"
// https://github.com/backstage/backstage/blob/v1.27.6/packages/create-app/templates/default-app/packages/backend/Dockerfile#L52
command: [
"node",
"packages/backend",
"--config",
"/config/app-config.yaml",
"--config",
"/config/app-config.production.yaml",
]
// Refer to https://backstage.io/docs/conf/writing#environment-variable-overrides
//
// Individual configuration values can be overridden using
// environment variables prefixed with APP_CONFIG_. Everything
// following that prefix in the environment variable name will be
// used as the config key, with _ replaced by .. For example, to
// override the app.baseUrl value, set the APP_CONFIG_app_baseUrl
// environment variable to the desired value.
//
// The value of the environment variable is parsed as JSON, but it will fall back
// to being interpreted as a string if it fails to parse. Note that if you for
// example want to pass on the string "false", you need to wrap it in double
// quotes, e.g. export APP_CONFIG_example='"false"'.
env: [
{
name: "BASE_URL"
value: "https://" + _Component.spec.hostname
},
{
name: "ORG_DOMAIN"
value: _Platform.Model.org.domain
},
{
name: "POSTGRES_HOST"
valueFrom: secretKeyRef: {
name: "\(_DBName)-pguser-\(_DBName)-admin"
key: "pgbouncer-host"
optional: false
}
},
{
name: "POSTGRES_PORT"
valueFrom: secretKeyRef: {
name: "\(_DBName)-pguser-\(_DBName)-admin"
key: "pgbouncer-port"
optional: false
}
},
{
name: "POSTGRES_USER"
valueFrom: secretKeyRef: {
name: "\(_DBName)-pguser-\(_DBName)-admin"
key: "user"
optional: false
}
},
{
name: "POSTGRES_PASSWORD"
valueFrom: secretKeyRef: {
name: "\(_DBName)-pguser-\(_DBName)-admin"
key: "password"
optional: false
}
},
{
name: "PGBOUNCER_CA_ROOT"
valueFrom: secretKeyRef: {
name: "\(_DBName)-pgbouncer"
key: "pgbouncer-frontend.ca-roots"
optional: false
}
},
]
ports: [{
name: "backend"
containerPort: ContainerPort
protocol: "TCP"
}]
volumeMounts: [
{
name: "config"
mountPath: "/config"
},
{
name: "github-app-credentials"
mountPath: "/secrets/github-app-credentials"
},
]
}]
volumes: [
{
name: "config"
configMap: name: ConfigMap.config.metadata.name
},
{
name: "github-app-credentials"
secret: secretName: ExternalSecret.githubAppCredentials.metadata.name
},
]
}
}
}
}
Service: backstage: {
metadata: labels: MatchLabels
spec: {
selector: MatchLabels
_ports: http: {
port: ContainerPort
targetPort: ContainerPort
protocol: "TCP"
name: "http"
}
ports: [for x in _ports {x}]
}
}
}
}

View File

@@ -0,0 +1,47 @@
package holos
_BackstageIAMConfig: {
groupAdmin: {
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
apiVersion: "backstage.io/v1alpha1"
kind: "Group"
metadata: name: "prod-cluster-admin"
spec: {
type: "team"
children: []
}
}
user1: {
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: "backstage.io/v1alpha1"
kind: "User"
metadata: name: "jeff"
spec: {
profile: email: "jeff@openinfrastructure.co"
memberOf: ["prod-cluster-admin"]
}
}
user2: {
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: "backstage.io/v1alpha1"
kind: "User"
metadata: name: "gary"
spec: {
profile: email: "gary@openinfrastructure.co"
memberOf: ["prod-cluster-admin"]
}
}
user3: {
// https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: "backstage.io/v1alpha1"
kind: "User"
metadata: name: "nate"
spec: {
profile: email: "nate@openinfrastructure.co"
memberOf: ["prod-cluster-admin"]
}
}
}

View File

@@ -0,0 +1,22 @@
# Backstage Secrets
Backstage needs secrets in place on the management cluster to operate.
ExternalSecret `github-app-credentials` generated using:
Refer to the [portal](https://github.com/holos-run/portal) repo.
```sh
# portal is your backstage repository created with `npx @backstage/create-app`
cd portal
# my-org is your github organization.
yarn backstage-cli create-github-app my-org
# Create the secret in your management cluster.
mv github-app-backstage-*-credentials.yaml github-app-credentials.yaml
holos create secret -n backstage --append-hash=false --from-file=github-app-credentials.yaml github-app-credentials
# Remove the secret from the local host.
rm -f github-app-credentials.yaml
```

View File

@@ -0,0 +1,188 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
// Restore from backup. Flip this to true after the database is provisioned and
// a backup has been taken.
let RestoreFromBackup = false
// The Secret containing the pgbackrest s3.conf file.
let S3Secret = "pgbackrest"
let Cluster = _Clusters[_ClusterName]
let DatabaseUser = _DBName
let DatabaseAdmin = "\(_DBName)-admin"
// This must be an external storage bucket for our architecture.
let BucketRepoName = "repo2"
// Restore options. Set the timestamp to a known good point in time.
// time="2024-03-11T17:08:58Z" level=info msg="crunchy-pgbackrest ends"
// let RestoreOptions = ["--type=time", "--target=\"2024-03-11 17:10:00+00\""]
// Restore the most recent backup.
let RestoreOptions = []
let Objects = {
Name: "backstage-database"
Namespace: "backstage"
Resources: {
// All resources go into the same namespace
[_]: [_]: metadata: namespace: Namespace
PostgresCluster: db: HighlyAvailable & {
metadata: name: _DBName
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
postgresVersion: 16
// Custom certs are necessary for streaming standby replication which we use to replicate between two regions.
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/disaster-recovery#streaming-standby
customTLSSecret: name: "\(_DBName)-primary-tls"
customReplicationTLSSecret: name: "\(_DBName)-repl-tls"
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/references/crd/5.5.x/postgrescluster#postgresclusterspecusersindex
users: [
{name: DatabaseUser},
// NOTE: Users with SUPERUSER role cannot log in through pgbouncer. Use options that allow zitadel admin to use pgbouncer.
// Refer to: https://github.com/CrunchyData/postgres-operator/issues/3095#issuecomment-1904712211
{name: DatabaseAdmin, options: "CREATEDB CREATEROLE", databases: [_DBName, "postgres"]},
]
users: [...{databases: [_DBName, ...]}]
instances: [{
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "20Gi"
}
}]
standby: {
repoName: BucketRepoName
if Cluster.primary {
enabled: false
}
if !Cluster.primary {
enabled: true
}
}
// Monitoring configuration
monitoring: pgmonitor: exporter: image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-5.5.1-0"
// Restore from backup if and only if the cluster is primary and
// RestoreFromBackup has transitioned from false to true.
if Cluster.primary && RestoreFromBackup {
dataSource: pgbackrest: {
stanza: "db"
configuration: backups.pgbackrest.configuration
// Restore from known good full backup taken
options: RestoreOptions
global: {
"\(BucketRepoName)-path": "/pgbackrest/\(metadata.namespace)/\(metadata.name)/\(BucketRepoName)"
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
}
repo: {
name: BucketRepoName
s3: backups.pgbackrest.repos[1].s3
}
}
}
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups
backups: pgbackrest: {
configuration: [{secret: name: S3Secret}]
// Defines details for manual pgBackRest backup Jobs
manual: {
// Note: the repoName value must match the config keys in the S3Secret.
// This must be an external repository for backup / restore / regional failovers.
repoName: BucketRepoName
options: ["--type=full", ...]
}
// Defines details for performing an in-place restore using pgBackRest
restore: {
// Enables triggering a restore by annotating the postgrescluster with postgres-operator.crunchydata.com/pgbackrest-restore="$(date)"
enabled: true
repoName: BucketRepoName
}
global: {
// Store only one full backup in the PV because it's more expensive than object storage.
"\(repos[0].name)-retention-full": "1"
// Store 14 days of full backups in the bucket.
"\(BucketRepoName)-retention-full": string | *"14"
"\(BucketRepoName)-retention-full-type": "count" | *"time" // time in days
// Refer to https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#encryption
"\(BucketRepoName)-cipher-type": "aes-256-cbc"
// "The convention we recommend for setting this variable is /pgbackrest/$NAMESPACE/$CLUSTER_NAME/repoN"
// Ref: https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/backups-disaster-recovery/backups#understanding-backup-configuration-and-basic-operations
"\(BucketRepoName)-path": "/pgbackrest/\(metadata.namespace)/\(metadata.name)/\(manual.repoName)"
}
repos: [
{
name: "repo1"
volume: volumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"4Gi"
}
},
{
name: BucketRepoName
// Full backup weekly on Sunday at 1am, differntial daily at 1am every day except Sunday.
schedules: full: string | *"0 1 * * 0"
schedules: differential: string | *"0 1 * * 1-6"
s3: {
bucket: _BackupBucket.metadata.name
region: _BackupBucket.spec.region
endpoint: "s3.dualstack.\(region).amazonaws.com"
}
},
]
}
}
}
}
}
// Refer to https://github.com/holos-run/postgres-operator-examples/blob/main/kustomize/high-availability/ha-postgres.yaml
let HighlyAvailable = {
apiVersion: "postgres-operator.crunchydata.com/v1beta1"
kind: "PostgresCluster"
metadata: name: string
spec: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0"
postgresVersion: 16
instances: [{
name: "pgha1"
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"20Gi"
}
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
weight: 1
podAffinityTerm: {
topologyKey: "topology.kubernetes.io/zone"
labelSelector: matchLabels: {
"postgres-operator.crunchydata.com/cluster": metadata.name
"postgres-operator.crunchydata.com/instance-set": name
}
}
}]
}]
backups: pgbackrest: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0"
}
proxy: pgBouncer: {
image: "registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.21-3"
replicas: 2
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
weight: 1
podAffinityTerm: {
topologyKey: "topology.kubernetes.io/zone"
labelSelector: matchLabels: {
"postgres-operator.crunchydata.com/cluster": metadata.name
"postgres-operator.crunchydata.com/role": "pgbouncer"
}
}
}]
}
}
}

View File

@@ -0,0 +1,29 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let Objects = {
Name: "\(_Component.metadata.name)-routes"
Namespace: #IstioGatewaysNamespace
Resources: [_]: [_]: metadata: namespace: Namespace
Resources: HTTPRoute: (_Component.metadata.name): {
spec: hostnames: [_Component.spec.hostname]
spec: parentRefs: [{
name: "default"
namespace: #IstioGatewaysNamespace
}]
spec: rules: [
{
matches: [{path: {type: "PathPrefix", value: "/"}}]
backendRefs: [{
name: _Component.metadata.name
namespace: _Component.metadata.namespace
port: _Component.spec.port
}]
},
]
}
}

View File

@@ -0,0 +1,20 @@
package holos
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let Objects = {
Name: "backstage-secrets"
Namespace: "backstage"
Resources: {
ExternalSecret: [_]: #ExternalSecret & {metadata: namespace: Namespace}
ExternalSecret: "\(_DBName)-primary-tls": #ExternalCert
ExternalSecret: "\(_DBName)-repl-tls": #ExternalCert
ExternalSecret: "\(_DBName)-client-tls": #ExternalCert
ExternalSecret: "\(_DBName)-root-ca": #ExternalCert
ExternalSecret: "pgbackrest": _
}
}

View File

@@ -0,0 +1,104 @@
package holos
import (
cpv1 "pkg.crossplane.io/provider/v1"
cpdrcv1beta1 "pkg.crossplane.io/deploymentruntimeconfig/v1beta1"
cpfuncv1beta1 "pkg.crossplane.io/function/v1beta1"
cpawspcv1beta1 "aws.upbound.io/providerconfig/v1beta1"
)
// Produce a helm chart build plan.
(#Helm & Chart).Output
// https://github.com/crossplane/crossplane/releases
let CrossplaneVersion = "1.16.0"
// https://github.com/crossplane-contrib/provider-upjet-aws/releases
let AWSProviderVersion = "v1.5.0"
let Chart = {
Name: "crossplane"
Version: CrossplaneVersion
Namespace: "crossplane-system"
Repo: name: "crossplane-stable"
Repo: url: "https://charts.crossplane.io/stable"
Values: {
podSecurityContextCrossplane: {
runAsNonRoot: true
seccompProfile: type: "RuntimeDefault"
}
securityContextCrossplane: capabilities: drop: ["ALL"]
podSecurityContextRBACManager: {
runAsNonRoot: true
seccompProfile: type: "RuntimeDefault"
}
securityContextRBACManager: capabilities: drop: ["ALL"]
}
Resources: {
// This DeploymentRuntimeConfig adds annotations to the service accounts spun up for the AWS providers.
// https://docs.crossplane.io/latest/concepts/providers/#configuring-metadata-of-runtime-resources
//
// Adding this SA annotation causes the EKS Pod Identity Webhook to inject
// environment variables for AWS authentication as well as mount the AWS token
// file to the provider pod that uses the SA. For example:
//
// Environment:
// AWS_STS_REGIONAL_ENDPOINTS: regional
// AWS_DEFAULT_REGION: us-east-1
// AWS_REGION: us-east-1
// AWS_ROLE_ARN: arn:aws:iam::271053619184:role/holos-crossplane
// AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
// Mounts:
// /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
//
// Docs: https://docs.crossplane.io/latest/concepts/providers/#runtime-configuration
DeploymentRuntimeConfig: "aws-irsa": cpdrcv1beta1.#DeploymentRuntimeConfig & {
metadata: name: "aws-irsa"
spec: serviceAccountTemplate: metadata: annotations: "eks.amazonaws.com/role-arn": "arn:aws:iam::\(_Platform.Model.aws.accountNumber):role/holos-crossplane"
spec: deploymentTemplate: spec: template: spec: containers: [
{
name: "package-runtime"
args: ["--enable-external-secret-stores"]
},
]
}
// https://marketplace.upbound.io/providers/upbound/provider-family-aws
Provider: "upbound-provider-family-aws": cpv1.#Provider & {
metadata: name: "upbound-provider-family-aws"
spec: package: "xpkg.upbound.io/upbound/provider-family-aws:\(AWSProviderVersion)"
// The provider-family-aws provider doesn't need the IRSA SA annotation.
spec: runtimeConfigRef: name: "default"
}
// https://marketplace.upbound.io/providers/upbound/provider-aws-s3
Provider: "provider-aws-s3": cpv1.#Provider & {
metadata: name: "provider-aws-s3"
spec: package: "xpkg.upbound.io/upbound/provider-aws-s3:\(AWSProviderVersion)"
spec: runtimeConfigRef: name: "aws-irsa"
}
// https://marketplace.upbound.io/providers/upbound/provider-aws-rds
Provider: "provider-aws-rds": cpv1.#Provider & {
metadata: name: "provider-aws-rds"
spec: package: "xpkg.upbound.io/upbound/provider-aws-rds:\(AWSProviderVersion)"
spec: runtimeConfigRef: name: "aws-irsa"
}
// The patch-and-transform function is used in Compositions.
// https://github.com/crossplane-contrib/function-patch-and-transform/releases
Function: "function-patch-and-transform": cpfuncv1beta1.#Function & {
metadata: name: "function-patch-and-transform"
spec: package: "xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.5.0"
}
// By setting the ProviderConfig's name to "default", all resources with an
// apiVersion of "aws.upbound.io/v1beta1" will use this ProviderConfig unless
// otherwise specified with a providerConfigRef.
ProviderConfig: default: cpawspcv1beta1.#ProviderConfig & {
metadata: name: "default"
spec: credentials: source: "IRSA"
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
---
# Install the CRDs for Crossplane and the providers we use.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: "crossplane-system"
resources:
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_deploymentruntimeconfigs.yaml
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_providers.yaml
- https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/pkg.crossplane.io_functions.yaml
- https://raw.githubusercontent.com/crossplane-contrib/provider-upjet-aws/v1.5.0/package/crds/aws.upbound.io_providerconfigs.yaml

View File

@@ -0,0 +1 @@
package holos

View File

@@ -0,0 +1,206 @@
package holos
import (
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
batchv1 "k8s.io/api/batch/v1"
)
let NAME = "ecr-creds-manager"
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
// The path Pod Identity uses.
let MOUNT = "/var/run/secrets/eks.amazonaws.com/serviceaccount/"
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
let AWS_REGION = _Platform.Model.aws.primaryRegion
let AWS_ROLE_ARN = "arn:aws:iam::\(AWS_ACCOUNT):role/\(NAME)"
let Objects = {
Name: NAME
Namespace: "holos-system"
Resources: {
// Kubernetes ServiceAccount used by the Job.
ServiceAccount: "\(Name)": corev1.#ServiceAccount & {
metadata: {
name: Name
namespace: Namespace
annotations: "holos.run/description": "Refreshes image pull credentials for use with AWS ECR."
// annotations: "eks.amazonaws.com/role-arn": AWS_ROLE_ARN
}
}
// Job needs to read and write secrets across the cluster.
ClusterRole: "\(Name)": rbacv1.#ClusterRole & {
metadata: name: Name
rules: [
{
apiGroups: [""]
resources: ["secrets"]
verbs: ["*"]
},
{
apiGroups: [""]
resources: ["namespaces"]
verbs: ["list"]
},
]
}
// Bind the Role to the ServiceAccount for the Job.
ClusterRoleBinding: "\(Name)": rbacv1.#ClusterRoleBinding & {
metadata: name: Name
roleRef: {
apiGroup: "rbac.authorization.k8s.io"
kind: "ClusterRole"
name: Name
}
subjects: [
{
kind: "ServiceAccount"
name: Name
namespace: Namespace
},
]
}
// Make the CronJob and Job identical.
let JobSpec = {
serviceAccountName: Name
restartPolicy: "OnFailure"
securityContext: {
seccompProfile: type: "RuntimeDefault"
runAsNonRoot: true
runAsUser: 8192 // app
}
nodeSelector: {
"cloud.google.com/gke-spot": "true"
"kubernetes.io/os": "linux"
}
containers: [
{
name: "toolkit"
image: "quay.io/holos/toolkit:latest"
securityContext: {
capabilities: drop: ["ALL"]
allowPrivilegeEscalation: false
}
command: ["/bin/bash"]
args: ["/config/entrypoint"]
env: [
{
name: "HOME"
value: "/tmp"
},
{
name: "AWS_DEFAULT_REGION"
value: AWS_REGION
},
{
name: "AWS_REGION"
value: AWS_REGION
},
{
name: "AWS_ROLE_ARN"
value: AWS_ROLE_ARN
},
{
name: "AWS_WEB_IDENTITY_TOKEN_FILE"
value: MOUNT + "token"
},
{
name: "AWS_STS_REGIONAL_ENDPOINTS"
value: "regional"
},
]
volumeMounts: [
{
name: "config"
mountPath: "/config"
readOnly: true
},
{
name: "aws-token"
mountPath: MOUNT
readOnly: true
},
]
},
]
volumes: [
{
name: "config"
configMap: name: Name
},
{
name: "aws-token"
projected: sources: [{
serviceAccountToken: {
path: "token"
expirationSeconds: 3600
audience: "sts.amazonaws.com"
}
}]
},
]
}
Job: "\(Name)": batchv1.#Job & {
metadata: {
name: Name
namespace: Namespace
}
spec: template: spec: JobSpec
}
CronJob: "\(Name)": batchv1.#CronJob & {
metadata: name: Name
metadata: namespace: Namespace
spec: {
schedule: "0 */8 * * *"
jobTemplate: spec: {
template: spec: JobSpec
backoffLimit: 20
}
}
}
ConfigMap: "\(Name)": corev1.#ConfigMap & {
metadata: name: Name
metadata: namespace: Namespace
data: entrypoint: ENTRYPOINT
}
}
}
let ENTRYPOINT = """
#! /bin/bash
#
tmpdir="$(mktemp -d)"
finish() {
rm -rf "${tmpdir}"
}
trap finish EXIT
set -xeuo pipefail
aws sts get-caller-identity
aws ecr get-login-password --region \(AWS_REGION) \\
| docker login --username AWS --password-stdin \(AWS_ACCOUNT).dkr.ecr.\(AWS_REGION).amazonaws.com
kubectl create secret docker-registry ecr-creds-\(AWS_ACCOUNT) \\
--from-file=.dockerconfigjson=${HOME}/.docker/config.json \\
--dry-run=client -o yaml \\
> "${tmpdir}/secret.yaml"
# Copy the secret to all namespaces
for ns in $(kubectl -o=jsonpath='{.items[*].metadata.name}' get namespaces); do
echo -n "Copying secret to namespace ${ns}: "
kubectl -n $ns apply --server-side=true -f "${tmpdir}/secret.yaml" || continue
echo "Usage: "kubectl -n $ns patch serviceaccount default -p "'"'{"imagePullSecrets": [{"name": "ecr-creds-\(AWS_ACCOUNT)"}]}'"'"
done
"""

View File

@@ -0,0 +1,33 @@
# ECR Credentials Manager
This component manages a `ecr-creds-refresher` `CronJob` in the `holos-system` `Namespace` of the Management Cluster. This job authenticates to AWS using workload identity. Refer to [Use workload identity with AWS](https://cloud.google.com/kubernetes-engine/multi-cloud/docs/aws/how-to/use-workload-identity-aws) for information on how to configure AWS to accepts kubernetes service account tokens from the GKE Management Cluster.
Refer also to [Pod Identity](https://github.com/aws/amazon-eks-pod-identity-webhook?tab=readme-ov-file#eks-walkthrough)
> [!NOTE]
> Both documents refer to EKS, but the process is the same and works on any kubernetes cluster.
Example [trust policy][trust-policy]:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::637423192589:oidc-provider/container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management:aud": "sts.amazonaws.com",
"container.googleapis.com/v1/projects/holos-ops/locations/us-central1/clusters/management:sub": "system:serviceaccount:holos-system:ecr-creds-manager"
}
}
}
]
}
```
[trust-policy]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-logic-multiple-context-keys-or-values.html

View File

@@ -0,0 +1,20 @@
package holos
let NAME = "ecr-creds-refresher"
// Produce a kubernetes objects build plan.
(#Kubernetes & Objects).Output
let AWS_ACCOUNT = _Platform.Model.aws.accountNumber
let Objects = {
Name: NAME
Namespace: "default"
for Namespace in _Namespaces {
Resources: ExternalSecret: "\(Namespace.metadata.name)/ecr-creds-\(AWS_ACCOUNT)": #ExternalSecret & {
metadata: name: "ecr-creds-\(AWS_ACCOUNT)"
metadata: namespace: Namespace.metadata.name
}
}
}

View File

@@ -0,0 +1,48 @@
// This sets up the AWS EKS Pod Identity Webhook, which is used to inject AWS
// credentials into pods so services can use IRSA for AWS authentication.
//
// See: https://github.com/aws/amazon-eks-pod-identity-webhook
//
// There isn't an official Helm chart for the EKS Pod Identity Webhook, so we
// use https://github.com/jkroepke/helm-charts/tree/main/charts/amazon-eks-pod-identity-webhook
// See: https://github.com/aws/amazon-eks-pod-identity-webhook/issues/4
package holos
// https://github.com/jkroepke/helm-charts/tree/main/charts/amazon-eks-pod-identity-webhook
let ChartVersion = "2.1.3"
// https://github.com/aws/amazon-eks-pod-identity-webhook/releases
// https://registry.hub.docker.com/r/amazon/amazon-eks-pod-identity-webhook/tags
let AppVersion = "v0.5.4"
let Chart = {
Name: "amazon-eks-pod-identity-webhook"
Version: ChartVersion
Namespace: "aws-pod-identity"
Repo: name: "jkroepke"
Repo: url: "https://jkroepke.github.io/helm-charts"
Values: {
image: tag: AppVersion
config: {
tokenAudience: "sts.amazonaws.com"
defaultAwsRegion: _Platform.Model.aws.primaryRegion
extraArgs: ["-v=4"] // verbosity of at least 4 is needed to see mutation events.
}
securityContext: {
allowPrivilegeEscalation: false
capabilities: drop: ["ALL"]
runAsNonRoot: true
}
podSecurityContext: {
// https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/README.md#usage-with-non-root-container-user
fsGroup: 2000
seccompProfile: type: "RuntimeDefault"
}
}
}
// Produce a helm chart build plan.
(#Helm & Chart).Output

View File

@@ -21,6 +21,16 @@ let Objects = {
}
}
// Manage a service account to prevent ArgoCD from pruning it.
ServiceAccount: "default-istio": {
metadata: namespace: Namespace
metadata: labels: {
"gateway.istio.io/managed": "istio.io-gateway-controller"
"gateway.networking.k8s.io/gateway-name": "default"
"istio.io/gateway-name": "default"
}
}
// The default gateway with all listeners attached to tls certs.
Gateway: default: {
metadata: {
@@ -32,8 +42,10 @@ let Objects = {
}
spec: {
// Work with a struct of listeners instead of a list.
_listeners: (#WildcardListener & {Name: "admin", Selector: _Selector.GrantSubdomainAdmin}).Output
_listeners: (#WildcardListener & {Name: "admin", Selector: _Selector.GrantSubdomainAdmin, Cluster: true}).Output
_listeners: (#WildcardListener & {Name: "login", Selector: _Selector.GrantSubdomainLogin, Cluster: false}).Output
_listeners: (#WildcardListener & {Name: "app", Selector: _Selector.GrantSubdomainApp, Cluster: false}).Output
_listeners: (#WildcardListener & {Name: "app", Selector: _Selector.GrantSubdomainApp, Cluster: true}).Output
listeners: [for x in _listeners {x}]
}
}
@@ -46,16 +58,19 @@ let Objects = {
Selector: matchLabels: {[string]: string}
_Hostname: string
_Prefix: string
if Cluster == true {
_Hostname: "\(Name).\(_ClusterName).\(_Platform.Model.org.domain)"
_Prefix: "region-\(Name)"
}
if Cluster == false {
_Hostname: "\(Name).\(_Platform.Model.org.domain)"
_Prefix: "global-\(Name)"
}
Output: [NAME=string]: {name: NAME}
Output: {
"\(Name)-apex": {
"\(_Prefix)-apex": {
hostname: _Hostname
port: 443
protocol: "HTTPS"
@@ -68,7 +83,7 @@ let Objects = {
allowedRoutes: namespaces: from: "Selector"
allowedRoutes: namespaces: selector: Selector
}
"\(Name)-prefix": {
"\(_Prefix)-prefix": {
hostname: "*.\(_Hostname)"
port: 443
protocol: "HTTPS"

View File

@@ -10,8 +10,7 @@ let Objects = {
// Constrain the metadata of all component resources.
Resources: [_]: [_]: metadata: _HTTPBin.metadata
// Grant the Gateway namespace the ability to refer to the backend httpbin
// service in HTTPRoutes.
// Grant the Gateway ns the ability to refer to the Service from HTTPRoutes.
Resources: ReferenceGrant: (#IstioGatewaysNamespace): #ReferenceGrant
Resources: {

View File

@@ -7,17 +7,19 @@ let Objects = {
Name: "authpolicy"
Namespace: _AuthProxy.metadata.namespace
let Metadata = _IAP.metadata
let Selector = {matchLabels: "istio.io/gateway-name": "default"}
Resources: [_]: [_]: metadata: namespace: Namespace
Resources: [_]: [NAME=string]: {
metadata: _IAP.metadata
metadata: name: NAME
metadata: namespace: Namespace
}
// Auth policy resources represent the RequestAuthentication and
// AuthorizationPolicy resources in the istio-gateways namespace governing the
// default Gateway.
Resources: {
RequestAuthentication: (Name): {
metadata: Metadata & {name: Name}
spec: jwtRules: [{
audiences: ["\(_AuthProxy.projectID)"]
forwardOriginalToken: true
@@ -30,7 +32,6 @@ let Objects = {
AuthorizationPolicy: "\(Name)-custom": {
_description: "Route all requests through the auth proxy by default"
metadata: Metadata & {name: "\(Name)-custom"}
spec: {
action: "CUSTOM"
provider: name: _AuthProxy.provider
@@ -58,5 +59,220 @@ let Objects = {
selector: Selector
}
}
AuthorizationPolicy: "\(Name)-allow-nothing": {
_description: "Allow nothing"
spec: {
action: "ALLOW"
selector: Selector
}
}
AuthorizationPolicy: "\(Name)-allow-login": {
_description: "Allow login"
spec: {
action: "ALLOW"
selector: Selector
rules: [
{
to: [{
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
operation: hosts: [
// Allow requests to the login service
_AuthProxy.issuerHost,
_AuthProxy.issuerHost + ":*",
]
}]
},
]
}
}
AuthorizationPolicy: "\(Name)-allow-admin": {
_description: "Allow cluster admin roles"
spec: {
action: "ALLOW"
selector: Selector
rules: [
{
to: [{
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
operation: hosts: [
// Allow authenticated users with cluster admin, edit, or view
// roles to access admin interfaces.
// TODO(jeff): The set of admin services should be defined in a
// nice root-level struct somewhere, probably as part of the
// _Projects struct.
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
"httpbin.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
"httpbin.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
]
}]
when: [
// Must be issued by the platform identity provider.
{
key: "request.auth.principal"
values: [_AuthProxy.issuerURL + "/*"]
},
// Must be intended for an app within the Holos Platform ZITADEL project.
{
key: "request.auth.audiences"
values: [_AuthProxy.projectID]
},
// Must be presented by the istio ExtAuthz auth proxy.
{
key: "request.auth.presenter"
values: [_AuthProxy.clientID]
},
// Must have one of the listed roles.
AdminRoleGroups,
]
},
]
}
}
AuthorizationPolicy: "\(Name)-allow-holos-server": {
_description: "Allow authenticated access to holos server"
spec: {
action: "ALLOW"
selector: Selector
rules: [
{
to: [{
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
operation: hosts: [
// Allow authenticated users with cluster admin, edit, or view
// roles to access admin interfaces.
// TODO(jeff): The set of admin services should be defined in a
// nice root-level struct somewhere, probably as part of the
// _Projects struct.
"app.\(_ClusterName).\(_Platform.Model.org.domain)",
"app.\(_ClusterName).\(_Platform.Model.org.domain):*",
"dev.app.\(_ClusterName).\(_Platform.Model.org.domain)",
"dev.app.\(_ClusterName).\(_Platform.Model.org.domain):*",
"app.\(_Platform.Model.org.domain)",
"app.\(_Platform.Model.org.domain):*",
"dev.app.\(_Platform.Model.org.domain)",
"dev.app.\(_Platform.Model.org.domain):*",
]
}]
when: [
// Must be issued by the platform identity provider.
{
key: "request.auth.principal"
values: [_AuthProxy.issuerURL + "/*"]
},
// Must be intended for an app within the Holos Platform ZITADEL project.
{
key: "request.auth.audiences"
values: [_AuthProxy.projectID]
},
]
},
]
}
}
AuthorizationPolicy: "\(Name)-allow-portal": {
_description: "Allow portal access"
spec: {
action: "ALLOW"
selector: Selector
rules: [
{
to: [{
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
operation: hosts: [
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
"backstage.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
]
}]
when: [
// Must be issued by the platform identity provider.
{
key: "request.auth.principal"
values: [_AuthProxy.issuerURL + "/*"]
},
// Must be intended for an app within the Holos Platform ZITADEL project.
{
key: "request.auth.audiences"
values: [_AuthProxy.projectID]
},
// Must be presented by the istio ExtAuthz auth proxy.
{
key: "request.auth.presenter"
values: [_AuthProxy.clientID]
},
{
key: "request.auth.claims[groups]"
values: ["portal-view"]
},
]
},
]
}
}
AuthorizationPolicy: "\(Name)-allow-argocd": {
_description: "Allow portal access"
spec: {
action: "ALLOW"
selector: Selector
rules: [
{
to: [{
// Refer to https://istio.io/latest/docs/ops/best-practices/security/#writing-host-match-policies
operation: hosts: [
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain)",
"argocd.admin.\(_ClusterName).\(_Platform.Model.org.domain):*",
]
}]
when: [
// Must be issued by the platform identity provider.
{
key: "request.auth.principal"
values: [_AuthProxy.issuerURL + "/*"]
},
// Must be intended for an app within the Holos Platform ZITADEL project.
{
key: "request.auth.audiences"
values: [_AuthProxy.projectID]
},
// Must be presented by the istio ExtAuthz auth proxy.
{
key: "request.auth.presenter"
values: [_AuthProxy.clientID]
},
{
key: "request.auth.claims[groups]"
values: ["argocd-view"]
},
]
},
]
}
}
}
}
let AdminRoleGroups = {
key: "request.auth.claims[groups]"
values: [
"prod-cluster-admin",
"prod-cluster-edit",
"prod-cluster-view",
]
}

View File

@@ -33,10 +33,32 @@ let Objects = {
metadata: ProxyMetadata
data: "config.yaml": yaml.Marshal(AuthProxyConfig)
let AuthProxyConfig = {
injectResponseHeaders: [{
name: _AuthProxy.idTokenHeader
values: [{claim: "id_token"}]
}]
injectResponseHeaders: [
{
name: _AuthProxy.idTokenHeader
values: [{claim: "id_token"}]
},
{
name: "x-auth-request-email"
values: [{claim: "email"}]
},
{
name: "x-auth-request-groups"
values: [{claim: "groups"}]
},
{
name: "x-forwarded-email"
values: [{claim: "email"}]
},
{
name: "x-forwarded-user"
values: [{claim: "email"}]
},
{
name: "x-forwarded-preferred-username"
values: [{claim: "preferred_username"}]
},
]
providers: [{
id: "Holos Platform"
name: "Holos Platform"
@@ -55,7 +77,7 @@ let Objects = {
audienceClaims: ["aud"]
emailClaim: "email"
groupsClaim: "groups"
userIDClaim: "sub"
userIDClaim: "email"
}
}]
server: BindAddress: ":\(_AuthProxy.servicePort)"

View File

@@ -56,7 +56,7 @@ let Objects = {
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: "20Gi"
resources: requests: storage: "50Gi"
}
}]
standby: {
@@ -122,7 +122,7 @@ let Objects = {
name: "repo1"
volume: volumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"4Gi"
resources: requests: storage: string | *"50Gi"
}
},
{
@@ -156,7 +156,7 @@ let HighlyAvailable = {
replicas: 2
dataVolumeClaimSpec: {
accessModes: ["ReadWriteOnce"]
resources: requests: storage: string | *"20Gi"
resources: requests: storage: string | *"50Gi"
}
affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: [{
weight: 1

View File

@@ -3,7 +3,7 @@ package holos
#Values: {
// https://github.com/zitadel/zitadel/releases
// Overrides the image tag whose default is the chart appVersion.
image: tag: "v2.49.1"
image: tag: "v2.54.1"
// Database credentials
// Refer to https://access.crunchydata.com/documentation/postgres-operator/5.2.0/architecture/user-management/

View File

@@ -63,6 +63,29 @@ let FormBuilder = v1.#FormBuilder & {
}
}
Sections: github: {
displayName: "GitHub"
description: "Configure the platform GitHub integration. These values are used by the Backstage component."
fieldConfigs: {
primaryOrg: {
type: "input"
props: {
label: "Organization"
description: "Primary GitHub orgranization where code repositories reside. \(validation.messages.required)"
pattern: "^[a-z]([a-z0-9]|-){\(minLength-2),\(maxLength-2)}[a-z]$"
minLength: 2
maxLength: 39
required: true
}
validation: messages: {
pattern: "It must be \(props.minLength) to \(props.maxLength) lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited. \(required)"
required: "GitHub organization name"
}
}
}
}
Sections: eso: {
displayName: "Secret Store"
description: "Configure the platform secret store. These values are used by the external-secrets-creds component. Note: this information is not sufficient to read secrets. To read secrets, the credential refresher job requires the workload clusters to be configured as workload identity providers."
@@ -267,9 +290,76 @@ let FormBuilder = v1.#FormBuilder & {
Sections: argocd: {
displayName: "ArgoCD"
description: "Enter configuration values from the ArgoCD application in the Holos Platform project in ZITADEL."
description: "Configure ArgoCD platform settings."
fieldConfigs: clientID: (#StandardFields & {DisplayName: displayName}).clientID
fieldConfigs: {
clientID: (#StandardFields & {DisplayName: displayName}).clientID
repoURL: {
type: "input"
props: {
label: "Git repository URL"
description: "Git repository URL, e.g. 'git@github.com:myorg/holos-infra.git'"
minLength: 3
maxLength: 128
required: true
}
validation: messages: {
minLength: "Must be at least \(props.minLength) characters"
maxLength: "Must be at most \(props.maxLength) characters"
}
}
deployRoot: {
type: "input"
defaultValue: ""
props: {
label: "Deploy Root"
description: "Path to the parent directory of the holos deploy directory."
required: false
}
}
targetRevision: {
type: "input"
defaultValue: "HEAD"
props: {
label: "Target Revision"
description: "Git reference to deploy."
required: true
}
}
}
}
Sections: aws: {
displayName: "AWS"
description: "Configure AWS settings."
fieldConfigs: {
accountNumber: {
type: "input"
props: {
label: "AWS Account Number"
description: "AWS Account Number. \(validation.messages.required)"
pattern: "^[0-9]+$"
required: true
}
validation: messages: {
pattern: "Must be a positive integer. \(required)"
required: "aws sts get-caller-identity"
}
}
primaryRegion: {
type: "select"
props: {
label: "Primary Region"
description: "Select the primary region for AWS resources."
multiple: false
options: AWSRegions
required: true
}
}
}
}
}
@@ -295,73 +385,73 @@ let FormBuilder = v1.#FormBuilder & {
}
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

@@ -40,6 +40,13 @@ package holos
"authorization",
"path",
_AuthProxy.idTokenHeader,
"x-forwaded-access-token",
// For Backstage oauth2-proxy auth provider
"x-forwarded-email",
"x-forwarded-user",
"x-forwarded-preferred-username",
"x-auth-request-email",
"x-auth-request-groups",
]
includeAdditionalHeadersInCheck: "X-Auth-Request-Redirect": "%REQ(x-forwarded-proto)%://%REQ(:authority)%%REQ(:path)%%REQ(:query)%"
includeRequestHeadersInCheck: [

View File

@@ -29,6 +29,7 @@ _Projects: {
// Admin projects accessible at *.admin.<cluster>.<org.domain>
holos: spec: namespaces: "holos-system": _
argocd: spec: namespaces: argocd: _
backstage: spec: namespaces: backstage: _
// Sync secrets from the management cluster to workload clusters.
"external-secrets": spec: namespaces: "external-secrets": _
@@ -62,24 +63,45 @@ _Projects: {
spec: commonName: "*." + Subdomain
}
}
}
// Manage certificates for admin services in workload clusters.
for Cluster in _Fleets.workload.clusters {
// Issue a wildcard cert for all admin interfaces. We need to verify this is
// well-behaved with Istio and HTTP2.
let Subdomain = "admin.\(Cluster.name).\(_Platform.Model.org.domain)"
_Projects: holos: spec: {
// Crossplane
crossplane: spec: namespaces: {
"aws-pod-identity": _
"crossplane-system": _
}
holosapp: spec: {
namespaces: "dev-holos": _
namespaces: "prod-holos": _
namespaces: "jeff-holos": _
let Subdomain = "app.\(_Platform.Model.org.domain)"
certificates: "\(Subdomain)": #IngressCertificate
certificates: "any.\(Subdomain)": #IngressCertificate & {
spec: commonName: "*." + Subdomain
}
}
}
// Issue a dedicated cert for argocd. This may be removed if the wildcard
// works with the Gateway API.
let Name = "argocd.\(Subdomain)"
_Projects: argocd: spec: certificates: "\(Name)": #IngressCertificate & {metadata: name: Name}
// Manage per-cluster certificates for services in workload clusters.
for Cluster in _Fleets.workload.clusters {
// Issue a wildcard cert for all admin interfaces. We need to verify this is
// well-behaved with Istio and HTTP2.
let CertPair = #ClusterCertPair & {cluster: Cluster.name}
_Projects: holos: spec: (CertPair & {name: "admin"}).spec
// Holos app certs
_Projects: holosapp: spec: (CertPair & {name: "app"}).spec
}
#ClusterCertPair: {
name: string
cluster: string
let Subdomain = name + ".\(cluster).\(_Platform.Model.org.domain)"
spec: certificates: (Subdomain): #IngressCertificate
spec: certificates: "any.\(Subdomain)": #IngressCertificate & {
spec: commonName: "*." + Subdomain
}
}
// Platform components to manage.
@@ -118,6 +140,28 @@ _Platform: Components: {
path: "components/login/zitadel-certs"
cluster: Cluster.name
}
// ECR Credentials (ecr-creds-<account-number>)
"\(Cluster.name)/ecr-creds-manager": {
path: "components/ecr-creds-manager"
cluster: Cluster.name
}
"\(Cluster.name)/eks-pod-identity-webhook": {
path: "components/eks-pod-identity-webhook"
cluster: Cluster.name
}
"\(Cluster.name)/crossplane_crds": {
path: "components/crossplane/crds"
cluster: Cluster.name
}
"\(Cluster.name)/crossplane": {
path: "components/crossplane/controller"
cluster: Cluster.name
}
// Backstage certs
"\(Cluster.name)/backstage-certs": {
path: "components/backstage/management/certs"
cluster: Cluster.name
}
}
// Components to manage on workload clusters.
@@ -134,6 +178,14 @@ _Platform: Components: {
path: "components/secretstores"
cluster: Cluster.name
}
// Secret ecr-creds-<aws-account-number> in each Namespace to pull images
// from the private ECR registry.
"\(Cluster.name)/ecr-creds-refresher": {
path: "components/ecr-creds-refresher"
cluster: Cluster.name
}
// We use HTTPRoute from the Kubernetes Gateway API v1 instead of
// VirtualService from the Istio Gateway API.
"\(Cluster.name)/gateway-api": {
path: "components/gateway-api"
cluster: Cluster.name
@@ -213,6 +265,34 @@ _Platform: Components: {
path: "components/argo/creds"
cluster: Cluster.name
}
// Holos server
"\(Cluster.name)/apps/dev-holos-infra": {
path: "apps/dev/holos/infra"
cluster: Cluster.name
}
"\(Cluster.name)/apps/dev-holos-app": {
path: "apps/dev/holos/app"
cluster: Cluster.name
}
// Backstage
"\(Cluster.name)/backstage-secrets": {
path: "components/backstage/workload/secrets"
cluster: Cluster.name
}
"\(Cluster.name)/backstage-database": {
path: "components/backstage/workload/database"
cluster: Cluster.name
}
"\(Cluster.name)/backstage-backend": {
path: "components/backstage/workload/backend"
cluster: Cluster.name
}
"\(Cluster.name)/backstage-routes": {
path: "components/backstage/workload/routes"
cluster: Cluster.name
}
}
}
@@ -266,3 +346,8 @@ _AuthProxy: {
// provider is the istio meshconfig extauthz provider of the authproxy
provider: "default-gateway-authproxy"
}
_BackupBucket: {
metadata: name: _Platform.Model.zitadel.backupBucketName
spec: region: _Platform.Model.zitadel.backupBucketRegion
}

View File

@@ -2,7 +2,7 @@ package holos
import (
"encoding/json"
v1 "github.com/holos-run/holos/api/v1alpha1"
core "github.com/holos-run/holos/api/core/v1alpha2"
dto "github.com/holos-run/holos/service/gen/holos/object/v1alpha1:object"
corev1 "k8s.io/api/core/v1"
certv1 "cert-manager.io/certificate/v1"
@@ -50,12 +50,12 @@ _Platform: #Platform & {
Name: string | *"holos"
// Components represent the platform components to render.
Components: [string]: v1.#PlatformSpecComponent
Components: [string]: core.#PlatformSpecComponent
// Model represents the platform model from the web app form.
Model: dto.#PlatformConfig.platform_model
Output: v1.#Platform & {
Output: core.#Platform & {
metadata: name: Name
spec: {
@@ -164,3 +164,34 @@ _Projects: #Projects
// #Selector represents label selectors.
#Selector: [string]: matchLabels: {[string]: string}
_Selector: #Selector
// #AppInfo represents the data structure for an application deployed onto the
// platform.
#AppInfo: {
metadata: {
name: string
namespace: string
labels: {[string]: string}
annotations: {[string]: string}
}
spec: env: string
spec: component: string
spec: region: hostname: string
spec: global: hostname: string
spec: dns: segments: {
env: [] | [string]
name: [] | [string]
cluster: [] | [string]
domain: [] | [string]
}
// The primary port for HTTPRoute
spec: port: number
spec: selector: matchLabels: {[string]: string}
status: component: string
}

View File

@@ -0,0 +1,52 @@
package render
import (
"context"
"github.com/holos-run/holos"
"github.com/holos-run/holos/api/core/v1alpha2"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/logger"
"github.com/holos-run/holos/internal/util"
)
const KubernetesObjectsKind = "KubernetesObjects"
// KubernetesObjects represents CUE output which directly provides Kubernetes api objects to holos.
type KubernetesObjects struct {
Component v1alpha2.KubernetesObjects `json:"component" yaml:"component"`
}
// Render produces kubernetes api objects from the APIObjectMap of the holos component.
func (o *KubernetesObjects) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
result := NewResult(o.Component.HolosComponent)
result.addObjectMap(ctx, o.Component.APIObjectMap)
return result, nil
}
// KustomizeBuild renders plain yaml files in the holos component directory
// using kubectl kustomize build.
type KustomizeBuild struct {
Component v1alpha2.KustomizeBuild `json:"component" yaml:"component"`
}
// Render produces a Result by executing kubectl kustomize on the holos
// component path. Useful for processing raw yaml files.
func (kb *KustomizeBuild) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
if kb == nil {
return nil, nil
}
log := logger.FromContext(ctx)
result := NewResult(kb.Component.HolosComponent)
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", string(path))
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return nil, errors.Wrap(err)
}
// Replace the accumulated output
result.accumulatedOutput = kOut.Stdout.String()
// Add CUE based api objects.
result.addObjectMap(ctx, kb.Component.APIObjectMap)
return result, nil
}

171
internal/render/helm.go Normal file
View File

@@ -0,0 +1,171 @@
package render
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/holos-run/holos"
"github.com/holos-run/holos/api/core/v1alpha2"
core "github.com/holos-run/holos/api/core/v1alpha2"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/logger"
"github.com/holos-run/holos/internal/util"
)
type HelmChart struct {
Component core.HelmChart `json:"component"`
}
func (hc *HelmChart) Render(ctx context.Context, path holos.InstancePath) (*Result, error) {
if hc == nil {
return nil, nil
}
result := NewResult(hc.Component.HolosComponent)
if err := hc.helm(ctx, result, path); err != nil {
return nil, err
}
result.addObjectMap(ctx, hc.Component.APIObjectMap)
if err := result.kustomize(ctx); err != nil {
return nil, errors.Wrap(fmt.Errorf("could not kustomize: %w", err))
}
return result, nil
}
// runHelm provides the values produced by CUE to helm template and returns
// the rendered kubernetes api objects in the result.
func (hc *HelmChart) helm(ctx context.Context, r *Result, path holos.InstancePath) error {
log := logger.FromContext(ctx).With("chart", hc.Component.Chart.Name)
if hc.Component.Chart.Name == "" {
log.WarnContext(ctx, "skipping helm: no chart name specified, use a different component type")
return nil
}
cachedChartPath := filepath.Join(string(path), v1alpha2.ChartDir, filepath.Base(hc.Component.Chart.Name))
if isNotExist(cachedChartPath) {
// Add repositories
repo := hc.Component.Chart.Repository
if repo.URL != "" {
out, err := util.RunCmd(ctx, "helm", "repo", "add", repo.Name, repo.URL)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return errors.Wrap(fmt.Errorf("could not run helm repo add: %w", err))
}
// Update repository
out, err = util.RunCmd(ctx, "helm", "repo", "update", repo.Name)
if err != nil {
log.ErrorContext(ctx, "could not run helm", "stderr", out.Stderr.String(), "stdout", out.Stdout.String())
return errors.Wrap(fmt.Errorf("could not run helm repo update: %w", err))
}
} else {
log.DebugContext(ctx, "no chart repository url proceeding assuming oci chart")
}
// Cache the chart
if err := cacheChart(ctx, path, v1alpha2.ChartDir, hc.Component.Chart); err != nil {
return fmt.Errorf("could not cache chart: %w", err)
}
}
// Write values file
tempDir, err := os.MkdirTemp("", "holos")
if err != nil {
return errors.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, tempDir)
valuesPath := filepath.Join(tempDir, "values.yaml")
if err := os.WriteFile(valuesPath, []byte(hc.Component.ValuesContent), 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write values: %w", err))
}
log.DebugContext(ctx, "helm: wrote values", "path", valuesPath, "bytes", len(hc.Component.ValuesContent))
// Run charts
chart := hc.Component.Chart
args := []string{"template"}
if !hc.Component.EnableHooks {
args = append(args, "--no-hooks")
}
namespace := hc.Component.Metadata.Namespace
args = append(args, "--include-crds", "--values", valuesPath, "--namespace", namespace, "--kubeconfig", "/dev/null", "--version", chart.Version, chart.Release, cachedChartPath)
helmOut, err := util.RunCmd(ctx, "helm", args...)
if err != nil {
stderr := helmOut.Stderr.String()
lines := strings.Split(stderr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Error:") {
err = fmt.Errorf("%s: %w", line, err)
}
}
return errors.Wrap(fmt.Errorf("could not run helm template: %w", err))
}
r.accumulatedOutput = helmOut.Stdout.String()
return nil
}
// 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 v1alpha2.Chart) error {
log := logger.FromContext(ctx)
cacheTemp, err := os.MkdirTemp(string(path), chartDir)
if err != nil {
return errors.Wrap(fmt.Errorf("could not make temp dir: %w", err))
}
defer util.Remove(ctx, cacheTemp)
chartName := chart.Name
if chart.Repository.Name != "" {
chartName = fmt.Sprintf("%s/%s", chart.Repository.Name, chart.Name)
}
helmOut, err := util.RunCmd(ctx, "helm", "pull", "--destination", cacheTemp, "--untar=true", "--version", chart.Version, chartName)
if err != nil {
return errors.Wrap(fmt.Errorf("could not run helm pull: %w", err))
}
log.Debug("helm pull", "stdout", helmOut.Stdout, "stderr", helmOut.Stderr)
cachePath := filepath.Join(string(path), chartDir)
if err := os.MkdirAll(cachePath, 0777); err != nil {
return errors.Wrap(fmt.Errorf("could not mkdir: %w", err))
}
items, err := os.ReadDir(cacheTemp)
if err != nil {
return errors.Wrap(fmt.Errorf("could not read directory: %w", err))
}
for _, item := range items {
src := filepath.Join(cacheTemp, item.Name())
dst := filepath.Join(cachePath, item.Name())
log.DebugContext(ctx, "rename", "src", src, "dst", dst)
if err := os.Rename(src, dst); err != nil {
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, "chart_version", chart.Version, "path", cachePath)
return nil
}
func isNotExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}

View File

@@ -6,27 +6,58 @@ import (
"io"
"time"
"github.com/holos-run/holos/api/v1alpha1"
core "github.com/holos-run/holos/api/core/v1alpha2"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/logger"
"github.com/holos-run/holos/internal/util"
"golang.org/x/sync/errgroup"
)
func Platform(ctx context.Context, pf *v1alpha1.Platform, stderr io.Writer) error {
func Platform(ctx context.Context, concurrency int, pf *core.Platform, stderr io.Writer) error {
total := len(pf.Spec.Components)
for idx, component := range pf.Spec.Components {
start := time.Now()
log := logger.FromContext(ctx).With("path", component.Path, "cluster", component.Cluster, "num", idx+1, "total", total)
log.DebugContext(ctx, "render component")
// Execute a sub-process to limit CUE memory usage.
args := []string{"render", "component", "--cluster-name", component.Cluster, component.Path}
result, err := util.RunCmd(ctx, "holos", args...)
if err != nil {
_, _ = io.Copy(stderr, result.Stderr)
return errors.Wrap(fmt.Errorf("could not render component: %w", err))
g, ctx := errgroup.WithContext(ctx)
// Limit the number of concurrent goroutines due to CUE memory usage concerns
// while rendering components. One more for the producer.
g.SetLimit(concurrency + 1)
// Spawn a producer because g.Go() blocks when the group limit is reached.
g.Go(func() error {
for idx, component := range pf.Spec.Components {
select {
case <-ctx.Done():
return ctx.Err()
default:
// Capture idx and component to avoid issues with closure. Can be removed on Go 1.22.
idx, component := idx, component
// Worker go routine. Blocks if limit has been reached.
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
start := time.Now()
log := logger.FromContext(ctx).With("path", component.Path, "cluster", component.Cluster, "num", idx+1, "total", total)
log.DebugContext(ctx, "render component")
// Execute a sub-process to limit CUE memory usage.
args := []string{"render", "component", "--cluster-name", component.Cluster, component.Path}
result, err := util.RunCmd(ctx, "holos", args...)
if err != nil {
_, _ = io.Copy(stderr, result.Stderr)
return errors.Wrap(fmt.Errorf("could not render component: %w", err))
}
duration := time.Since(start)
log.InfoContext(ctx, "ok render component", "duration", duration)
return nil
}
})
}
}
duration := time.Since(start)
log.InfoContext(ctx, "ok render component", "duration", duration)
}
return nil
return nil
})
// Wait for completion and return the first error (if any)
return g.Wait()
}

230
internal/render/result.go Normal file
View File

@@ -0,0 +1,230 @@
package render
import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/holos-run/holos/api/core/v1alpha2"
"github.com/holos-run/holos/internal/errors"
"github.com/holos-run/holos/internal/server/middleware/logger"
"github.com/holos-run/holos/internal/util"
)
// NewResult returns a new Result with the given holos component.
func NewResult(component v1alpha2.HolosComponent) *Result {
return &Result{
Kind: "Result",
APIVersion: "v1alpha2",
Component: component,
accumulatedOutput: "",
}
}
// Result is the build result for display or writing. Holos components Render
// the Result as a data pipeline.
type Result struct {
// Kind is a string value representing the resource this object represents.
Kind string `json:"kind" yaml:"kind" cue:"string | *\"Result\""`
// APIVersion represents the versioned schema of this representation of an object.
APIVersion string `json:"apiVersion" yaml:"apiVersion" cue:"string | *\"v1alpha2\""`
// Component represents the common fields of all holos component kinds.
Component v1alpha2.HolosComponent
// accumulatedOutput accumulates rendered api objects.
accumulatedOutput string
}
func (r *Result) GetAPIVersion() string {
if r == nil {
return ""
}
return r.APIVersion
}
func (r *Result) GetKind() string {
if r == nil {
return ""
}
return r.Kind
}
// Continue returns true if the result should be skipped over.
func (r *Result) Continue() bool {
// Skip over a nil result
if r == nil {
return true
}
return r.Component.Skip
}
// Name returns the name of the component from the Metadata field.
func (r *Result) Name() string {
if r == nil {
return ""
}
return r.Component.Metadata.Name
}
// Filename returns the filename representing the rendered api objects of the Result.
func (r *Result) Filename(writeTo string, cluster string) string {
name := r.Name()
return filepath.Join(writeTo, "clusters", cluster, "components", name, name+".gen.yaml")
}
// KustomizationFilename returns the Flux Kustomization file path.
//
// Deprecated: Use DeployFiles instead.
func (r *Result) KustomizationFilename(writeTo string, cluster string) string {
return filepath.Join(writeTo, "clusters", cluster, "holos", "components", r.Name()+"-kustomization.gen.yaml")
}
// AccumulatedOutput returns the accumulated rendered output.
func (r *Result) AccumulatedOutput() string {
if r == nil {
return ""
}
return r.accumulatedOutput
}
// addObjectMap renders the provided APIObjectMap into the accumulated output.
func (r *Result) addObjectMap(ctx context.Context, objectMap v1alpha2.APIObjectMap) {
if r == nil {
return
}
log := logger.FromContext(ctx)
b := []byte(r.AccumulatedOutput())
kinds := make([]v1alpha2.Kind, 0, len(objectMap))
// Sort the keys
for kind := range objectMap {
kinds = append(kinds, kind)
}
slices.Sort(kinds)
for _, kind := range kinds {
v := objectMap[kind]
// Sort the keys
names := make([]v1alpha2.Label, 0, len(v))
for name := range v {
names = append(names, name)
}
slices.Sort(names)
for _, name := range names {
yamlString := v[name]
log.Debug(fmt.Sprintf("%s/%s", kind, name), "kind", kind, "name", name)
b = util.EnsureNewline(b)
header := fmt.Sprintf("---\n# Source: CUE apiObjects.%s.%s\n", kind, name)
b = append(b, []byte(header+yamlString)...)
b = util.EnsureNewline(b)
}
}
r.accumulatedOutput = string(b)
}
// kustomize replaces the accumulated output with the output of kustomize build
func (r *Result) kustomize(ctx context.Context) error {
if r == nil {
return nil
}
log := logger.FromContext(ctx)
if r.Component.Kustomize.ResourcesFile == "" {
log.DebugContext(ctx, "skipping kustomize: no resourcesFile")
return nil
}
if len(r.Component.Kustomize.KustomizeFiles) < 1 {
log.DebugContext(ctx, "skipping kustomize: no kustomizeFiles")
return nil
}
tempDir, err := os.MkdirTemp("", "holos.kustomize")
if err != nil {
return errors.Wrap(err)
}
defer util.Remove(ctx, tempDir)
// Write the main api object resources file for kustomize.
target := filepath.Join(tempDir, r.Component.Kustomize.ResourcesFile)
b := []byte(r.AccumulatedOutput())
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write resources: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
// Write the kustomization tree, kustomization.yaml must be in this map for kustomize to work.
for file, content := range r.Component.Kustomize.KustomizeFiles {
target := filepath.Join(tempDir, string(file))
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return errors.Wrap(err)
}
b := []byte(content)
b = util.EnsureNewline(b)
if err := os.WriteFile(target, b, 0644); err != nil {
return errors.Wrap(fmt.Errorf("could not write: %w", err))
}
log.DebugContext(ctx, "wrote: "+target, "op", "write", "path", target, "bytes", len(b))
}
// Run kustomize.
kOut, err := util.RunCmd(ctx, "kubectl", "kustomize", tempDir)
if err != nil {
log.ErrorContext(ctx, kOut.Stderr.String())
return errors.Wrap(err)
}
// Replace the accumulated output
r.accumulatedOutput = kOut.Stdout.String()
return nil
}
func (r *Result) WriteDeployFiles(ctx context.Context, path string) error {
if r == nil {
return nil
}
log := logger.FromContext(ctx)
if len(r.Component.DeployFiles) == 0 {
return nil
}
for k, content := range r.Component.DeployFiles {
path := filepath.Join(path, string(k))
if err := r.Save(ctx, path, string(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)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, os.FileMode(0775)); err != nil {
log.WarnContext(ctx, "could not mkdir", "path", dir, "err", err)
return errors.Wrap(err)
}
// 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)
}
log.DebugContext(ctx, "out: wrote "+path, "action", "write", "path", path, "status", "ok")
return nil
}
// SkipWriteAccumulatedOutput returns true if writing the accumulated output of
// k8s api objects should be skipped. Useful for results which only write
// deployment files, like Flux or ArgoCD GitOps resources.
func (r *Result) SkipWriteAccumulatedOutput() bool {
if r == nil {
return true
}
// This is a hack and should be moved to a HolosComponent field or similar.
if strings.HasPrefix(r.Component.Metadata.Name, "gitops/") {
return true
}
return false
}

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