Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a47edbc3d | ||
|
|
67d00f1dd4 | ||
|
|
bfa02cd6ed | ||
|
|
2d8ca474f3 | ||
|
|
c7cd6f5190 | ||
|
|
21e3e6f5e4 | ||
|
|
16a4f89c2f | ||
|
|
f15dea5ee7 | ||
|
|
a3bbadd1f5 | ||
|
|
30cbb0d537 | ||
|
|
6041fd4d76 | ||
|
|
fec1de0004 | ||
|
|
6ad24a6eec | ||
|
|
57dedc6450 | ||
|
|
8d2a9dd659 | ||
|
|
e3c53f5655 | ||
|
|
3b833cdacd |
31
.cspell.json
@@ -5,25 +5,34 @@
|
||||
"mdx"
|
||||
],
|
||||
"words": [
|
||||
"acraccesstokens",
|
||||
"admissionregistration",
|
||||
"anthos",
|
||||
"apiextensions",
|
||||
"applicationset",
|
||||
"applicationsets",
|
||||
"appproject",
|
||||
"appprojects",
|
||||
"argoproj",
|
||||
"authcode",
|
||||
"authorizationpolicies",
|
||||
"authpolicy",
|
||||
"authproxy",
|
||||
"authroutes",
|
||||
"balancereader",
|
||||
"buildplan",
|
||||
"cainjector",
|
||||
"CAROOT",
|
||||
"certificaterequests",
|
||||
"certificatesigningrequests",
|
||||
"clsx",
|
||||
"clusterexternalsecrets",
|
||||
"clusterissuer",
|
||||
"clusterissuers",
|
||||
"clusterrole",
|
||||
"clusterrolebinding",
|
||||
"clustersecretstores",
|
||||
"CNCF",
|
||||
"configmap",
|
||||
"cookiesecret",
|
||||
"coredns",
|
||||
@@ -40,16 +49,22 @@
|
||||
"devicecode",
|
||||
"dnsmasq",
|
||||
"dscacheutil",
|
||||
"ecrauthorizationtokens",
|
||||
"entgo",
|
||||
"envoyfilters",
|
||||
"errgroup",
|
||||
"etcdsnapshotfiles",
|
||||
"externalsecret",
|
||||
"externalsecrets",
|
||||
"fctr",
|
||||
"fieldmaskpb",
|
||||
"flushcache",
|
||||
"gatewayclasses",
|
||||
"gcraccesstokens",
|
||||
"gendoc",
|
||||
"ggnpl",
|
||||
"ghaction",
|
||||
"githubaccesstokens",
|
||||
"gitops",
|
||||
"godoc",
|
||||
"golangci",
|
||||
@@ -57,6 +72,9 @@
|
||||
"grpcreflect",
|
||||
"grpcroutes",
|
||||
"grpcurl",
|
||||
"healthz",
|
||||
"helmchartconfigs",
|
||||
"helmcharts",
|
||||
"holos",
|
||||
"holoslogger",
|
||||
"horizontalpodautoscaler",
|
||||
@@ -67,8 +85,10 @@
|
||||
"isatty",
|
||||
"istiod",
|
||||
"jbrx",
|
||||
"jeffmccune",
|
||||
"jetstack",
|
||||
"Jsonnet",
|
||||
"kfbh",
|
||||
"killall",
|
||||
"kubeadm",
|
||||
"kubeconfig",
|
||||
@@ -78,6 +98,7 @@
|
||||
"kustomize",
|
||||
"ldflags",
|
||||
"leaderelection",
|
||||
"ledgerwriter",
|
||||
"libnss",
|
||||
"loadbalancer",
|
||||
"mattn",
|
||||
@@ -101,6 +122,7 @@
|
||||
"pipefail",
|
||||
"PKCE",
|
||||
"platformconnect",
|
||||
"podcli",
|
||||
"poddisruptionbudget",
|
||||
"podinfo",
|
||||
"portmapping",
|
||||
@@ -109,9 +131,11 @@
|
||||
"protojson",
|
||||
"proxyconfigs",
|
||||
"Pulumi",
|
||||
"pushsecrets",
|
||||
"putenv",
|
||||
"qjbp",
|
||||
"quickstart",
|
||||
"readyz",
|
||||
"referencegrant",
|
||||
"referencegrants",
|
||||
"requestauthentications",
|
||||
@@ -120,14 +144,17 @@
|
||||
"ropc",
|
||||
"seccomp",
|
||||
"SECRETKEY",
|
||||
"secretstore",
|
||||
"secretstores",
|
||||
"serverlb",
|
||||
"serverside",
|
||||
"serviceaccount",
|
||||
"servicebindings",
|
||||
"serviceentries",
|
||||
"spanid",
|
||||
"spiffe",
|
||||
"startupapicheck",
|
||||
"statefulset",
|
||||
"stefanprodan",
|
||||
"structpb",
|
||||
"subjectaccessreviews",
|
||||
@@ -142,14 +169,18 @@
|
||||
"Tokener",
|
||||
"Traceid",
|
||||
"traefik",
|
||||
"transactionhistory",
|
||||
"uibutton",
|
||||
"unstage",
|
||||
"untar",
|
||||
"Upsert",
|
||||
"urandom",
|
||||
"usecases",
|
||||
"userconnect",
|
||||
"userdata",
|
||||
"userservice",
|
||||
"validatingwebhookconfiguration",
|
||||
"vaultdynamicsecrets",
|
||||
"virtualservices",
|
||||
"wasmplugins",
|
||||
"workloadentries",
|
||||
|
||||
@@ -161,3 +161,22 @@ type Platform struct {
|
||||
// platform operators to define.
|
||||
Domain string `cue:"string | *\"holos.localhost\""`
|
||||
}
|
||||
|
||||
// Organization represents organizational metadata useful across the platform.
|
||||
type Organization struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
// OrganizationStrict represents organizational metadata useful across the
|
||||
// platform. This is an example of using CUE regular expressions to constrain
|
||||
// and validate configuration.
|
||||
type OrganizationStrict struct {
|
||||
Organization `json:",inline"`
|
||||
// Name represents the organization name as a resource name. Must be 63
|
||||
// characters or less. Must start with a letter. May contain non-repeating
|
||||
// hyphens, letters, and numbers. Must end with a letter or number.
|
||||
Name string `cue:"=~ \"^[a-z][0-9a-z-]{1,61}[0-9a-z]$\" & !~ \"--\""`
|
||||
// DisplayName represents the human readable organization name.
|
||||
DisplayName string `cue:"=~ \"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$\" & !~ \" \""`
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Schema API
|
||||
# Author API
|
||||
|
||||
<DocCardList />
|
||||
@@ -3,7 +3,7 @@
|
||||
# v1alpha3
|
||||
|
||||
```go
|
||||
import "github.com/holos-run/holos/api/schema/v1alpha3"
|
||||
import "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
```
|
||||
|
||||
Package v1alpha3 contains CUE definitions intended as convenience wrappers around the core data types defined in package core. The purpose of these wrappers is to make life easier for platform engineers by reducing boiler plate code and generating component build plans in a consistent manner.
|
||||
@@ -17,6 +17,8 @@ Package v1alpha3 contains CUE definitions intended as convenience wrappers aroun
|
||||
- [type Helm](<#Helm>)
|
||||
- [type Kubernetes](<#Kubernetes>)
|
||||
- [type Kustomize](<#Kustomize>)
|
||||
- [type Organization](<#Organization>)
|
||||
- [type OrganizationStrict](<#OrganizationStrict>)
|
||||
- [type Platform](<#Platform>)
|
||||
- [type StandardFleets](<#StandardFleets>)
|
||||
|
||||
@@ -172,6 +174,35 @@ type Kustomize struct {
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Organization"></a>
|
||||
## type Organization {#Organization}
|
||||
|
||||
Organization represents organizational metadata useful across the platform.
|
||||
|
||||
```go
|
||||
type Organization struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
}
|
||||
```
|
||||
|
||||
<a name="OrganizationStrict"></a>
|
||||
## type OrganizationStrict {#OrganizationStrict}
|
||||
|
||||
OrganizationStrict represents organizational metadata useful across the platform. This is an example of using CUE regular expressions to constrain and validate configuration.
|
||||
|
||||
```go
|
||||
type OrganizationStrict struct {
|
||||
Organization `json:",inline"`
|
||||
// Name represents the organization name as a resource name. Must be 63
|
||||
// characters or less. Must start with a letter. May contain non-repeating
|
||||
// hyphens, letters, and numbers. Must end with a letter or number.
|
||||
Name string `cue:"=~ \"^[a-z][0-9a-z-]{1,61}[0-9a-z]$\" & !~ \"--\""`
|
||||
// DisplayName represents the human readable organization name.
|
||||
DisplayName string `cue:"=~ \"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$\" & !~ \" \""`
|
||||
}
|
||||
```
|
||||
|
||||
<a name="Platform"></a>
|
||||
## type Platform {#Platform}
|
||||
|
||||
724
doc/md/archive/guides/2024-09-15-quickstart.mdx
Normal file
@@ -0,0 +1,724 @@
|
||||
---
|
||||
description: Try Holos with this quick start guide.
|
||||
slug: /archive/2024-09-15-quickstart
|
||||
sidebar_position: 100
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Quickstart
|
||||
|
||||
In this guide, you'll experience how Holos makes the process of operating a
|
||||
Platform safer, easier, and more consistent. We'll use Holos to manage a
|
||||
vendor-provided Helm chart as a Component. Next, we'll mix in our own custom
|
||||
resources to manage the Component with GitOps. Finally, you'll see how Holos
|
||||
makes it safer and easier to maintain software over time by surfacing the exact
|
||||
changes that will be applied when upgrading the vendor's chart to a new version,
|
||||
before they are actually made.
|
||||
|
||||
The [Concepts](/docs/concepts) page defines capitalized terms such as Platform
|
||||
and Component.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
You'll need the following tools installed to complete this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Holos Components that
|
||||
wrap upstream Helm charts.
|
||||
|
||||
Optionally, if you'd like to apply the rendered manifests to a real Cluster,
|
||||
first complete the [Local Cluster Guide](/docs/guides/local-cluster).
|
||||
|
||||
## Install Holos
|
||||
|
||||
Install Holos with the following command or other methods listed on the
|
||||
[Installation](/docs/install/) page.
|
||||
|
||||
```bash
|
||||
go install github.com/holos-run/holos/cmd/holos@latest
|
||||
```
|
||||
|
||||
## Create a Git Repository
|
||||
|
||||
Start by initializing an empty Git repository. Holos operates on local files
|
||||
stored in a Git repository.
|
||||
|
||||
<Tabs groupId="init">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
mkdir holos-quickstart
|
||||
cd holos-quickstart
|
||||
git init
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
Initialized empty Git repository in /holos-quickstart/.git/
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This guide assumes you will run commands from the root directory of the Git
|
||||
repository unless stated otherwise.
|
||||
|
||||
## Generate the Platform {#Generate-Platform}
|
||||
|
||||
Generate the Platform code in the repository root. A Platform refers to the
|
||||
entire set of software holistically integrated to provide a software development
|
||||
platform for your organization. In this guide, the Platform will include a
|
||||
single Component to demonstrate how the concepts fit together.
|
||||
|
||||
```bash
|
||||
holos generate platform quickstart
|
||||
```
|
||||
|
||||
Commit the generated platform config to the repository.
|
||||
|
||||
<Tabs groupId="commit-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos generate platform quickstart - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main (root-commit) 0b17b7f] holos generate platform quickstart
|
||||
213 files changed, 72349 insertions(+)
|
||||
...
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Generate a Component {#generate-component}
|
||||
|
||||
The platform you generated is currently empty. Run the following command to
|
||||
generate the CUE code that defines a Helm Component.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component podinfo --component-version 6.6.1
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The --component-version 6.6.1 flag intentionally installs an older release.
|
||||
You'll see how Holos assists with software upgrades later in this guide.
|
||||
|
||||
The generate component command creates two files: a leaf file,
|
||||
`components/podinfo/podinfo.gen.cue`, and a root file, `podinfo.gen.cue`. Holos
|
||||
leverages the fact that [order is
|
||||
irrelevant](https://cuelang.org/docs/tour/basics/order-irrelevance/) in CUE to
|
||||
register the component with the Platform by adding a file to the root of the Git
|
||||
repository. The second file defines the component in the leaf component
|
||||
directory.
|
||||
|
||||
<Tabs groupId="podinfo-files">
|
||||
<TabItem value="components/podinfo/podinfo.gen.cue" label="Leaf">
|
||||
`components/podinfo/podinfo.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a helm chart build plan.
|
||||
(#Helm & Chart).Output
|
||||
|
||||
let Chart = {
|
||||
Name: "podinfo"
|
||||
Version: "6.6.1"
|
||||
Namespace: "default"
|
||||
|
||||
Repo: name: "podinfo"
|
||||
Repo: url: "https://stefanprodan.github.io/podinfo"
|
||||
|
||||
Values: {}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="podinfo.gen.cue" label="Root">
|
||||
`podinfo.gen.cue`
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Manage podinfo on workload clusters only
|
||||
for Cluster in #Fleets.workload.clusters {
|
||||
#Platform: Components: "\(Cluster.name)/podinfo": {
|
||||
path: "components/podinfo"
|
||||
cluster: Cluster.name
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this example, we provide the minimal information needed to manage the Helm
|
||||
chart: the name, version, Kubernetes namespace for deployment, and the chart
|
||||
repository location.
|
||||
|
||||
This chart deploys cleanly without any values provided, but we include an empty
|
||||
Values struct to show how Holos improves consistency and safety in Helm by
|
||||
leveraging the strong type-checking in CUE. You can safely pass shared values,
|
||||
such as the organization’s domain name, to all Components across all clusters in
|
||||
the Platform by defining them at the root of the configuration.
|
||||
|
||||
Commit the generated component config to the repository.
|
||||
|
||||
<Tabs groupId="commit-component">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos generate component podinfo - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main cc0e90c] holos generate component podinfo
|
||||
2 files changed, 24 insertions(+)
|
||||
create mode 100644 components/podinfo/podinfo.gen.cue
|
||||
create mode 100644 podinfo.gen.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Render the Component
|
||||
|
||||
You can render individual components without adding them to a Platform, which is
|
||||
helpful when developing a new component.
|
||||
|
||||
<Tabs groupId="render-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render component ./components/podinfo --cluster-name=default
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
cached
|
||||
rendered podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
First, the command caches the Helm chart locally to speed up subsequent
|
||||
renderings. Then, the command runs Helm to produce the output and writes it into
|
||||
the deploy directory.
|
||||
|
||||
<Tabs groupId="tree-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
└── default
|
||||
└── components
|
||||
└── podinfo
|
||||
└── podinfo.gen.yaml
|
||||
|
||||
5 directories, 1 file
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The component deploys to one cluster named `default`. In practice, the same
|
||||
component is often deployed to multiple clusters, such as `east` and `west` to
|
||||
provide redundancy and increase availability.
|
||||
|
||||
:::tip
|
||||
This example is equivalent to running `helm template` on the chart and saving
|
||||
the output to a file. Holos simplifies this task, making it safer and more
|
||||
consistent when managing many charts.
|
||||
:::
|
||||
|
||||
## Mix in an ArgoCD Application
|
||||
|
||||
We've seen how Holos works with Helm, but we haven't yet explored how Holos
|
||||
makes it easier to consistently and safely manage all of the software in a
|
||||
Platform.
|
||||
|
||||
Holos allows you to easily mix in resources that differentiate your Platform.
|
||||
We'll use this feature to mix in an ArgoCD [Application][application] to manage
|
||||
the podinfo Component with GitOps. We'll define this configuration in a way that
|
||||
can be automatically and consistently reused across all future Components added
|
||||
to the Platform.
|
||||
|
||||
Create a new file named `argocd.cue` in the root of your Git repository with the
|
||||
following contents:
|
||||
|
||||
<Tabs groupId="argocd-config">
|
||||
<TabItem value="command" label="argocd.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#ArgoConfig: {
|
||||
Enabled: true
|
||||
RepoURL: "https://github.com/holos-run/holos-quickstart-guide"
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
If you plan to apply the rendered output to a real cluster, change the
|
||||
`example.com` RepoURL to the URL of the Git repository you created in this
|
||||
guide. You don't need to change the example if you're just exploring Holos by
|
||||
inspecting the rendered output without applying it to a live cluster.
|
||||
:::
|
||||
|
||||
With this file in place, render the component again.
|
||||
|
||||
<Tabs groupId="render-podinfo-argocd">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render component ./components/podinfo --cluster-name=default
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
wrote deploy file
|
||||
rendered gitops/podinfo
|
||||
rendered podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Holos uses the locally cached chart to improve performance and reliability. It
|
||||
then renders the Helm template output along with an ArgoCD Application resource
|
||||
for GitOps.
|
||||
|
||||
:::tip
|
||||
By defining the ArgoCD configuration at the root, we again take advantage of the
|
||||
fact that [order is
|
||||
irrelevant](https://cuelang.org/docs/tour/basics/order-irrelevance/) in CUE.
|
||||
:::
|
||||
|
||||
Defining the configuration at the root ensures all future leaf Components take
|
||||
the ArgoCD configuration and render an Application manifest for GitOps
|
||||
management.
|
||||
|
||||
<Tabs groupId="tree-podinfo-argocd">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
└── default
|
||||
├── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
└── gitops
|
||||
└── podinfo.application.gen.yaml
|
||||
|
||||
6 directories, 2 files
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Notice the new `podinfo.application.gen.yaml` file created by enabling ArgoCD in
|
||||
the Helm component. The Application resource in the file looks like this:
|
||||
|
||||
<Tabs groupId="podinfo-application">
|
||||
<TabItem value="file" label="podinfo.application.gen.yaml">
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
path: ./deploy/clusters/default/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
Holos generates a similar Application resource for every additional Component
|
||||
added to your Platform.
|
||||
:::
|
||||
|
||||
Finally, add and commit the results to your Platform's Git repository.
|
||||
|
||||
<Tabs groupId="commit-argo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos render component ./components/podinfo --cluster-name=default"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main f95cef1] holos render component ./components/podinfo --cluster-name=default
|
||||
3 files changed, 134 insertions(+)
|
||||
create mode 100644 argocd.cue
|
||||
create mode 100644 deploy/clusters/default/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/default/gitops/podinfo.application.gen.yaml
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In this section, we learned how Holos simplifies mixing resources into
|
||||
Components, like an ArgoCD Application. Holos ensures consistency by managing an
|
||||
Application resource for every Component added to the Platform through the
|
||||
configuration you define in `argocd.cue` at the root of the repository.
|
||||
|
||||
## Define Workload Clusters {#workload-clusters}
|
||||
|
||||
We've generated a Component to manage podinfo and integrated it with our
|
||||
Platform, but rendering the Platform doesn't render podinfo. Podinfo isn't
|
||||
rendered because we haven't assigned any Clusters to the workload Fleet.
|
||||
|
||||
Define two new clusters, `east` and `west`, and assign them to the workload
|
||||
Fleet. Create a new file named `clusters.cue` in the root of your Git repository
|
||||
with the following contents:
|
||||
|
||||
<Tabs groupId="clusters">
|
||||
<TabItem value="clusters.cue" label="clusters.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Define two workload clusters for disaster recovery.
|
||||
#Fleets: workload: clusters: {
|
||||
// In CUE _ indicates values are defined elsewhere.
|
||||
east: _
|
||||
west: _
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This example shows how Holos simplifies configuring multiple clusters with
|
||||
similar configuration by grouping them into a Fleet.
|
||||
|
||||
:::tip
|
||||
Fleets help segment a group of Clusters into one leader and multiple followers
|
||||
by designating one cluster as the primary. Holos makes it safer, easier, and
|
||||
more consistent to reconfigure which cluster is the primary. The primary can be
|
||||
set to automatically restore persistent data from backups, while non-primary
|
||||
clusters can be configured to automatically replicate from the primary.
|
||||
|
||||
Automatic database backup, restore, and streaming replication is an advanced
|
||||
topic enabled by Cloud Native PG and CUE. Check back for a guide on this and
|
||||
other Day 2 operations topics.
|
||||
:::
|
||||
|
||||
## Render the Platform {#render-platform}
|
||||
|
||||
Render the Platform to render the podinfo Component for each of the workload
|
||||
clusters.
|
||||
|
||||
<Tabs groupId="render-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered components/podinfo for cluster west in 99.480792ms
|
||||
rendered components/podinfo for cluster east in 99.882667ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The render platform command iterates over every Cluster in the Fleet and renders
|
||||
each Component assigned to the Fleet. Notice the two additional subdirectories
|
||||
created under the deploy directory, one for each cluster: `east` and `west`.
|
||||
|
||||
<Tabs groupId="tree-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
tree deploy
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
deploy
|
||||
└── clusters
|
||||
├── default
|
||||
│ ├── components
|
||||
│ │ └── podinfo
|
||||
│ │ └── podinfo.gen.yaml
|
||||
│ └── gitops
|
||||
│ └── podinfo.application.gen.yaml
|
||||
# highlight-next-line
|
||||
├── east
|
||||
│ ├── components
|
||||
│ │ └── podinfo
|
||||
│ │ └── podinfo.gen.yaml
|
||||
│ └── gitops
|
||||
│ └── podinfo.application.gen.yaml
|
||||
# highlight-next-line
|
||||
└── west
|
||||
├── components
|
||||
│ └── podinfo
|
||||
│ └── podinfo.gen.yaml
|
||||
└── gitops
|
||||
└── podinfo.application.gen.yaml
|
||||
|
||||
14 directories, 6 files
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Holos ensures consistency and safety by defining the ArgoCD Application once,
|
||||
with strong type checking, at the configuration root.
|
||||
|
||||
New Application resources are automatically generated for the `east` and `west`
|
||||
workload Clusters.
|
||||
|
||||
<Tabs groupId="applications">
|
||||
<TabItem value="east" label="east">
|
||||
`deploy/clusters/east/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/east/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="west" label="west">
|
||||
`deploy/clusters/west/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/west/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="default" label="default">
|
||||
`deploy/clusters/default/gitops/podinfo.application.gen.yaml`
|
||||
```yaml showLineNumbers
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: podinfo
|
||||
namespace: argocd
|
||||
spec:
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
project: default
|
||||
source:
|
||||
# highlight-next-line
|
||||
path: ./deploy/clusters/default/components/podinfo
|
||||
repoURL: https://example.com/holos-quickstart.git
|
||||
targetRevision: main
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Add and commit the rendered Platform and workload Clusters.
|
||||
|
||||
<Tabs groupId="commit-render-platform">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "holos render platform ./platform - $(holos --version)"
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main 5aebcf5] holos render platform ./platform - 0.93.2
|
||||
5 files changed, 263 insertions(+)
|
||||
create mode 100644 clusters.cue
|
||||
create mode 100644 deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/east/gitops/podinfo.application.gen.yaml
|
||||
create mode 100644 deploy/clusters/west/components/podinfo/podinfo.gen.yaml
|
||||
create mode 100644 deploy/clusters/west/gitops/podinfo.application.gen.yaml
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Upgrade a Helm Chart
|
||||
|
||||
Holos is designed to ease the burden of Day 2 operations. With Holos, upgrading
|
||||
software, integrating new software, and making safe platform-wide configuration
|
||||
changes become easier.
|
||||
|
||||
Let's upgrade the podinfo Component to see how this works in practice. First,
|
||||
update the Component version field to the latest upstream Helm chart version.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos generate component podinfo --component-version 6.6.2
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
generated component
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Remove the cached chart version.
|
||||
|
||||
<Tabs groupId="gen-podinfo">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
rm -rf components/podinfo/vendor
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Now re-render the Platform.
|
||||
|
||||
<Tabs groupId="render-platform2">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered components/podinfo for cluster east in 327.10475ms
|
||||
rendered components/podinfo for cluster west in 327.796541ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Notice we're still using the upstream chart without modifying it. The Holos
|
||||
component wraps around the chart to mix in additional resources and integrate
|
||||
the component with the broader Platform.
|
||||
|
||||
## Visualize the Changes
|
||||
|
||||
Holos makes it easier to see exactly what changes are made and which resources
|
||||
will be applied to the API server. By design, Holos operates on local files,
|
||||
leaving the task of applying them to ecosystem tools like `kubectl` and ArgoCD.
|
||||
This allows platform operators to inspect changes during code review, or before
|
||||
committing the change at all.
|
||||
|
||||
For example, using `git diff`, we see that the only functional change when
|
||||
upgrading this Helm chart is the deployment of a new container image tag to each
|
||||
cluster. Additionally, we can roll out this change gradually by applying it to
|
||||
the east cluster first, then to the west cluster, limiting the potential blast
|
||||
radius of a problematic change.
|
||||
|
||||
<Tabs groupId="git-diff">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff deploy/clusters/east
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff showLineNumbers
|
||||
diff --git a/deploy/clusters/east/components/podinfo/podinfo.gen.yaml b/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
index 7cc3332..8c1647d 100644
|
||||
--- a/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
+++ b/deploy/clusters/east/components/podinfo/podinfo.gen.yaml
|
||||
@@ -5,9 +5,9 @@ kind: Service
|
||||
metadata:
|
||||
name: podinfo
|
||||
labels:
|
||||
- helm.sh/chart: podinfo-6.6.1
|
||||
+ helm.sh/chart: podinfo-6.6.2
|
||||
app.kubernetes.io/name: podinfo
|
||||
- app.kubernetes.io/version: "6.6.1"
|
||||
+ app.kubernetes.io/version: "6.6.2"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
@@ -29,9 +29,9 @@ kind: Deployment
|
||||
metadata:
|
||||
name: podinfo
|
||||
labels:
|
||||
- helm.sh/chart: podinfo-6.6.1
|
||||
+ helm.sh/chart: podinfo-6.6.2
|
||||
app.kubernetes.io/name: podinfo
|
||||
- app.kubernetes.io/version: "6.6.1"
|
||||
+ app.kubernetes.io/version: "6.6.2"
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
@@ -53,7 +53,7 @@ spec:
|
||||
terminationGracePeriodSeconds: 30
|
||||
containers:
|
||||
- name: podinfo
|
||||
# highlight-next-line
|
||||
- image: "ghcr.io/stefanprodan/podinfo:6.6.1"
|
||||
# highlight-next-line
|
||||
+ image: "ghcr.io/stefanprodan/podinfo:6.6.2"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- ./podinfo
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
Holos is designed to surface the _fully rendered_ manifests intended for the
|
||||
Kubernetes API server, making it easier to see and reason about platform-wide
|
||||
configuration changes.
|
||||
:::
|
||||
|
||||
## Recap {#recap}
|
||||
|
||||
In this quickstart guide, we learned how Holos makes it easier, safer, and more
|
||||
consistent to manage a Platform composed of multiple Clusters and upstream Helm
|
||||
charts.
|
||||
|
||||
We covered how to:
|
||||
|
||||
1. Generate a Git repository for the Platform config.
|
||||
2. Wrap the unmodified upstream podinfo Helm chart into a Component.
|
||||
3. Render an individual Component.
|
||||
4. Mix-in your Platform's unique resources to all Components. For example, ArgoCD Application resources.
|
||||
5. Define multiple similar, but not identical, workload clusters.
|
||||
6. Render the manifests for the entire Platform with the `holos render platform` command.
|
||||
7. Upgrade a Helm chart to the latest version as an important Day 2 task.
|
||||
8. Visualize and surface the details of planned changes Platform wide.
|
||||
|
||||
## Dive Deeper
|
||||
|
||||
If you'd like to dive deeper, check out the [Schema API][schema] and [Core
|
||||
API][core] reference docs. The main difference between the schema and core
|
||||
packages is that the schema is used by users to write refined CUE, while the
|
||||
core package is what the schema produces for `holos` to execute. Users rarely
|
||||
need to interact with the Core API when on the happy path, but can use the core
|
||||
package as an escape hatch when the happy path doesn't go where you want.
|
||||
|
||||
|
||||
[application]: https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/
|
||||
[schema]: /docs/api/author/v1alpha3/
|
||||
[core]: /docs/api/core/v1alpha3/
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Self service platform resource management for project teams.
|
||||
slug: /guides/manage-a-project
|
||||
sidebar_position: 200
|
||||
slug: /archive/guides/2024-09-17-manage-a-project
|
||||
sidebar_position: 250
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
@@ -2,4 +2,23 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Guides
|
||||
|
||||
## Start with the [Quickstart] guide
|
||||
|
||||
The guides are organized as a progression. We'll use Holos to manage a
|
||||
fictional bank's platform, the Bank of Holos in each of the guides. In doing so
|
||||
we'll take the time to explain the foundational concepts of Holos.
|
||||
|
||||
1. [Quickstart] covers the foundational concepts of Holos.
|
||||
2. [Deploy a Service] explains how to deploy a containerized service using an
|
||||
existing Helm chart. This guide then explains how to deploy a similar service
|
||||
safely and consistently with CUE instead of Helm.
|
||||
3. [Change a Service] covers the day two task of making configuration changes to
|
||||
deployed services safely and consistently.
|
||||
|
||||
---
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[Deploy a Service]: /docs/guides/deploy-a-service/
|
||||
[Change a Service]: /docs/guides/change-a-service/
|
||||
|
||||
700
doc/md/guides/change-a-service.mdx
Normal file
@@ -0,0 +1,700 @@
|
||||
---
|
||||
description: Change a service on your platform.
|
||||
slug: /guides/change-a-service
|
||||
sidebar_position: 300
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
# Change a Service
|
||||
|
||||
In this guide we'll explore how Holos helps the frontend development team at the
|
||||
[Bank of Holos] reconfigures a service they've already deployed. In doing so,
|
||||
we'll see how simple configuration configuration changes are safer with type
|
||||
checking, and how rendering the complete platform provides clear visibility into
|
||||
changes.
|
||||
|
||||
We'll build on the concepts we learned in the [Quickstart] and [Deploy a
|
||||
Service] guides.
|
||||
|
||||
## What you'll need {#requirements}
|
||||
|
||||
Like our other guides, this guide is intended to be useful without needing to
|
||||
run the commands. If you'd like to render the platform and apply the manifests
|
||||
to a real Cluster, complete the [Local Cluster
|
||||
Guide](/docs/guides/local-cluster) before this guide.
|
||||
|
||||
You'll need the following tools installed to run the commands in this guide.
|
||||
|
||||
1. [holos](/docs/install) - to build the Platform.
|
||||
2. [helm](https://helm.sh/docs/intro/install/) - to render Holos Components that
|
||||
wrap Helm charts.
|
||||
3. [kubectl](https://kubernetes.io/docs/tasks/tools/) - to render Holos
|
||||
Components that render with Kustomize.
|
||||
|
||||
If you plan to apply the changes we make, you may delete and re-create your
|
||||
local platform with the following commands.
|
||||
|
||||
```bash
|
||||
./scripts/reset-cluster
|
||||
```
|
||||
|
||||
Then apply the current manifests with:
|
||||
|
||||
```bash
|
||||
./scripts/apply
|
||||
```
|
||||
|
||||
## Fork the Git Repository
|
||||
|
||||
If you haven't already done so, [fork the Bank of
|
||||
Holos](https://github.com/holos-run/bank-of-holos/fork) then clone the
|
||||
repository to your local machine.
|
||||
|
||||
<Tabs groupId="git-clone">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
# Change YourName
|
||||
git clone https://github.com/YourName/bank-of-holos
|
||||
cd bank-of-holos
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
Cloning into 'bank-of-holos'...
|
||||
remote: Enumerating objects: 1177, done.
|
||||
remote: Counting objects: 100% (1177/1177), done.
|
||||
remote: Compressing objects: 100% (558/558), done.
|
||||
remote: Total 1177 (delta 394), reused 1084 (delta 303), pack-reused 0 (from 0)
|
||||
Receiving objects: 100% (1177/1177), 2.89 MiB | 6.07 MiB/s, done.
|
||||
Resolving deltas: 100% (394/394), done.
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Run the rest of the commands in this guide from the root of the repository.
|
||||
|
||||
## Rename the Bank
|
||||
|
||||
Let's imagine the bank recently re-branded from The Bank of Holos to The
|
||||
Holistic Bank. The software development team responsible for the front end
|
||||
website needs to update the branding accordingly.
|
||||
|
||||
Let's explore how Holos catches errors early, before they land in production,
|
||||
then guides the team to the correct place to make a change.
|
||||
|
||||
The bank front end web service is managed by the
|
||||
`projects/bank-of-holos/frontend/components/bank-frontend/` component which
|
||||
refers to the organization display name in `schema.gen.cue`.
|
||||
|
||||
<Tabs groupId="F5B546EB-566F-4B83-84C3-C55B40F55555">
|
||||
<TabItem value="schema.gen.cue" label="schema.gen.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
import api "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
|
||||
// Define the default organization name
|
||||
// highlight-next-line
|
||||
#Organization: DisplayName: string | *"Bank of Holos"
|
||||
#Organization: Name: string | *"bank-of-holos"
|
||||
|
||||
#Organization: api.#OrganizationStrict
|
||||
#Platform: api.#Platform
|
||||
#Fleets: api.#StandardFleets
|
||||
|
||||
_ComponentConfig: {
|
||||
Resources: #Resources
|
||||
ArgoConfig: #ArgoConfig
|
||||
}
|
||||
|
||||
#Helm: api.#Helm & _ComponentConfig
|
||||
#Kustomize: api.#Kustomize & _ComponentConfig
|
||||
#Kubernetes: api.#Kubernetes & _ComponentConfig
|
||||
|
||||
#ArgoConfig: api.#ArgoConfig & {
|
||||
ClusterName: _ClusterName
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="projects/bank-of-holos/frontend/components/bank-frontend/bank-frontend.cue" label="projects/bank-of-holos/frontend/components/bank-frontend/bank-frontend.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
// Produce a kubernetes objects build plan.
|
||||
(#Kubernetes & Objects).BuildPlan
|
||||
|
||||
let Objects = {
|
||||
Name: "bank-frontend"
|
||||
Namespace: #BankOfHolos.Frontend.Namespace
|
||||
|
||||
// Ensure resources go in the correct namespace
|
||||
Resources: [_]: [_]: metadata: namespace: Namespace
|
||||
|
||||
// https://github.com/GoogleCloudPlatform/bank-of-anthos/blob/release/v0.6.5/kubernetes-manifests/frontend.yaml
|
||||
Resources: {
|
||||
Service: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
_ports: http: {
|
||||
name: "http"
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
protocol: "TCP"
|
||||
}
|
||||
ports: [for x in _ports {x}]
|
||||
}
|
||||
}
|
||||
|
||||
Deployment: frontend: {
|
||||
metadata: name: "frontend"
|
||||
metadata: labels: {
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
selector: matchLabels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
template: {
|
||||
metadata: labels: {
|
||||
app: "frontend"
|
||||
application: "bank-of-holos"
|
||||
environment: "development"
|
||||
team: "frontend"
|
||||
tier: "web"
|
||||
}
|
||||
spec: {
|
||||
securityContext: {
|
||||
seccompProfile: type: "RuntimeDefault"
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
}
|
||||
serviceAccountName: "bank-of-holos"
|
||||
terminationGracePeriodSeconds: 5
|
||||
containers: [{
|
||||
env: [{
|
||||
name: "BANK_NAME"
|
||||
// highlight-next-line
|
||||
value: #Organization.DisplayName
|
||||
}, {
|
||||
name: "ENV_PLATFORM"
|
||||
value: "local"
|
||||
}, {
|
||||
name: "VERSION"
|
||||
value: "v0.6.5"
|
||||
}, {
|
||||
name: "PORT"
|
||||
value: "8080"
|
||||
}, {
|
||||
name: "ENABLE_TRACING"
|
||||
value: "false"
|
||||
}, {
|
||||
name: "SCHEME"
|
||||
value: "https"
|
||||
}, {
|
||||
name: "LOG_LEVEL"
|
||||
value: "info"
|
||||
}, {
|
||||
name: "DEFAULT_USERNAME"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_USERNAME"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "DEFAULT_PASSWORD"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_LOGIN_PASSWORD"
|
||||
name: "demo-data-config"
|
||||
}
|
||||
}, {
|
||||
name: "REGISTERED_OAUTH_CLIENT_ID"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_CLIENT_ID"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}, {
|
||||
name: "ALLOWED_OAUTH_REDIRECT_URI"
|
||||
valueFrom: configMapKeyRef: {
|
||||
key: "DEMO_OAUTH_REDIRECT_URI"
|
||||
name: "oauth-config"
|
||||
optional: true
|
||||
}
|
||||
}]
|
||||
envFrom: [{
|
||||
configMapRef: name: "environment-config"
|
||||
}, {
|
||||
configMapRef: name: "service-api-config"
|
||||
}]
|
||||
image: "us-central1-docker.pkg.dev/bank-of-anthos-ci/bank-of-anthos/frontend:v0.6.5@sha256:d72050f70d12383e4434ad04d189b681dc625f696087ddf0b5df641645c9dafa"
|
||||
livenessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 30
|
||||
}
|
||||
name: "front"
|
||||
readinessProbe: {
|
||||
httpGet: {
|
||||
path: "/ready"
|
||||
port: 8080
|
||||
}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
}
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
}
|
||||
requests: {
|
||||
cpu: "100m"
|
||||
memory: "64Mi"
|
||||
}
|
||||
}
|
||||
securityContext: {
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: drop: ["all"]
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
}
|
||||
volumeMounts: [{
|
||||
mountPath: "/tmp"
|
||||
name: "tmp"
|
||||
}, {
|
||||
mountPath: "/tmp/.ssh"
|
||||
name: "publickey"
|
||||
readOnly: true
|
||||
}]
|
||||
}]
|
||||
volumes: [
|
||||
{
|
||||
emptyDir: {}
|
||||
name: "tmp"
|
||||
},
|
||||
{
|
||||
name: "publickey"
|
||||
secret: {
|
||||
items: [{key: "jwtRS256.key.pub", path: "publickey"}]
|
||||
secretName: "jwt-key"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow HTTPRoutes in the ingress gateway namespace to reference Services
|
||||
// in this namespace.
|
||||
ReferenceGrant: grant: #ReferenceGrant & {
|
||||
metadata: namespace: Namespace
|
||||
}
|
||||
|
||||
// Include shared resources
|
||||
#BankOfHolos.Resources
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Line 6 of the schema cue file defines the _default_ display name by using
|
||||
`string | *"..."`. In CUE, the asterisk denotes a [default value].
|
||||
|
||||
Line 78 of the bank-frontend cue file refers to `#Organization.DisplayName` to
|
||||
configure the front end web container.
|
||||
|
||||
Let's change the name of the bank by defining a new name at the root of the
|
||||
configuration. Create `projects/organization.cue` with the following content.
|
||||
|
||||
<Tabs groupId="B386181F-EBE7-469D-8CB5-37631067669B">
|
||||
<TabItem value="projects/organization.cue" label="projects/organization.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#Organization: DisplayName: "The Holistic-Bank"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Let's render the platform and see if this changes the name.
|
||||
|
||||
<Tabs groupId="A014333C-3271-4C22-87E6-2B7BF898EA3E">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
#Organization.DisplayName: 2 errors in empty disjunction:
|
||||
#Organization.DisplayName: conflicting values "Bank of Holos" and "The Holistic-Bank":
|
||||
/bank-of-holos/projects/organization.cue:3:29
|
||||
/bank-of-holos/schema.gen.cue:6:39
|
||||
// highlight-next-line
|
||||
#Organization.DisplayName: invalid value "The Holistic-Bank" (out of bound =~"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$"):
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:203:25
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:188:15
|
||||
/bank-of-holos/cue.mod/gen/github.com/holos-run/holos/api/author/v1alpha3/definitions_go_gen.cue:203:15
|
||||
/bank-of-holos/projects/organization.cue:3:29
|
||||
/bank-of-holos/schema.gen.cue:6:29
|
||||
could not run: could not render component: exit status 1 at internal/render/platform.go:50
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Whoops, the development team defined a value that isn't allowed by the
|
||||
configuration. Someone else in the organization placed a [constraint] on the
|
||||
configuration to ensure the display name contains only letters, numbers, and
|
||||
spaces.
|
||||
|
||||
:::tip
|
||||
CUE provides clear visibility where to start looking to resolve conflicts. Each
|
||||
file and line number listed is a place the `#Organization.DisplayName` field is
|
||||
defined.
|
||||
:::
|
||||
|
||||
Let's try again, this time replacing the hyphen with a space.
|
||||
|
||||
<Tabs groupId="F93B34FA-C0C6-4793-A32F-DAD094403208">
|
||||
<TabItem value="projects/organization.cue" label="projects/organization.cue">
|
||||
```cue showLineNumbers
|
||||
package holos
|
||||
|
||||
#Organization: DisplayName: "The Holistic Bank"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="5FD68778-476A-4F82-8817-71CEE205216E">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered bank-ledger-db for cluster workload in 139.863625ms
|
||||
rendered bank-accounts-db for cluster workload in 151.74875ms
|
||||
rendered bank-balance-reader for cluster workload in 154.356083ms
|
||||
rendered bank-ledger-writer for cluster workload in 161.209541ms
|
||||
rendered bank-userservice for cluster workload in 163.373417ms
|
||||
rendered bank-backend-config for cluster workload in 179.271208ms
|
||||
rendered bank-secrets for cluster workload in 204.35625ms
|
||||
rendered gateway for cluster workload in 118.707583ms
|
||||
rendered httproutes for cluster workload in 140.981541ms
|
||||
rendered bank-transaction-history for cluster workload in 156.066875ms
|
||||
rendered bank-frontend for cluster workload in 300.102292ms
|
||||
rendered bank-contacts for cluster workload in 159.89625ms
|
||||
rendered cni for cluster workload in 150.754458ms
|
||||
rendered istiod for cluster workload in 222.922625ms
|
||||
rendered app-projects for cluster workload in 118.422792ms
|
||||
rendered ztunnel for cluster workload in 142.840625ms
|
||||
rendered cert-manager for cluster workload in 190.938834ms
|
||||
rendered base for cluster workload in 340.679416ms
|
||||
rendered local-ca for cluster workload in 107.120334ms
|
||||
rendered external-secrets for cluster workload in 145.020834ms
|
||||
rendered argocd for cluster workload in 299.690917ms
|
||||
rendered namespaces for cluster workload in 115.862334ms
|
||||
rendered gateway-api for cluster workload in 225.783833ms
|
||||
rendered external-secrets-crds for cluster workload in 339.741166ms
|
||||
rendered crds for cluster workload in 421.849041ms
|
||||
rendered platform in 718.015959ms
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Great, the platform rendered successfully so we know the display name is valid
|
||||
according to the constraints. Let's see if the new display name value updated
|
||||
the configuration for the bank frontend.
|
||||
|
||||
<Tabs groupId="6C068651-2061-4262-BE1E-7BB3E7EB66CB">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
On branch main
|
||||
Your branch and 'jeffmccune/main' have diverged,
|
||||
and have 2 and 4 different commits each, respectively.
|
||||
(use "git pull" to merge the remote branch into yours)
|
||||
|
||||
Changes not staged for commit:
|
||||
(use "git add <file>..." to update what will be committed)
|
||||
(use "git restore <file>..." to discard changes in working directory)
|
||||
// highlight-next-line
|
||||
modified: deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
modified: deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
|
||||
Untracked files:
|
||||
(use "git add <file>..." to include in what will be committed)
|
||||
projects/organization.cue
|
||||
|
||||
no changes added to commit (use "git add" and/or "git commit -a")
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="4A20831E-461B-4EDE-8F6E-E73C3AEC12DB">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff
|
||||
diff --git a/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml b/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
index 7914756..250c660 100644
|
||||
--- a/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/app-projects/app-projects.gen.yaml
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
@@ -60,7 +60,7 @@ spec:
|
||||
clusterResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
- description: Holos managed AppProject for Bank of Holos
|
||||
+ description: Holos managed AppProject for The Holistic Bank
|
||||
destinations:
|
||||
- namespace: '*'
|
||||
server: '*'
|
||||
diff --git a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
index dae6f93..d41516b 100644
|
||||
--- a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: BANK_NAME
|
||||
- value: Bank of Holos
|
||||
+ value: The Holistic Bank
|
||||
- name: ENV_PLATFORM
|
||||
value: local
|
||||
- name: VERSION
|
||||
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The new display name changed the frontend container, but it _also_ affected the
|
||||
app-projects component. Submitting a pull request would trigger a code review
|
||||
from the platform engineering team who manages the app-projects component.
|
||||
Let's see how to narrow the change down to limit the scope to the bank's user
|
||||
facing services. All of these serves are managed under
|
||||
`projects/bank-of-holos/` Move the `organization.cue` file we created into this
|
||||
folder to limit the scope of configuration to the the components contained within.
|
||||
|
||||
```bash
|
||||
mv projects/organization.cue projects/bank-of-holos/
|
||||
```
|
||||
|
||||
Render the platform and review what changed.
|
||||
|
||||
<Tabs groupId="0FFEC244-B59B-4136-9C82-837985DC2AB8">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
holos render platform ./platform
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
rendered bank-ledger-db for cluster workload in 163.814917ms
|
||||
rendered bank-accounts-db for cluster workload in 163.960208ms
|
||||
rendered bank-userservice for cluster workload in 164.1625ms
|
||||
rendered bank-ledger-writer for cluster workload in 169.185291ms
|
||||
rendered bank-balance-reader for cluster workload in 174.5455ms
|
||||
rendered bank-backend-config for cluster workload in 178.092125ms
|
||||
rendered bank-secrets for cluster workload in 202.305334ms
|
||||
rendered gateway for cluster workload in 122.81725ms
|
||||
rendered httproutes for cluster workload in 134.121084ms
|
||||
rendered bank-contacts for cluster workload in 146.4185ms
|
||||
rendered bank-frontend for cluster workload in 311.35425ms
|
||||
rendered bank-transaction-history for cluster workload in 160.103ms
|
||||
rendered cni for cluster workload in 145.762083ms
|
||||
rendered istiod for cluster workload in 216.0065ms
|
||||
rendered app-projects for cluster workload in 117.684333ms
|
||||
rendered ztunnel for cluster workload in 144.555292ms
|
||||
rendered cert-manager for cluster workload in 178.247917ms
|
||||
rendered base for cluster workload in 336.679ms
|
||||
rendered external-secrets for cluster workload in 142.21825ms
|
||||
rendered local-ca for cluster workload in 101.249ms
|
||||
rendered argocd for cluster workload in 280.54525ms
|
||||
rendered namespaces for cluster workload in 106.822042ms
|
||||
rendered gateway-api for cluster workload in 200.459791ms
|
||||
rendered external-secrets-crds for cluster workload in 470.125833ms
|
||||
rendered crds for cluster workload in 844.388666ms
|
||||
rendered platform in 1.154937084s
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Tabs groupId="DE4FEEE5-FC53-48A6-BC6F-D0EA1DBFD00C">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git diff
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```diff
|
||||
diff --git a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
index dae6f93..d41516b 100644
|
||||
--- a/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
+++ b/deploy/clusters/workload/components/bank-frontend/bank-frontend.gen.yaml
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: BANK_NAME
|
||||
- value: Bank of Holos
|
||||
+ value: The Holistic Bank
|
||||
- name: ENV_PLATFORM
|
||||
value: local
|
||||
- name: VERSION
|
||||
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Great! This time, the only manifest affected is our `bank-frontend.gen.yaml`.
|
||||
The `BANK_NAME` environment variable will change as we expected.
|
||||
|
||||
Let's commit and push this change and see if it works in the platform.
|
||||
|
||||
<Tabs groupId="435D9C60-F841-4CF1-A947-506422E6BAC9">
|
||||
<TabItem value="command" label="Command">
|
||||
```bash
|
||||
git add .
|
||||
git commit -m 'frontend: rename bank to The Holistic Bank'
|
||||
git push
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="output" label="Output">
|
||||
```txt
|
||||
[main fda74ec] frontend: rename bank to The Holistic Bank
|
||||
2 files changed, 4 insertions(+), 1 deletion(-)
|
||||
create mode 100644 projects/bank-of-holos/organization.cue
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Now that we've pushed the change, let's apply the change to the platform.
|
||||
|
||||
## Apply the Change
|
||||
|
||||
Once we've pushed the change, navigate to the [bank-frontend GitOps
|
||||
Application](https://argocd.holos.localhost/applications/argocd/bank-frontend?view=tree&resource=).
|
||||
We can see the Deployment needs to sync to the desired state we just pushed.
|
||||
|
||||

|
||||
|
||||
Clicking on the frontend Deployment, we see the diff with the change we expect.
|
||||
|
||||

|
||||
|
||||
Sync the change, ArgoCD applies the desired configuration state to the cluster
|
||||
and Kubernetes handles rolling out the updated Deployment resource.
|
||||
|
||||

|
||||
|
||||
Soon, the deployment finishes and the component is in sync again.
|
||||
|
||||

|
||||
|
||||
Finally, let's see if the name actually changed on the website. Navigate to
|
||||
https://bank.holos.localhost/.
|
||||
|
||||

|
||||
|
||||
Great! We successfully made our change and successfully applied the changed
|
||||
configuration to the platform.
|
||||
|
||||
- We saw how multiple teams are could be impacted by defining configuration at
|
||||
the `projects/` path.
|
||||
- We scoped our change to only affect components within the
|
||||
`projects/bank-of-holos/` path, limiting the impact on other teams.
|
||||
- We saw how CUE can be used to constrain values in Holos, increasing safety.
|
||||
- We saw how CUE surfaces the file and line number of _every_ place to look for
|
||||
where a value is defined, making it faster and easier to troubleshoot problems..
|
||||
|
||||
Thanks for taking the time to work through this guide.
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[Deploy a Service]: /docs/guides/deploy-a-service/
|
||||
[Change a Service]: /docs/guides/change-a-service/
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
[ComponentFields]: /docs/api/author/v1alpha3/#ComponentFields
|
||||
[platform-files]: /docs/quickstart/#how-platform-rendering-works
|
||||
[AppProject]: https://argo-cd.readthedocs.io/en/stable/user-guide/projects/
|
||||
[unification operator]: https://cuelang.org/docs/reference/spec/#unification
|
||||
[code-owners]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
[Kustomization API]: https://github.com/kubernetes-sigs/kustomize/blob/release-kustomize-v5.2/api/types/kustomization.go#L34
|
||||
[cue import]: https://cuelang.org/docs/reference/command/cue-help-import/
|
||||
[cue get go]: https://cuelang.org/docs/concept/how-cue-works-with-go/
|
||||
[timoni-crds]: https://timoni.sh/cue/module/custom-resources/
|
||||
[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/?h=filter
|
||||
[Ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
||||
[hidden field]: https://cuelang.org/docs/tour/references/hidden/
|
||||
[comprehension]: https://cuelang.org/docs/reference/spec/#comprehensions
|
||||
[code owners]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
[ReferenceGrant]: https://gateway-api.sigs.k8s.io/api-types/referencegrant/
|
||||
[Local Cluster Guide]: /docs/guides/local-cluster
|
||||
[Bank of Holos]: https://github.com/holos-run/bank-of-holos
|
||||
[default value]: https://cuelang.org/docs/tour/types/defaults/
|
||||
[constraint]: https://cuelang.org/docs/tour/basics/constraints/
|
||||
1450
doc/md/guides/deploy-a-service.mdx
Normal file
15
doc/md/guides/helm-component.mdx
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
description: Helm Component
|
||||
slug: /guides/helm-component
|
||||
sidebar_position: 400
|
||||
---
|
||||
|
||||
# Helm Component
|
||||
|
||||
The [Deploy a Service](/docs/guides/deploy-a-service/) guide is the best guide
|
||||
we have on wrapping a Helm chart in a Holos Component. The [Helm] section of
|
||||
the Author API may also be useful.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
BIN
doc/md/guides/img/bank-home.png
Normal file
|
After Width: | Height: | Size: 690 KiB |
BIN
doc/md/guides/img/change-a-service-diff.png
Normal file
|
After Width: | Height: | Size: 997 KiB |
BIN
doc/md/guides/img/change-a-service-in-sync.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/change-a-service-login-page.png
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
doc/md/guides/img/change-a-service-out-of-sync.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/change-a-service-progressing.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-deployed.png
Normal file
|
After Width: | Height: | Size: 1009 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-greetings.png
Normal file
|
After Width: | Height: | Size: 617 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-missing.png
Normal file
|
After Width: | Height: | Size: 706 KiB |
BIN
doc/md/guides/img/deploy-a-service-podinfo-sync.png
Normal file
|
After Width: | Height: | Size: 794 KiB |
20
doc/md/guides/kubernetes-component.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Kubernetes Component
|
||||
slug: /guides/kubernetes-component
|
||||
sidebar_position: 500
|
||||
---
|
||||
|
||||
# Kubernetes Component
|
||||
|
||||
:::warning
|
||||
TODO
|
||||
:::
|
||||
|
||||
This is a placeholder for a guide for managing Kubernetes resources directly
|
||||
from a Holos Component with strong type checking.
|
||||
|
||||
In the meantime, please refer to the [Kubernetes] section of the Author API.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
20
doc/md/guides/kustomize-component.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Wrap a Kustomize Kustomization in a Holos Component.
|
||||
slug: /guides/kustomize-component
|
||||
sidebar_position: 600
|
||||
---
|
||||
|
||||
# Kustomize Component
|
||||
|
||||
:::warning
|
||||
TODO
|
||||
:::
|
||||
|
||||
This is a placeholder for a guide on wrapping a Kustomize Kustomization base
|
||||
with a Holos component.
|
||||
|
||||
In the meantime, please refer to the [Kustomize] section of the Author API.
|
||||
|
||||
[Helm]: /docs/api/author/v1alpha3/#Helm
|
||||
[Kubernetes]: /docs/api/author/v1alpha3/#Kubernetes
|
||||
[Kustomize]: /docs/api/author/v1alpha3/#Kustomize
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Build a local Cluster to use with these guides.
|
||||
slug: /guides/local-cluster
|
||||
sidebar_position: 300
|
||||
sidebar_position: 999
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
|
||||
@@ -5,6 +5,112 @@ slug: /
|
||||
|
||||
# Introduction
|
||||
|
||||
:::warning TODO
|
||||
See [introduction](https://github.com/facebook/docusaurus/blob/main/website/docs/introduction.mdx?plain=1)
|
||||
:::
|
||||
Welcome to Holos. Holos is an open source tool to manage software development
|
||||
platforms safely, easily, and consistently. We built Holos to help engineering
|
||||
teams work more efficiently together by empowering them to build golden paths
|
||||
and paved roads for other teams to leverage for quicker delivery.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Guides] are organized into example use-cases of how Holos helps engineering
|
||||
teams at the fictional Bank of Holos deliver business value on the bank's
|
||||
platform.
|
||||
- The [API Reference] is a technical reference for when you're writing CUE code to define your platform.
|
||||
|
||||
## Backstory
|
||||
|
||||
At [Open Infrastructure Services], each of us has helped dozens of companies
|
||||
build and operate their software development platform over the course of our
|
||||
career. During the U.S. presidential election just before the pandemic our
|
||||
second largest client, Twitter, had a global outage lasting the better part of a
|
||||
day. At the time, we were helping the core infrastructure team by managing
|
||||
their production configuration management system so the team could focus on
|
||||
delivering key objectives for the business. In this position, we had a front
|
||||
row seat into what happened that day.
|
||||
|
||||
One of Twitter's employees, a close friend of ours and engineer on the team,
|
||||
landed a trivial one line change to the firewall configuration. Less than 30
|
||||
minutes later literally everything was down. The one line change, which passed
|
||||
code review and seemed harmless, resulted in the host firewall reverting to the
|
||||
default state on hundreds of thousands of servers. All connections to all
|
||||
servers globally were blocked and dropped. Except SSH, thankfully. At least
|
||||
one Presidential candidate complained loudly.
|
||||
|
||||
This incident led us to deeply consider a few key problems about Twitter's
|
||||
software development platform, problems which made their way all the way up to
|
||||
the board of directors to solve.
|
||||
|
||||
1. **Lack of Visibility** - There was no way to see clearly the impact a simple one-line
|
||||
change could have on the platform. This lack of visibility made it difficult
|
||||
for engineers to reason about changes they were writing and reviewing.
|
||||
2. **Large Blast Radius** - All changes, no matter how small or large, affected the
|
||||
entire global fleet within 30 minutes. Twitter needed a way to cap the
|
||||
potential blast radius to prevent global outages.
|
||||
3. **Incomplete Tooling** - Twitter had the correct processes in place, but
|
||||
their tooling didn't support their process. The one line change was tested and
|
||||
peer reviewed prior to landing, but the tooling didn't surface the information
|
||||
they needed when they needed it.
|
||||
|
||||
Over the next few years we built features for Twitter's configuration management
|
||||
system that solved each of these problems. At the same time, I started
|
||||
exploring my curiosity of what these solutions would look like in the context of
|
||||
Kubernetes and cloud native software instead of a traditional configuration
|
||||
management context.
|
||||
|
||||
As Google Cloud partners, we had the opportunity to work with Google's largest
|
||||
customers and learn how they built their software development platforms on
|
||||
Kubernetes. Over the course of the pandemic, we built a software development
|
||||
platform made largely in the same way, taking off the shelf CNCF projects like
|
||||
ArgoCD, the Kubernetes Prometheus Stack, Istio, Cert Manager, External Secrets
|
||||
Operator, etc... and integrating them into a holistic software development
|
||||
platform. We started with the packaging recommended by the upstream project.
|
||||
Helm was and still is the most common distribution method, but many projects
|
||||
also provided plain yaml, kustomize bases, or sometimes even jsonnet in the case
|
||||
of the prometheus community. We then wrote scripts to mix-in the resources we
|
||||
needed to integrate each piece of software with the platform as a whole. For
|
||||
example, we often passed Helm's output to Kustomize to add common labels or fix
|
||||
bugs in the upstream chart, like missing namespace fields. We wrote umbrella
|
||||
charts to mix in Ingress, HTTPRoute, and ExternalSecret resources to the vendor
|
||||
provided chart.
|
||||
|
||||
We viewed these scripts as necessary glue to assemble and fasten the components
|
||||
together into a platform, but we were never fully satisfied with them. Umbrella
|
||||
charts became difficult to maintain once there were multiple environments,
|
||||
regions, and cloud providers in the platform. Nested for loops in yaml
|
||||
templates created significant friction and were a challenge to troubleshoot
|
||||
because they obfuscated what was happening. The scripts, too, made it difficult
|
||||
to see what was happening, when, and fix issues in them that affected all
|
||||
components in the platform.
|
||||
|
||||
Despite the makeshift scripts and umbrella charts, the overall approach had a
|
||||
significant advantage. The scripts always produced fully rendered manifests
|
||||
stored in plain text files. We committed these files to version control and
|
||||
used ArgoCD to apply them. The ability to make a one-line change, render the
|
||||
whole platform, then see clearly what changed platform-wide resulted in less
|
||||
time spent troubleshooting and fewer errors making their way to production.
|
||||
|
||||
For awhile, we lived with the scripts and charts. I couldn't stop thinking
|
||||
about the [Why are we templating
|
||||
YAML?](https://hn.algolia.com/?dateRange=all&page=0&prefix=false&query=https%3A%2F%2Fleebriggs.co.uk%2Fblog%2F2019%2F02%2F07%2Fwhy-are-we-templating-yaml&sort=byDate&type=story)
|
||||
post on Hacker News though. I was curious what it would look like to replace
|
||||
our scripts and umbrella charts with something that helped address the 3 main
|
||||
problems Twitter experienced.
|
||||
|
||||
After doing quite a bit of digging and talking with folks, I found
|
||||
[CUE](https://cuelang.org). I took our scripts and charts and re-wrote the same
|
||||
functionality we needed from the glue-layer in Go and CUE. The result is a new
|
||||
tool, `holos`, built to complement Helm, Kustomize, and JSonnet, but not replace
|
||||
them. Holos leverages CUE to make it easier and safer for teams to define
|
||||
golden paths and paved roads without having to write bespoke, makeshift scripts
|
||||
or template text.
|
||||
|
||||
Thanks for reading this far, give Holos a try locally with out [Quickstart]
|
||||
guide.
|
||||
|
||||
[Guides]: /docs/guides/
|
||||
[API Reference]: /docs/api/
|
||||
[Quickstart]: /docs/quickstart/
|
||||
[CUE]: https://cuelang.org/
|
||||
[Author API]: /docs/api/author/
|
||||
[Core API]: /docs/api/core/
|
||||
[Open Infrastructure Services]: https://openinfrastructure.co/
|
||||
|
||||
@@ -2,4 +2,12 @@ import DocCardList from '@theme/DocCardList';
|
||||
|
||||
# Get Started
|
||||
|
||||
## Start with the [Quickstart] guide
|
||||
|
||||
---
|
||||
|
||||
These documents provide additional context to supplement the [Quickstart] guide.
|
||||
|
||||
<DocCardList />
|
||||
|
||||
[Quickstart]: /docs/quickstart/
|
||||
|
||||
@@ -6,65 +6,10 @@ sidebar_position: 300
|
||||
|
||||
# Comparison
|
||||
|
||||
:::tip
|
||||
Holos is designed to complement and improve, not replace, existing tools in the
|
||||
cloud native ecosystem.
|
||||
:::
|
||||
|
||||
## Helm
|
||||
|
||||
### Chart Users
|
||||
|
||||
Describe how things are different when using an upstream helm chart.
|
||||
|
||||
### Chart Authors
|
||||
|
||||
Describe how things are different when writing a new helm chart.
|
||||
|
||||
## Kustomize
|
||||
|
||||
TODO
|
||||
|
||||
## ArgoCD
|
||||
|
||||
TODO
|
||||
|
||||
## Flux
|
||||
|
||||
TODO
|
||||
|
||||
## Timoni
|
||||
|
||||
| Aspect | Timoni | Holos | Comment |
|
||||
| ---------- | -------------------- | -------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Language | CUE | CUE | Like Holos, Timoni is also built on CUE. |
|
||||
| Artifact | OCI Image | Plain YAML Files | The Holos Authors find plain files easier to work with and reason about than OCI images. |
|
||||
| Outputs to | OCI Image Repository | Local Git repository | Holos is designed for use with existing GitOps tools. |
|
||||
| Concept | Module | Component | A Timoni Module is analogous to a Holos Component. |
|
||||
| Concept | Bundle | Platform | A Timoni Bundle is somewhat similar, but smaller in scope to a Holos Platform. |
|
||||
|
||||
:::important
|
||||
|
||||
The Holos Authors are deeply grateful to Stefan and Timoni for the capability of
|
||||
importing Kubernetes custom resource definitions into CUE. Without this
|
||||
functionality, much of the Kubernetes ecosystem would be more difficult to
|
||||
manage in CUE and therefore in Holos.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## KubeVela
|
||||
|
||||
1. Also built on CUE.
|
||||
2. Intended to create an Application abstraction.
|
||||
3. Holos prioritizes composition over abstraction.
|
||||
4. An abstraction of an Application acts as a filter that removes all but the lowest common denominator functionality. The Holos Authors have found this filtering effect to create excessive friction for software developers.
|
||||
5. Holos focuses instead on composition to empower developers and platform engineers to leverage the unique features and functionality of their software and platform.
|
||||
|
||||
## Pulumi
|
||||
|
||||
TODO
|
||||
|
||||
## Jsonnet
|
||||
|
||||
TODO
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: 'Holos',
|
||||
tagline: 'The Holistic Package Manager for Cloud Native Applications',
|
||||
tagline: 'Holos helps you manage your software development platform safely and easily.',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
@@ -12,6 +12,7 @@ const config: Config = {
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
// trailing slash is necessary for Cloudflare pages.
|
||||
trailingSlash: true,
|
||||
|
||||
// GitHub pages deployment config.
|
||||
@@ -65,7 +66,7 @@ const config: Config = {
|
||||
blogSidebarTitle: "All posts",
|
||||
feedOptions: {
|
||||
type: 'all',
|
||||
copyright: `Copyright © ${new Date().getFullYear()}, The Holos Authors.`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()}, The Holos Authors`,
|
||||
},
|
||||
showReadingTime: false,
|
||||
},
|
||||
|
||||
@@ -44,14 +44,14 @@ const sidebars: SidebarsConfig = {
|
||||
link: { type: 'doc', id: 'api' },
|
||||
items: [
|
||||
{
|
||||
label: 'Schema API',
|
||||
label: 'Author API',
|
||||
type: 'category',
|
||||
link: { type: 'doc', id: 'api/schema' },
|
||||
link: { type: 'doc', id: 'api/author' },
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'api/schema',
|
||||
dirName: 'api/author',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -8,53 +8,58 @@ type FeatureItem = {
|
||||
description: JSX.Element;
|
||||
};
|
||||
|
||||
// TODO: Consider focusing on the three pillars of Safe, Easy, Consistent.
|
||||
// We don't focus on features, but rather problems and solutions.
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: 'Kustomize Helm',
|
||||
Svg: require('@site/static/img/base00/undraw_together_re_a8x4.svg').default,
|
||||
title: 'For Platform Engineers',
|
||||
Svg: require('@site/static/img/base00/undraw_software_engineer_re_tnjc.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Super charge your existing Helm charts by providing well defined,
|
||||
validated input values, post-processing the output with Kustomize,
|
||||
and mixing in your own custom resources. All without modifying upstream
|
||||
charts to alleviate the pain of upgrades.
|
||||
Spend more time building platform features and less time maintaining
|
||||
scripts and text templates. Add type checking and validation to your
|
||||
existing Helm charts and manifests. Define golden paths for teams to
|
||||
safely spin up new projects without interrupting you. Automatically
|
||||
manage Namespaces, Certificates, Secrets, Roles, and labels with
|
||||
strongly typed CUE definitions. Take unmodified, upstream software and
|
||||
mix-in the resources making your platform unique and valuable.
|
||||
<br />
|
||||
<a href="/docs/">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Unified Data Model',
|
||||
Svg: require('@site/static/img/base00/undraw_fitting_pieces_re_nss7.svg').default,
|
||||
title: 'For Software Developers',
|
||||
Svg: require('@site/static/img/base00/undraw_through_the_park_lxnl.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Unify all of your platform components into one well-defined, strongly
|
||||
typed data model with CUE. Holos makes it easier and safer to integrate
|
||||
seamlessly with software distributed with current and future tools that
|
||||
produce Kubernetes resource manifests.
|
||||
Move faster along paved roads provided by your platform and security
|
||||
teams. Quickly spin up new projects and environments without filing
|
||||
tickets. Develop where it works best for you, locally or in the cloud.
|
||||
Deploy your existing Helm charts, Kubernetes manifests, or containers
|
||||
safely and confidently with strong type checking. Reduce the friction of
|
||||
integrating your services into your organization's platform.
|
||||
<br />
|
||||
<a href="/docs/">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Deep Insights',
|
||||
Svg: require('@site/static/img/base00/undraw_code_review_re_woeb.svg').default,
|
||||
title: 'For Security Teams',
|
||||
Svg: require('@site/static/img/base00/undraw_security_on_re_e491.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Reduce risk and increase confidence in your configuration changes.
|
||||
Holos offers clear visibility into complete resource configuration
|
||||
<i>before</i> being applied.
|
||||
Express your security policies as reusable, well defined, typed
|
||||
configuration. Build guard rails making it easy to develop services
|
||||
securely. Automatically provision and configure SecretStores,
|
||||
ExternalSecrets, AuthorizationPolicies, RoleBindings, etc... for project
|
||||
teams. Gain clear visibility into the complete configuration of the
|
||||
platform to quickly identify risk and audit your security posture.
|
||||
Integrate your preferred security tools with the platform.
|
||||
<br />
|
||||
<a href="/docs/">Learn More</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Interoperable',
|
||||
Svg: require('@site/static/img/base00/undraw_version_control_re_mg66.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Holos is designed for compatibility with your preferred tools and
|
||||
processes, for example git diff and code reviews.
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
function Feature({ title, Svg, description }: FeatureItem) {
|
||||
|
||||
@@ -10,6 +10,18 @@
|
||||
overflow-wrap: anywhere;
|
||||
} */
|
||||
|
||||
.hero__title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.projectDesc {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-link-color: #268bd2;
|
||||
|
||||
@@ -17,10 +17,16 @@ function HomepageHeader() {
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<p className="projectDesc">
|
||||
Holos adds CUE's type safety, unified structure, and strong validation
|
||||
features to your current software packages, including Helm and
|
||||
Kustomize. These features make the experience of integrating software
|
||||
packages into a holistic platform a pleasant journey.
|
||||
Engineering teams spend considerable time and effort gluing software
|
||||
projects together into a platform. This integration glue is usually
|
||||
made with bespoke scripts and error prone text templates. This glue
|
||||
layer is often makeshift, slowing teams down and inviting outages due
|
||||
to configuration errors.
|
||||
</p>
|
||||
<p className="projectDesc">
|
||||
Holos helps teams deliver value faster by offering a well defined,
|
||||
safe, and automated integration layer to manage your platform
|
||||
holistically.
|
||||
</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
@@ -31,11 +37,12 @@ function HomepageHeader() {
|
||||
<span className={styles.divider}></span>
|
||||
<Link
|
||||
className="button button--primary button--lg"
|
||||
to="docs/concepts">
|
||||
to="docs/">
|
||||
Learn More
|
||||
</Link>
|
||||
<span className={styles.divider}></span>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
</header >
|
||||
);
|
||||
}
|
||||
@@ -44,8 +51,8 @@ export default function Home(): JSX.Element {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`${siteConfig.title} Package Manager`}
|
||||
description="Holos adds CUE's type safety, unified structure, and strong validation features to your current software packages, including Helm and Kustomize.">
|
||||
title={`${siteConfig.title} Platform Manager`}
|
||||
description="Holos adds CUE's type safety, unified structure, and strong validation features to your Kubernetes configuration manifests, including Helm and Kustomize.">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
|
||||
|
Before Width: | Height: | Size: 461 KiB After Width: | Height: | Size: 480 KiB |
BIN
doc/website/static/img/social-card-blank.png
Normal file
|
After Width: | Height: | Size: 477 KiB |
@@ -1,6 +1,6 @@
|
||||
// Code generated by cue get go. DO NOT EDIT.
|
||||
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/schema/v1alpha3
|
||||
//cue:generate cue get go github.com/holos-run/holos/api/author/v1alpha3
|
||||
|
||||
// Package v1alpha3 contains CUE definitions intended as convenience wrappers
|
||||
// around the core data types defined in package core. The purpose of these
|
||||
@@ -181,3 +181,24 @@ import (
|
||||
// platform operators to define.
|
||||
Domain: string & (string | *"holos.localhost")
|
||||
}
|
||||
|
||||
// Organization represents organizational metadata useful across the platform.
|
||||
#Organization: {
|
||||
Name: string
|
||||
DisplayName: string
|
||||
}
|
||||
|
||||
// OrganizationStrict represents organizational metadata useful across the
|
||||
// platform. This is an example of using CUE regular expressions to constrain
|
||||
// and validate configuration.
|
||||
#OrganizationStrict: {
|
||||
#Organization
|
||||
|
||||
// Name represents the organization name as a resource name. Must be 63
|
||||
// characters or less. Must start with a letter. May contain non-repeating
|
||||
// hyphens, letters, and numbers. Must end with a letter or number.
|
||||
Name: string & (=~"^[a-z][0-9a-z-]{1,61}[0-9a-z]$" & !~"--")
|
||||
|
||||
// DisplayName represents the human readable organization name.
|
||||
DisplayName: string & (=~"^[0-9A-Za-z][0-9A-Za-z ]{2,61}[0-9A-Za-z]$" & !~" ")
|
||||
}
|
||||
@@ -9,8 +9,8 @@ package platforms
|
||||
//go generate rm -rf cue.mod/gen/github.com/holos-run/holos/api/meta
|
||||
//go:generate cue get go github.com/holos-run/holos/api/meta/...
|
||||
|
||||
//go generate rm -rf cue.mod/gen/github.com/holos-run/holos/api/schema
|
||||
//go:generate cue get go github.com/holos-run/holos/api/schema/...
|
||||
//go generate rm -rf cue.mod/gen/github.com/holos-run/holos/api/author
|
||||
//go:generate cue get go github.com/holos-run/holos/api/author/...
|
||||
|
||||
//go generate rm -rf cue.mod/gen/github.com/holos-run/holos/service/gen/holos/object
|
||||
//go:generate cue import ../../../service/holos/object/v1alpha1/object.proto -o cue.mod/gen/github.com/holos-run/holos/service/gen/holos/object/v1alpha1/object.proto_gen.cue -I ../../../proto -f
|
||||
|
||||
1
internal/generate/platforms/guide/.gitignore
vendored
@@ -6,3 +6,4 @@
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package holos
|
||||
|
||||
import schema "github.com/holos-run/holos/api/schema/v1alpha3"
|
||||
import api "github.com/holos-run/holos/api/author/v1alpha3"
|
||||
|
||||
#Platform: schema.#Platform
|
||||
#Fleets: schema.#StandardFleets
|
||||
// Define the default organization name
|
||||
#Organization: DisplayName: string | *"Bank of Holos"
|
||||
#Organization: Name: string | *"bank-of-holos"
|
||||
|
||||
#Organization: api.#OrganizationStrict
|
||||
#Platform: api.#Platform
|
||||
#Fleets: api.#StandardFleets
|
||||
|
||||
_ComponentConfig: {
|
||||
Resources: #Resources
|
||||
ArgoConfig: #ArgoConfig
|
||||
}
|
||||
|
||||
#Helm: schema.#Helm & _ComponentConfig
|
||||
#Kustomize: schema.#Kustomize & _ComponentConfig
|
||||
#Kubernetes: schema.#Kubernetes & _ComponentConfig
|
||||
#Helm: api.#Helm & _ComponentConfig
|
||||
#Kustomize: api.#Kustomize & _ComponentConfig
|
||||
#Kubernetes: api.#Kubernetes & _ComponentConfig
|
||||
|
||||
#ArgoConfig: schema.#ArgoConfig & {
|
||||
#ArgoConfig: api.#ArgoConfig & {
|
||||
ClusterName: _ClusterName
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ deps:
|
||||
- remote: buf.build
|
||||
owner: bufbuild
|
||||
repository: protovalidate
|
||||
commit: a6c49f84cc0f4e038680d390392e2ab0
|
||||
digest: shake256:3deb629c655e469d87c58babcfbed403275a741fb4a269366c4fd6ea9db012cf562a1e64819508d73670c506f96d01f724c43bc97b44e2e02aa6e8bbdd160ab2
|
||||
commit: 5a7b106cbb87462d9a8c9ffecdbd2e38
|
||||
digest: shake256:2f7efa5a904668219f039d4f6eeb51e871f8f7f5966055a10663cba335bd65f76cac84da3fa758ab7b5dcb489ec599521390ce3951d119fb56df1fc2def16bb0
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
3
|
||||
|
||||