mirror of
https://github.com/holos-run/holos.git
synced 2026-03-03 03:28:55 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03b796312a | ||
|
|
20fb39e49b | ||
|
|
c9c8c13810 | ||
|
|
374cd872e9 | ||
|
|
8db06dd0e1 | ||
|
|
66acadf86d |
@@ -11,6 +11,7 @@
|
||||
"admissionregistration",
|
||||
"alertmanager",
|
||||
"alertmanagers",
|
||||
"anchore",
|
||||
"anthos",
|
||||
"apiextensions",
|
||||
"apimachinery",
|
||||
@@ -56,6 +57,7 @@
|
||||
"Cmds",
|
||||
"CNCF",
|
||||
"CODEOWNERS",
|
||||
"componentconfig",
|
||||
"configdir",
|
||||
"configmap",
|
||||
"configmapargs",
|
||||
@@ -75,6 +77,7 @@
|
||||
"deploymentruntimeconfig",
|
||||
"destinationrule",
|
||||
"destinationrules",
|
||||
"devel",
|
||||
"devicecode",
|
||||
"dnsmasq",
|
||||
"dscacheutil",
|
||||
@@ -137,6 +140,7 @@
|
||||
"httproute",
|
||||
"httproutes",
|
||||
"iampolicygenerator",
|
||||
"incpatch",
|
||||
"Infima",
|
||||
"intstr",
|
||||
"isatty",
|
||||
@@ -256,6 +260,7 @@
|
||||
"rolebinding",
|
||||
"rootfs",
|
||||
"ropc",
|
||||
"sboms",
|
||||
"seccomp",
|
||||
"secretargs",
|
||||
"SECRETKEY",
|
||||
|
||||
13
.github/workflows/release.yaml
vendored
13
.github/workflows/release.yaml
vendored
@@ -35,6 +35,9 @@ jobs:
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Setup Syft
|
||||
uses: anchore/sbom-action/download-syft@1ca97d9028b51809cf6d3c934c3e160716e1b605 # v0.17.5
|
||||
|
||||
# Necessary to run these outside of goreleaser, otherwise
|
||||
# /home/runner/_work/holos/holos/internal/frontend/node_modules/.bin/protoc-gen-connect-query is not in PATH
|
||||
- name: Install Tools
|
||||
@@ -54,11 +57,19 @@ jobs:
|
||||
- name: Git diff
|
||||
run: git diff
|
||||
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
owner: ${{ github.repository_owner }}
|
||||
app-id: ${{ vars.GORELEASER_APP_ID }}
|
||||
private-key: ${{ secrets.GORELEASER_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
version: '~> v2'
|
||||
args: release --clean
|
||||
env:
|
||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 1
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
@@ -50,3 +50,39 @@ changelog:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
source:
|
||||
enabled: true
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_source_code'
|
||||
|
||||
sboms:
|
||||
- id: source
|
||||
artifacts: source
|
||||
documents:
|
||||
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"
|
||||
|
||||
brews:
|
||||
- name: holos
|
||||
repository:
|
||||
owner: holos-run
|
||||
name: homebrew-tap
|
||||
branch: main
|
||||
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
|
||||
directory: Formula
|
||||
homepage: "https://holos.run"
|
||||
description: "Holos CLI"
|
||||
dependencies:
|
||||
- name: helm
|
||||
type: optional
|
||||
- name: kubectl
|
||||
type: optional
|
||||
install: |
|
||||
bin.install "holos"
|
||||
bash_output = Utils.safe_popen_read(bin/"holos", "completion", "bash")
|
||||
(bash_completion/"holos").write bash_output
|
||||
zsh_output = Utils.safe_popen_read(bin/"holos", "completion", "zsh")
|
||||
(zsh_completion/"_holos").write zsh_output
|
||||
fish_output = Utils.safe_popen_read(bin/"holos", "completion", "fish")
|
||||
(fish_completion/"holos.fish").write fish_output
|
||||
test: |
|
||||
system "#{bin}/holos --version"
|
||||
|
||||
16
doc/md/common/example-component-integrate.mdx
Normal file
16
doc/md/common/example-component-integrate.mdx
Normal file
@@ -0,0 +1,16 @@
|
||||
Integrate the `podinfo` component into the platform.
|
||||
|
||||
```bash
|
||||
cat <<EOF >platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
Platform: Components: podinfo: {
|
||||
name: "podinfo"
|
||||
path: "components/podinfo"
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
34
doc/md/common/example-component.mdx
Normal file
34
doc/md/common/example-component.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
Create a directory for the example `podinfo` component we'll use to render
|
||||
platform manifests.
|
||||
|
||||
```bash
|
||||
mkdir -p components/podinfo
|
||||
```
|
||||
|
||||
Create the CUE configuration for the example `podinfo` component.
|
||||
|
||||
```bash
|
||||
cat <<EOF >components/podinfo/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
holos: Component.BuildPlan
|
||||
|
||||
Component: #Helm & {
|
||||
Name: "podinfo"
|
||||
Chart: {
|
||||
version: "6.6.2"
|
||||
repository: {
|
||||
name: "podinfo"
|
||||
url: "https://stefanprodan.github.io/podinfo"
|
||||
}
|
||||
}
|
||||
Values: ui: {
|
||||
message: string | *"Hello World" @tag(message, type=string)
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Architecture diagrams.
|
||||
slug: architecture
|
||||
sidebar_position: 90
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
import RenderPlatformDiagram from '@site/src/diagrams/render-platform-sequence.mdx';
|
||||
|
||||
@@ -7,6 +7,8 @@ sidebar_position: 110
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import CommonComponent from '../../common/example-component.mdx';
|
||||
import CommonComponentIntegrate from '../../common/example-component-integrate.mdx';
|
||||
|
||||
# ArgoCD Application
|
||||
|
||||
@@ -30,62 +32,10 @@ mkdir holos-argocd-application && cd holos-argocd-application
|
||||
holos init platform v1alpha5
|
||||
```
|
||||
|
||||
### Creating a component
|
||||
### Creating an example Component
|
||||
|
||||
Create a directory for the `podinfo` component. Create an empty file and then
|
||||
add the following CUE configuration to it.
|
||||
|
||||
<Tabs groupId="1D2C6013-3D19-4516-8147-5A6EE214CAFF">
|
||||
<TabItem value="components/podinfo/podinfo.cue" label="Podinfo Helm Chart">
|
||||
```bash
|
||||
mkdir -p components/podinfo
|
||||
touch components/podinfo/podinfo.cue
|
||||
```
|
||||
```bash
|
||||
cat <<EOF >components/podinfo/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
holos: Component.BuildPlan
|
||||
|
||||
Component: #Helm & {
|
||||
Name: "podinfo"
|
||||
Chart: {
|
||||
version: "6.6.2"
|
||||
repository: {
|
||||
name: "podinfo"
|
||||
url: "https://stefanprodan.github.io/podinfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Integrate the `podinfo` component into the platform.
|
||||
|
||||
<Tabs groupId="tutorial-hello-register-podinfo-component">
|
||||
<TabItem value="platform/podinfo.cue" label="Register Podinfo">
|
||||
```bash
|
||||
cat <<EOF >platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
Platform: Components: podinfo: {
|
||||
name: "podinfo"
|
||||
path: "components/podinfo"
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
<CommonComponent />
|
||||
<CommonComponentIntegrate />
|
||||
|
||||
## Adding ArgoCD Application
|
||||
|
||||
@@ -267,8 +217,8 @@ spec:
|
||||
[podinfo]: https://github.com/stefanprodan/podinfo
|
||||
[CUE Module]: https://cuelang.org/docs/reference/modules/
|
||||
[CUE Tags]: https://cuelang.org/docs/howto/inject-value-into-evaluation-using-tag-attribute/
|
||||
[Platform]: ../api/author.md#Platform
|
||||
[Component Parameters]: ../topics/component-parameters.mdx
|
||||
[Application]: https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/
|
||||
[ComponentConfig]: ../api/author.md#ComponentConfig
|
||||
[Artifact]: ../api/core.md#Artifact
|
||||
[Component Parameters]: ../component-parameters.mdx
|
||||
[Platform]: ../../api/author.md#Platform
|
||||
[ComponentConfig]: ../../api/author.md#ComponentConfig
|
||||
[Artifact]: ../../api/core.md#Artifact
|
||||
19
doc/md/topics/gitops/index.mdx
Normal file
19
doc/md/topics/gitops/index.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
slug: .
|
||||
title: GitOps
|
||||
description: Managing resources with GitOps.
|
||||
sidebar_position: 120
|
||||
---
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# GitOps
|
||||
|
||||
This section has self contained articles covering how to manage resources using
|
||||
GitOps tooling like [ArgoCD] and [Flux].
|
||||
|
||||
---
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[ArgoCD]: https://argo-cd.readthedocs.io/en/stable/
|
||||
[Flux]: https://fluxcd.io/
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Build a local cluster for use with Holos.
|
||||
slug: local-cluster
|
||||
sidebar_position: 100
|
||||
sidebar_position: 50
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
424
doc/md/topics/structures/clusters.mdx
Normal file
424
doc/md/topics/structures/clusters.mdx
Normal file
@@ -0,0 +1,424 @@
|
||||
---
|
||||
slug: clusters
|
||||
title: Clusters
|
||||
description: Managing clusters - management and workload sets.
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import CommonComponent from '../../common/example-component.mdx';
|
||||
|
||||
# Clusters
|
||||
|
||||
## Overview
|
||||
|
||||
This topic covers one common method to manage multiple clusters with Holos. We'll
|
||||
define two schemas to hold cluster attributes. First, a single `#Cluster` then
|
||||
a `#Clusters` collection. We'll use a `Clusters: #Clusters` struct to look up
|
||||
configuration data using a key. We'll use the cluster name as the lookup key
|
||||
identifying the cluster.
|
||||
|
||||
We'll also organize sets of similar clusters by defining `#ClusterSet` and
|
||||
`#ClusterSets`. We'll use a `ClusterSets:
|
||||
#ClusterSets` struct to configure a management cluster and iterate over all
|
||||
workload clusters.
|
||||
|
||||
## The Code
|
||||
|
||||
### Initializing the structure
|
||||
|
||||
Use `holos` to generate a minimal platform directory structure. Start by
|
||||
creating a blank directory to hold the platform configuration.
|
||||
|
||||
```shell
|
||||
mkdir holos-multiple-clusters && cd holos-multiple-clusters
|
||||
```
|
||||
|
||||
```shell
|
||||
holos init platform v1alpha5
|
||||
```
|
||||
|
||||
### Example Component
|
||||
|
||||
<CommonComponent />
|
||||
|
||||
We'll integrate the component with the platform after we define clusters.
|
||||
|
||||
## Defining Clusters
|
||||
|
||||
We'll define a `#Cluster` schema and a `#Clusters` collection in this section.
|
||||
We'll use these schemas to define a `Clusters` structure we use to manage
|
||||
multiple clusters.
|
||||
|
||||
### Assumptions
|
||||
|
||||
We'll make the following assumptions, which hold true for many real world
|
||||
environments.
|
||||
|
||||
1. There are two sets of clusters, workload clusters and management clusters.
|
||||
2. There is one management cluster.
|
||||
3. There are multiple workload clusters.
|
||||
4. Each workload cluster is configured similarly, but not identically, to the
|
||||
others.
|
||||
|
||||
### Prototyping the data
|
||||
|
||||
Before we define the schema, let's prototype the data structure we want to work
|
||||
with. We want a structure that makes it easy to iterate over each cluster in
|
||||
two distinct sets of clusters, management clusters and workload clusters. The
|
||||
following `ClusterSets` struct accomplishes this goal.
|
||||
|
||||
```yaml showLineNumbers
|
||||
management:
|
||||
name: management
|
||||
clusters:
|
||||
management:
|
||||
name: management
|
||||
region: us-central1
|
||||
set: management
|
||||
workload:
|
||||
name: workload
|
||||
clusters:
|
||||
e1:
|
||||
name: e1
|
||||
region: us-east1
|
||||
set: workload
|
||||
w1:
|
||||
name: w1
|
||||
region: us-west1
|
||||
set: workload
|
||||
```
|
||||
|
||||
:::tip
|
||||
The `ClusterSets` data structure supports iterating over each cluster in each
|
||||
cluster set.
|
||||
:::
|
||||
|
||||
:::important
|
||||
You're free to define your own fields and structures like we define `region` in
|
||||
this topic.
|
||||
:::
|
||||
|
||||
### Defining the schema
|
||||
|
||||
Armed with a concrete example of the structure, we can write a schema to define
|
||||
and validate the data.
|
||||
|
||||
In CUE, schema definitions are usually defined at the root so they're accessible
|
||||
in all subdirectories. The following is one example schema, you're free to
|
||||
modify it to your situation. Holos is flexible, supporting schemas that match
|
||||
your unique use case.
|
||||
|
||||
```bash
|
||||
cat <<EOF > clusters.schema.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
import "strings"
|
||||
|
||||
// #Cluster represents one cluster
|
||||
#Cluster: {
|
||||
// name represents the cluster name.
|
||||
name: string & =~"[a-z][a-z0-9]+" & strings.MinRunes(2) & strings.MaxRunes(63)
|
||||
// Constrain the regions. No default, the region must be specified.
|
||||
region: "us-east1" | "us-central1" | "us-west1"
|
||||
// Each cluster must be in only one set of clusters. All but one cluster are
|
||||
// workload clusters, so make it the default.
|
||||
set: "management" | *"workload"
|
||||
}
|
||||
|
||||
// #Clusters represents a cluster collection structure
|
||||
#Clusters: {
|
||||
// name is the lookup key for the collection.
|
||||
[NAME=string]: #Cluster & {
|
||||
// name must match the struct field name.
|
||||
name: NAME
|
||||
}
|
||||
}
|
||||
|
||||
// #ClusterSet represents a set of clusters.
|
||||
#ClusterSet: {
|
||||
// name represents the cluster set name.
|
||||
name: string & =~"[a-z][a-z0-9]+" & strings.MinRunes(2) & strings.MaxRunes(63)
|
||||
clusters: #Clusters & {
|
||||
// Constrain the cluster set to clusters having the same set. Ensures
|
||||
// clusters are never mis-categorized.
|
||||
[_]: set: name
|
||||
}
|
||||
}
|
||||
|
||||
// #ClusterSets represents a cluster set collection.
|
||||
#ClusterSets: {
|
||||
// name is the lookup key for the collection.
|
||||
[NAME=string]: #ClusterSet & {
|
||||
// name must match the struct field name.
|
||||
name: NAME
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
### Defining the data
|
||||
|
||||
With a schema defined, we also define the data close to the root so it's
|
||||
accessible through the unified configuration tree.
|
||||
|
||||
```bash
|
||||
cat <<EOF > clusters.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
Clusters: #Clusters & {
|
||||
// Management Cluster
|
||||
management: region: "us-central1"
|
||||
management: set: "management"
|
||||
// Local Cluster
|
||||
local: region: "us-west1"
|
||||
// Some example clusters. Add new clusters to the Clusters struct like this.
|
||||
e1: region: "us-east1"
|
||||
e2: region: "us-east1"
|
||||
e3: region: "us-east1"
|
||||
w1: region: "us-west1"
|
||||
w2: region: "us-west1"
|
||||
w3: region: "us-west1"
|
||||
}
|
||||
|
||||
// ClusterSets is dynamically built from the Clusters structure.
|
||||
ClusterSets: #ClusterSets & {
|
||||
// Map every cluster into the correct set.
|
||||
for CLUSTER in Clusters {
|
||||
(CLUSTER.set): clusters: (CLUSTER.name): CLUSTER
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
### Inspecting the data
|
||||
|
||||
We'll use the `holos cue` command to inspect the `ClusterSets` data structure we
|
||||
just defined.
|
||||
|
||||
<Tabs groupId="9190BDAD-B4C5-4386-9C94-8E178AA6178A">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos cue export --expression ClusterSets --out=yaml ./
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```yaml showLineNumbers
|
||||
management:
|
||||
name: management
|
||||
clusters:
|
||||
management:
|
||||
name: management
|
||||
region: us-central1
|
||||
set: management
|
||||
workload:
|
||||
name: workload
|
||||
clusters:
|
||||
local:
|
||||
name: local
|
||||
region: us-west1
|
||||
set: workload
|
||||
e1:
|
||||
name: e1
|
||||
region: us-east1
|
||||
set: workload
|
||||
e2:
|
||||
name: e2
|
||||
region: us-east1
|
||||
set: workload
|
||||
e3:
|
||||
name: e3
|
||||
region: us-east1
|
||||
set: workload
|
||||
w1:
|
||||
name: w1
|
||||
region: us-west1
|
||||
set: workload
|
||||
w2:
|
||||
name: w2
|
||||
region: us-west1
|
||||
set: workload
|
||||
w3:
|
||||
name: w3
|
||||
region: us-west1
|
||||
set: workload
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This looks like our prototype, we're confident we can iterate over each cluster
|
||||
in each set.
|
||||
|
||||
## Integrating Components
|
||||
|
||||
The `ClusterSets` data structure unlocks the capability to iterate over each
|
||||
cluster in each cluster set. We'll use this capability to integrate the
|
||||
`podinfo` component with each cluster in the platform.
|
||||
|
||||
### Configuring the Output directory
|
||||
|
||||
We need to configure `holos` to write output manifests into a cluster specific
|
||||
output directory. We'll use the [ComponentConfig] `OutputBaseDir` field for
|
||||
this purpose. We'll pass the value of this field as a component parameter.
|
||||
|
||||
```bash
|
||||
cat <<EOF > componentconfig.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#ComponentConfig: {
|
||||
// Inject the output base directory from platform component parameters.
|
||||
OutputBaseDir: string @tag(outputBaseDir, type=string)
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
### Integrating Podinfo
|
||||
|
||||
```bash
|
||||
cat <<EOF >platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Manage podinfo on all workload clusters.
|
||||
for CLUSTER in ClusterSets.workload.clusters {
|
||||
// We use the cluster name to disambiguate different podinfo build plans.
|
||||
Platform: Components: "\(CLUSTER.name)-podinfo": {
|
||||
name: "podinfo"
|
||||
// Reuse the same component across multiple workload clusters.
|
||||
path: "components/podinfo"
|
||||
// Configure a cluster-unique message in the podinfo UI.
|
||||
parameters: message: "Hello, I am cluster \(CLUSTER.name) in region \(CLUSTER.region)"
|
||||
// Write to deploy/{outputBaseDir}/components/{name}/{name}.gen.yaml
|
||||
parameters: outputBaseDir: "clusters/\(CLUSTER.name)"
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
## Rendering manifests
|
||||
|
||||
### Rendering the Platform
|
||||
|
||||
Render the platform to configure `podinfo` on each cluster.
|
||||
|
||||
<Tabs groupId="34A2D80B-0E86-4142-B65B-7DF70C47E1D2">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
cached podinfo 6.6.2
|
||||
rendered podinfo in 164.278583ms
|
||||
rendered podinfo in 165.48525ms
|
||||
rendered podinfo in 165.186208ms
|
||||
rendered podinfo in 165.831792ms
|
||||
rendered podinfo in 166.845208ms
|
||||
rendered podinfo in 167.000208ms
|
||||
rendered podinfo in 167.012208ms
|
||||
rendered platform in 167.06525ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Inspecting the Tree
|
||||
|
||||
Rendering the platform produces the following rendered manifests.
|
||||
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
```txt showLineNumbers
|
||||
deploy
|
||||
└── clusters
|
||||
├── e1
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
├── e2
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
├── e3
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
├── local
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
├── w1
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
├── w2
|
||||
│ └── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
└── w3
|
||||
└── components
|
||||
└── podinfo
|
||||
└── podinfo.gen.yaml
|
||||
|
||||
23 directories, 7 files
|
||||
```
|
||||
|
||||
### Inspecting the Variation
|
||||
|
||||
Note how each component has slight variation using the component parameters.
|
||||
|
||||
```bash
|
||||
diff -U2 deploy/clusters/{e,w}1/components/podinfo/podinfo.gen.yaml
|
||||
```
|
||||
|
||||
```diff
|
||||
--- deploy/clusters/e1/components/podinfo/podinfo.gen.yaml 2024-11-17 14:20:17
|
||||
+++ deploy/clusters/w1/components/podinfo/podinfo.gen.yaml 2024-11-17 14:20:17
|
||||
@@ -61,5 +61,5 @@
|
||||
env:
|
||||
- name: PODINFO_UI_MESSAGE
|
||||
- value: Hello, I am cluster e1 in region us-east1
|
||||
+ value: Hello, I am cluster w1 in region us-west1
|
||||
- name: PODINFO_UI_COLOR
|
||||
value: '#34577c'
|
||||
|
||||
```
|
||||
|
||||
## Concluding Remarks
|
||||
|
||||
In this topic we covered how to use CUE structures to organize multiple clusters
|
||||
into various sets.
|
||||
|
||||
1. Clusters are defined in one place at the root of the configuration.
|
||||
2. Clusters may be organized into sets by their purpose.
|
||||
3. Most organizations have at least two sets, a set of workload clusters and a
|
||||
set of management clusters.
|
||||
4. Holos uses CUE, a super set of JSON. New clusters may be added by dropping a
|
||||
JSON file into the root of the repository.
|
||||
5. The pattern of defining a `#Cluster` and a `#Clusters` collection is a
|
||||
general pattern. We'll see the same pattern for environments, projects, owners,
|
||||
and more.
|
||||
6. Component parameters are a flexible way to inject user defined configuration
|
||||
from the platform level into a reusable component.
|
||||
|
||||
[ClusterSet]: https://multicluster.sigs.k8s.io/api-types/cluster-set/
|
||||
[Environments]: ./environments.mdx
|
||||
[Namespace Sameness - SIG Multicluster Position Statement]: https://github.com/kubernetes/community/blob/master/sig-multicluster/namespace-sameness-position-statement.md
|
||||
[ComponentConfig]: ../../api/author.md#ComponentConfig
|
||||
29
doc/md/topics/structures/environments.mdx
Normal file
29
doc/md/topics/structures/environments.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
slug: environments
|
||||
title: Environments
|
||||
description: Managing Environments - dev, test, stage, prod.
|
||||
sidebar_position: 130
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Environments
|
||||
|
||||
## Overview
|
||||
|
||||
## The Code
|
||||
|
||||
### Generating the structure
|
||||
|
||||
### Using an example Component
|
||||
|
||||
## Defining Environments
|
||||
|
||||
### Defining one Environment
|
||||
|
||||
### Defining a collection of Environments
|
||||
|
||||
## Rendering manifests
|
||||
|
||||
## Reviewing the Manifests
|
||||
25
doc/md/topics/structures/index.mdx
Normal file
25
doc/md/topics/structures/index.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
slug: .
|
||||
title: Structures
|
||||
description: Commonly used CUE structures.
|
||||
sidebar_position: 120
|
||||
---
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Structures
|
||||
|
||||
This section has self contained articles covering commonly used CUE structures.
|
||||
These structures are organized and presented as recipes you may adopt and adjust
|
||||
to your unique organization.
|
||||
|
||||
:::important
|
||||
Structures are defined by Holos Users, unlike the standardized [Core] and
|
||||
[Author] schemas defined by the Holos Authors.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[Core]: ../../api/core.md
|
||||
[Author]: ../../api/author.md
|
||||
29
doc/md/topics/structures/owners.mdx
Normal file
29
doc/md/topics/structures/owners.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
slug: owners
|
||||
title: Owners
|
||||
description: Managing and mapping projects to owners.
|
||||
sidebar_position: 150
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Owners
|
||||
|
||||
## Overview
|
||||
|
||||
## The Code
|
||||
|
||||
### Generating the structure
|
||||
|
||||
### Using an example Component
|
||||
|
||||
## Defining Owners
|
||||
|
||||
### Defining one Owner
|
||||
|
||||
### Defining a collection of Owners
|
||||
|
||||
## Rendering manifests
|
||||
|
||||
## Reviewing the Manifests
|
||||
29
doc/md/topics/structures/projects.mdx
Normal file
29
doc/md/topics/structures/projects.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
slug: projects
|
||||
title: Projects
|
||||
description: Managing components organizing them into projects.
|
||||
sidebar_position: 140
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Projects
|
||||
|
||||
## Overview
|
||||
|
||||
## The Code
|
||||
|
||||
### Generating the structure
|
||||
|
||||
### Using an example Component
|
||||
|
||||
## Defining Projects
|
||||
|
||||
### Defining one Project
|
||||
|
||||
### Defining a collection of Projects
|
||||
|
||||
## Rendering manifests
|
||||
|
||||
## Reviewing the Manifests
|
||||
@@ -43,7 +43,9 @@ add the following CUE configuration to it.
|
||||
|
||||
```bash
|
||||
mkdir -p components/podinfo
|
||||
touch components/podinfo/podinfo.cue
|
||||
```
|
||||
```bash
|
||||
cat <<EOF > components/podinfo/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -66,11 +68,14 @@ Component: #Helm & {
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
Integrate the component with the platform.
|
||||
|
||||
```bash
|
||||
touch platform/podinfo.cue
|
||||
cat <<EOF > platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -80,6 +85,9 @@ Platform: Components: podinfo: {
|
||||
path: "components/podinfo"
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
Render the platform.
|
||||
|
||||
@@ -113,7 +121,7 @@ component kind. This field is a convenient wrapper around the core [BuildPlan]
|
||||
Create the mixins.cue file.
|
||||
|
||||
```bash
|
||||
touch components/podinfo/mixins.cue
|
||||
cat <<EOF > components/podinfo/mixins.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -142,6 +150,9 @@ Component: {
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
:::important
|
||||
Holos uses CUE to validate mixed in resources against a schema. The `Resources`
|
||||
|
||||
@@ -109,11 +109,11 @@ initialization.
|
||||
Start by creating a directory for the `podinfo` component. Create an empty file
|
||||
and then add the following CUE configuration to it.
|
||||
|
||||
<Tabs groupId="tutorial-hello-podinfo-helm-cue-code">
|
||||
<TabItem value="components/podinfo/podinfo.cue" label="Podinfo Helm Chart">
|
||||
```bash
|
||||
mkdir -p components/podinfo
|
||||
touch components/podinfo/podinfo.cue
|
||||
```
|
||||
```bash
|
||||
cat <<EOF > components/podinfo/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -140,8 +140,9 @@ HelmChart: #Helm & {
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
:::important
|
||||
CUE loads all of `*.cue` files in the component directory to define component,
|
||||
@@ -156,13 +157,11 @@ platform root directory. In this example, `#Helm` on line 6 is defined in
|
||||
|
||||
### Integrating the component
|
||||
|
||||
Integrate the `podinfo` component into the platform by creating a new cue file
|
||||
Integrate the `podinfo` component into the platform by creating a new CUE file
|
||||
in the `platform` directory with the following content.
|
||||
|
||||
<Tabs groupId="tutorial-hello-register-podinfo-component">
|
||||
<TabItem value="platform/podinfo.cue" label="Register Podinfo">
|
||||
```bash
|
||||
touch platform/podinfo.cue
|
||||
cat <<EOF > platform/podinfo.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -174,8 +173,9 @@ Platform: Components: podinfo: {
|
||||
parameters: greeting: "Hello Holos!"
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
:::tip
|
||||
Component parameters may have any name as long as they don't start with
|
||||
@@ -348,7 +348,7 @@ grep -B2 Hello deploy/components/podinfo/podinfo.gen.yaml
|
||||
|
||||
## Breaking it down
|
||||
|
||||
We run `holos render platform ./platform` because the cue files in the platform
|
||||
We run `holos render platform ./platform` because the CUE files in the platform
|
||||
directory export a [Platform] resource to `holos`. The platform directory is
|
||||
the entrypoint to the platform rendering process.
|
||||
|
||||
@@ -358,7 +358,7 @@ file integrates the `podinfo` Component with the Platform.
|
||||
Holos requires two fields to integrate a component with the platform.
|
||||
|
||||
1. A unique name for the component.
|
||||
2. The component path to the directory containing the cue files exporting a
|
||||
2. The component path to the directory containing the CUE files exporting a
|
||||
`BuildPlan` defining the component.
|
||||
|
||||
Component parameters are optional. They allow re-use of the same component.
|
||||
|
||||
@@ -59,14 +59,12 @@ the following file contents.
|
||||
|
||||
```bash
|
||||
mkdir -p components/prometheus components/blackbox
|
||||
touch components/prometheus/prometheus.cue
|
||||
touch components/blackbox/blackbox.cue
|
||||
```
|
||||
|
||||
<Tabs groupId="D15A3008-1EFC-4D34-BED1-15BC0C736CC3">
|
||||
<TabItem value="prometheus.cue" label="prometheus.cue">
|
||||
```txt
|
||||
components/prometheus/prometheus.cue
|
||||
```bash
|
||||
cat <<EOF > components/prometheus/prometheus.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -84,11 +82,14 @@ Helm: #Helm & {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="blackbox.cue" label="blackbox.cue">
|
||||
```txt
|
||||
components/blackbox/blackbox.cue
|
||||
```bash
|
||||
cat <<EOF > components/blackbox/blackbox.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
@@ -106,6 +107,9 @@ Helm: #Helm & {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -116,9 +120,8 @@ Integrate the components with the platform by adding the following file to the
|
||||
platform directory.
|
||||
|
||||
```bash
|
||||
touch platform/prometheus.cue
|
||||
cat <<EOF > platform/prometheus.cue
|
||||
```
|
||||
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
@@ -133,6 +136,9 @@ Platform: Components: {
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
Render the platform.
|
||||
|
||||
@@ -196,8 +202,8 @@ holos cue import \
|
||||
components/blackbox/vendor/9.0.1/prometheus-blackbox-exporter/values.yaml
|
||||
```
|
||||
|
||||
These command convert the YAML data into CUE code and nest the values under the
|
||||
`Values` field of the `Holos` struct.
|
||||
These commands convert the YAML data into CUE code and nest the values under the
|
||||
`Values` field of the `Helm` struct.
|
||||
|
||||
:::important
|
||||
CUE unifies `values.cue` with the other `*.cue` files in the same directory.
|
||||
@@ -243,9 +249,8 @@ use. We add this configuration to the `components` directory so it's in scope
|
||||
for all components.
|
||||
|
||||
```bash
|
||||
touch components/blackbox.cue
|
||||
cat <<EOF > components/blackbox.cue
|
||||
```
|
||||
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
@@ -263,6 +268,9 @@ Blackbox: #Blackbox & {
|
||||
port: 9115
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
:::important
|
||||
1. CUE loads and unifies all `*.cue` files from the root directory containing
|
||||
|
||||
@@ -61,11 +61,12 @@ Create the `httpbin` component directory and add the `httpbin.cue` and
|
||||
<TabItem value="setup" label="Setup">
|
||||
```bash
|
||||
mkdir -p components/httpbin
|
||||
touch components/httpbin/httpbin.cue
|
||||
touch components/httpbin/httpbin.yaml
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="components/httpbin/httpbin.cue" label="httpbin.cue">
|
||||
```bash
|
||||
cat <<EOF > components/httpbin/httpbin.cue
|
||||
```
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
@@ -97,9 +98,15 @@ Kustomize: #Kustomize & {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="components/httpbin/httpbin.yaml" label="httpbin.yaml">
|
||||
```bash
|
||||
cat <<EOF > components/httpbin/httpbin.yaml
|
||||
```
|
||||
```yaml showLineNumbers
|
||||
# https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/resources.yaml
|
||||
apiVersion: apps/v1
|
||||
@@ -137,6 +144,9 @@ spec:
|
||||
protocol: TCP
|
||||
name: http
|
||||
appProtocol: http
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -150,9 +160,8 @@ Integrate `httpbin` with the platform by adding the following file to the
|
||||
platform directory.
|
||||
|
||||
```bash
|
||||
touch platform/httpbin.cue
|
||||
cat <<EOF > platform/httpbin.cue
|
||||
```
|
||||
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
@@ -163,6 +172,9 @@ Platform: Components: {
|
||||
}
|
||||
}
|
||||
```
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
Render the platform.
|
||||
|
||||
@@ -275,13 +287,9 @@ makes this easier with CUE. We don't need to edit any yaml files.
|
||||
Add a new `patches.cue` file to the `httpbin` component with the following
|
||||
content.
|
||||
|
||||
<Tabs groupId="104D40FD-ED59-4F66-8B91-435436084743">
|
||||
<TabItem value="touch" label="touch">
|
||||
```bash
|
||||
touch components/httpbin/patches.cue
|
||||
cat <<EOF > components/httpbin/patches.cue
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="patches.cue" label="patches.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
@@ -300,8 +308,9 @@ Kustomize: KustomizeConfig: Kustomization: _patches: {
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
```bash
|
||||
EOF
|
||||
```
|
||||
|
||||
:::note
|
||||
We use a hidden `_patches` field to easily unify data into a struct, then
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
slug: schema-definitions
|
||||
title: Schema Definitions
|
||||
description: Define your own custom data structures.
|
||||
sidebar_position: 70
|
||||
---
|
||||
|
||||
# Schema Definitions
|
||||
|
||||
- Work through defining a `#Cluster` schema and a `Clusters` struct.
|
||||
- Direct the reader to [topics] for more recipes.
|
||||
|
||||
[topics]: ../topics.mdx
|
||||
@@ -17,22 +17,54 @@ This tutorial will guide you through the installation of Holos and its
|
||||
dependencies, as well as the initialization of a minimal Platform that you can
|
||||
extend to meet your specific needs.
|
||||
|
||||
## Installing Holos
|
||||
## Installing
|
||||
|
||||
Holos is distributed as a single file executable that can be installed in a
|
||||
couple of ways.
|
||||
|
||||
<Tabs groupId="FE2C74C8-B3A3-4AEA-BBD3-F57FAA654B6F">
|
||||
<TabItem value="brew" label="Install with brew">
|
||||
```bash
|
||||
brew install holos-run/tap/holos
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="go" label="Go">
|
||||
```bash
|
||||
go install github.com/holos-run/holos/cmd/holos@latest
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Completion
|
||||
|
||||
<Tabs groupId="65F79D28-2E57-4A90-8EBA-3D8758C80233">
|
||||
<TabItem value="zsh" label="zsh">
|
||||
```bash
|
||||
source <(holos completion zsh)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="bash" label="bash">
|
||||
```bash
|
||||
source <(holos completion bash)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="fish" label="fish">
|
||||
```bash
|
||||
source <(holos completion fish)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="ksh" label="ksh">
|
||||
```bash
|
||||
source <(holos completion ksh)
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Releases
|
||||
|
||||
Download `holos` from the [releases] page and place the executable into your
|
||||
shell path.
|
||||
|
||||
### Go Install
|
||||
|
||||
```shell
|
||||
go install github.com/holos-run/holos/cmd/holos@latest
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Holos integrates with the following tools that should be installed to enable
|
||||
@@ -41,8 +73,12 @@ their functionality.
|
||||
- [Helm] to fetch and render Helm chart Components.
|
||||
- [Kubectl] to [kustomize] components.
|
||||
|
||||
Holos is tested with Helm version `v3.16.2`. Please try upgrading helm if you
|
||||
encounter `Error: chart requires kubeVersion ...` errors.
|
||||
:::note
|
||||
Holos is tested with Helm version `v3.16.2`.
|
||||
:::
|
||||
|
||||
Please try upgrading helm if you encounter `Error: chart requires kubeVersion
|
||||
...` errors.
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ func New(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
|
||||
cmd := command.New("render")
|
||||
cmd.Args = cobra.NoArgs
|
||||
cmd.Short = "render platforms and components to manifest files"
|
||||
cmd.AddCommand(NewComponent(cfg))
|
||||
cmd.AddCommand(NewPlatform(cfg))
|
||||
cmd.AddCommand(NewComponent(cfg, feature))
|
||||
cmd.AddCommand(NewPlatform(cfg, feature))
|
||||
return cmd
|
||||
}
|
||||
|
||||
// New returns the component subcommand for the render command
|
||||
func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
func NewComponent(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
|
||||
cmd := command.New("component DIRECTORY")
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Short = "render a platform component"
|
||||
@@ -43,8 +43,10 @@ func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
cmd.Flags().AddGoFlagSet(cfg.ClusterFlagSet())
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
if feature.Flag(holos.ClientFeature) {
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
}
|
||||
|
||||
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
|
||||
@@ -176,15 +178,17 @@ func NewComponent(cfg *holos.Config) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewPlatform(cfg *holos.Config) *cobra.Command {
|
||||
func NewPlatform(cfg *holos.Config, feature holos.Flagger) *cobra.Command {
|
||||
cmd := command.New("platform DIRECTORY")
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Example = " holos render platform ./platform"
|
||||
cmd.Short = "render an entire platform"
|
||||
|
||||
config := client.NewConfig(cfg)
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
if feature.Flag(holos.ClientFeature) {
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.ClientFlagSet())
|
||||
cmd.PersistentFlags().AddGoFlagSet(config.TokenFlagSet())
|
||||
}
|
||||
|
||||
var concurrency int
|
||||
cmd.Flags().IntVar(&concurrency, "concurrency", min(runtime.NumCPU(), 8), "number of components to render concurrently")
|
||||
|
||||
@@ -31,6 +31,7 @@ type feature string
|
||||
|
||||
const BuildFeature = feature("BUILD")
|
||||
const ServerFeature = feature("SERVER")
|
||||
const ClientFeature = feature("CLIENT")
|
||||
const PreflightFeature = feature("PREFLIGHT")
|
||||
const GenerateComponentFeature = feature("GENERATE_COMPONENT")
|
||||
const SecretsFeature = feature("SECRETS")
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
3
|
||||
|
||||
Reference in New Issue
Block a user