From 2276b99a12d02ce28c9710ccaa27e5744bc85550 Mon Sep 17 00:00:00 2001 From: Rae Sharp Date: Wed, 1 Jul 2026 13:27:08 -0400 Subject: [PATCH 01/43] Removes process scripts for CRDs, remove unused packages Signed-off-by: Rae Sharp --- AGENTS.md | 2 - CONTRIBUTING.md | 18 + Makefile | 9 +- README.md | 3 +- .../upgrade-to-mcps.md | 2 +- .../1-project-foundations.md | 2 +- .../2-create-configuration.md | 550 +----- .../builders-workshop/4-deployment.md | 27 +- .../5-build-and-push.md} | 14 +- .../6-integrate-ai.md} | 18 +- .../builders-workshop/_category_.json | 2 +- docs/getstarted/introduction/_category_.json | 4 - docs/getstarted/introduction/project.md | 1139 ------------ docs/getstarted/introduction/whats-next.md | 70 - docs/getstarted/new-users/_category_.json | 4 + .../new-users/controlplanes-quickstart.md | 65 + .../new-users/new-to-control-planes.md | 78 + docs/getstarted/overview.md | 81 +- .../upgrade-to-upbound/_category_.json | 2 +- .../upgrade-to-upbound/upgrade-quickstart.md | 99 ++ .../upgrading-to-upbound.md | 44 + .../platform-tutorial.md | 2 +- docs/guides/solutions/design.md | 1 - docs/guides/usecases/cloud-resources-qs.md | 54 +- .../cli/howtos/compositions/go-template.md | 9 +- docs/manuals/cli/howtos/compositions/go.md | 16 +- .../manuals/cli/howtos/compositions/python.md | 37 +- docs/manuals/cli/howtos/testing.md | 8 +- docs/manuals/console/_category_.json | 5 - docs/manuals/console/self-service.md | 77 - .../marketplace/repositories/overview.md | 2 +- docs/manuals/platform/_category_.json | 8 +- .../manuals/platform/identity/_category_.json | 5 + docs/manuals/platform/{ => identity}/iam.md | 1 + .../platform/{ => identity}/k8s-rbac.md | 1 + docs/manuals/platform/{ => identity}/oidc.md | 1 + .../platform/{ => identity}/organizations.md | 1 + docs/manuals/platform/{ => identity}/rbac.md | 1 + .../manuals/platform/{ => identity}/robots.md | 3 +- docs/manuals/platform/{ => identity}/sso.md | 1 + docs/manuals/platform/{ => identity}/teams.md | 1 + docs/manuals/platform/{ => identity}/users.md | 1 + .../manuals/platform/insights/_category_.json | 5 + docs/manuals/platform/insights/ai.md | 35 + docs/manuals/platform/insights/connections.md | 94 + .../platform/insights/feature-flags.md | 56 + .../insights/install/oidc/entra-id.md | 16 + .../platform/insights/install/oidc/generic.md | 22 + .../insights/install/oidc/keycloak.md | 16 + .../insights/install/oidc/overview.md | 34 + .../platform/insights/install/overview.md | 49 + .../platform/insights/install/self-hosted.md | 57 + .../insights/navigating.md} | 0 docs/manuals/platform/insights/overview.md | 106 ++ .../platform/insights/production/overview.md | 20 + .../insights/production/scalability.md | 26 + .../platform/insights/production/security.md | 28 + .../insights}/query-api.md | 0 docs/manuals/platform/insights/reference.md | 52 + docs/manuals/platform/insights/use-cases.md | 30 + .../uxp/concepts/observability-views.md | 2 +- docs/manuals/uxp/crossplane-web-ui.md | 69 +- .../uxp/howtos}/migrate-configurations-v2.md | 0 .../uxp/howtos}/upgrade-to-uxp.md | 80 +- .../uxp}/upgrade-to-projects.md | 2 +- docs/reference/index.md | 2 +- docusaurus.config.js | 21 +- package-lock.json | 339 ---- package.json | 10 +- scripts/manifest-embed-plugin.js | 49 + scripts/plan-plugin.js | 46 +- scripts/process-crds.js | 92 - .../howtos/spaces-management.md | 2 +- .../upgrade-to-mcps.md | 431 +++++ .../version-1.13/howtos/spaces-management.md | 2 +- .../version-1.14/howtos/spaces-management.md | 2 +- .../version-1.15/howtos/spaces-management.md | 2 +- .../version-1.16/howtos/spaces-management.md | 2 +- src/components/CardGrid.js | 14 + src/components/CopyPageMenu.js | 124 ++ src/components/CopyPageMenu.module.css | 182 ++ src/components/GetStartedCallout.js | 249 --- src/components/GetUpboundHero.js | 48 + src/components/GetUpboundHero.module.css | 141 ++ src/components/GlobalLanguageSelector.js | 153 +- src/components/GuidesCards.js | 124 +- src/components/ManualsCards.js | 171 +- src/components/ReferenceCards.js | 164 +- src/components/VersionSelector.js | 143 +- src/css/custom.css | 950 +--------- src/pages/index.js | 307 ---- src/pages/index.module.css | 15 - src/sidebars/main.js | 92 +- src/theme/DocItem/Content/index.js | 47 + src/theme/DocItem/Content/styles.module.css | 17 + src/theme/MDXContent/index.js | 45 - src/theme/Root.js | 5 +- .../aws-00-prelude.yaml.gotmpl | 2 + .../aws-01-bucket.yaml.gotmpl | 23 + .../aws-02-acl.yaml.gotmpl | 17 + .../aws-03-versioning.yaml.gotmpl | 21 + .../getstarted/create-configuration/aws-fn.go | 197 +++ .../create-configuration/aws-main.k | 115 ++ .../create-configuration/aws-main.py | 114 ++ .../create-configuration/azure-main.k | 75 + .../create-configuration/azure-main.py | 77 + .../create-configuration/gcp-main.k | 43 + .../create-configuration/gcp-main.py | 44 + utils/requirements.txt | 9 - utils/robot/resources/common.robot | 55 - utils/robot/resources/orgainzation.robot | 47 - utils/robot/resources/register.robot | 65 - utils/robot/resources/robot-tokens.robot | 59 - utils/robot/tests/register.robot | 14 - utils/robot/tests/robot-tokens.robot | 25 - utils/vagrant/Vagrantfile | 481 ----- utils/webpack/package-lock.json | 1543 ----------------- utils/webpack/package.json | 21 - utils/webpack/src/LICENSE | 22 - utils/webpack/src/js/colorMode.js | 87 - utils/webpack/src/js/customClipboard.js | 161 -- utils/webpack/src/js/globalScripts.js | 54 - utils/webpack/src/js/hoverHighlight.js | 31 - utils/webpack/src/js/navIconSwitcher.js | 54 - utils/webpack/src/js/search.js | 309 ---- utils/webpack/src/js/tabDeepAnchor.js | 76 - utils/webpack/webpack.config.js | 49 - 127 files changed, 3456 insertions(+), 7692 deletions(-) rename {docs/getstarted/upgrade-to-upbound => cloud-spaces-docs}/upgrade-to-mcps.md (99%) rename docs/getstarted/{introduction/build-and-push.md => builders-workshop/5-build-and-push.md} (85%) rename docs/getstarted/{introduction/integrate-ai.md => builders-workshop/6-integrate-ai.md} (96%) delete mode 100644 docs/getstarted/introduction/_category_.json delete mode 100644 docs/getstarted/introduction/project.md delete mode 100644 docs/getstarted/introduction/whats-next.md create mode 100644 docs/getstarted/new-users/_category_.json create mode 100644 docs/getstarted/new-users/controlplanes-quickstart.md create mode 100644 docs/getstarted/new-users/new-to-control-planes.md create mode 100644 docs/getstarted/upgrade-to-upbound/upgrade-quickstart.md create mode 100644 docs/getstarted/upgrade-to-upbound/upgrading-to-upbound.md rename docs/{getstarted => guides}/platform-tutorial.md (99%) delete mode 100644 docs/manuals/console/_category_.json delete mode 100644 docs/manuals/console/self-service.md create mode 100644 docs/manuals/platform/identity/_category_.json rename docs/manuals/platform/{ => identity}/iam.md (99%) rename docs/manuals/platform/{ => identity}/k8s-rbac.md (99%) rename docs/manuals/platform/{ => identity}/oidc.md (99%) rename docs/manuals/platform/{ => identity}/organizations.md (98%) rename docs/manuals/platform/{ => identity}/rbac.md (99%) rename docs/manuals/platform/{ => identity}/robots.md (95%) rename docs/manuals/platform/{ => identity}/sso.md (99%) rename docs/manuals/platform/{ => identity}/teams.md (99%) rename docs/manuals/platform/{ => identity}/users.md (99%) create mode 100644 docs/manuals/platform/insights/_category_.json create mode 100644 docs/manuals/platform/insights/ai.md create mode 100644 docs/manuals/platform/insights/connections.md create mode 100644 docs/manuals/platform/insights/feature-flags.md create mode 100644 docs/manuals/platform/insights/install/oidc/entra-id.md create mode 100644 docs/manuals/platform/insights/install/oidc/generic.md create mode 100644 docs/manuals/platform/insights/install/oidc/keycloak.md create mode 100644 docs/manuals/platform/insights/install/oidc/overview.md create mode 100644 docs/manuals/platform/insights/install/overview.md create mode 100644 docs/manuals/platform/insights/install/self-hosted.md rename docs/manuals/{console/upbound-console.md => platform/insights/navigating.md} (100%) create mode 100644 docs/manuals/platform/insights/overview.md create mode 100644 docs/manuals/platform/insights/production/overview.md create mode 100644 docs/manuals/platform/insights/production/scalability.md create mode 100644 docs/manuals/platform/insights/production/security.md rename docs/manuals/{console => platform/insights}/query-api.md (100%) create mode 100644 docs/manuals/platform/insights/reference.md create mode 100644 docs/manuals/platform/insights/use-cases.md rename docs/{getstarted/upgrade-to-upbound => manuals/uxp/howtos}/migrate-configurations-v2.md (100%) rename docs/{getstarted/upgrade-to-upbound => manuals/uxp/howtos}/upgrade-to-uxp.md (87%) rename docs/{getstarted/upgrade-to-upbound => manuals/uxp}/upgrade-to-projects.md (99%) create mode 100644 scripts/manifest-embed-plugin.js delete mode 100644 scripts/process-crds.js create mode 100644 self-hosted-spaces_versioned_docs/upgrade-to-mcps.md create mode 100644 src/components/CardGrid.js create mode 100644 src/components/CopyPageMenu.js create mode 100644 src/components/CopyPageMenu.module.css delete mode 100644 src/components/GetStartedCallout.js create mode 100644 src/components/GetUpboundHero.js create mode 100644 src/components/GetUpboundHero.module.css delete mode 100644 src/pages/index.js delete mode 100644 src/pages/index.module.css create mode 100644 src/theme/DocItem/Content/index.js create mode 100644 src/theme/DocItem/Content/styles.module.css delete mode 100644 src/theme/MDXContent/index.js create mode 100644 static/manifests/getstarted/create-configuration/aws-00-prelude.yaml.gotmpl create mode 100644 static/manifests/getstarted/create-configuration/aws-01-bucket.yaml.gotmpl create mode 100644 static/manifests/getstarted/create-configuration/aws-02-acl.yaml.gotmpl create mode 100644 static/manifests/getstarted/create-configuration/aws-03-versioning.yaml.gotmpl create mode 100644 static/manifests/getstarted/create-configuration/aws-fn.go create mode 100644 static/manifests/getstarted/create-configuration/aws-main.k create mode 100644 static/manifests/getstarted/create-configuration/aws-main.py create mode 100644 static/manifests/getstarted/create-configuration/azure-main.k create mode 100644 static/manifests/getstarted/create-configuration/azure-main.py create mode 100644 static/manifests/getstarted/create-configuration/gcp-main.k create mode 100644 static/manifests/getstarted/create-configuration/gcp-main.py delete mode 100644 utils/requirements.txt delete mode 100644 utils/robot/resources/common.robot delete mode 100644 utils/robot/resources/orgainzation.robot delete mode 100644 utils/robot/resources/register.robot delete mode 100644 utils/robot/resources/robot-tokens.robot delete mode 100644 utils/robot/tests/register.robot delete mode 100644 utils/robot/tests/robot-tokens.robot delete mode 100644 utils/vagrant/Vagrantfile delete mode 100644 utils/webpack/package-lock.json delete mode 100644 utils/webpack/package.json delete mode 100644 utils/webpack/src/LICENSE delete mode 100644 utils/webpack/src/js/colorMode.js delete mode 100644 utils/webpack/src/js/customClipboard.js delete mode 100644 utils/webpack/src/js/globalScripts.js delete mode 100644 utils/webpack/src/js/hoverHighlight.js delete mode 100644 utils/webpack/src/js/navIconSwitcher.js delete mode 100644 utils/webpack/src/js/search.js delete mode 100644 utils/webpack/src/js/tabDeepAnchor.js delete mode 100644 utils/webpack/webpack.config.js diff --git a/AGENTS.md b/AGENTS.md index ca9ae4ffb..b5d086f3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - Start local docs server: `npm start` - Build documentation: `npm run build` - Serve built documentation: `npm run serve` -- Process CRDs: `npm run process-crds` - Clear Docusaurus cache: `npm run clear` - Deploy documentation: `npm run deploy` - Generate translations: `npm run write-translations` @@ -38,5 +37,4 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - This is a Docusaurus site, not Hugo - Requires Node.js 18.x and npm 9.x - Use `npm start` for local development with hot reloading -- CRDs are processed via script before start/build commands - Site uses React components and MDX for enhanced functionality \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddb9f71dc..6c1821e55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,24 @@ make build All PRs require maintainer approval to merge, regardless of Vale status. +## Updating CRDs + +CRD reference pages (`docs/reference/apis/*`) render live from raw CRD YAML +files committed under `static/crds//`. There's no generation step — +Docusaurus serves `static/` files as-is, and the `CrdDocViewer` component +fetches the YAML client-side by URL: + +```mdx + +``` + +To add or update a CRD: + +1. Copy the CRD's YAML file from its source repo (UXP, Crossplane, Spaces, + etc.) into the matching `static/crds//` directory. +2. Reference it from the relevant page with ``. +3. Commit the YAML file alongside your doc changes in the same PR. + ## VSCode Setup (Recommended) For the best documentation editing experience, install these extensions: diff --git a/Makefile b/Makefile index cfd17fe17..4f21df103 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ VALE_BINARY := vale_$(VALE_VERSION)_$(VALE_OS)_$(VALE_ARCH).tar.gz VALE_INSTALL_DIR := ./bin VALE_EXEC := $(VALE_INSTALL_DIR)/vale -.PHONY: help install-vale vale vale-docs vale-file vale-summary clean build start serve process-crds +.PHONY: help install-vale vale vale-docs vale-file vale-summary clean build start serve # Default target help: ## Show this help message @@ -46,7 +46,7 @@ help: ## Show this help message install: ## Install dependencies npm install -build: process-crds ## Build the documentation site +build: ## Build the documentation site npm run build start: ## Start local development server @@ -55,9 +55,6 @@ start: ## Start local development server serve: ## Serve built documentation npm run serve -process-crds: ## Process CRDs for documentation - npm run process-crds - # Vale installation and linting commands install-vale: ## Install Vale binary locally @echo "Installing Vale $(VALE_VERSION) for $(VALE_OS) $(VALE_ARCH)..." @@ -130,7 +127,7 @@ dev: install start ## Install dependencies and start development server # CI/CD targets ci-lint: install-vale vale-summary ## Run linting for CI/CD pipeline -ci-build: install process-crds build ## Build for CI/CD pipeline +ci-build: install build ## Build for CI/CD pipeline # Version info version: ## Show versions of tools diff --git a/README.md b/README.md index 5ea682a96..fdc587bbe 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,9 @@ make check-vale-config | Command | Description | |---------|-------------| | `make install` | Install npm dependencies | -| `make build` | Build the documentation site (includes CRD processing) | +| `make build` | Build the documentation site | | `make start` | Start local development server with hot reload | | `make serve` | Serve the built documentation | -| `make process-crds` | Process Custom Resource Definitions for documentation | | `make dev` | Install dependencies and start development server | | `make ci-build` | Full build process for CI/CD | diff --git a/docs/getstarted/upgrade-to-upbound/upgrade-to-mcps.md b/cloud-spaces-docs/upgrade-to-mcps.md similarity index 99% rename from docs/getstarted/upgrade-to-upbound/upgrade-to-mcps.md rename to cloud-spaces-docs/upgrade-to-mcps.md index 2fce16834..15e200be1 100644 --- a/docs/getstarted/upgrade-to-upbound/upgrade-to-mcps.md +++ b/cloud-spaces-docs/upgrade-to-mcps.md @@ -1,6 +1,6 @@ --- title: Upgrade to Spaces -sidebar_position: 3 +sidebar_position: 4 description: A guide to how to update to a control plane in an Upbound Space --- diff --git a/docs/getstarted/builders-workshop/1-project-foundations.md b/docs/getstarted/builders-workshop/1-project-foundations.md index a08423bca..1717bf7b8 100644 --- a/docs/getstarted/builders-workshop/1-project-foundations.md +++ b/docs/getstarted/builders-workshop/1-project-foundations.md @@ -131,7 +131,7 @@ you want to apply to this `StorageBucket` resource. Paste this XR into your XR file: - + ```yaml title="upbound-hello-world/examples/storagebucket/example.yaml" diff --git a/docs/getstarted/builders-workshop/2-create-configuration.md b/docs/getstarted/builders-workshop/2-create-configuration.md index c104117af..b841a2d5d 100644 --- a/docs/getstarted/builders-workshop/2-create-configuration.md +++ b/docs/getstarted/builders-workshop/2-create-configuration.md @@ -4,6 +4,8 @@ description: Create a composition function --- import GlobalLanguageSelector, { CodeBlock } from '@site/src/components/GlobalLanguageSelector'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; @@ -46,7 +48,7 @@ outputs. Generate the composition function scaffolding and choose your preferred language: - + ```shell up function generate example-function apis/storagebuckets/composition.yaml --language=kcl @@ -54,7 +56,7 @@ up function generate example-function apis/storagebuckets/composition.yaml --lan - + ```shell up function generate example-function apis/storagebuckets/composition.yaml --language=python @@ -62,41 +64,22 @@ up function generate example-function apis/storagebuckets/composition.yaml --lan - - - -```shell -up function generate example-function apis/storagebuckets/composition.yaml --language=kcl -``` - - - - + ```shell -up function generate example-function apis/storagebuckets/composition.yaml --language=python +up function generate example-function apis/storagebuckets/composition.yaml --language=go ``` - - + ```shell -up function generate example-function apis/storagebuckets/composition.yaml --language=kcl +up function generate example-function apis/storagebuckets/composition.yaml --language=go-templating ``` - - -```shell -up function generate example-function apis/storagebuckets/composition.yaml --language=python -``` - - - - This command creates a function directory and creates a new file based on your chosen language. @@ -109,123 +92,7 @@ Next, create the actual program logic that builds your cloud resources. Paste the following into `main.k`: -```yaml title="upbound-hello-world/functions/example-function/main.k" -import models.io.upbound.awsm.s3.v1beta1 as awsms3v1beta1 - -oxr = option("params").oxr # observed composite resource -params = oxr.spec.parameters # extract parameter values from XR - -_metadata = lambda name: str -> any { - { - generateName = name # due to global S3 naming restrictions we'll have - # Crossplane generate a name to garauntee uniqueness - annotations = { - "krm.kcl.dev/composition-resource-name" = name - } - } -} - -_items: [any] = [ - # Create S3 Bucket - awsms3v1beta1.Bucket { - metadata: _metadata("{}-bucket".format(oxr.metadata.name)) - spec = { - forProvider = { - region = params.region - } - } - }, - - # Bucket BOC - awsms3v1beta1.BucketOwnershipControls { - metadata: _metadata("{}-boc".format(oxr.metadata.name)) - spec = { - forProvider = { - bucketSelector: { - matchControllerRef: True - } - region = params.region - rule = { - objectOwnership = "BucketOwnerPreferred" - } - } - } - }, - - # Bucket PAB - awsms3v1beta1.BucketPublicAccessBlock { - metadata: _metadata("{}-pab".format(oxr.metadata.name)) - spec = { - forProvider = { - bucketSelector: { - matchControllerRef: True - } - region = params.region - blockPublicAcls: False - ignorePublicAcls: False - restrictPublicBuckets: False - blockPublicPolicy: False - } - } - }, - - # Bucket ACL - awsms3v1beta1.BucketACL { - metadata: _metadata("{}-acl".format(oxr.metadata.name)) - spec = { - forProvider = { - bucketSelector: { - matchControllerRef: True - } - region = params.region - acl = params.acl - } - } - }, - - # Default encryption for the bucket - awsms3v1beta1.BucketServerSideEncryptionConfiguration { - metadata: _metadata("{}-encryption".format(oxr.metadata.name)) - spec = { - forProvider = { - region = params.region - bucketSelector: { - matchControllerRef: True - } - rule = [ - { - applyServerSideEncryptionByDefault = { - sseAlgorithm = "AES256" - } - bucketKeyEnabled = True - } - ] - } - } - } -] - -# Set up versioning for the bucket if desired -if params.versioning: - _items += [ - awsms3v1beta1.BucketVersioning{ - metadata: _metadata("{}-versioning".format(oxr.metadata.name)) - spec = { - forProvider = { - region = params.region - bucketSelector: { - matchControllerRef: True - } - versioningConfiguration = { - status = "Enabled" - } - } - } - } - ] - -items = _items - +```yaml title="upbound-hello-world/functions/example-function/main.k" manifest="/manifests/getstarted/create-configuration/aws-main.k" ``` @@ -234,121 +101,7 @@ items = _items Paste the following into `main.py`: -```python title="upbound-hello-world/functions/example-function/main.py" -from crossplane.function import resource -from crossplane.function.proto.v1 import run_function_pb2 as fnv1 - -from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 -from .model.com.example.platform.storagebucket import v1alpha1 -from .model.io.upbound.m.aws.s3.bucket import v1beta1 as mbucketv1beta1 -from .model.io.upbound.m.aws.s3.bucketacl import v1beta1 as maclv1beta1 -from .model.io.upbound.m.aws.s3.bucketownershipcontrols import v1beta1 as mbocv1beta1 -from .model.io.upbound.m.aws.s3.bucketpublicaccessblock import v1beta1 as mpabv1beta1 -from .model.io.upbound.m.aws.s3.bucketversioning import v1beta1 as mverv1beta1 -from .model.io.upbound.m.aws.s3.bucketserversideencryptionconfiguration import v1beta1 as mssev1beta1 - -def resource_name(xr, resource): - return "{}-{}".format(xr.metadata.name, resource) - -def default_metadata(name): - return { - "generateName": name - } - -def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) - params = observed_xr.spec.parameters - - # Create S3 Bucket - desired_bucket = mbucketv1beta1.Bucket( - metadata = default_metadata(resource_name(observed_xr, "bucket")), - spec = mbucketv1beta1.Spec( - forProvider = mbucketv1beta1.ForProvider( - region = params.region, - ), - ), - ) - resource.update(rsp.desired.resources["bucket"], desired_bucket) - - # Bucket BOC - desired_boc = mbocv1beta1.BucketOwnershipControls( - metadata = default_metadata(resource_name(observed_xr, "boc")), - spec = mbocv1beta1.Spec( - forProvider = mbocv1beta1.ForProvider( - region = params.region, - bucketSelector = mbocv1beta1.BucketSelector(matchControllerRef = True), - rule = { - "objectOwnership": "BucketOwnerPreferred" - } - ) - ), - ) - resource.update(rsp.desired.resources["boc"], desired_boc) - - # Bucket PAB - desired_pab = mpabv1beta1.BucketPublicAccessBlock( - metadata = default_metadata(resource_name(observed_xr, "pab")), - spec=mpabv1beta1.Spec( - forProvider = mpabv1beta1.ForProvider( - region = params.region, - bucketSelector = mpabv1beta1.BucketSelector(matchControllerRef = True), - blockPublicAcls = False, - ignorePublicAcls = False, - restrictPublicBuckets = False, - blockPublicPolicy = False, - ) - ), - ) - resource.update(rsp.desired.resources["pab"], desired_pab) - - # Bucket ACL - desired_acl = maclv1beta1.BucketACL( - metadata = default_metadata(resource_name(observed_xr, "acl")), - spec = maclv1beta1.Spec( - forProvider = maclv1beta1.ForProvider( - region = params.region, - bucketSelector = maclv1beta1.BucketSelector(matchControllerRef = True), - acl = params.acl, - ), - ), - ) - resource.update(rsp.desired.resources["acl"], desired_acl) - - # Default encryption for the bucket - desired_sse = mssev1beta1.BucketServerSideEncryptionConfiguration( - metadata = default_metadata(resource_name(observed_xr, "encryption")), - spec = mssev1beta1.Spec( - forProvider = mssev1beta1.ForProvider( - region = params.region, - bucketSelector = mssev1beta1.BucketSelector(matchControllerRef = True), - rule = [ - mssev1beta1.RuleItem( - applyServerSideEncryptionByDefault = { - "sseAlgorithm": "AES256" - }, - bucketKeyEnabled = True - ) - ] - ), - ), - ) - resource.update(rsp.desired.resources["sse"], desired_sse) - - # Set up versioning for the bucket if desired - if params.versioning: - desired_versioning = mverv1beta1.BucketVersioning( - metadata = default_metadata(resource_name(observed_xr, "versioning")), - spec = mverv1beta1.Spec( - forProvider = mverv1beta1.ForProvider( - region = params.region, - bucketSelector = mverv1beta1.BucketSelector(matchControllerRef = True), - versioningConfiguration = { - "status": "Enabled" - } - ), - ), - ) - resource.update(rsp.desired.resources["versioning"], desired_versioning) +```python title="upbound-hello-world/functions/example-function/main.py" manifest="/manifests/getstarted/create-configuration/aws-main.py" ``` @@ -358,84 +111,7 @@ def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): Paste the following into `main.k`: -```yaml title="upbound-hello-world/functions/example-function/main.k" - -import regex -import models.io.upbound.azurem.v1beta1 as azuremv1beta1 -import models.io.upbound.azurem.storage.v1beta1 as azuremstoragev1beta1 - -oxr = option("params").oxr # observed composite resource -params = oxr.spec.parameters # extract parameter values from XR -xr_metadata = oxr.metadata # store XR metadata - -_metadata = lambda name: str -> any { - { - name = name - annotations = { - "krm.kcl.dev/composition-resource-name" = name - } - } -} - -sanitize_azure_storage_account_name = lambda name: str -> str { - # Due to Azure's naming restrictions, storage account names must - # be between 3-24 characters in length and use numbers and lower-case letters only - - # lower string and remove illegal characters - sanitized = name.lower() - sanitized = regex.replace(sanitized, "[^a-z0-9]", "") - - # pad with 0s if string name less than 3 characters - if len(sanitized) < 3: - sanitized = "{:0<3}".format(sanitized) - - # trim string to 24 characters - sanitized = sanitized[:24] -} - -_items = [ - azuremv1beta1.ResourceGroup { - metadata = _metadata("{}-group".format(xr_metadata.name)) - spec = { - forProvider = { - location = params.location - } - } - }, - azuremstoragev1beta1.Account { - metadata = _metadata(sanitize_azure_storage_account_name("{}account".format(xr_metadata.name))) - spec = { - forProvider = { - accountTier = "Standard" - accountReplicationType = "LRS" - location = params.location - blobProperties = { - versioningEnabled = params.versioning - } - infrastructureEncryptionEnabled = True - resourceGroupNameSelector = { - matchControllerRef = True - } - } - } - }, - azuremstoragev1beta1.Container { - metadata: _metadata("{}-container".format(xr_metadata.name)) - spec = { - forProvider = { - if params.acl == "public": - containerAccessType = "blob" - else: - containerAccessType = "private" - storageAccountNameSelector = { - matchControllerRef = True - } - } - } - } -] -items = _items - +```yaml title="upbound-hello-world/functions/example-function/main.k" manifest="/manifests/getstarted/create-configuration/azure-main.k" ``` @@ -444,200 +120,80 @@ items = _items Paste the following into `main.py`: +```python title="upbound-hello-world/functions/example-function/main.py" manifest="/manifests/getstarted/create-configuration/azure-main.py" +``` -```python title="upbound-hello-world/functions/example-function/main.py" -import re + -from crossplane.function import resource -from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 -from .model.io.upbound.m.azure.resourcegroup import v1beta1 as rgv1beta1 -from .model.io.upbound.m.azure.storage.account import v1beta1 as acctv1beta1 -from .model.io.upbound.m.azure.storage.container import v1beta1 as contv1beta1 -from .model.com.example.platform.storagebucket import v1alpha1 + -def resource_name(xr, resource): - return "{}-{}".format(xr.metadata.name, resource) +Paste the following into `main.k`: -def default_metadata(name): - return { - "name": name - } +```yaml title="upbound-hello-world/functions/example-function/main.k" manifest="/manifests/getstarted/create-configuration/gcp-main.k" +``` -def sanitize_azure_storage_account_name(account_name): - # Due to Azure's naming restrictions, storage account names must - # be between 3-24 characters in length and use numbers and lower-case letters only + - # Convert to lowercase and remove all non-alphanumeric characters - sanitized = re.sub(r'[^a-z0-9]', '', account_name.lower()) - - # Ensure minimum length of 3 - if len(sanitized) < 3: - sanitized = sanitized.ljust(3, '0') - - # Ensure maximum length of 24 - if len(sanitized) > 24: - sanitized = sanitized[:24] - - return sanitized + -def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.StorageBucket(**resource.struct_to_dict(req.observed.composite.resource)) - params = observed_xr.spec.parameters +Paste the following into `main.py`: - desired_group = rgv1beta1.ResourceGroup( - metadata = default_metadata(resource_name(observed_xr, "group")), - spec = rgv1beta1.Spec( - forProvider = rgv1beta1.ForProvider( - location = params.location, - ), - ), - ) - resource.update(rsp.desired.resources["group"], desired_group) - - desired_acct = acctv1beta1.Account( - metadata = default_metadata(sanitize_azure_storage_account_name(resource_name(observed_xr, "account"))), - spec = acctv1beta1.Spec( - forProvider = acctv1beta1.ForProvider( - accountTier = "Standard", - accountReplicationType = "LRS", - location = params.location, - infrastructureEncryptionEnabled = True, - blobProperties = { - "versioningEnabled": params.versioning - }, - resourceGroupNameSelector = acctv1beta1.ResourceGroupNameSelector(matchControllerRef = True) - ), - ), - ) - resource.update(rsp.desired.resources["account"], desired_acct) - - desired_cont = contv1beta1.Container( - metadata = default_metadata(resource_name(observed_xr, "container")), - spec = contv1beta1.Spec( - forProvider = contv1beta1.ForProvider( - containerAccessType = "blob" if params.acl == "public" else "private", - storageAccountNameSelector = contv1beta1.StorageAccountNameSelector(matchControllerRef = True) - ), - ), - ) - resource.update(rsp.desired.resources["container"], desired_cont) +```python title="upbound-hello-world/functions/example-function/main.py" manifest="/manifests/getstarted/create-configuration/gcp-main.py" ``` + - +This example targets AWS. Paste the following into `fn.go`: -Paste the following into `main.k`: +```go title="upbound-hello-world/functions/example-function/fn.go" manifest="/manifests/getstarted/create-configuration/aws-fn.go" +``` -```yaml title="upbound-hello-world/functions/example-function/main.k" +For the full Go composition function guide, including working with generated +models, see [Create a composition with Go][go-compositions]. -import models.io.upbound.gcpm.storage.v1beta1 as gcpmstoragev1beta1 + -oxr = option("params").oxr # observed composite resource -params = oxr.spec.parameters # extract parameter values from XR + -_metadata = lambda name: str -> any { - { - name = name - annotations = { - "krm.kcl.dev/composition-resource-name" = name - } - } -} +This example targets AWS and covers the bucket, ACL, and versioning resources. +Paste each file below into your function's directory, matching the filenames: -_items: [any] = [ - # Create GCP Bucket - gcpmstoragev1beta1.Bucket { - metadata: _metadata("{}-bucket".format(oxr.metadata.name)) - spec = { - forProvider = { - location = params.location - versioning = { - enabled = params.versioning - } - } - } - }, - - # Bucket ACL - gcpmstoragev1beta1.BucketACL { - metadata: _metadata("{}-acl".format(oxr.metadata.name)) - spec = { - forProvider = { - predefinedAcl = params.acl - bucketSelector = { - matchControllerRef = True - } - } - } - } -] - -items = _items + + +```yaml manifest="/manifests/getstarted/create-configuration/aws-00-prelude.yaml.gotmpl" ``` - - - - -Paste the following into `main.py`: + + -```python title="upbound-hello-world/functions/example-function/main.py" +```yaml manifest="/manifests/getstarted/create-configuration/aws-01-bucket.yaml.gotmpl" +``` -from crossplane.function import resource -from crossplane.function.proto.v1 import run_function_pb2 as fnv1 + + -from .model.io.upbound.m.gcp.storage.bucket import v1beta1 as mbucketv1beta1 -from .model.io.upbound.m.gcp.storage.bucketacl import v1beta1 as maclv1beta1 -from .model.com.example.platform.storagebucket import v1alpha1 +```yaml manifest="/manifests/getstarted/create-configuration/aws-02-acl.yaml.gotmpl" +``` -def resource_name(xr, resource): - return "{}-{}".format(xr.metadata.name, resource) + + -def default_metadata(name): - return { - "name": name - } +```yaml manifest="/manifests/getstarted/create-configuration/aws-03-versioning.yaml.gotmpl" +``` -def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) - params = observed_xr.spec.parameters - - # Create GCP Bucket - desired_bucket = mbucketv1beta1.Bucket( - metadata = default_metadata(resource_name(observed_xr, "bucket")), - spec = mbucketv1beta1.Spec( - forProvider = mbucketv1beta1.ForProvider( - location = params.location, - versioning = { - "enabled": params.versioning - } - ), - ), - ) - resource.update(rsp.desired.resources["bucket"], desired_bucket) - - # Bucket ACL - desired_acl = maclv1beta1.BucketACL( - metadata = default_metadata(resource_name(observed_xr, "acl")), - spec = maclv1beta1.Spec( - forProvider = maclv1beta1.ForProvider( - predefinedAcl=params.acl, - bucketSelector = maclv1beta1.BucketSelector(matchControllerRef = True) - ), - ), - ) - resource.update(rsp.desired.resources["acl"], desired_acl) + + -``` +For the full Go templating guide, including the bucket ownership, public +access block, and encryption resources this example skips, see [Create a +composition with Go Templates][go-template-compositions]. - Save your composition file. ## Review your function @@ -1522,3 +1078,5 @@ the built-in test suite. [up-cli]: /manuals/cli/overview [kubectl-installed]: https://kubernetes.io/docs/tasks/tools/ [docker-desktop]: https://www.docker.com/products/docker-desktop/ +[go-compositions]: /manuals/cli/howtos/compositions/go +[go-template-compositions]: /manuals/cli/howtos/compositions/go-template diff --git a/docs/getstarted/builders-workshop/4-deployment.md b/docs/getstarted/builders-workshop/4-deployment.md index ea83d9530..33a32c940 100644 --- a/docs/getstarted/builders-workshop/4-deployment.md +++ b/docs/getstarted/builders-workshop/4-deployment.md @@ -244,13 +244,33 @@ kubectl apply --filename examples/storagebucket/example.yaml Return the resource state with the up CLI. ```shell -kubectl get storagebuckets.platform.example.com -o yaml +kubectl get f examples/storagebucket/example.yaml -o yaml +``` + + +```shell-noCopy +NAME SYNCED READY COMPOSITION AGE +webapp True True app-yaml 56s +``` + + + + +```shell {copy-lines="1"} + +kubectl get deploy,service -l crossplane.io/composite=webapp +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/webapp-2r2rk 2/2 2 2 11m + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/webapp-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` Now, you can validate your results through the Upbound Console, and make any changes to test your resources required. + ## Clean up Be sure to destroy your resources to avoid cloud costs: @@ -270,8 +290,8 @@ up project stop You just created an Upbound project from scratch with an embedded function and a resource claim. -Next, try out an Intelligent Control Plane solution or build your own Internal -Developer Platform. +Continue the workshop with [Build and push your first +Configuration][build-and-push] to package and share your control plane. For more information on projects and how to build control planes, checkout the [CLI Build][build] manuals. @@ -284,3 +304,4 @@ Build][build] manuals. [kubectl-installed]: https://kubernetes.io/docs/tasks/tools/ [docker-desktop]: https://www.docker.com/products/docker-desktop/ [build]: /manuals/cli/howtos/project +[build-and-push]: /getstarted/builders-workshop/build-and-push diff --git a/docs/getstarted/introduction/build-and-push.md b/docs/getstarted/builders-workshop/5-build-and-push.md similarity index 85% rename from docs/getstarted/introduction/build-and-push.md rename to docs/getstarted/builders-workshop/5-build-and-push.md index 0c9fc0078..760829578 100644 --- a/docs/getstarted/introduction/build-and-push.md +++ b/docs/getstarted/builders-workshop/5-build-and-push.md @@ -1,6 +1,6 @@ --- -title: Build and push your first Configuration -sidebar_position: 3 +title: 5. Build and push your first Configuration +description: Package your control plane and share it via the Upbound Marketplace --- @@ -25,7 +25,8 @@ Before you begin, make sure you have: * The `up` CLI installed * A control plane project ready to package -Read the [Create a Control Plane][create] quickstart if you haven't yet. +If you missed any of the previous steps, go to the [project +foundations][project-foundations] guide to get started. ## Sign in to Upbound @@ -109,10 +110,11 @@ extensions. ## Next steps -Read the [What's Next][whats-next] to continue your learning journey. +Continue the workshop with [Create an AI-augmented operation][integrate-ai] to +add AI-powered monitoring to your control plane. -[create]: /getstarted/introduction/project +[project-foundations]: /getstarted/builders-workshop/project-foundations [configurations]: /manuals/uxp/concepts/packages/configurations [repositories]: /manuals/marketplace/repositories/overview [marketplace]: https://marketplace.upbound.io/ -[whats-next]: /getstarted/introduction/whats-next +[integrate-ai]: /getstarted/builders-workshop/integrate-ai diff --git a/docs/getstarted/introduction/integrate-ai.md b/docs/getstarted/builders-workshop/6-integrate-ai.md similarity index 96% rename from docs/getstarted/introduction/integrate-ai.md rename to docs/getstarted/builders-workshop/6-integrate-ai.md index 6c9a25d3c..c12ae2313 100644 --- a/docs/getstarted/introduction/integrate-ai.md +++ b/docs/getstarted/builders-workshop/6-integrate-ai.md @@ -1,7 +1,6 @@ --- -title: Create an AI-augmented operation +title: 6. Create an AI-augmented operation description: "Use Upbound Crossplane to build and manage an AI-powered control plane" -sidebar_position: 2 --- @@ -29,7 +28,7 @@ practitioners. Before you begin, make sure you have: -* a defined project from the [previous guide][project] +* a defined project from the [project foundations][project-foundations] guide * an Anthropic API key for Claude AI integration * `kubectl` access to your Kubernetes cluster * the [Upbound CLI][up] installed and configured @@ -608,15 +607,14 @@ remediation steps. ## Next steps -Now that your control plane is running locally with AI-powered operations, consider these next steps: - -* Package your control plane as a [Configuration][Configuration] image and push it to the Upbound Marketplace -* Complete the [Build and push your first Configuration][buildAndPush] tutorial -* Explore additional [AI-powered operations][aiOperations] for other infrastructure scenarios +Now that your control plane is running locally with AI-powered operations, +explore additional [AI-powered operations][aiOperations] for other +infrastructure scenarios, or try out an [Intelligent Control Plane +solution][intelligentControlPlanes]. [up]: /manuals/cli/overview -[project]: /getstarted/introduction/project +[project-foundations]: /getstarted/builders-workshop/project-foundations [anthropic-key]: https://docs.anthropic.com/en/api/admin-api/apikeys/get-api-key [Configuration]: /manuals/uxp/concepts/packages/configurations [aiOperations]: /manuals/uxp/concepts/operations/intelligent-operations/ -[buildAndPush]: /getstarted/introduction/build-and-push +[intelligentControlPlanes]: /guides/intelligent-control-planes/ diff --git a/docs/getstarted/builders-workshop/_category_.json b/docs/getstarted/builders-workshop/_category_.json index 960ed02c2..c3c03baa3 100644 --- a/docs/getstarted/builders-workshop/_category_.json +++ b/docs/getstarted/builders-workshop/_category_.json @@ -1,4 +1,4 @@ { "label": "Builder's Workshop", - "position": 5 + "position": 4 } diff --git a/docs/getstarted/introduction/_category_.json b/docs/getstarted/introduction/_category_.json deleted file mode 100644 index 69cc2d008..000000000 --- a/docs/getstarted/introduction/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Introduction", - "position": 4 -} diff --git a/docs/getstarted/introduction/project.md b/docs/getstarted/introduction/project.md deleted file mode 100644 index 8ece55d4d..000000000 --- a/docs/getstarted/introduction/project.md +++ /dev/null @@ -1,1139 +0,0 @@ ---- -title: Create a control plane project -sidebar_position: 1 ---- - -Now that you have an Upbound account and the up CLI installed, you're ready to -create a control plane. - - - -In this quickstart, you will: - - - -1. Scaffold a control plane project -2. Define your own resource abstraction and templatization -3. See the changes immediately - -:::tip - -This quickstart teaches how to use Crossplane to build workflows for templating -resources and exposing them as simplified resource abstraction. If you just want -to manage the lifecycle of resources in an external system through Crossplane -and Kubernetes, read [Manage external resources with providers][providers] - -::: - -## Prerequisites - -This quickstart takes around 10 minutes to complete. You should be familiar with -YAML or programming in Go, Python, or KCL. - -Before beginning, make sure you have: - -- The [up][up-cli] CLI installed -- A Docker-compatible container runtime installed and running on your system -- A free Community [Upbound account][register] - -:::tip -**Podman users** -If you're using Podman instead of Docker, set the `DOCKER_HOST` environment variable to the Podman socket before running `up` commands: - -```shell -export DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock -``` -::: - -## Login to Upbound - -The `up` CLI requires you to log in to run local control planes. You can [register] for a free Community account. - -In your terminal, run the following command: - -```shell -up login -``` -Your browser opens and prompts you to log in to your organization. Once you're authenticated, you can close the page. - -## Create a control plane project - -Crossplane works by letting you define new resource types in Kubernetes that -invoke function pipelines to template and generate other resources. Just like -any other software project, a _control plane project_ is a source-level -representation of your control plane. - -Create a control plane project on your machine by running the following command: - -```shell -up project init --scratch getting-started -``` - -This scaffolds a new project in a folder called `getting-started`. Change your -current working directory to the project root folder: - -```shell -cd getting-started -``` - -## Deploy your control plane - -In the root directory of your project, build and run your project by running the -following: - -```shell -up project run --local --ingress -``` - -This launches an instance of Upbound Crossplane on your machine, wrapped and -deployed in a container. Upbound Crossplane comes bundled with a Web UI. - -![image][webUI] - -## Define your own resource type - -Customize your control plane by defining your own resource type. - -Create an example instance of your custom resource type with: - -```shell -up example generate \ - --api-group platform.example.com \ - --api-version v1alpha1 \ - --kind WebApp\ - --name webapp \ - --scope namespace \ - --namespace default -``` - -Open the project in your IDE of choice and replace the contents of the generated file -`getting-started/examples/webapp/webapp.yaml` with the following: - -```yaml title="getting-started/examples/webapp/webapp.yaml" -apiVersion: platform.example.com/v1alpha1 -kind: WebApp -metadata: - name: webapp - namespace: default -spec: - parameters: - image: nginx - port: 8080 - replicas: 1 - service: - enabled: true - ingress: - enabled: false - serviceAccount: default - resources: - requests: - memory: "64Mi" - cpu: "250m" - limits: - memory: "1Gi" - cpu: "1" -status: - availableReplicas: 1 - url: "http://localhost:8080" -``` - -Next, generate the definition files needed by Crossplane with the following commands: - - - - -```shell -up xrd generate examples/webapp/webapp.yaml -up composition generate apis/webapps/definition.yaml -up function generate --language=go-templating compose-resources apis/webapps/composition.yaml -up dependency add --api k8s:v1.33.0 -``` - - -```shell -up xrd generate examples/webapp/webapp.yaml -up composition generate apis/webapps/definition.yaml -up function generate --language=python compose-resources apis/webapps/composition.yaml -up dependency add --api k8s:v1.33.0 -``` - - -```shell -up xrd generate examples/webapp/webapp.yaml -up composition generate apis/webapps/definition.yaml -up function generate --language=go compose-resources apis/webapps/composition.yaml -up dependency add --api k8s:v1.33.0 -``` - - -```shell -up xrd generate examples/webapp/webapp.yaml -up composition generate apis/webapps/definition.yaml -up function generate --language=kcl compose-resources apis/webapps/composition.yaml -up dependency add --api k8s:v1.33.0 -``` - - - -You just created your own resource type called `WebApp`. You generated a function -containing the logic Crossplane uses to determine what should happen when you -create the `WebApp`. - -:::tip - -To define a new resource type with Crossplane, you need to: - -* create a [CompositeResourceDefinition (XRD)][xrd], which defines the API schema of your resource type -* create a [Composition][composition], which defines the implementation of that API schema. - -A Composition is a pipeline of [functions][functions], which contain the user-defined logic of your composition. - -::: - -Open the function definition file at -`getting-started/functions/compose-resources/` and replace the contents with the -following: - - - - - -```yaml title="getting-started/functions/compose-resources/01-compose.yaml.gotmpl" -# code: language=yaml -# yaml-language-server: $schema=../../.up/json/models/index.schema.json - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - name: {{ .observed.composite.resource.metadata.name }} - namespace: {{ .observed.composite.resource.metadata.namespace }} - labels: - app.kubernetes.io/name: {{ .observed.composite.resource.metadata.name }} -spec: - replicas: {{ .observed.composite.resource.spec.parameters.replicas }} - selector: - matchLabels: - app.kubernetes.io/name: {{ .observed.composite.resource.metadata.name }} - app: {{ .observed.composite.resource.metadata.name }} - strategy: {} - template: - metadata: - labels: - app.kubernetes.io/name: {{ .observed.composite.resource.metadata.name }} - app: {{ .observed.composite.resource.metadata.name }} - spec: - serviceAccountName: {{ .observed.composite.resource.spec.parameters.serviceAccount }} - containers: - - name: {{ .observed.composite.resource.metadata.name }} - image: {{ .observed.composite.resource.spec.parameters.image }} - imagePullPolicy: Always - ports: - - name: http - containerPort: {{ .observed.composite.resource.spec.parameters.port }} - protocol: TCP - resources: - requests: - memory: "64Mi" - cpu: "250m" - limits: - memory: "1Gi" - cpu: "1" - restartPolicy: Always -status: {} -# code: language=yaml -# yaml-language-server: $schema=../../.up/json/models/index.schema.json - -{{ if .observed.composite.resource.spec.parameters.ingress.enabled }} ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: ingress - {{ if (get (getComposedResource . "ingress").status.loadBalancer.ingress 0).hostname }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - kubernetes.io/ingress.class: alb - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-path: /health - alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]' - alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60 - name: {{ .observed.composite.resource.metadata.name }} - namespace: {{ .observed.composite.resource.metadata.namespace }} -spec: - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: {{ .observed.composite.resource.metadata.name }} - port: - number: 80 -{{ end }} -# code: language=yaml -# yaml-language-server: $schema=../../.up/json/models/index.schema.json - -{{ if .observed.composite.resource.spec.parameters.service.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - name: {{ .observed.composite.resource.metadata.name }} - namespace: {{ .observed.composite.resource.metadata.namespace }} -spec: - selector: - app: {{ .observed.composite.resource.metadata.name }} - ports: - - name: http - protocol: TCP - port: 80 - targetPort: http -status: - loadBalancer: {} -{{ end }} -# code: language=yaml -# yaml-language-server: $schema=../../.up/json/models/index.schema.json - ---- -apiVersion: {{ .observed.composite.resource.apiVersion }} -kind: {{ .observed.composite.resource.kind }} -status: - {{ with $deployment := getComposedResource . "deployment" }} - deploymentConditions: {{ $deployment.status.conditions | toJson }} - availableReplicas: {{ $deployment.status.availableReplicas | default 0 }} - {{ else }} - deploymentConditions: [] - availableReplicas: 0 - {{ end }} - {{ with $ingress := getComposedResource . "ingress" }} - {{ with $hostname := (get $ingress.status.loadBalancer.ingress 0).hostname }} - url: {{ $hostname | quote }} - {{ else }} - url: "" - {{ end }} - {{ else }} - url: "" - {{ end }} -``` - - - - -```python title="getting-started/functions/compose-resources/main.py" -from crossplane.function import resource -from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.io.k8s.api.apps import v1 as appsv1 -from .model.io.k8s.api.core import v1 as corev1 -from .model.io.k8s.api.networking import v1 as networkingv1 -from .model.com.example.platform.webapp import v1alpha1 as platformv1alpha1 -from .model.io.k8s.apimachinery.pkg.apis.core.meta import v1 as coremetav1 -def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - oxr = platformv1alpha1.WebApp(**req.observed.composite.resource) - ocds = req.observed.resources - - # Create a Status object to collect updates - status = platformv1alpha1.Status() - - deployment = appsv1.Deployment( - metadata=coremetav1.ObjectMeta( - name=oxr.metadata.name, - namespace=oxr.metadata.namespace, - labels={ - "app.kubernetes.io/name": oxr.metadata.name - }, - ), - spec=appsv1.DeploymentSpec( - replicas=oxr.spec.parameters.replicas, - selector=coremetav1.LabelSelector( - matchLabels={ - "app.kubernetes.io/name": oxr.metadata.name, - "app": oxr.metadata.name - } - ), - template=corev1.PodTemplateSpec( - metadata=coremetav1.ObjectMeta( - labels={ - "app.kubernetes.io/name": oxr.metadata.name, - "app": oxr.metadata.name - } - ), - spec=corev1.PodSpec( - serviceAccountName=oxr.spec.parameters.serviceAccount, - containers=[ - corev1.Container( - name=oxr.metadata.name, - image=oxr.spec.parameters.image, - imagePullPolicy="Always", - ports=[ - corev1.ContainerPort( - name="http", - containerPort=int(oxr.spec.parameters.port), - protocol="TCP", - ) - ], - resources=corev1.ResourceRequirements( - requests={ - "memory": "64Mi", - "cpu": "250m" - }, - limits={ - "memory": "1Gi", - "cpu": "1" - } - ) - ) - ], - restartPolicy="Always" - ) - ) - ) - ) - - if "deployment" in ocds: - observed_deployment = appsv1.Deployment(**ocds["deployment"].resource) - if observed_deployment.status and observed_deployment.status.conditions: - for condition in observed_deployment.status.conditions: - if condition.type == "Available" and condition.status == "True": - rsp.desired.resources["deployment"].ready = True - break - - resource.update(rsp.desired.resources["deployment"], deployment) - - if oxr.spec.parameters.service and oxr.spec.parameters.service.enabled: - service = corev1.Service( - metadata=coremetav1.ObjectMeta( - name=oxr.metadata.name, - namespace=oxr.metadata.namespace, - ), - spec=corev1.ServiceSpec( - selector={ - "app": oxr.metadata.name - }, - ports=[ - corev1.ServicePort( - name="http", - protocol="TCP", - port=80, - targetPort="http" - ) - ] - ) - ) - - if "service" in ocds: - observed_service = corev1.Service(**ocds["service"].resource) - if observed_service.spec and observed_service.spec.clusterIP: - rsp.desired.resources["service"].ready = True - resource.update(rsp.desired.resources["service"], service) - - if oxr.spec.parameters.ingress and oxr.spec.parameters.ingress.enabled: - ingress = networkingv1.Ingress( - metadata=coremetav1.ObjectMeta( - name=oxr.metadata.name, - namespace=oxr.metadata.namespace, - annotations={ - "kubernetes.io/ingress.class": "alb", - "alb.ingress.kubernetes.io/scheme": "internet-facing", - "alb.ingress.kubernetes.io/target-type": "ip", - "alb.ingress.kubernetes.io/healthcheck-path": "/health", - "alb.ingress.kubernetes.io/listen-ports": '[{"HTTP": 80}]', - "alb.ingress.kubernetes.io/target-group-attributes": "stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60" - } - ), - spec=networkingv1.IngressSpec( - rules=[ - networkingv1.IngressRule( - http=networkingv1.HTTPIngressRuleValue( - paths=[ - networkingv1.HTTPIngressPath( - path="/", - pathType="Prefix", - backend=networkingv1.IngressBackend( - service=networkingv1.IngressServiceBackend( - name=oxr.metadata.name, - port=networkingv1.ServiceBackendPort( - number=80 - ) - ) - ) - ) - ] - ) - ) - ] - ) - ) - - if "ingress" in ocds: - observed_ingress = networkingv1.Ingress(**ocds["ingress"].resource) - if (observed_ingress.status and - observed_ingress.status.loadBalancer and - observed_ingress.status.loadBalancer.ingress and - len(observed_ingress.status.loadBalancer.ingress) > 0 and - observed_ingress.status.loadBalancer.ingress[0].hostname): - rsp.desired.resources["ingress"].ready = True - resource.update(rsp.desired.resources["ingress"], ingress) - - # Set status with defaults - if "deployment" in ocds: - observed_deployment = appsv1.Deployment(**ocds["deployment"].resource) - if observed_deployment.status and observed_deployment.status.conditions: - status.deploymentConditions = [] - for condition in observed_deployment.status.conditions: - condition_dict = condition.model_dump(exclude_none=True) - # Convert datetime objects to ISO format strings - if 'lastTransitionTime' in condition_dict and condition_dict['lastTransitionTime']: - condition_dict['lastTransitionTime'] = condition_dict['lastTransitionTime'].isoformat() - if 'lastUpdateTime' in condition_dict and condition_dict['lastUpdateTime']: - condition_dict['lastUpdateTime'] = condition_dict['lastUpdateTime'].isoformat() - status.deploymentConditions.append(condition_dict) - else: - status.deploymentConditions = [] - status.availableReplicas = observed_deployment.status.availableReplicas if observed_deployment.status and observed_deployment.status.availableReplicas else 0 - else: - status.deploymentConditions = [] - status.availableReplicas = 0 - - if "ingress" in ocds: - observed_ingress = networkingv1.Ingress(**ocds["ingress"].resource) - status.url = ( - observed_ingress.status.loadBalancer.ingress[0].hostname - if (observed_ingress.status and - observed_ingress.status.loadBalancer and - observed_ingress.status.loadBalancer.ingress and - len(observed_ingress.status.loadBalancer.ingress) > 0 and - observed_ingress.status.loadBalancer.ingress[0].hostname) - else "" - ) - else: - status.url = "" - - resource.update(rsp.desired.composite, {"status": status.model_dump(exclude_none=True)}) -``` - - - - -```go title="getting-started/functions/compose-resources/fn.go" -package main - -import ( - "context" - "encoding/json" - - "dev.upbound.io/models/com/example/platform/v1alpha1" - appsv1 "dev.upbound.io/models/io/k8s/apps/v1" - coremetav1 "dev.upbound.io/models/io/k8s/core/meta/v1" - corev1 "dev.upbound.io/models/io/k8s/core/v1" - networkingv1 "dev.upbound.io/models/io/k8s/networking/v1" - resourcev1 "dev.upbound.io/models/io/k8s/resource/v1" - "github.com/crossplane/crossplane-runtime/pkg/logging" - "github.com/crossplane/function-sdk-go/errors" - fnv1 "github.com/crossplane/function-sdk-go/proto/v1" - "github.com/crossplane/function-sdk-go/request" - "github.com/crossplane/function-sdk-go/resource" - "github.com/crossplane/function-sdk-go/resource/composed" - "github.com/crossplane/function-sdk-go/response" - "k8s.io/utils/ptr" -) - -// Function is your composition function. -type Function struct { - fnv1.UnimplementedFunctionRunnerServiceServer - - log logging.Logger -} - -// RunFunction runs the Function. -func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) { - f.log.Info("Running function", "tag", req.GetMeta().GetTag()) - rsp := response.To(req, response.DefaultTTL) - - observedComposite, err := request.GetObservedCompositeResource(req) - if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot get xr")) - return rsp, nil - } - - observedComposed, err := request.GetObservedComposedResources(req) - if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot get observed resources")) - return rsp, nil - } - - var xr v1alpha1.WebApp - if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) - return rsp, nil - } - - params := xr.Spec.Parameters - if params == nil { - response.Fatal(rsp, errors.New("missing parameters")) - return rsp, nil - } - - // We'll collect our desired composed resources into this map, then convert - // them to the SDK's types and set them in the response when we return. - desiredComposed := make(map[resource.Name]any) - defer func() { - desiredComposedResources, err := request.GetDesiredComposedResources(req) - if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot get desired resources")) - return - } - - for name, obj := range desiredComposed { - c := composed.New() - if err := convertViaJSON(c, obj); err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot convert %s to unstructured", name)) - return - } - dc := &resource.DesiredComposed{Resource: c} - - // Check if this resource should be marked as ready - if c.GetAnnotations()["go.upbound.io/ready"] == "True" { - dc.Ready = resource.ReadyTrue - } - - desiredComposedResources[name] = dc - } - - if err := response.SetDesiredComposedResources(rsp, desiredComposedResources); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot set desired resources")) - return - } - }() - - // Create Deployment - deployment := &appsv1.Deployment{ - APIVersion: ptr.To(appsv1.DeploymentAPIVersionAppsV1), - Kind: ptr.To(appsv1.DeploymentKindDeployment), - Metadata: &coremetav1.ObjectMeta{ - Name: xr.Metadata.Name, - Namespace: xr.Metadata.Namespace, - Labels: &map[string]string{ - "app.kubernetes.io/name": *xr.Metadata.Name, - }, - }, - Spec: &appsv1.DeploymentSpec{ - Replicas: ptr.To(int32(*params.Replicas)), - Selector: &coremetav1.LabelSelector{ - MatchLabels: &map[string]string{ - "app.kubernetes.io/name": *xr.Metadata.Name, - "app": *xr.Metadata.Name, - }, - }, - // ToDo(haarchri): remove this - Strategy: &appsv1.IoK8SApiAppsV1DeploymentStrategy{}, - Template: &corev1.PodTemplateSpec{ - Metadata: &coremetav1.ObjectMeta{ - Labels: &map[string]string{ - "app.kubernetes.io/name": *xr.Metadata.Name, - "app": *xr.Metadata.Name, - }, - }, - Spec: &corev1.PodSpec{ - ServiceAccountName: params.ServiceAccount, - Containers: &[]corev1.Container{{ - Name: xr.Metadata.Name, - Image: params.Image, - ImagePullPolicy: ptr.To("Always"), - Ports: &[]corev1.ContainerPort{{ - Name: ptr.To("http"), - ContainerPort: ptr.To(int32(*params.Port)), - Protocol: ptr.To("TCP"), - }}, - Resources: &corev1.ResourceRequirements{ - Requests: &map[string]resourcev1.Quantity{ - "memory": "64Mi", - "cpu": "250m", - }, - Limits: &map[string]resourcev1.Quantity{ - "memory": "1Gi", - "cpu": "1", - }, - }, - }}, - RestartPolicy: ptr.To("Always"), - }, - }, - }, - // ToDo(haarchri): remove this - Status: &appsv1.IoK8SApiAppsV1DeploymentStatus{}, - } - - // Check if deployment is ready - observedDeployment, ok := observedComposed["deployment"] - if ok && observedDeployment.Resource != nil { - var obsDeployment appsv1.Deployment - if err := convertViaJSON(&obsDeployment, observedDeployment.Resource); err == nil { - if obsDeployment.Status != nil && obsDeployment.Status.Conditions != nil { - for _, c := range *obsDeployment.Status.Conditions { - if c.Type != nil && *c.Type == "Available" && - c.Status != nil && *c.Status == "True" { - if deployment.Metadata.Annotations == nil { - deployment.Metadata.Annotations = &map[string]string{} - } - (*deployment.Metadata.Annotations)["go.upbound.io/ready"] = "True" - break - } - } - } - } - } - desiredComposed["deployment"] = deployment - - // Create Service if enabled - if params.Service != nil && params.Service.Enabled != nil && *params.Service.Enabled { - service := &corev1.Service{ - APIVersion: ptr.To(corev1.ServiceAPIVersionV1), - Kind: ptr.To(corev1.ServiceKindService), - Metadata: &coremetav1.ObjectMeta{ - Name: xr.Metadata.Name, - Namespace: xr.Metadata.Namespace, - }, - Spec: &corev1.ServiceSpec{ - Selector: &map[string]string{ - "app": *xr.Metadata.Name, - }, - Ports: &[]corev1.ServicePort{{ - Name: ptr.To("http"), - Protocol: ptr.To("TCP"), - Port: ptr.To(int32(80)), - TargetPort: ptr.To("http"), - }}, - }, - // ToDo(haarchri): remove this - Status: &corev1.ServiceStatus{ - LoadBalancer: &corev1.LoadBalancerStatus{}, - }, - } - - // Check if service is ready - observedService, ok := observedComposed["service"] - if ok && observedService.Resource != nil { - var obsService corev1.Service - if err := convertViaJSON(&obsService, observedService.Resource); err == nil { - if obsService.Spec != nil && obsService.Spec.ClusterIP != nil && *obsService.Spec.ClusterIP != "" { - if service.Metadata.Annotations == nil { - service.Metadata.Annotations = &map[string]string{} - } - (*service.Metadata.Annotations)["go.upbound.io/ready"] = "True" - } - } - } - desiredComposed["service"] = service - } - - // Create Ingress if enabled - if params.Ingress != nil && params.Ingress.Enabled != nil && *params.Ingress.Enabled { - ingress := &networkingv1.Ingress{ - APIVersion: ptr.To(networkingv1.IngressAPIVersionNetworkingK8SIoV1), - Kind: ptr.To(networkingv1.IngressKindIngress), - Metadata: &coremetav1.ObjectMeta{ - Name: xr.Metadata.Name, - Namespace: xr.Metadata.Namespace, - Annotations: &map[string]string{ - "kubernetes.io/ingress.class": "alb", - "alb.ingress.kubernetes.io/scheme": "internet-facing", - "alb.ingress.kubernetes.io/target-type": "ip", - "alb.ingress.kubernetes.io/healthcheck-path": "/health", - "alb.ingress.kubernetes.io/listen-ports": `[{"HTTP": 80}]`, - "alb.ingress.kubernetes.io/target-group-attributes": "stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60", - }, - }, - Spec: &networkingv1.IngressSpec{ - Rules: &[]networkingv1.IngressRule{{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: &[]networkingv1.HTTPIngressPath{{ - Path: ptr.To("/"), - PathType: ptr.To("Prefix"), - Backend: &networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: xr.Metadata.Name, - Port: &networkingv1.ServiceBackendPort{ - Number: ptr.To(int32(80)), - }, - }, - }, - }}, - }, - }}, - }, - } - - // Check if ingress is ready - observedIngress, ok := observedComposed["ingress"] - if ok && observedIngress.Resource != nil { - var obsIngress networkingv1.Ingress - if err := convertViaJSON(&obsIngress, observedIngress.Resource); err == nil { - if obsIngress.Status != nil && obsIngress.Status.LoadBalancer != nil && - obsIngress.Status.LoadBalancer.Ingress != nil && len(*obsIngress.Status.LoadBalancer.Ingress) > 0 { - firstIngress := (*obsIngress.Status.LoadBalancer.Ingress)[0] - if firstIngress.Hostname != nil && *firstIngress.Hostname != "" { - if ingress.Metadata.Annotations == nil { - ingress.Metadata.Annotations = &map[string]string{} - } - (*ingress.Metadata.Annotations)["go.upbound.io/ready"] = "True" - } - } - } - } - desiredComposed["ingress"] = ingress - } - - // Update XR status - desiredXR, err := request.GetDesiredCompositeResource(req) - if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite resource")) - return rsp, nil - } - - // Convert desired XR to WebApp - var desiredWebApp v1alpha1.WebApp - desiredWebApp.APIVersion = ptr.To(v1alpha1.WebAppAPIVersionplatformExampleComV1Alpha1) - desiredWebApp.Kind = ptr.To(v1alpha1.WebAppKindWebApp) - if err := convertViaJSON(&desiredWebApp, desiredXR.Resource); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot convert desired xr")) - return rsp, nil - } - - // Update status fields - if desiredWebApp.Status == nil { - desiredWebApp.Status = &v1alpha1.WebAppStatus{} - } - - // Set deployment conditions - if observedDeployment, ok := observedComposed["deployment"]; ok && observedDeployment.Resource != nil { - var obsDeployment appsv1.Deployment - if err := convertViaJSON(&obsDeployment, observedDeployment.Resource); err == nil { - if obsDeployment.Status != nil { - if obsDeployment.Status.Conditions != nil { - deploymentConditions := []v1alpha1.WebAppStatusDeploymentConditionsItem{} - for _, c := range *obsDeployment.Status.Conditions { - condition := v1alpha1.WebAppStatusDeploymentConditionsItem{ - Type: c.Type, - Status: c.Status, - Message: c.Message, - Reason: c.Reason, - } - if c.LastUpdateTime != nil { - condition.LastUpdateTime = ptr.To(c.LastUpdateTime.String()) - } - if c.LastTransitionTime != nil { - condition.LastTransitionTime = ptr.To(c.LastTransitionTime.String()) - } - deploymentConditions = append(deploymentConditions, condition) - } - desiredWebApp.Status.DeploymentConditions = &deploymentConditions - } else { - // Set empty conditions if no conditions exist - deploymentConditions := []v1alpha1.WebAppStatusDeploymentConditionsItem{} - desiredWebApp.Status.DeploymentConditions = &deploymentConditions - } - if obsDeployment.Status.AvailableReplicas != nil { - desiredWebApp.Status.AvailableReplicas = ptr.To(int(*obsDeployment.Status.AvailableReplicas)) - } else { - // Set default value when no available replicas - desiredWebApp.Status.AvailableReplicas = ptr.To(0) - } - } else { - // Set defaults when status is nil - deploymentConditions := []v1alpha1.WebAppStatusDeploymentConditionsItem{} - desiredWebApp.Status.DeploymentConditions = &deploymentConditions - desiredWebApp.Status.AvailableReplicas = ptr.To(0) - } - } - } else { - // Set defaults when deployment doesn't exist - deploymentConditions := []v1alpha1.WebAppStatusDeploymentConditionsItem{} - desiredWebApp.Status.DeploymentConditions = &deploymentConditions - desiredWebApp.Status.AvailableReplicas = ptr.To(0) - } - - // Set ingress URL - if observedIngress, ok := observedComposed["ingress"]; ok && observedIngress.Resource != nil { - var obsIngress networkingv1.Ingress - if err := convertViaJSON(&obsIngress, observedIngress.Resource); err == nil { - if obsIngress.Status != nil && obsIngress.Status.LoadBalancer != nil && - obsIngress.Status.LoadBalancer.Ingress != nil && len(*obsIngress.Status.LoadBalancer.Ingress) > 0 { - firstIngress := (*obsIngress.Status.LoadBalancer.Ingress)[0] - if firstIngress.Hostname != nil { - desiredWebApp.Status.URL = firstIngress.Hostname - } else { - // Set empty string when hostname is nil - desiredWebApp.Status.URL = ptr.To("") - } - } else { - // Set empty string when no load balancer ingress - desiredWebApp.Status.URL = ptr.To("") - } - } else { - // Set empty string when conversion fails - desiredWebApp.Status.URL = ptr.To("") - } - } else { - // Set empty string when ingress doesn't exist - desiredWebApp.Status.URL = ptr.To("") - } - - // Convert back to unstructured - if err := convertViaJSON(desiredXR.Resource, &desiredWebApp); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot convert desired webapp back to unstructured")) - return rsp, nil - } - - if err := response.SetDesiredCompositeResource(rsp, desiredXR); err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot set desired composite resource")) - return rsp, nil - } - - return rsp, nil -} - -func convertViaJSON(to, from any) error { - bs, err := json.Marshal(from) - if err != nil { - return err - } - return json.Unmarshal(bs, to) -} - -``` - - - -```yaml title="getting-started/functions/compose-resources/main.k" - -import models.io.k8s.api.apps.v1 as appsv1 -import models.io.k8s.api.core.v1 as corev1 -import models.io.k8s.api.networking.v1 as networkingv1 -import models.com.example.platform.v1alpha1 as platformv1alpha1 - -oxr = platformv1alpha1.WebApp{**option("params").oxr} # observed claim -_ocds = option("params").ocds # observed composed resources -_dxr = option("params").dxr # desired composite resource -dcds = option("params").dcds # desired composed resources - -_metadata = lambda name: str -> any { - { annotations = { "krm.kcl.dev/composition-resource-name" = name }} -} - -_desired_deployment = appsv1.Deployment{ - metadata: _metadata("deployment") | { - name: oxr.metadata.name - namespace: oxr.metadata.namespace - labels: { - "app.kubernetes.io/name": oxr.metadata.name - } - } - spec: { - replicas: oxr.spec.parameters.replicas - selector: { - matchLabels: { - "app.kubernetes.io/name": oxr.metadata.name - app: oxr.metadata.name - } - } - template: { - metadata: { - labels: { - "app.kubernetes.io/name": oxr.metadata.name - app: oxr.metadata.name - } - } - spec: { - serviceAccountName: oxr.spec.parameters.serviceAccount - containers: [{ - name: oxr.metadata.name - image: oxr.spec.parameters.image - imagePullPolicy: "Always" - ports: [{ - name: "http" - containerPort: int(oxr.spec.parameters.port) - protocol: "TCP" - }] - resources: { - requests: { - memory: "64Mi" - cpu: "250m" - } - limits: { - memory: "1Gi" - cpu: "1" - } - } - }] - restartPolicy: "Always" - } - } - } -} - -observed_deployment = option("params").ocds["deployment"]?.Resource -if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - -if oxr.spec.parameters.service.enabled: - _desired_service = corev1.Service{ - metadata: _metadata("service") | { - name: oxr.metadata.name - namespace: oxr.metadata.namespace - } - spec: { - selector: { - app: oxr.metadata.name - } - ports: [{ - name: "http" - protocol: "TCP" - port: 80 - targetPort: "http" - }] - } - } - -observed_service = option("params").ocds["service"]?.Resource -if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - -if oxr.spec.parameters.ingress.enabled: - _desired_ingress = networkingv1.Ingress{ - metadata: _metadata("ingress") | { - name: oxr.metadata.name - namespace: oxr.metadata.namespace - annotations: { - "kubernetes.io/ingress.class": "alb" - "alb.ingress.kubernetes.io/scheme": "internet-facing" - "alb.ingress.kubernetes.io/target-type": "ip" - "alb.ingress.kubernetes.io/healthcheck-path": "/health" - "alb.ingress.kubernetes.io/listen-ports": '[{"HTTP": 80}]' - "alb.ingress.kubernetes.io/target-group-attributes": "stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60" - } - } - spec: { - rules: [{ - http: { - paths: [{ - path: "/" - pathType: "Prefix" - backend: { - service: { - name: oxr.metadata.name - port: { - number: 80 - } - } - } - }] - } - }] - } - } - - -observed_ingress = option("params").ocds["ingress"]?.Resource -if observed_ingress?.status?.loadBalancer?.ingress?[0]?.hostname: - _desired_ingress.metadata.annotations["krm.kcl.dev/ready"] = "True" - -_desired_xr = { - **option("params").dxr - status.deploymentConditions = observed_deployment?.status?.conditions or [] - status.availableReplicas = observed_deployment?.status?.availableReplicas or 0 - status.url = observed_ingress?.status?.loadBalancer?.ingress?[0]?.hostname or "" -} - - -items = [ - _desired_deployment, - _desired_service, - _desired_ingress, - _desired_xr -] -``` - - - - -Deploy the changes you made to your control plane: - -```shell -up project run --local --ingress -``` - -:::tip - -The `project run` command builds and deploys any changes. If you don't have a -control plane running yet, it creates one, otherwise it targets your existing -control plane. - -::: - -## Use the custom resource - -Your control plane now understands _WebApp_ resources. Create a _WebApp_: - -```shell -kubectl apply -f examples/webapp/webapp.yaml -``` - - -Check that the _WebApp_ is ready: - - - -:::note -Resource and deployment names are auto-generated and will differ from the example outputs below. -::: - - - -```shell {copy-lines="1"} -kubectl get -f examples/webapp/webapp.yaml -NAME SYNCED READY COMPOSITION AGE -webapp True True app-yaml 56s -``` - -Observe the _Deployment_ and _Service_ Crossplane created when you created the -_WebApp_: - - -```shell {copy-lines="1"} -kubectl get deploy,service -l crossplane.io/composite=webapp -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/webapp-2r2rk 2/2 2 2 11m - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/webapp-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m -``` - -## Next steps - -Now that you know the basics of building with Upbound, create an AI-augmented -operation to detect and remediate Kubernetes app workload errors. Read [Create an AI-augmented operation][integrate-ai]. - -[register]: https://accounts.upbound.io/register -[up-cli]: /manuals/cli/overview -[marketplace]: https://marketplace.upbound.io -[functions]: /manuals/uxp/concepts/composition/composite-resource-definitions -[providers]: https://upbound.io -[xrd]: /manuals/uxp/concepts/composition/composite-resource-definitions -[composition]: /manuals/uxp/concepts/composition/overview -[functions]: /manuals/uxp/concepts/composition/composite-resource-definitions -[webUI]: /img/uxp-webui.png -[integrate-ai]: /getstarted/introduction/integrate-ai diff --git a/docs/getstarted/introduction/whats-next.md b/docs/getstarted/introduction/whats-next.md deleted file mode 100644 index 3316e3355..000000000 --- a/docs/getstarted/introduction/whats-next.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: What's Next -sidebar_position: 4 ---- - - -Congratulations! You created your first control plane, defined -custom resource types, integrated AI-powered operations, and packaged everything -as a distributable Configuration. - - -Your local control plane now automatically provisions Kubernetes deployments -through your custom `App` resource, detects pod issues using AI analysis, and -suggests intelligent remediation steps. You built the foundation of a modern -infrastructure platform. - -## Keep building - -Gain more knowledge on with hands-on workshops and tutorials: - -**Keep learning with the Builder's Workshop**: [Build custom -composition functions][workshop], handle complex resource relationships, and master error -handling patterns. - -**Build intelligent control planes**: Learn to create [AI-augmented control planes][intelligent-control-planes] that adapt and self-heal. - - -**Create an internal developer platform**: Follow our [Internal Developer -Platform guide][idp-guide] to build comprehensive developer experiences. - -Dive deeper into these core concepts: - -- [Compositions][compositions]: Learn how resource templating and logic work -- [Project structure][project-structure]: Organize and manage your control plane code -- [Control plane architecture][control-planes]: Understand the deployment architecture - - -## Deploy to production - -When you're ready to share your control plane with your team: - -**Upbound Spaces**: [Managed hosting environment][spaces] that -handles operations for you. Choose [Cloud Spaces][control-planes] for true SaaS or Self-hosted -Spaces for your infrastructure. - -**Self-managed UXP**: [Deploy into your own Kubernetes -cluster][uxp] with full control. - - -## More resources - -- Join our [Slack community][slack] for support, best practices, and to share what you build. -- Follow our [blog][blog] for talks, workshops, and more guides. - - -## Next steps - -Explore additional resources and deepen your knowledge of Upbound Crossplane. - - -[workshop]: /getstarted/builders-workshop/project-foundations/ -[intelligent-control-planes]: /guides/intelligent-control-planes/ -[idp-guide]: /guides/solutions/get-started/ -[spaces]: /self-hosted-spaces/overview/ -[control-planes]: /self-hosted-spaces/concepts/control-planes/ -[uxp]: /manuals/uxp/overview/ -[compositions]: /manuals/uxp/concepts/composition/overview/ -[project-structure]: /manuals/cli/howtos/project/ -[slack]: https://slack.crossplane.io/ -[blog]: https://blog.upbound.io/ diff --git a/docs/getstarted/new-users/_category_.json b/docs/getstarted/new-users/_category_.json new file mode 100644 index 000000000..ada306956 --- /dev/null +++ b/docs/getstarted/new-users/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Begin the Upbound Journey", + "position": 2 +} diff --git a/docs/getstarted/new-users/controlplanes-quickstart.md b/docs/getstarted/new-users/controlplanes-quickstart.md new file mode 100644 index 000000000..e02016d7b --- /dev/null +++ b/docs/getstarted/new-users/controlplanes-quickstart.md @@ -0,0 +1,65 @@ +--- +title: Quickstart +pagination_prev: null +pagination_next: null +--- +TEMP + +**Prerequisites:** kind · `kubectl` · Helm · Upbound account · [up CLI][upCli]. + +## Step 1: Log in to Upbound + +- `up login` (community UXP requires login). Browser auth, then close. + +## Step 2: Install Insights (demo) + +1. Create the KIND cluster with the gateway port map (`30443` to host `8443`), + `kind create cluster --config kind-demo.yaml`. +2. `helm install hub --namespace hub --create-namespace --set global.demo.enabled=true`. +3. `kubectl -n hub wait --for=condition=ready pod --all --timeout=5m`. +4. Open the Console at `{console-demo-url}`, log in `admin`/`admin`. + +- Placeholders: ``, `{console-demo-url}` (from release notes). +- Dedup note: this demo block should become a shared partial once the Insights + section's demo page exists (currently inlined, single consumer). + +## Step 3: Initialize a project + +- `up project init -t project-template-k8s-webapp -l python my-webapp`. + +## Step 4: Run it locally + +- `up project run --local --ingress`. Builds functions and config, creates a + local dev control plane, and prints its context and Console endpoint. + +## Step 5: Deploy an example resource + +- `cd my-webapp/examples/webapps && kubectl apply -f example.yaml`. + +## Step 6: See it in the Console + +TODO: open question. `up project run --local` creates its own dev control plane, +separate from the demo's bundled one. So the Step 5 resources won't appear in the +demo Console until you connect that dev control plane to Insights. Confirm the +intended path with engineering (auto-register on `up project run`, or manual +[connect][connect]) before writing this step. Don't publish until you verify it. + +## Step 7: Change and reconcile + +- Edit the example (`nginx` to `httpd`), reapply, `kubectl get managed -w`, and + watch the Console update. + +## Step 8: Clean up + +- Run `kubectl delete -f example.yaml`, then `kind delete cluster --name + up-my-webapp` and `kind delete cluster --name hub-demo`. + +## Next steps + +- [Builders workshop][workshop] for real cloud resources. +- [Insights overview][insights] for the full-fleet story. + +[upCli]: /manuals/cli/overview +[connect]: /manuals/platform/insights/connections +[insights]: /manuals/platform/insights/overview +[workshop]: /getstarted/builders-workshop/project-foundations diff --git a/docs/getstarted/new-users/new-to-control-planes.md b/docs/getstarted/new-users/new-to-control-planes.md new file mode 100644 index 000000000..fe1458d76 --- /dev/null +++ b/docs/getstarted/new-users/new-to-control-planes.md @@ -0,0 +1,78 @@ +--- +title: What is a control plane? +sidebar_position: 2 +pagination_prev: null +pagination_next: null +--- +import CardGrid from '@site/src/components/CardGrid'; + +If you're new to Crossplane and Upbound, you should start by getting familiar +with _control planes_. A control plane in Kubernetes is the layer that makes +scheduling and orchestration decisions for a cluster. In this guide, you'll +learn about how Crossplane has extended the control plane model outside the +cluster. + +## Kubernetes control planes - for cluster control + +A Kubernetes control plane is the central management layer for your workloads +and the state of your cluster. + +Every component in the cluster communicates with the control plane to read and +write state. + +It exposes the Kubernetes API, stores cluster data, schedules pods, and runs the +control loops that keep everything in sync. + + +## Crossplane control planes - for infrastructure control + +Crossplane takes this same idea and applies it to the resources that live +outside the cluster. A Crossplane control plane becomes the central management +layer for your databases, IAM policies, compute resources, and more. If it has +an API, you can manage it in Crossplane with a provider which is a package that +enables Crossplane to provision infrastructure on an external service. + +Crossplane doesn't simply create the infrastructure - like the Kubernetes +control plane, it manages everything in a reconciliation loop. Meaning it checks +the state of its resources through its Provider - like provider-aws - which +calls the underlying services API. If the resources doesn't match the manifest, +Crossplane takes action to fix it. + +Those reconciliation loops give resources managed by Crossplane the same +resilience Kubernetes gives your workloads. If a pod fails, the control plane +notices the drift and creates a replacement. If someone manually updates a +security group in a cloud console, Crossplane notices the drift and works to +return the resource to your desired state. + +## Upbound Crossplane and Spaces - for scale and beyond + +Running one Crossplane control plane is powerful. + +Running dozens or hundreds of them, across teams, environments, and clouds, can +become difficult to manage. That's why we created a downstream distribution of +Crossplane called Upbound Crossplane (UXP). UXP gives you some operational +features Crossplane doesn't out of the box like a secrets proxy, backup and +restore, and control plane insights. + +We also offer Spaces for enterprise and business critical operations. A Space is +a hosting environment for control-planes-as-a-service. Instead of standing up a +new cluster for every control plane you need, a Space lets you run many isolated +control planes on shared infrastructure, in Upbound's cloud or self-hosted . + +The goal of UXP and Spaces is to give you the features you need to run control +planes at scale. + +In next guide, you'll install UXP on a local kind cluster and use it to deploy +an image to see the control plane workflow. You'll also see the single pane of +glass Upbound gives you to monitor your control plane. + + + + + diff --git a/docs/getstarted/overview.md b/docs/getstarted/overview.md index cd8f2b024..0b3152e38 100644 --- a/docs/getstarted/overview.md +++ b/docs/getstarted/overview.md @@ -1,9 +1,14 @@ --- -title: Get Upbound +title: Welcome sidebar_position: 1 -slug: "/getstarted" +slug: "/" +pagination_prev: null +pagination_next: null --- -import { GetStarted } from '@site/src/components/GetStartedCallout'; +import CardGrid from '@site/src/components/CardGrid'; +import GetUpboundHero from '@site/src/components/GetUpboundHero'; + + Welcome to Upbound, the enterprise platform for Crossplane that helps you build @@ -21,55 +26,31 @@ reliability, performance, and developer experience. -## Install Upbound Crossplane +## Download the CLI -Install Upbound Crossplane (UXP) into a fresh Kubernetes cluster with Helm or -the `up` CLI. - -:::important -Already running open source Crossplane? Don't run the commands below — follow -the [Upgrade Guide][upgrade] instead. -::: - - - - - -```shell -helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update -helm install crossplane --namespace crossplane-system --create-namespace upbound-stable/crossplane --devel -``` - - - - - - -Download the `up` CLI and install UXP into the cluster pointed to by your -current kubeconfig context. - -**Download the CLI:** +Install the [up][up] CLI to get access to all of Upbound's tooling on your machine. ```shell curl -sL "https://cli.upbound.io" | sh ``` -**Install Upbound Crossplane:** - -```shell -up uxp install -``` - - - - - +Find more installation methods on the [Up CLI installation guide][up]. - -## New to Crossplane and Upbound? - - +## Choose your path + + ## What is Upbound? @@ -93,17 +74,5 @@ define in your custom APIs. You define your resources and Upbound parses, connects with the service, and manages the lifecycle on your behalf. -## Next steps - - -* Follow the [introduction][intro] guide to get started building your own control plane. -* For OSS Crossplane users, follow the [Upgrade][upgrade] guide. - - -[upgrade]: /getstarted/upgrade-to-upbound/upgrade-to-uxp -[guides]: /guides -[register]: https://www.upbound.io/register/?utm_source=docs&utm_medium=cta&utm_campaign=docs_get_started [up]: /manuals/cli/overview -[pricing]: https://upbound.io/pricing -[intro]: /getstarted/introduction/project diff --git a/docs/getstarted/upgrade-to-upbound/_category_.json b/docs/getstarted/upgrade-to-upbound/_category_.json index e937463d6..64eb3a109 100644 --- a/docs/getstarted/upgrade-to-upbound/_category_.json +++ b/docs/getstarted/upgrade-to-upbound/_category_.json @@ -1,4 +1,4 @@ { "label": "Upgrade to Upbound", - "position": 10 + "position": 3 } diff --git a/docs/getstarted/upgrade-to-upbound/upgrade-quickstart.md b/docs/getstarted/upgrade-to-upbound/upgrade-quickstart.md new file mode 100644 index 000000000..fbb5659b9 --- /dev/null +++ b/docs/getstarted/upgrade-to-upbound/upgrade-quickstart.md @@ -0,0 +1,99 @@ +--- +title: Migration quickstart +sidebar_position: 2 +pagination_prev: null +pagination_next: null +--- + +import CardGrid from '@site/src/components/CardGrid'; + + +This guide is for existing Crossplane users to see how to upgrade to UXP and see +a subset of some of the added functionality. + +In this guide, you'll create a kind cluster, install Crossplane `${version}`, and +deploy a single composite resource. Next, you'll begin the upgrade process to +UXP on the cluster and watch as your composite resource is migrated to the new +control plane. + +## Prerequisites + +- kind installed (for the disposable "before" cluster) +- kubectl +- An Upbound account +- The up CLI installed + +## Step 1: Log in to Upbound + +- Same as new-user quickstart — `up login` +- [Reuse verbatim; no reason for this to diverge] + +## Step 2: Stand up a throwaway Crossplane control plane + +- Minimal vanilla install — plain XRD + Composition, no Projects + tooling, no `up project` anything +- Deploy one small composite resource so there's something real to + migrate (not zero resources, not a whole platform) +- [Decide: script this with a one-shot install command, or walk + through it step by step? Probably the former — this cluster is + scaffolding, not the lesson] + +## Step 3: Export your control plane's state + +- `up controlplane migration export --kubeconfig --output ` +- **Check-in:** confirm the `--kubeconfig` path points at the throwaway + cluster, not whatever's currently active — this is the first of the + three risk points we flagged +- Show expected export output (types found, resources exported, + archive written) + +## Step 4: Create your Upbound control plane + +- `up controlplane create ` +- `up ctx "///"` +- **Check-in:** this command silently repoints your active context — + confirm you're now targeting the new control plane before continuing + (second risk point) + +## Step 5: Import the archive + +- `up controlplane migration import --input ` +- Note: resources land paused by default — nothing reconciles yet +- Show expected import output + +## Step 6: Review before activating + +- Spot-check the imported resources/claims look right +- **Check-in:** confirm you're pointed at the *new* control plane, not + the old source cluster, before running the unpause command — this is + the highest-stakes moment in the guide (third risk point); getting + this wrong means two control planes reconciling the same external + resources at once + +## Step 7: Activate + +- `kubectl annotate managed --all crossplane.io/paused-` +- Confirm resources move to a synced/ready state + +## Step 8: See it in the console + +- Same beat as the new-user quickstart's "see it in the console" step — + same destination, different path to get there +- [Once Hub is wired up, this becomes the same link/step as the + new-user guide's equivalent moment] + +## Step 9: Clean up the throwaway cluster + +- Delete the original OSS Crossplane cluster now that migration is + confirmed working +- [Decide: is this "delete it" or "you can decommission this whenever + you're satisfied" — tone matters here since a real reader's actual + source cluster isn't disposable the way the tutorial one is] + +## Next steps + +- Link to the real migration path for their production cluster + (this quickstart proved the mechanics; production has real stakes) +- [Once the Projects-coupling and v1→v2 questions from earlier resolve, + this is also where that guide would get linked, if it still exists + in this section] diff --git a/docs/getstarted/upgrade-to-upbound/upgrading-to-upbound.md b/docs/getstarted/upgrade-to-upbound/upgrading-to-upbound.md new file mode 100644 index 000000000..1530ace1f --- /dev/null +++ b/docs/getstarted/upgrade-to-upbound/upgrading-to-upbound.md @@ -0,0 +1,44 @@ +--- +title: Already using Crossplane? +sidebar_position: 1 +pagination_prev: null +pagination_next: null +--- + +import CardGrid from '@site/src/components/CardGrid'; + +If you're already using Crossplane in your organization, you understand how +control planes manage your resources. This guide shows you what Upbound +Crossplane and Spaces add on top of that and helps you determine an upgrade +path for your existing control planes. + +## What does upgrading to Upbound actually get you? + +UXP for observability, dev tooling, IDE integration, etc. + +Spaces for scaling, support, hosting, group management. + + +## Are you on Crossplane v1 or v2? + +- Still on v1? → start with **Migrate Crossplane v1 Configurations to v2** +- Already on v2.0.1+? → skip ahead + +## Do you want Upbound's distribution of Crossplane, or Upbound's hosting? + + +- **Upgrade to Upbound Crossplane (UXP)** — swap your OSS Crossplane + install for Upbound's distribution: same control plane, added + stability, package management, and observability. +- **Upgrade to Control Plane Projects in UXP** —Manage your compositions like as + software project (IDE tooling, linting, embedded functions) instead of raw YAML. +- **Upgrade to Spaces** — stop hosting the cluster yourself. Export + your control plane's state and import it into a managed Space, + either in Upbound's cloud or self-hosted. + +## Which guide should you start with? + +- First, try upgrading an example from Crossplane to UXP. +- Want project-based tooling for your compositions → Upgrade to Projects +- Running Crossplane v1 → Migrate to v2 first +- Want to scale your control plane fleet? → Upgrade to Spaces diff --git a/docs/getstarted/platform-tutorial.md b/docs/guides/platform-tutorial.md similarity index 99% rename from docs/getstarted/platform-tutorial.md rename to docs/guides/platform-tutorial.md index 74d0957ce..6bd54b02f 100644 --- a/docs/getstarted/platform-tutorial.md +++ b/docs/guides/platform-tutorial.md @@ -1,5 +1,5 @@ --- -title: Build your first platform with Upbound Crossplane +title: Build your first platform with UXP description: Deploy a real app with a cloud database, observe drift detection, enforce policies, and change infrastructure live, all from a single control plane. weight: 5 validation: diff --git a/docs/guides/solutions/design.md b/docs/guides/solutions/design.md index 539f034e6..afdcb7b62 100644 --- a/docs/guides/solutions/design.md +++ b/docs/guides/solutions/design.md @@ -62,6 +62,5 @@ provide. Pick one or two offerings, or use any of the available [Configurations [configurations]: https://marketplace.upbound.io/configurations -[bootstrap]: bootstrap.md [platformParts]: /img/solutions/platform-on-upbound.png diff --git a/docs/guides/usecases/cloud-resources-qs.md b/docs/guides/usecases/cloud-resources-qs.md index f819715fa..cc52c4324 100644 --- a/docs/guides/usecases/cloud-resources-qs.md +++ b/docs/guides/usecases/cloud-resources-qs.md @@ -51,17 +51,17 @@ up project init --template="project-template-gcp-storage" --language="python" my ## Understand the project -The project defines a resource type called `XStorageBucket`, which implements an +The project defines a resource type called `StorageBucket`, which implements an opinionated storage bucket abstraction. The type is defined as a Crossplane Composite Resource Definition (XRD) in the file -`apis/xstoragebuckets/definition.yaml`. +`apis/storagebuckets/definition.yaml`. -A Crossplane Composition defines the function pipeline that will run whenever an -`XStorageBucket` is created or updated. The Composition for the `XStorageBucket` -type is in `apis/xstoragebuckets/composition.yaml` and contains two functions: a +A Crossplane Composition defines the function pipeline that will run whenever a +`StorageBucket` is created or updated. The Composition for the `StorageBucket` +type is in `apis/storagebuckets/composition.yaml` and contains two functions: a project-specific one that creates resources, and a generic one that detects when the created resources become ready. The function that creates resources is implemented in the file `functions/compose-bucket/main.py`. @@ -332,7 +332,7 @@ The `examples/` directory in the project contains example resource manifests that you can deploy to test your project. Deploy an example: ```shell -kubectl apply -f examples/xstoragebuckets/example.yaml +kubectl apply -f examples/storagebucket/example.yaml ``` Navigate to the "Composite Resources" tab in the Web UI and click "Relationship @@ -340,18 +340,18 @@ View" to explore the cloud resources Crossplane creates. ## Update the custom resource type -You can add more fields to the `XStorageBucket` type to customize its +You can add more fields to the `StorageBucket` type to customize its behavior. For example, you may want create a README file in every bucket with user-specified contents. -Open the example resource `examples/xstoragebuckets/example.yaml` in your IDE of +Open the example resource `examples/storagebucket/example.yaml` in your IDE of choice and add a new field: -```yaml title="examples/xstoragebuckets/example.yaml" +```yaml title="examples/storagebucket/example.yaml" apiVersion: platform.example.com/v1alpha1 -kind: XStorageBucket +kind: StorageBucket metadata: name: example spec: @@ -363,9 +363,9 @@ spec: ``` -```yaml title="examples/xstoragebuckets/example.yaml" +```yaml title="examples/storagebucket/example.yaml" apiVersion: platform.example.com/v1alpha1 -kind: XStorageBucket +kind: StorageBucket metadata: name: example spec: @@ -377,9 +377,9 @@ spec: ``` -```yaml title="examples/xstoragebuckets/example.yaml" +```yaml title="examples/storagebucket/example.yaml" apiVersion: platform.example.com/v1alpha1 -kind: XStorageBucket +kind: StorageBucket metadata: name: example spec: @@ -398,17 +398,17 @@ prompted, confirm the overwrite: ```shell -up xrd generate examples/xstoragebuckets/example.yaml --path xstoragebucket/definition.yaml +up xrd generate examples/storagebucket/example.yaml --path apis/storagebuckets/definition.yaml ``` ```shell -up xrd generate examples/xstoragebuckets/example.yaml +up xrd generate examples/storagebucket/example.yaml ``` ```shell -up xrd generate examples/xstoragebuckets/example.yaml +up xrd generate examples/storagebucket/example.yaml ``` @@ -424,7 +424,7 @@ from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 from .model.io.upbound.aws.s3.bucketacl import v1beta1 as aclv1beta1 from .model.io.upbound.aws.s3.bucketownershipcontrols import v1beta1 as bocv1beta1 @@ -437,7 +437,7 @@ from .model.io.upbound.aws.s3.bucketserversideencryptionconfiguration import ( def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) params = observed_xr.spec.parameters desired_bucket = bucketv1beta1.Bucket( @@ -573,11 +573,11 @@ from .model.io.upbound.azure.resourcegroup import v1beta1 as rgv1beta1 from .model.io.upbound.azure.storage.account import v1beta1 as acctv1beta1 from .model.io.upbound.azure.storage.container import v1beta1 as contv1beta1 from .model.io.upbound.azure.storage.blob import v1beta1 as blobv1beta1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) params = observed_xr.spec.parameters # Create the resource group @@ -671,11 +671,11 @@ from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.upbound.gcp.storage.bucket import v1beta1 as bucketv1beta1 from .model.io.upbound.gcp.storage.bucketacl import v1beta1 as aclv1beta1 from .model.io.upbound.gcp.storage.bucketobject import v1beta1 as objv1beta1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) params = observed_xr.spec.parameters desired_bucket = bucketv1beta1.Bucket( @@ -746,7 +746,7 @@ up project run --local If you haven't already deployed an example resource, do so now: ```shell -kubectl apply -f examples/xstoragebuckets/example.yaml +kubectl apply -f examples/storagebucket/example.yaml ``` Use the Web UI to observe that Crossplane created the additional readme @@ -758,10 +758,10 @@ kubectl get managed ## Clean up -To avoid leaving cloud resources behind, delete your `XStorageBucket`: +To avoid leaving cloud resources behind, delete your `StorageBucket`: ```shell -kubectl delete -f examples/xstoragebuckets/example.yaml +kubectl delete -f examples/storagebucket/example.yaml ``` @@ -775,7 +775,7 @@ up project stop ## Do more with your control plane -The example above creates storage bucket resources whenever an `XStorageBucket` +The example above creates storage bucket resources whenever a `StorageBucket` gets created. But what if you don't want to create buckets? The Upbound Marketplace is the hub for finding additional packages to extend your control plane, such as Providers, or pre-built Functions. diff --git a/docs/manuals/cli/howtos/compositions/go-template.md b/docs/manuals/cli/howtos/compositions/go-template.md index a9af223c1..e8509a19b 100644 --- a/docs/manuals/cli/howtos/compositions/go-template.md +++ b/docs/manuals/cli/howtos/compositions/go-template.md @@ -300,11 +300,18 @@ To update the composite resource's status, have your templates output a resource --- apiVersion: devexdemo.upbound.io/v1alpha1 -kind: XBucket +kind: Bucket status: someInformation: cool-status ``` +:::tip +The `kind` here must match your XRD's `kind` exactly. This example assumes a +`v2` XRD (`apiextensions.crossplane.io/v2`) with `kind: Bucket`. If your XRD +uses the legacy `v1` API (`apiextensions.crossplane.io/v1`), your XRD's `kind` +uses an `X` prefix, for example `kind: XBucket`, so use that `kind` instead. +::: + To set conditions on the claim and composite, you can add a `ClaimConditions` resource to your templates: ```yaml diff --git a/docs/manuals/cli/howtos/compositions/go.md b/docs/manuals/cli/howtos/compositions/go.md index cdea57ebd..2a3ed1b64 100644 --- a/docs/manuals/cli/howtos/compositions/go.md +++ b/docs/manuals/cli/howtos/compositions/go.md @@ -100,6 +100,14 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) The following example function composes an S3 bucket based on a simplified bucket XRD: +:::tip +The generated model type name matches your XRD's `kind` exactly. This example +assumes a `v2` XRD (`apiextensions.crossplane.io/v2`) with `kind: StorageBucket`. +If your XRD uses the legacy `v1` API (`apiextensions.crossplane.io/v1`), your +XRD's `kind` uses an `X` prefix, for example `kind: XStorageBucket`, so import +`v1alpha1.XStorageBucket` instead. +::: + ```go package main @@ -138,7 +146,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) return rsp, nil } - var xr v1alpha1.XStorageBucket + var xr v1alpha1.StorageBucket if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) return rsp, nil @@ -209,8 +217,8 @@ import ( "dev.upbound.io/models/com/example/platform/v1alpha1" "dev.upbound.io/models/io/upbound/aws/s3/v1beta1" - "github.com/crossplane/crossplane-runtime/pkg/logging" "github.com/crossplane/function-sdk-go/errors" + "github.com/crossplane/function-sdk-go/logging" fnv1 "github.com/crossplane/function-sdk-go/proto/v1" "github.com/crossplane/function-sdk-go/request" "github.com/crossplane/function-sdk-go/resource" @@ -243,7 +251,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) return rsp, nil } - var xr v1alpha1.XStorageBucket + var xr v1alpha1.StorageBucket if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) return rsp, nil @@ -520,7 +528,7 @@ if err != nil { return rsp, nil } -var xr v1alpha1.XStorageBucket +var xr v1alpha1.StorageBucket if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) return rsp, nil diff --git a/docs/manuals/cli/howtos/compositions/python.md b/docs/manuals/cli/howtos/compositions/python.md index 339bf76d7..0b7408ad4 100644 --- a/docs/manuals/cli/howtos/compositions/python.md +++ b/docs/manuals/cli/howtos/compositions/python.md @@ -120,10 +120,19 @@ Add the model to your function: ```python from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 ``` +:::tip +The generated model module and class name match your XRD's `kind` exactly. The +examples on this page assume a `v2` XRD (`apiextensions.crossplane.io/v2`) with +`kind: StorageBucket`. If your XRD uses the legacy `v1` API +(`apiextensions.crossplane.io/v1`), your XRD's `kind` uses an `X` prefix, for +example `kind: XStorageBucket`, so import from `.model...xstoragebucket` and +use `v1alpha1.XStorageBucket` instead. +::: + The imports in this example are specifically for AWS S3 buckets. They follow a similar structure for all resources: @@ -153,7 +162,7 @@ using the resource's status model directly. from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): @@ -185,11 +194,11 @@ from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 -from .model.org.example.xstoragebucket import v1alpha1 +from .model.org.example.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) desired_bucket = bucketv1beta1.Bucket( spec=bucketv1beta1.Spec( @@ -211,11 +220,11 @@ from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 -from .model.org.example.xstoragebucket import v1alpha1 +from .model.org.example.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) region = "us-west-2" if observed_xr.spec.region is not None: @@ -239,11 +248,11 @@ from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 -from .model.org.example.xstoragebucket import v1alpha1 +from .model.org.example.storagebucket import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) desired_bucket = bucketv1beta1.Bucket( from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 @@ -268,13 +277,13 @@ Next, add the function logic. The example below creates an S3 bucket: from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): # Load the observed XR into a Pydantic model. - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) # Create the cloud resource specification desired_bucket = bucketv1beta1.Bucket( @@ -297,7 +306,7 @@ In the `RunFunctionRequest`, there are four _inputs_ that Crossplane can parse: ```python # The API request - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) ``` 2. **Desired state**: What resources should exist? @@ -350,7 +359,7 @@ from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 -from .model.com.example.platform.xstoragebucket import v1alpha1 +from .model.com.example.platform.storagebucket import v1alpha1 from .model.io.upbound.aws.s3.bucket import v1beta1 as bucketv1beta1 from .model.io.upbound.aws.s3.bucketacl import v1beta1 as aclv1beta1 from .model.io.upbound.aws.s3.bucketownershipcontrols import v1beta1 as bocv1beta1 @@ -362,7 +371,7 @@ from .model.io.upbound.aws.s3.bucketserversideencryptionconfiguration import ( def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): - observed_xr = v1alpha1.XStorageBucket(**req.observed.composite.resource) + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) params = observed_xr.spec.parameters desired_bucket = bucketv1beta1.Bucket( @@ -517,7 +526,7 @@ the response: from crossplane.function import resource from crossplane.function.proto.v1 import run_function_pb2 as fnv1 -from .model.com.example.platform.xmytype import v1alpha1 +from .model.com.example.platform.mytype import v1alpha1 def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): diff --git a/docs/manuals/cli/howtos/testing.md b/docs/manuals/cli/howtos/testing.md index c096a1dd5..7152a165a 100644 --- a/docs/manuals/cli/howtos/testing.md +++ b/docs/manuals/cli/howtos/testing.md @@ -90,7 +90,7 @@ You can write tests in KCL, Python, or Go. For example, to generate a composition test: - + ```shell {copy-lines="all"} up test generate my-test --language=go @@ -123,7 +123,7 @@ exercise the Crossplane composition controller, which handles reconciliation and external resource management. - + ```go @@ -286,7 +286,7 @@ You can write tests in KCL, Python, or Go. For example, to generate an end-to-end test: - + ```shell {copy-lines="all"} up test generate my-e2e-test --e2e --language=go @@ -314,7 +314,7 @@ up test generate my-e2e-test --e2e --language=kcl End-to-end tests use the `E2ETest` API, written in KCL, Python, or Go. - + diff --git a/docs/manuals/console/_category_.json b/docs/manuals/console/_category_.json deleted file mode 100644 index 3f524757e..000000000 --- a/docs/manuals/console/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Console", - "position": 4, - "collapsed": true -} diff --git a/docs/manuals/console/self-service.md b/docs/manuals/console/self-service.md deleted file mode 100644 index 2f7b09ff0..000000000 --- a/docs/manuals/console/self-service.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Crossplane WebUI -sidebar_position: 2 -description: The Crossplane WebUI ---- - -The Crossplane Web UI delivers a local, read-only dashboard for inspecting and -understanding your UXP-based control plane behavior directly from within your -cluster. - -This dashboard provides visibility into key Crossplane resources, pipelines, and -controller behavior. This visibility helps developers and platform operators -reason about control plane activity without requiring access to cloud-hosted -systems. - -:::important -This local Web UI runs inside the same cluster as your UXP installation. -For multi-cluster or enterprise-level management, see the [Upbound -Console][console]. -::: - -## Key features - -- Local, cluster-scoped Web UI that maps 1:1 with a UXP instance -- Introspects Crossplane v2 control planes and AI-powered pipelines -- Shows real-time structure of Operations, Agents, and Function Pipelines -- Works with up project run and up project test workflows -- Displays composed resources, function server packages, and more - -## Installation - -The Crossplane WebUI comes pre-installed when you install your UXP 2.0 instance using a helm chart. - -## Version compatibility - -Each Web UI version explicitly ties to a corresponding UXP version. -To ensure feature parity across UXP and the dashboard `upbound/crossplane-web-ui:v1.0.x` works with `upbound/crossplane:v1.0.x`. - -## CLI integration - -You can launch or manage the Web UI directly from the Upbound CLI: - -```shell -up uxp web-ui open -``` - -To stop the dashboard: - -```shell -up uxp web-ui disable -``` - -Re-enable the dashboard: - -```shell -up uxp web-ui enable -``` - -When using up project run or up project test (for local ephemeral development -clusters), the Web UI automatically configures into the local cluster. - -## Dashboard views - -The Crossplane Web UI provides a read-only visualization layer like the -Control Plane Explorer in the Upbound Cloud Console, with enhancements for XPv2: - -You can: -- View installed controller packages and associated CRDs -- See all resources Crossplane composes, including those with OwnerRef metadata -- Visualize Operations and inspect function pipelines: Understand how functions interact with specific XRs (Composite Resources) and MRs (Managed Resources) - -The Crossplane WebUI scopes to UXP clusters only. It doesn't support: -- Spaces -- Teams or Groups -- Authentication or account-based views - -[console]: /manuals/console/upbound-console/ diff --git a/docs/manuals/marketplace/repositories/overview.md b/docs/manuals/marketplace/repositories/overview.md index 8fcb0374d..a987e97f9 100644 --- a/docs/manuals/marketplace/repositories/overview.md +++ b/docs/manuals/marketplace/repositories/overview.md @@ -26,7 +26,7 @@ Upbound repositories support these Crossplane package types: `Configurations`, ` * **`Provider`** packages combine a [Kubernetes controller][kubernetes-controller] container, associated _Custom Resource Definitions_ (`CRDs`) and metadata. The Crossplane open source [AWS provider package][aws-provider-package] is an example a provider's metadata and `CRDs`. -[up-cli]: / +[up-cli]: /manuals/cli/overview [upbound-marketplace]: /manuals/marketplace/overview [store-configurations-in-a-repostiory]: /manuals/marketplace/repositories/store-configurations [kubernetes-controller]: https://kubernetes.io/docs/concepts/architecture/controller/ diff --git a/docs/manuals/platform/_category_.json b/docs/manuals/platform/_category_.json index 79ac690e6..91c72f1f8 100644 --- a/docs/manuals/platform/_category_.json +++ b/docs/manuals/platform/_category_.json @@ -1,7 +1,5 @@ { - "label": "Account & Access", - "position": 8, - "collapsed": true + "label": "Platform", + "position": 2, + "collapsed": true } - - diff --git a/docs/manuals/platform/identity/_category_.json b/docs/manuals/platform/identity/_category_.json new file mode 100644 index 000000000..09c645946 --- /dev/null +++ b/docs/manuals/platform/identity/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Identity & Access", + "position": 3, + "collapsed": true +} diff --git a/docs/manuals/platform/iam.md b/docs/manuals/platform/identity/iam.md similarity index 99% rename from docs/manuals/platform/iam.md rename to docs/manuals/platform/identity/iam.md index c18e177fb..f656db64f 100644 --- a/docs/manuals/platform/iam.md +++ b/docs/manuals/platform/identity/iam.md @@ -2,6 +2,7 @@ title: Upbound IAM sidebar_position: 2 tier: "standard" +slug: /manuals/platform/iam --- Upbound allows administrators to deploy Role-based Access Control (RBAC) diff --git a/docs/manuals/platform/k8s-rbac.md b/docs/manuals/platform/identity/k8s-rbac.md similarity index 99% rename from docs/manuals/platform/k8s-rbac.md rename to docs/manuals/platform/identity/k8s-rbac.md index 9eb111ff9..70f8c2e35 100644 --- a/docs/manuals/platform/k8s-rbac.md +++ b/docs/manuals/platform/identity/k8s-rbac.md @@ -2,6 +2,7 @@ title: Authorize actions in control planes sidebar_position: 8 description: A guide to implementing and configuring Kuberentes RBAC in Upbound +slug: /manuals/platform/k8s-rbac --- :::note diff --git a/docs/manuals/platform/oidc.md b/docs/manuals/platform/identity/oidc.md similarity index 99% rename from docs/manuals/platform/oidc.md rename to docs/manuals/platform/identity/oidc.md index 5356cef71..c72e8a3ee 100644 --- a/docs/manuals/platform/oidc.md +++ b/docs/manuals/platform/identity/oidc.md @@ -2,6 +2,7 @@ title: Connect control planes to external services sidebar_position: 9 description: A guide for authenticating control plane with external services, including using OIDC +slug: /manuals/platform/oidc --- diff --git a/docs/manuals/platform/organizations.md b/docs/manuals/platform/identity/organizations.md similarity index 98% rename from docs/manuals/platform/organizations.md rename to docs/manuals/platform/identity/organizations.md index 0a97a981c..2e0d64d74 100644 --- a/docs/manuals/platform/organizations.md +++ b/docs/manuals/platform/identity/organizations.md @@ -2,6 +2,7 @@ title: Organizations sidebar_position: 3 description: Configuring Organizations for Upbound +slug: /manuals/platform/organizations --- Organizations allow multiple [user accounts][user-accounts] to share Upbound resources and collaborate. diff --git a/docs/manuals/platform/rbac.md b/docs/manuals/platform/identity/rbac.md similarity index 99% rename from docs/manuals/platform/rbac.md rename to docs/manuals/platform/identity/rbac.md index cdb5ff752..979f1c1b4 100644 --- a/docs/manuals/platform/rbac.md +++ b/docs/manuals/platform/identity/rbac.md @@ -3,6 +3,7 @@ title: Upbound RBAC sidebar_position: 7 description: A guide to implementing and configuring access control in Upbound plan: "standard" +slug: /manuals/platform/rbac --- diff --git a/docs/manuals/platform/robots.md b/docs/manuals/platform/identity/robots.md similarity index 95% rename from docs/manuals/platform/robots.md rename to docs/manuals/platform/identity/robots.md index 72404749a..eeca3ba7d 100644 --- a/docs/manuals/platform/robots.md +++ b/docs/manuals/platform/identity/robots.md @@ -2,6 +2,7 @@ title: Robots sidebar_position: 6 description: Creating and assigning Upbound robot tokens +slug: /manuals/platform/robots --- Robot accounts are non-user accounts with unique credentials and permissions. Organization _admins_ grant robot accounts access to individual repositories. Robot accounts access the repositories without using credentials tied to an individual user. @@ -41,6 +42,6 @@ You should use a robot token for your Upbound Marketplace CI to push new tags or [up-cli]: /reference/cli-reference -[personal-access-token]: /manuals/console/upbound-console/ +[personal-access-token]: /manuals/platform/insights/navigating/ [team]: /manuals/platform/teams/#manage-repository-permissions [upbound-console]: https://console.upbound.io/ diff --git a/docs/manuals/platform/sso.md b/docs/manuals/platform/identity/sso.md similarity index 99% rename from docs/manuals/platform/sso.md rename to docs/manuals/platform/identity/sso.md index 48158d532..997c7fb02 100644 --- a/docs/manuals/platform/sso.md +++ b/docs/manuals/platform/identity/sso.md @@ -4,6 +4,7 @@ sidebar_position: 10 description: Configure single sign-on in your Upbound organization to manage user access plan: "enterprise" +slug: /manuals/platform/sso --- diff --git a/docs/manuals/platform/teams.md b/docs/manuals/platform/identity/teams.md similarity index 99% rename from docs/manuals/platform/teams.md rename to docs/manuals/platform/identity/teams.md index 81c530606..938f1a393 100644 --- a/docs/manuals/platform/teams.md +++ b/docs/manuals/platform/identity/teams.md @@ -2,6 +2,7 @@ title: Teams sidebar_position: 5 description: Creating and managing Upbound teams +slug: /manuals/platform/teams --- Teams are groups of Upbound [users][users] within an [organization][organization]. Teams provide more fine-grained permissions controls for users and robots accessing control planes and repositories. diff --git a/docs/manuals/platform/users.md b/docs/manuals/platform/identity/users.md similarity index 99% rename from docs/manuals/platform/users.md rename to docs/manuals/platform/identity/users.md index e47f3b7c1..960766df0 100644 --- a/docs/manuals/platform/users.md +++ b/docs/manuals/platform/identity/users.md @@ -2,6 +2,7 @@ title: Users sidebar_position: 4 description: Creating and managing Upbound users +slug: /manuals/platform/users --- User accounts in Upbound belong to individuals. Each user can be a member of one or more [teams][teams], which have permissions on control planes within an [organization][organization]. A user can be a member of multiple organizations. diff --git a/docs/manuals/platform/insights/_category_.json b/docs/manuals/platform/insights/_category_.json new file mode 100644 index 000000000..307d88369 --- /dev/null +++ b/docs/manuals/platform/insights/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Insights", + "position": 2, + "collapsed": true +} diff --git a/docs/manuals/platform/insights/ai.md b/docs/manuals/platform/insights/ai.md new file mode 100644 index 000000000..b608c7d4c --- /dev/null +++ b/docs/manuals/platform/insights/ai.md @@ -0,0 +1,35 @@ +--- +title: Insights for AI assistants and agents +sidebar_label: AI assistants & agents +description: One API that lets assistants and agents query and act across the whole estate. +--- + +## One API for the estate + +- Assistants and agents need one place to understand and act on infrastructure. + Insights exposes a single API across every control plane. + +## What the API gives an agent + +- **One API to connect**. Query the entire estate through `hub.upbound.io/v1alpha1`. +- **Understands your APIs**. The schema of every defined type is available, so an + agent knows what it can ask for (cross-link: [Query API][queryApi], + `typedefinitions`). +- **Searches embedded knowledge**. Docs included in deployed packages are + searchable through the Catalog (`catalog.hub.upbound.io/v1alpha1`). +- **Catalog and knowledge overlay**. Layer your own runbooks, policies, and context + on top. Open: confirm what exists today vs planned. +- **Seconds, not SSH sessions**. One call replaces many clusters and raw YAML. + +## Auth for agents + +- Same authorization model as humans: scoped to what the identity can see. +- Open: how do agents authenticate (service identity or token)? Confirm. + +## Next steps + +- [Query API][queryApi] +- [Overview][overview] + +[queryApi]: ./query-api.md +[overview]: ./overview.md diff --git a/docs/manuals/platform/insights/connections.md b/docs/manuals/platform/insights/connections.md new file mode 100644 index 000000000..db4a86a67 --- /dev/null +++ b/docs/manuals/platform/insights/connections.md @@ -0,0 +1,94 @@ +--- +title: Connect a control plane +sidebar_label: Connect a control plane +sidebar_position: 2 +description: Register a control plane so Insights can watch, index, and query it. +--- + + + +## What a connection does + +- Registering a control plane starts a `hub-connector` inside it. The connector + watches resources and **pushes** state to `hub-api`. Insights never dials back. +- The connector tracks the resource inventory, each resource's health, and the + installed package versions. +- Insights is read-only. It shows fleet state without managing the control plane + lifecycle. No `Connection` kind exists. The real resources are `ControlPlane` + and `RegistrationToken`. +- You can connect UXP, Spaces, upstream Crossplane, and generic Kubernetes clusters. + +## Before you begin + +- A running Insights install (see [overview][overview]). +- The `hub-api` URL and token-exchange URL from that install. +- `kubectl` access to the target control plane. + +## Procedure (verified commands, needs prose pass) + +1. Apply a `ControlPlane` to Insights. + + ```yaml + apiVersion: hub.upbound.io/v1alpha1 + kind: ControlPlane + metadata: + name: production + namespace: default + spec: + identityProviders: + - name: oidc + userMappingType: Exact + ``` + +2. Mint a `RegistrationToken`. Insights writes the token to the resource's + `status` and shows it once. + + ```yaml + apiVersion: hub.upbound.io/v1alpha1 + kind: RegistrationToken + metadata: + generateName: production- + namespace: default + spec: + controlPlaneRef: + name: production + ``` + + Store it in a Secret under the key `registrationToken`. + +3. Install the connector on the target cluster. + + ```shell + helm install hub-connector \ + --namespace hub --create-namespace \ + --set connector.hubApiUrl= \ + --set connector.tokenExchangeUrl= \ + --set connector.credentials.secretRef.name=hub-connector-credentials + ``` + +4. Verify with `kubectl get controlplanes.hub.upbound.io -n default`. The control + plane appears in the Console within a few minutes. + +Alt path to document: registering from the Console UI, which displays the token. + +## Key resources + +| Kind | Purpose | +|---|---| +| `ControlPlane` | A registered control plane. | +| `RegistrationToken` | One-time token authorizing a connector to register. | + +## Open questions and placeholders + +- ``, ``, and `` come from + release notes or install output. +- Confirm the Console registration walkthrough and add screenshots. + +## Next steps + +- [Query API][queryApi] +- [Navigate the Console][navigating] + +[overview]: ./overview.md +[navigating]: ./navigating.md +[queryApi]: ./query-api.md diff --git a/docs/manuals/platform/insights/feature-flags.md b/docs/manuals/platform/insights/feature-flags.md new file mode 100644 index 000000000..4a688c91e --- /dev/null +++ b/docs/manuals/platform/insights/feature-flags.md @@ -0,0 +1,56 @@ +--- +title: Public preview and alpha features +sidebar_label: Feature flags +description: How to enable preview features and what to expect. +--- + +## What preview features are + +- Some capabilities sit behind feature flags as public preview or alpha. + They're off by default. + +:::warning +Upbound can change or remove preview and alpha features without notice, and they +may introduce breaking changes between releases. Don't depend on them in +production. +::: + +## How to enable a flag + + + + +- Outline: `up hub configure` or an equivalent flag-setting command. **Open:** the + `up` CLI path for Hub is product-intended but not yet in the current eng docs. + Confirm the command before publishing. + + + + +- Set the flag as a Helm value on the `hub` chart: + + ```shell + helm upgrade hub -n hub --reuse-values \ + --set hub-api.catalog.enabled=true + ``` + + + + +## What flags exist + +| Flag | Enables | Status | +|---|---|---| +| `hub-api.catalog.enabled` | Catalog API | preview | +| `hub-api.catalog.ingest.enabled` | Catalog ingestion | preview | +| `hub-api.catalog.enrichment.enabled` | Catalog enrichment | preview | +| _…_ | _complete from values reference_ | | + +- Note: a flag that's off typically returns `404` on its endpoints. +- Open: document the OFREP feature-flag endpoint if it's user-facing. + +## Next steps + +- [Query API][queryApi] (Catalog endpoints) + +[queryApi]: ./query-api.md diff --git a/docs/manuals/platform/insights/install/oidc/entra-id.md b/docs/manuals/platform/insights/install/oidc/entra-id.md new file mode 100644 index 000000000..de6034d9c --- /dev/null +++ b/docs/manuals/platform/insights/install/oidc/entra-id.md @@ -0,0 +1,16 @@ +--- +title: OIDC with Microsoft Entra ID +sidebar_label: Entra ID +description: Configure Microsoft Entra ID as the OIDC provider for Insights. +--- + +## Steps (outline) + +1. Register an application in Entra ID. +2. Add the redirect URI `/oidc/callback`. +3. Configure a groups claim. +4. Capture the issuer URL, client ID, and client secret. +5. Set the OIDC values in `values.yaml` and install/upgrade. +6. Verify login and group mapping. + +[oidc]: ./overview.md diff --git a/docs/manuals/platform/insights/install/oidc/generic.md b/docs/manuals/platform/insights/install/oidc/generic.md new file mode 100644 index 000000000..4013ddeb6 --- /dev/null +++ b/docs/manuals/platform/insights/install/oidc/generic.md @@ -0,0 +1,22 @@ +--- +title: OIDC with a generic provider +sidebar_label: Generic +description: Configure any standards-compliant OIDC provider for Insights. +--- + +## Requirements + +- OIDC discovery endpoint and authorization-code flow. +- `email` and `email_verified` claims. +- A configurable group claim (default `groups`). + +## Steps (outline) + +1. Create an OIDC client in your provider. +2. Set the redirect URI `/oidc/callback`. +3. Map the group claim. +4. Capture the issuer URL, client ID, and client secret. +5. Set the OIDC values in `values.yaml` and install/upgrade. +6. Verify login and group mapping. + +[oidc]: ./overview.md diff --git a/docs/manuals/platform/insights/install/oidc/keycloak.md b/docs/manuals/platform/insights/install/oidc/keycloak.md new file mode 100644 index 000000000..f0271263c --- /dev/null +++ b/docs/manuals/platform/insights/install/oidc/keycloak.md @@ -0,0 +1,16 @@ +--- +title: OIDC with Keycloak +sidebar_label: Keycloak +description: Configure Keycloak as the OIDC provider for Insights. +--- + +## Steps (outline) + +1. Create a realm and an OIDC client in Keycloak. +2. Set the redirect URI `/oidc/callback`. +3. Add a group membership mapper (`groups` claim). +4. Capture the issuer URL, client ID, and client secret. +5. Set the OIDC values in `values.yaml` and install/upgrade. +6. Verify login and group mapping. + +[oidc]: ./overview.md diff --git a/docs/manuals/platform/insights/install/oidc/overview.md b/docs/manuals/platform/insights/install/oidc/overview.md new file mode 100644 index 000000000..80a847381 --- /dev/null +++ b/docs/manuals/platform/insights/install/oidc/overview.md @@ -0,0 +1,34 @@ +--- +title: Set up OIDC +sidebar_label: Overview +description: Configure an OIDC provider for Insights authentication. +--- + +## What Insights needs from OIDC + +- OIDC discovery and the authorization-code flow. +- `email` and `email_verified` claims. +- A configurable group claim (default `groups`). +- Redirect URI `/oidc/callback`. + +Insights supports one OIDC provider per deployment. Insights delegates sessions to +the provider. It doesn't own the session lifecycle. + +## Supported providers + +| Provider | Guide | +|---|---| +| Microsoft Entra ID | [Entra ID][entra] | +| Keycloak | [Keycloak][keycloak] | +| Generic OIDC | [Generic][generic] | + +## Setup order + +1. Create the app or client in your provider. +2. Configure the redirect URI and group claim. +3. Set the OIDC values in `values.yaml` and install or upgrade. +4. Verify login and group-to-role mapping. + +[entra]: ./entra-id.md +[keycloak]: ./keycloak.md +[generic]: ./generic.md diff --git a/docs/manuals/platform/insights/install/overview.md b/docs/manuals/platform/insights/install/overview.md new file mode 100644 index 000000000..72e989843 --- /dev/null +++ b/docs/manuals/platform/insights/install/overview.md @@ -0,0 +1,49 @@ +--- +title: Install Insights +sidebar_label: Overview +description: Pick an install path and a deployment method. +--- + +## Pick an install path + +| Path | Use when | Guide | +|---|---|---| +| Demo | Evaluate on your laptop, single KIND cluster | [Quickstart][demo] | +| Self-hosted | Real cluster on AWS, GCP, or Azure | [Self-hosted quickstart][selfhosted] | + +## Deployment method + +Both paths install with either the up CLI or Helm. The up CLI wraps the Helm +install and is the recommended path. + + + + +- Outline: `up hub install` installs the components and wires defaults. + + + + +- Install the umbrella `hub` chart: + + ```shell + helm install hub --namespace hub --create-namespace --values values.yaml + ``` + +- The connector installs separately per control plane. See [connect a control + plane][connect]. + + + + +## Next steps + +- [Self-hosted quickstart (AWS/GCP/Azure)][selfhosted] +- [Set up OIDC][oidc] +- [Production guidelines][prod] + +[demo]: /getstarted/new-users/controlplanes-quickstart +[selfhosted]: ./self-hosted.md +[oidc]: ./oidc/overview.md +[connect]: ../connections.md +[prod]: ../production/overview.md diff --git a/docs/manuals/platform/insights/install/self-hosted.md b/docs/manuals/platform/insights/install/self-hosted.md new file mode 100644 index 000000000..ca97d5af8 --- /dev/null +++ b/docs/manuals/platform/insights/install/self-hosted.md @@ -0,0 +1,57 @@ +--- +title: Self-hosted quickstart +sidebar_label: Self-hosted quickstart +description: Install Insights on your own AWS, GCP, or Azure Kubernetes cluster. +--- + + +## What you provide + +Self-hosted replaces the demo's bundled services with ones you operate: + +- A Kubernetes cluster (AWS, GCP, or Azure). +- A PostgreSQL database. +- An OIDC provider ([Entra ID, Keycloak, or generic][oidc]). +- An ingress/gateway with a CA-signed TLS certificate and DNS. + +## Prerequisites by cloud + + + + +- EKS cluster, Amazon RDS for PostgreSQL (password or IAM auth), ingress and TLS, + and DNS. Port from eng repo `self-hosted/databases/aws-rds.md`. + + + + +- GKE cluster, Cloud SQL for PostgreSQL, ingress and TLS, and DNS. **Open:** + GCP-specific database steps not in eng repo yet. + + + + +- AKS cluster, Azure Database for PostgreSQL, ingress and TLS, and DNS. + **Open:** the Azure-specific database steps aren't in the eng repo yet. + + + + +## Install + +1. Prepare `values.yaml` (database, OIDC, external URL, TLS). +2. Install the `hub` chart (up CLI recommended, or Helm. See [install + overview][install]). +3. Bootstrap access (org/realm role bindings). Open: confirm role names. +4. Verify components are ready and open the Console. + +## Next steps + +- [Set up OIDC][oidc] +- [Connect a control plane][connect] +- [Production guidelines][prod] + +[oidc]: ./oidc/overview.md +[install]: ./overview.md +[connect]: ../connections.md +[prod]: ../production/overview.md diff --git a/docs/manuals/console/upbound-console.md b/docs/manuals/platform/insights/navigating.md similarity index 100% rename from docs/manuals/console/upbound-console.md rename to docs/manuals/platform/insights/navigating.md diff --git a/docs/manuals/platform/insights/overview.md b/docs/manuals/platform/insights/overview.md new file mode 100644 index 000000000..e5108f5dd --- /dev/null +++ b/docs/manuals/platform/insights/overview.md @@ -0,0 +1,106 @@ +--- +title: Upbound Insights +sidebar_label: Overview +sidebar_position: 1 +description: See, search, and query every control plane across your estate through one API. +--- + +## Why Insights + +The answers you need live across many `kubectl` contexts. You can't easily ask +which control planes run an old provider version, what changed in the last day, +or how many claims are `Ready`. A connector in each control plane streams +state to a central store, and Insights re-exposes it through one Kubernetes-style +API and the Console. Insights is read-only. It gives you visibility, not +lifecycle management. + +## How it works + +Insights runs three components. + +| Component | Role | +|---|---| +| `hub-api` | The API server. It owns the aggregated store and handles OIDC login and token exchange. | +| `hub-connector` | Runs in each connected control plane and pushes state out. `hub-api` never dials back into your clusters. | +| `hub-webui` | The Console web UI, an optional static app. | + +To bring a control plane in, see [connect a control plane][connections]. + +## What you can query + +Query the `hub.upbound.io/v1alpha1` API group with `GET` and `LIST` requests. The +`/resources` endpoint returns resource instances filtered by group, kind, API +version, control plane, or label. `/controlplanes` lists connected control planes, +and the relationship endpoints show how resources connect. + +A separate Catalog API (`catalog.hub.upbound.io/v1alpha1`) indexes package images +through the `images` and `imagesearches` endpoints. It sits behind a feature flag, +so its endpoints return `404` when the flag is off. + +Query results are authorization-scoped. You see a resource through Insights only +if you could see it with `kubectl`. + +Open: confirm which endpoints are public in Beta. + +## The Console + +The Console gives operators four resource-centric views. A global summary rolls +up resource health across the fleet. Resource exploration filters and groups +resources by type, control plane, or team. Definitions surface package-version +drift, and the infrastructure view shows reconciliation health per control plane. + +TODO: add UI captures. See [navigate the Console][navigating]. + +## Identity and access + +Insights authenticates users through OIDC and hands the session lifecycle to your +identity provider. It supports Microsoft Entra ID and Keycloak, along with any +generic OIDC provider. Authorization mirrors `kubectl` visibility. + +Realms are in active development. Today, access maps to the control planes a user +can already reach. + +## Deployment + +Insights installs from Helm in two ways. The demo runs a single KIND cluster with +`global.demo.enabled=true`. The self-hosted path uses an external database, OIDC +provider, ingress, and TLS certificate that you provide. An Upbound Cloud mode is +in design and isn't a supported install path yet. + +Every UXP instance includes Insights scoped to one control plane, which is the +community tier. To connect more control planes, upgrade to Standard. + +Open: verify the tier gating. + + + +## Next steps + +- [Install Insights][install] +- [Connect a control plane][connections] +- [Navigate the Console][navigating] +- [Query API][queryApi] +- [Insights for AI assistants and agents][ai] +- [Use cases][useCases] +- [Insights in production][prod] +- [Public preview and alpha features][flags] +- [Reference][reference] + +## Open questions + +- The 3.0 spec names `/apis/controlplane/v1`, a `Connection` kind, and Catalog + `Package`, `API`, and `Utilization`. None exist in the current API. This + session documents what the code does today. Revisit if the rename happens. +- Engineering docs contradict themselves twice. The catalog group appears as both + `catalog.hub.upbound.io` and `catalog.upbound.io`, and the org role as both + `org-admin` and `admin`. Resolve with engineering. + +[connections]: ./connections.md +[navigating]: ./navigating.md +[queryApi]: ./query-api.md +[install]: ./install/overview.md +[ai]: ./ai.md +[useCases]: ./use-cases.md +[prod]: ./production/overview.md +[flags]: ./feature-flags.md +[reference]: ./reference.md diff --git a/docs/manuals/platform/insights/production/overview.md b/docs/manuals/platform/insights/production/overview.md new file mode 100644 index 000000000..df78d9fb9 --- /dev/null +++ b/docs/manuals/platform/insights/production/overview.md @@ -0,0 +1,20 @@ +--- +title: Insights in production +sidebar_label: Overview +description: Run a self-hosted Insights install well at scale. +--- + +## Before production + +You have a working [self-hosted install][selfhosted]. Now make it ready for real +use. The pages here cover sizing and scaling, plus how to secure the install and +keep it current. + +- [Scale and tune it][scale] +- [Lock it down][security] +- Keep it up to date (upgrades use forward-only schema changes) +- Keep it up (run more than one copy) + +[selfhosted]: ../install/self-hosted.md +[scale]: ./scalability.md +[security]: ./security.md diff --git a/docs/manuals/platform/insights/production/scalability.md b/docs/manuals/platform/insights/production/scalability.md new file mode 100644 index 000000000..186507128 --- /dev/null +++ b/docs/manuals/platform/insights/production/scalability.md @@ -0,0 +1,26 @@ +--- +title: Scalability and performance +sidebar_label: Scalability & performance +description: How to size, scale, and keep Insights up. +--- + +## Sizing (outline) + +- Small, medium, and large tiers, each with target replica counts and resource + limits, plus a chart to help you pick. Fill from `production/sizing.md`. + +## High availability (outline) + +- `hub-api` replicas, PodDisruptionBudgets, and topology spread. +- Notes on keeping the database up. + +## Autoscaling (outline) + +- HPA for `hub-api`. Scale the database on its own. + +## Performance (outline) + +- How the connector syncs, and the cost of its first pass. +- Query paging (`pageSize`, `page`, `.metadata.continue`). + +[prod]: ./overview.md diff --git a/docs/manuals/platform/insights/production/security.md b/docs/manuals/platform/insights/production/security.md new file mode 100644 index 000000000..9f23974d3 --- /dev/null +++ b/docs/manuals/platform/insights/production/security.md @@ -0,0 +1,28 @@ +--- +title: Security guidelines +sidebar_label: Security +description: Security guidance for a production Insights deployment. +--- + +## Authentication (outline) + +- OIDC authenticates humans. Use one provider per deployment. See [set up + OIDC][oidc]. +- Connectors authenticate with per-control-plane registration tokens. + +## Authorization (outline) + +- A three-tier model. Bind OIDC groups to roles. +- Insights scopes query results to `kubectl`-equivalent visibility. +- Open: resolve the role-name mismatch (`org-admin` vs `admin`) with engineering. + +## Transport and secrets (outline) + +- CA-signed TLS at the gateway. No plaintext `hubApiUrl` outside the local demo. +- Where registration tokens and OIDC client secrets live, and how they rotate. + +## Network (outline) + +- Connectors are push-only. `hub-api` never dials into observed clusters. + +[oidc]: ../install/oidc/overview.md diff --git a/docs/manuals/console/query-api.md b/docs/manuals/platform/insights/query-api.md similarity index 100% rename from docs/manuals/console/query-api.md rename to docs/manuals/platform/insights/query-api.md diff --git a/docs/manuals/platform/insights/reference.md b/docs/manuals/platform/insights/reference.md new file mode 100644 index 000000000..9fd37adc2 --- /dev/null +++ b/docs/manuals/platform/insights/reference.md @@ -0,0 +1,52 @@ +--- +title: Reference +sidebar_label: Reference +description: FAQ, debugging, and glossary for Insights. +--- + +:::note +**Skeleton.** Structure only, content pending. Bundles FAQ, debugging, and the +glossary on one page. Seed the FAQ and debugging sections with real support +questions and failure modes before publishing. +::: + +## FAQ + +- Is Insights read-only? Yes. It doesn't manage control plane lifecycle. +- Do I need an `up` account for the demo? No. The demo runs fully local. +- Why don't my resources appear? Usually a connector sync delay or an unhealthy + connection. +- What's the difference between the demo and self-hosted installs? +- _…add real questions._ + +## Debugging + +- **A control plane doesn't appear.** Check the connector is running and can + reach `hub-api`. Inspect connector logs with + `kubectl -n hub logs deployment/hub-connector`. +- **A control plane appears but reports no resources.** The initial inventory pass + is slow. Wait, then re-check. Confirm the connector registered. +- **The connector can't reach `hub-api`.** Check network and DNS between clusters. + Verify the `hubApiUrl` and `tokenExchangeUrl` values. +- **A query returns nothing.** Check authorization scoping. Confirm the identity + can see the resource with `kubectl`. +- **Catalog endpoints return `404`.** The feature flag is off. See [feature + flags][flags]. + +## Glossary + +- **Insights.** The product for seeing, searching, and querying every control + plane through one API and the Console. +- **Console.** The web UI for Insights. Replaces the retired Upbound Console. +- **`hub-api`.** The Insights API server. A runtime component, not a user-facing + name. +- **`hub-connector`.** The per-control-plane agent that pushes state to `hub-api`. +- **Control plane connection.** A registered control plane that Insights watches. +- **Registration token.** A one-time bootstrap secret a connector uses to register. +- **Realm.** An access boundary that groups control planes across Spaces. In + active development. +- **Catalog.** The index of package images discovered across control planes. +- **Query API.** The `hub.upbound.io/v1alpha1` API for querying resources. +- _…add remaining terms._ + +[flags]: ./feature-flags.md diff --git a/docs/manuals/platform/insights/use-cases.md b/docs/manuals/platform/insights/use-cases.md new file mode 100644 index 000000000..d08527eb0 --- /dev/null +++ b/docs/manuals/platform/insights/use-cases.md @@ -0,0 +1,30 @@ +--- +title: Use cases +sidebar_label: Use cases +description: End-to-end walkthroughs of the scenarios Insights is built for. +--- + + +## How to use this section + +- Each scenario runs a user end to end through a real workflow, from setup to + result. Start with the one closest to your goal. + +## Scenarios (outline) + +- **See the whole estate.** Connect control planes, open the Console, and triage + fleet health. Start with [connect][connect], then [navigate][navigating]. +- **Find and fix drift.** Definitions surfaces package-version drift, which you + then resolve. +- **Query across the fleet.** A cross-control-plane question resolves through the + [Query API][queryApi]. +- **Set up access.** Configure OIDC and realms so the right people reach the right + control planes. Follow [setting up OIDC][oidc]. +- **Agent-driven operations.** An assistant answers estate questions through one + API. Try [AI assistants and agents][ai]. + +[connect]: ./connections.md +[navigating]: ./navigating.md +[queryApi]: ./query-api.md +[oidc]: ./install/oidc/overview.md +[ai]: ./ai.md diff --git a/docs/manuals/uxp/concepts/observability-views.md b/docs/manuals/uxp/concepts/observability-views.md index 748798bfa..6b9dc8aca 100644 --- a/docs/manuals/uxp/concepts/observability-views.md +++ b/docs/manuals/uxp/concepts/observability-views.md @@ -105,4 +105,4 @@ Your Prometheus needs to scrape UXP components and have these metrics available: - `upjet_resource_external_api_calls_total`—emitted by **provider-aws only**; Azure and GCP upjet-based providers don't support this metric. - `function_run_function_seconds_bucket`, `_sum`, `_count` -[web-ui]: /manuals/console/self-service +[web-ui]: /manuals/platform/insights/overview diff --git a/docs/manuals/uxp/crossplane-web-ui.md b/docs/manuals/uxp/crossplane-web-ui.md index ef5c4246e..7536f0399 100644 --- a/docs/manuals/uxp/crossplane-web-ui.md +++ b/docs/manuals/uxp/crossplane-web-ui.md @@ -1,69 +1,8 @@ --- -title: Web UI for Crossplane -description: "Learn how to use the Crossplane Web UI" +title: Insights +description: "Learn how to use Upbound Insights" sidebar_position: 2 -draft: true --- + -The UXP Web UI is a local, read-only dashboard to inspect and review your -control plane. This dashboard gives you insight into key Crossplane resources, -pipelines, and AddOn behavior. - -The Web UI runs in your UXP control plane. For multi-cluster or Enterprise-level -management, review the [Upbound Console][console] documentation. - -With the WebUI you can: - -- Review UXP control planes and AI-powered pipelines -- View real-time actions of Operations, Agents, and Function Pipelines -- View composed resources, function server packages, and more - -:::note -The UXP WebUI is pre-installed with your UXP instance via Helm chart -::: - -## Version compatibility - -:::important -The WebUI dashboard is for UXP clusters only. It doesn't support Spaces Teams -or Groups Authentication or account-based views. -::: - -The WebUI version in your cluster matches its corresponding -UXP version. - -For example, `upbound/crossplane-web-ui:v1.0.x` is compatible with -`upbound/crossplane:v1.0.x` and ensures feature parity across UXP and the -dashboard. - -## Launch the dashboard - -Use the `up` CLI to launch the Web UI: - -```shell -up uxp web-ui open -``` - - -To stop the Web UI service use: - -```shell -up uxp web-ui enable -``` - - -When using up project run or up project test (for local ephemeral development -clusters), the Web UI is automatically configured into the local cluster. - - - - - - - - - - - - -[console]: /manuals/console/upbound-console/ +[console]: /manuals/platform/insights/navigating/ diff --git a/docs/getstarted/upgrade-to-upbound/migrate-configurations-v2.md b/docs/manuals/uxp/howtos/migrate-configurations-v2.md similarity index 100% rename from docs/getstarted/upgrade-to-upbound/migrate-configurations-v2.md rename to docs/manuals/uxp/howtos/migrate-configurations-v2.md diff --git a/docs/getstarted/upgrade-to-upbound/upgrade-to-uxp.md b/docs/manuals/uxp/howtos/upgrade-to-uxp.md similarity index 87% rename from docs/getstarted/upgrade-to-upbound/upgrade-to-uxp.md rename to docs/manuals/uxp/howtos/upgrade-to-uxp.md index 0be81259c..33dc3fab2 100644 --- a/docs/getstarted/upgrade-to-upbound/upgrade-to-uxp.md +++ b/docs/manuals/uxp/howtos/upgrade-to-uxp.md @@ -213,32 +213,32 @@ path uses a **free Community license**. 2. Choose your upgrade method and run the upgrade: - - + + - Add the Upbound repository and upgrade your Crossplane cluster: - ```shell - helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update - helm upgrade --install crossplane --namespace crossplane-system upbound-stable/crossplane --version "${UXP_VERSION}" - ``` - +Add the Upbound repository and upgrade your Crossplane cluster: +```shell +helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update +helm upgrade --install crossplane --namespace crossplane-system upbound-stable/crossplane --version "${UXP_VERSION}" +``` + - + - First, download the CLI: +First, download the CLI: - ```shell - curl -sL "https://cli.upbound.io" | sh - ``` +```shell +curl -sL "https://cli.upbound.io" | sh +``` - Next, upgrade your Crossplane cluster to UXP: +Next, upgrade your Crossplane cluster to UXP: - ```shell - up uxp upgrade "${UXP_VERSION}" - ``` +```shell +up uxp upgrade "${UXP_VERSION}" +``` - - + + ## Upgrade to Commercial UXP @@ -270,32 +270,32 @@ for more information. 2. Choose your upgrade method and run the upgrade: - - + + - Add the Upbound repository and upgrade your Crossplane cluster: - ```shell - helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update - helm upgrade --install crossplane --namespace crossplane-system upbound-stable/crossplane --version "${UXP_VERSION}" - ``` - +Add the Upbound repository and upgrade your Crossplane cluster: +```shell +helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update +helm upgrade --install crossplane --namespace crossplane-system upbound-stable/crossplane --version "${UXP_VERSION}" +``` + - + - First, download the CLI: +First, download the CLI: - ```shell - curl -sL "https://cli.upbound.io" | sh - ``` +```shell +curl -sL "https://cli.upbound.io" | sh +``` - Next, upgrade your Crossplane cluster to UXP: +Next, upgrade your Crossplane cluster to UXP: - ```shell - up uxp upgrade "${UXP_VERSION}" - ``` +```shell +up uxp upgrade "${UXP_VERSION}" +``` - - + + 3. Install your Commercial License: @@ -360,8 +360,8 @@ After upgrading to Upbound Crossplane, try out these features: [xp-upgrade]: https://docs.crossplane.io/latest/guides/upgrade-to-crossplane-v2/ [builders-workshop]: https://docs.upbound.io/getstarted/builders-workshop/project-foundations/ -[web-ui]: https://docs.upbound.io/manuals/console/self-service/#key-features -[query-api]: https://docs.upbound.io/manuals/console/query-api/ +[web-ui]: https://docs.upbound.io/manuals/platform/insights/overview/#key-features +[query-api]: https://docs.upbound.io/manuals/platform/insights/query-api/ [rds]: https://docs.upbound.io/guides/intelligent-control-planes/scale-database/ [slack]: https://crossplane.slack.com/archives/C01TRKD4623 diff --git a/docs/getstarted/upgrade-to-upbound/upgrade-to-projects.md b/docs/manuals/uxp/upgrade-to-projects.md similarity index 99% rename from docs/getstarted/upgrade-to-upbound/upgrade-to-projects.md rename to docs/manuals/uxp/upgrade-to-projects.md index d55607184..6c678da74 100644 --- a/docs/getstarted/upgrade-to-upbound/upgrade-to-projects.md +++ b/docs/manuals/uxp/upgrade-to-projects.md @@ -1,7 +1,7 @@ --- title: Upgrade to Control Plane Projects description: Adopt control plane projects -sidebar_position: 1 +sidebar_position: 3 --- If you're already running Crossplane and want to use Upbound Crossplane's diff --git a/docs/reference/index.md b/docs/reference/index.md index 34e32672c..839619442 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -13,4 +13,4 @@ materials for Upbound. For more information on how Upbound builds on Crossplane, review the [learning overview][learning-overview]. -[learning-overview]: / +[learning-overview]: /getstarted/ diff --git a/docusaurus.config.js b/docusaurus.config.js index b03a8f966..54a9caa71 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -72,12 +72,6 @@ const config = { ], ], plugins: [ - [ - "docusaurus-pushfeedback", - { - project: "0p5hvygqxb", - }, - ], [ "@docusaurus/plugin-content-docs", { @@ -85,6 +79,7 @@ const config = { path: "docs", routeBasePath: "/", sidebarPath: require.resolve("./src/sidebars/main.js"), + remarkPlugins: [require("./scripts/manifest-embed-plugin")], }, ], [ @@ -184,7 +179,7 @@ const config = { { label: "Get Started", position: "left", - to: "/getstarted/", + to: "/", }, { type: "dropdown", @@ -225,12 +220,12 @@ const config = { to: "/self-hosted-spaces/overview/", }, { - label: "CLI", - to: "/manuals/cli/overview/", + label: "Platform", + to: "/manuals/platform/overview/", }, { - label: "Console", - to: "/manuals/console/upbound-console/", + label: "CLI", + to: "/manuals/cli/overview/", }, { label: "Packages", @@ -240,10 +235,6 @@ const config = { label: "Marketplace", to: "/manuals/marketplace/overview/", }, - { - label: "Platform", - to: "/manuals/platform/overview/", - }, ], }, { diff --git a/package-lock.json b/package-lock.json index 6f1c1db61..cf374757d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@docusaurus/plugin-content-docs": "^3.10.2", "@docusaurus/plugin-google-gtag": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", - "@docusaurus/theme-live-codeblock": "^3.10.2", "@docusaurus/theme-mermaid": "^3.10.2", "@docusaurus/theme-search-algolia": "^3.10.2", "@mdx-js/react": "^3.0.0", @@ -27,7 +26,6 @@ "@upbound/utils": "0.0.1", "@upbound/ux": "0.0.2", "clsx": "^2.1.1", - "docusaurus-pushfeedback": "^1.0.5", "gray-matter": "^4.0.3", "js-yaml": "^4.3.0", "launchdarkly-js-client-sdk": "3.5.0", @@ -40,11 +38,8 @@ "devDependencies": { "@docusaurus/module-type-aliases": "3.9.1", "@docusaurus/types": "3.9.1", - "autoprefixer": "^10.4.22", "esbuild-loader": "^4.5.0", - "postcss": "^8.5.10", "process": "^0.11.10", - "tailwindcss": "^4.1.17", "yaml-loader": "^0.8.1" }, "engines": { @@ -4417,30 +4412,6 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/theme-live-codeblock": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.2.tgz", - "integrity": "sha512-rBWmjCFaInkWENi4EjHX4lgwN6uBfVurN8SdK0evZ5LwjIiAdVRTRkmnWNPswiud3UfdSeNalaLQl62Ks9d+pA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/theme-common": "3.10.2", - "@docusaurus/theme-translations": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "@philpl/buble": "^0.19.7", - "clsx": "^2.0.0", - "fs-extra": "^11.1.1", - "react-live": "^4.1.6", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@docusaurus/theme-mermaid": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.2.tgz", @@ -6128,136 +6099,6 @@ "node": ">=20.0.0" } }, - "node_modules/@philpl/buble": { - "version": "0.19.7", - "license": "MIT", - "dependencies": { - "acorn": "^6.1.1", - "acorn-class-fields": "^0.2.1", - "acorn-dynamic-import": "^4.0.0", - "acorn-jsx": "^5.0.1", - "chalk": "^2.4.2", - "magic-string": "^0.25.2", - "minimist": "^1.2.0", - "os-homedir": "^1.0.1", - "regexpu-core": "^4.5.4" - }, - "bin": { - "buble": "bin/buble" - } - }, - "node_modules/@philpl/buble/node_modules/acorn": { - "version": "6.4.2", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@philpl/buble/node_modules/ansi-styles": { - "version": "3.2.1", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@philpl/buble/node_modules/chalk": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@philpl/buble/node_modules/color-convert": { - "version": "1.9.3", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@philpl/buble/node_modules/color-name": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/@philpl/buble/node_modules/escape-string-regexp": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@philpl/buble/node_modules/has-flag": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@philpl/buble/node_modules/jsesc": { - "version": "0.5.0", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/@philpl/buble/node_modules/regenerate-unicode-properties": { - "version": "9.0.0", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@philpl/buble/node_modules/regexpu-core": { - "version": "4.8.0", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@philpl/buble/node_modules/regjsgen": { - "version": "0.5.2", - "license": "MIT" - }, - "node_modules/@philpl/buble/node_modules/regjsparser": { - "version": "0.7.0", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/@philpl/buble/node_modules/supports-color": { - "version": "5.5.0", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "license": "MIT", @@ -8721,23 +8562,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-class-fields": { - "version": "0.2.1", - "license": "MIT", - "engines": { - "node": ">=4.8.2" - }, - "peerDependencies": { - "acorn": "^6.0.0" - } - }, - "node_modules/acorn-dynamic-import": { - "version": "4.0.0", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0" - } - }, "node_modules/acorn-import-phases": { "version": "1.0.4", "license": "MIT", @@ -8937,10 +8761,6 @@ "node": ">=14" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "license": "MIT" - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -11303,13 +11123,6 @@ "node": ">=6" } }, - "node_modules/docusaurus-pushfeedback": { - "version": "1.0.9", - "license": "MIT", - "peerDependencies": { - "@docusaurus/core": "3.x" - } - }, "node_modules/dom-converter": { "version": "0.2.0", "license": "MIT", @@ -12084,21 +11897,6 @@ "node": ">=0.8.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/feed": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", @@ -13994,13 +13792,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/magic-string": { - "version": "0.25.9", - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, "node_modules/markdown-extensions": { "version": "2.0.0", "license": "MIT", @@ -16335,15 +16126,6 @@ "multicast-dns": "cli.js" } }, - "node_modules/mz": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.11", "funding": [ @@ -16636,13 +16418,6 @@ "opener": "bin/opener-bin.js" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-cancelable": { "version": "3.0.0", "license": "MIT", @@ -16948,13 +16723,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.7", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -18766,23 +18534,6 @@ "react": "^18.0.0 || ^19.0.0" } }, - "node_modules/react-live": { - "version": "4.1.8", - "license": "MIT", - "dependencies": { - "prism-react-renderer": "^2.4.0", - "sucrase": "^3.35.0", - "use-editable": "^2.3.3" - }, - "engines": { - "node": ">= 0.12.0", - "npm": ">= 2.0.0" - }, - "peerDependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - } - }, "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", "version": "6.0.0", @@ -20149,10 +19900,6 @@ "node": ">=0.10.0" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "license": "MIT" - }, "node_modules/space-separated-tokens": { "version": "2.0.2", "license": "MIT", @@ -20371,33 +20118,6 @@ "version": "4.2.0", "license": "MIT" }, - "node_modules/sucrase": { - "version": "3.35.1", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/supports-color": { "version": "7.2.0", "license": "MIT", @@ -20458,11 +20178,6 @@ "node": ">= 10" } }, - "node_modules/tailwindcss": { - "version": "4.1.18", - "dev": true, - "license": "MIT" - }, "node_modules/tapable": { "version": "2.3.0", "license": "MIT", @@ -20579,23 +20294,6 @@ "version": "2.20.3", "license": "MIT" }, - "node_modules/thenify": { - "version": "3.3.1", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/thingies": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", @@ -20635,32 +20333,6 @@ "node": ">=18" } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinypool": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", @@ -20743,10 +20415,6 @@ "node": ">=6.10" } }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "license": "Apache-2.0" - }, "node_modules/tslib": { "version": "2.8.1", "license": "0BSD" @@ -21205,13 +20873,6 @@ } } }, - "node_modules/use-editable": { - "version": "2.3.3", - "license": "MIT", - "peerDependencies": { - "react": ">= 16.8.0" - } - }, "node_modules/use-sidecar": { "version": "1.1.3", "license": "MIT", diff --git a/package.json b/package.json index afab984d3..52c460019 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,13 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "npm run process-crds && docusaurus start --host 0.0.0.0", - "build": "npm run process-crds && docusaurus build", + "start": "docusaurus start --host 0.0.0.0", + "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", - "process-crds": "node scripts/process-crds.js", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { @@ -20,7 +19,6 @@ "@docusaurus/plugin-content-docs": "^3.10.2", "@docusaurus/plugin-google-gtag": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", - "@docusaurus/theme-live-codeblock": "^3.10.2", "@docusaurus/theme-mermaid": "^3.10.2", "@docusaurus/theme-search-algolia": "^3.10.2", "@mdx-js/react": "^3.0.0", @@ -34,7 +32,6 @@ "@upbound/utils": "0.0.1", "@upbound/ux": "0.0.2", "clsx": "^2.1.1", - "docusaurus-pushfeedback": "^1.0.5", "gray-matter": "^4.0.3", "js-yaml": "^4.3.0", "launchdarkly-js-client-sdk": "3.5.0", @@ -47,11 +44,8 @@ "devDependencies": { "@docusaurus/module-type-aliases": "3.9.1", "@docusaurus/types": "3.9.1", - "autoprefixer": "^10.4.22", "esbuild-loader": "^4.5.0", - "postcss": "^8.5.10", "process": "^0.11.10", - "tailwindcss": "^4.1.17", "yaml-loader": "^0.8.1" }, "browserslist": { diff --git a/scripts/manifest-embed-plugin.js b/scripts/manifest-embed-plugin.js new file mode 100644 index 000000000..02752b01f --- /dev/null +++ b/scripts/manifest-embed-plugin.js @@ -0,0 +1,49 @@ +// Embeds the contents of a file under static/ into a fenced code block at +// build time, so long manifests/scripts live in their own file instead of +// bloating the markdown source. Usage: +// +// ```yaml title="./example.yaml" manifest="/manifests/getstarted/example.yaml" +// ``` +// +// The fence body is ignored (and should be left empty) -- it's replaced with +// the referenced file's contents. +const fs = require("fs"); +const path = require("path"); + +const EXT_TO_LANG = { + yaml: "yaml", + yml: "yaml", + py: "python", + k: "kcl", + go: "go", + sh: "shell", + json: "json", +}; + +module.exports = function manifestEmbedPlugin() { + return function transformer(tree) { + visit(tree); + }; + + function visit(node) { + if (node.type === "code" && node.meta) { + const match = node.meta.match(/manifest="([^"]+)"/); + if (match) { + const manifestPath = path.join(process.cwd(), "static", match[1]); + if (!fs.existsSync(manifestPath)) { + throw new Error( + `manifest-embed-plugin: file not found: ${manifestPath} (referenced as manifest="${match[1]}")` + ); + } + node.value = fs.readFileSync(manifestPath, "utf8").replace(/\n$/, ""); + if (!node.lang) { + const ext = path.extname(manifestPath).slice(1); + node.lang = EXT_TO_LANG[ext] || ext; + } + } + } + if (node.children) { + node.children.forEach(visit); + } + } +}; diff --git a/scripts/plan-plugin.js b/scripts/plan-plugin.js index edcae7adf..d625aa126 100644 --- a/scripts/plan-plugin.js +++ b/scripts/plan-plugin.js @@ -10,36 +10,28 @@ module.exports = function planPlugin(context, options) { async loadContent() { const planData = {}; const docsPath = path.join(context.siteDir, 'docs'); - - function processDirectory(dir, relativePath = '') { - const items = fs.readdirSync(dir); - - items.forEach(item => { - const itemPath = path.join(dir, item); - const stats = fs.statSync(itemPath); - - if (stats.isDirectory()) { - processDirectory(itemPath, path.join(relativePath, item)); - } else if (item.endsWith('.md') || item.endsWith('.mdx')) { - const content = fs.readFileSync(itemPath, 'utf8'); - const { data: frontmatter } = matter(content); - - if (frontmatter.plan) { - const docId = path.join(relativePath, item.replace(/\.(md|mdx)$/, '')); - planData[docId] = { - plan: frontmatter.plan, - title: frontmatter.title, - path: itemPath - }; - } + + if (fs.existsSync(docsPath)) { + const docFiles = fs + .readdirSync(docsPath, { recursive: true }) + .filter((item) => item.endsWith('.md') || item.endsWith('.mdx')); + + docFiles.forEach((relativePath) => { + const itemPath = path.join(docsPath, relativePath); + const content = fs.readFileSync(itemPath, 'utf8'); + const { data: frontmatter } = matter(content); + + if (frontmatter.plan) { + const docId = relativePath.replace(/\.(md|mdx)$/, ''); + planData[docId] = { + plan: frontmatter.plan, + title: frontmatter.title, + path: itemPath + }; } }); } - - if (fs.existsSync(docsPath)) { - processDirectory(docsPath); - } - + return { planData }; }, diff --git a/scripts/process-crds.js b/scripts/process-crds.js deleted file mode 100644 index 645e80556..000000000 --- a/scripts/process-crds.js +++ /dev/null @@ -1,92 +0,0 @@ -// scripts/process-crds.js -const fs = require('fs'); -const path = require('path'); -const yaml = require('js-yaml'); - -// Define the CRD directories and their output locations -const crdDirectories = [ - { - source: 'docs/apis-cli/query-api/yaml', - output: 'docs/apis-cli/query-api/yaml', - name: 'Query API' - }, - { - source: 'docs/apis-cli/spaces-api/yaml', - output: 'docs/apis-cli/spaces-api/yaml', - name: 'Spaces API' - }, - { - source: 'docs/apis-cli/testing-api/yaml', - output: 'docs/apis-cli/testing/yaml', - name: 'Testing API' - } -]; - -crdDirectories.forEach(dir => { - processDirectory(dir.source, dir.output, dir.name); -}); - -function processDirectory(sourceDir, outputDir, apiName) { - console.log(`Processing ${apiName} CRDs from: ${sourceDir}`); - - if (!fs.existsSync(sourceDir)) { - console.warn(`Source directory not found: ${sourceDir} - skipping`); - return; - } - - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - console.log(`Created output directory: ${outputDir}`); - } - - try { - const files = fs.readdirSync(sourceDir) - .filter(file => file.endsWith('.yaml') || file.endsWith('.yml')); - - console.log(`Found ${files.length} YAML files in ${sourceDir}`); - - if (files.length === 0) { - console.warn(`No YAML files found in ${sourceDir}`); - return; - } - - files.forEach(file => { - try { - const crdPath = path.join(sourceDir, file); - const crdContent = fs.readFileSync(crdPath, 'utf8'); - - try { - const crd = yaml.load(crdContent); - - if (!crd.kind || crd.kind !== 'CustomResourceDefinition') { - console.warn(`Skipping ${file}: Not a CRD`); - return; - } - - const kind = crd.spec.names.kind; - const group = crd.spec.group; - console.log(`Processing CRD: ${kind} (${group})`); - - } catch (yamlError) { - console.error(`Error parsing YAML in ${file}:`, yamlError.message); - return; - } - - const outputPath = path.join(outputDir, file); - - - fs.copyFileSync(crdPath, outputPath); - - console.log(`Copied ${crdPath} -> ${outputPath}`); - - } catch (error) { - console.error(`Error processing ${file}:`, error.message); - } - }); - - } catch (error) { - console.error(`Error reading directory ${sourceDir}:`, error.message); - } -} - -console.log('CRD processing complete!'); diff --git a/self-hosted-spaces-docs/howtos/spaces-management.md b/self-hosted-spaces-docs/howtos/spaces-management.md index 67fee932e..057ffbe9e 100644 --- a/self-hosted-spaces-docs/howtos/spaces-management.md +++ b/self-hosted-spaces-docs/howtos/spaces-management.md @@ -199,7 +199,7 @@ kubectl delete controlplane ctp1 [up-space-init]: /reference/cli-reference -[quickstart]: / +[quickstart]: /self-hosted-spaces/self-hosted-spaces-quickstart [aws]: /self-hosted-spaces/howtos/self-hosted-spaces-deployment [azure]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment [gcp]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment diff --git a/self-hosted-spaces_versioned_docs/upgrade-to-mcps.md b/self-hosted-spaces_versioned_docs/upgrade-to-mcps.md new file mode 100644 index 000000000..15e200be1 --- /dev/null +++ b/self-hosted-spaces_versioned_docs/upgrade-to-mcps.md @@ -0,0 +1,431 @@ +--- +title: Upgrade to Spaces +sidebar_position: 4 +description: A guide to how to update to a control plane in an Upbound Space +--- + +The Upbound migration [command][cli-command] that helps you update +your existing Crossplane control plane to a managed [Upbound Crossplane][uxp] +control plane in an [Upbound Space][spaces]. + +To upgrade from Crossplane to Upbound, you must: + +1. Export your existing Crossplane control plane configuration/state into an archive file. +2. Import the archive file into a control plane running in Upbound. +The migration tool is available in the [up CLI][up-cli] as +`up controlplane migration export` and `up controlplane migration import` commands. + +## Prerequisites + +Before you begin, you must have the following: +- The [up CLI][up-cli-1] version 0.23.0 or later. + +## Migration process + +To upgrade an existing Crossplane control plane to a control plane in Upbound, do the following: + +1. Run the `up controlplane migration export` command to export your existing Crossplane control plane configuration/state into an archive file: + + ```bash + up controlplane migration export --kubeconfig --output + ``` + + The command exports your existing Crossplane control plane configuration/state into an archive file. + +:::note +By default, the export command doesn't make any changes to your existing +Crossplane control plane state, leaving it intact. Use the +`--pause-before-export` flag to pause the reconciliation on managed resources +before exporting the archive file. + +This safety mechanism prevents the upgraded control plane from assuming resource +ownership before you're ready +::: + +2. Use the control plane [create command][create-command] to create a managed +control plane in Upbound: + + ```bash + up controlplane create my-controlplane + ``` + +3. Use [`up ctx`][up-ctx] to connect to the control plane created in the previous step: + + ```bash + up ctx "///my-controlplane" + ``` + + The command configures your local `kubeconfig` to connect to the control plane. + +4. Run the following command to import the archive file into the control plane: + + ```bash + up controlplane migration import --input + ``` + +:::note +The import command pauses reconciliation on managed resources, leaving the +control plane inactive. This lets you review the imported configuration before +activation. Use the `--unpause-after-import` flag to change the default behavior +and activate the control plane immediately after importing the archive file. +::: + +5. Review and validate the imported configuration/state. When you are ready, activate your managed + control plane by running the following command: + + ```bash + kubectl annotate managed --all crossplane.io/paused- + ``` + + At this point, you can delete the source Crossplane control plane. + +## CLI options + +### Filtering + +The migration tool captures the state of a Control Plane. The only filtering +supported is Kubernetes namespace and Kubernetes resource Type filtering. + +You can exclude namespaces using the `--exclude-namespaces` CLI option. This can prevent the CLI from including unwanted resources in the export. + +```bash +--exclude-namespaces=kube-system,kube-public,kube-node-lease,local-path-storage,... + +# A list of specific namespaces to exclude from the export. Defaults to 'kube-system', 'kube-public','kube-node-lease', and 'local-path-storage'. +``` + +You can exclude Kubernetes Resource types by using the `--exclude-resources` CLI option: + +```bash +--exclude-resources=EXCLUDE-RESOURCES,... + +# A list of resource types to exclude from the export in "resource.group" format. No resources are excluded by default. +``` + +For example, here's an example for excluding the CRDs installed by Crossplane functions (since they're not needed): + +```bash +up controlplane migration export \ + --exclude-resources=gotemplates.gotemplating.fn.crossplane.io,kclinputs.template.fn.crossplane.io +``` + +:::warning +You must specify resource names in lowercase "resource.group" format (for example, `gotemplates.gotemplating.fn.crossplane.io`). Using only the resource kind (for example, `GoTemplate`) isn't supported. +::: + +After export, users can also change the archive file to only include necessary resources. + +### Export non-Crossplane resources + +Use the `--include-extra-resources=` CLI option to select other CRD types to include in the export. + +### Set the kubecontext + +Currently `--context` isn't supported in the migration CLI. You should be able to use the `--kubeconfig` CLI option to use a file that's set to the correct context. For example: + +```bash +up controlplane migration export --kubeconfig +``` + +Use this in tandem with `up ctx` to export a control plane's kubeconfig: + +```bash +up ctx --kubeconfig ~/.kube/config + +# To list the current contet +up ctx . --kubeconfig ~/.kube/config +``` + +## Export archive + +The migration CLI exports an archive upon successful completion. Below is an example export of a control plane that excludes several CRD types and skips the confirmation prompt. A file gets written to the working directory, unless you select another output file: + +
+ +View the example export + +```bash +$ up controlplane migration export --exclude-resources=gotemplates.gotemplating.fn.crossplane.io,kclinputs.template.fn.crossplane.io --yes +Exporting control plane state... +✓ Scanning control plane for types to export... 121 types found! 👀 +✓ Exporting 121 Crossplane resources...60 resources exported! 📤 +✓ Exporting 3 native resources...8 resources exported! 📤 +✓ Archiving exported state... archived to "xp-state.tar.gz"! 📦 +``` + +
+ + +When an export occurs, a file named `xp-state.tar.gz` by default gets created in the working directory. You can unzip the file and all the contents of the export are all text YAML files. + +- Each CRD (for example `vpcs.ec2.aws.upbound.io`) gets its own directory +which contains: + - A `metadata.yaml` file that contains Kubernetes Object Metadata + - A list of Kubernetes Categories the resource belongs to +- A `cluster` directory that contains YAML manifests for all resources provisioned +using the CRD. + +Sample contents for a Cluster with a single `XNetwork` Composite from +[configuration-aws-network][configuration-aws-network] is show below: + + +
+ +View the example cluster content + +```bash +├── compositionrevisions.apiextensions.crossplane.io +│ ├── cluster +│ │ ├── kcl.xnetworks.aws.platform.upbound.io-4ca6a8a.yaml +│ │ └── xnetworks.aws.platform.upbound.io-9859a34.yaml +│ └── metadata.yaml +├── configurations.pkg.crossplane.io +│ ├── cluster +│ │ └── configuration-aws-network.yaml +│ └── metadata.yaml +├── deploymentruntimeconfigs.pkg.crossplane.io +│ ├── cluster +│ │ └── default.yaml +│ └── metadata.yaml +├── export.yaml +├── functions.pkg.crossplane.io +│ ├── cluster +│ │ ├── crossplane-contrib-function-auto-ready.yaml +│ │ ├── crossplane-contrib-function-go-templating.yaml +│ │ └── crossplane-contrib-function-kcl.yaml +│ └── metadata.yaml +├── internetgateways.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-xgl4q.yaml +│ └── metadata.yaml +├── mainroutetableassociations.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-t2qh7.yaml +│ └── metadata.yaml +├── namespaces +│ └── cluster +│ ├── crossplane-system.yaml +│ ├── default.yaml +│ └── upbound-system.yaml +├── providerconfigs.aws.upbound.io +│ ├── cluster +│ │ └── default.yaml +│ └── metadata.yaml +├── providerconfigusages.aws.upbound.io +│ ├── cluster +│ │ ├── 0a2a3ec6-ef13-45f9-9cf0-63af7f4a6b6b.yaml +...redacted +│ │ └── f7092b0f-3a78-4bfe-82c8-57e5085a9b11.yaml +│ └── metadata.yaml +├── providers.pkg.crossplane.io +│ ├── cluster +│ │ ├── upbound-provider-aws-ec2.yaml +│ │ └── upbound-provider-family-aws.yaml +│ └── metadata.yaml +├── routes.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-dt9cj.yaml +│ └── metadata.yaml +├── routetableassociations.ec2.aws.upbound.io +│ ├── cluster +│ │ ├── borrelli-backup-test-mr2sd.yaml +│ │ ├── borrelli-backup-test-ngq5h.yaml +│ │ ├── borrelli-backup-test-nrkgg.yaml +│ │ └── borrelli-backup-test-wq752.yaml +│ └── metadata.yaml +├── routetables.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-dv4mb.yaml +│ └── metadata.yaml +├── secrets +│ └── namespaces +│ ├── crossplane-system +│ │ ├── cert-token-signing-gateway-pub.yaml +│ │ ├── mxp-hostcluster-certs.yaml +│ │ ├── package-pull-secret.yaml +│ │ └── xgql-tls.yaml +│ └── upbound-system +│ └── aws-creds.yaml +├── securitygrouprules.ec2.aws.upbound.io +│ ├── cluster +│ │ ├── borrelli-backup-test-472f4.yaml +│ │ └── borrelli-backup-test-qftmw.yaml +│ └── metadata.yaml +├── securitygroups.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-w5jch.yaml +│ └── metadata.yaml +├── storeconfigs.secrets.crossplane.io +│ ├── cluster +│ │ └── default.yaml +│ └── metadata.yaml +├── subnets.ec2.aws.upbound.io +│ ├── cluster +│ │ ├── borrelli-backup-test-8btj6.yaml +│ │ ├── borrelli-backup-test-gbmrm.yaml +│ │ ├── borrelli-backup-test-m7kh7.yaml +│ │ └── borrelli-backup-test-nttt5.yaml +│ └── metadata.yaml +├── vpcs.ec2.aws.upbound.io +│ ├── cluster +│ │ └── borrelli-backup-test-7hwgh.yaml +│ └── metadata.yaml +└── xnetworks.aws.platform.upbound.io +├── cluster +│ └── borrelli-backup-test.yaml +└── metadata.yaml +43 directories, 87 files +``` + +
+ + +The `export.yaml` file contains metadata about the export, including the configuration of the export, Crossplane information, and what's included in the export bundle. + +
+ +View the export + +```yaml +version: v1alpha1 +exportedAt: 2025-01-06T17:39:53.173222Z +options: + excludedNamespaces: + - kube-system + - kube-public + - kube-node-lease + - local-path-storage + includedResources: + - namespaces + - configmaps + - secrets + excludedResources: + - gotemplates.gotemplating.fn.crossplane.io + - kclinputs.template.fn.crossplane.io +crossplane: + distribution: universal-crossplane + namespace: crossplane-system + version: 1.17.3-up.1 + featureFlags: + - --enable-provider-identity + - --enable-environment-configs + - --enable-composition-functions + - --enable-usages +stats: + total: 68 + nativeResources: + configmaps: 0 + namespaces: 3 + secrets: 5 + customResources: + amicopies.ec2.aws.upbound.io: 0 + amilaunchpermissions.ec2.aws.upbound.io: 0 + amis.ec2.aws.upbound.io: 0 + availabilityzonegroups.ec2.aws.upbound.io: 0 + capacityreservations.ec2.aws.upbound.io: 0 + carriergateways.ec2.aws.upbound.io: 0 + compositeresourcedefinitions.apiextensions.crossplane.io: 0 + compositionrevisions.apiextensions.crossplane.io: 2 + compositions.apiextensions.crossplane.io: 0 + configurationrevisions.pkg.crossplane.io: 0 + configurations.pkg.crossplane.io: 1 +...redacted +``` + +
+ +### Skipped resources + +Along with to the resources excluded via CLI options, the following resources aren't +included in the backup: + +- The `kube-root-ca.crt` ConfigMap, since this is cluster-specific +- Resources directly managed via Helm (ArgoCD's helm implementation, which templates +Helm resources and then applies them, get included in the backup). The migration creates the exclusion list by looking for: + - Any Resource with the label `"app.kubernetes.io/managed-by" == "Helm"` + - Kubernetes Secrets with the label prefix `helm.sh/release`. For example, `helm.sh/release.v1` +- Resources installed via a Crossplane package. These have an `ownerReference` with +a prefix `pkg.crossplane.io`. The expectation is that during import, the Crossplane Package Manager bears responsibility for installing the resources. +- Crossplane Locks: Any `Lock.pkg.crossplane.io` resource isn't included in the +export. + +## Restore + +The following is an example of a successful import run. At the end of the import, all Managed Resources are in a paused state. + +
+ +View the migration import + +```bash +$ up controlplane migration import +Importing control plane state... +✓ Reading state from the archive... Done! 👀 +✓ Importing base resources... 18 resources imported! 📥 +✓ Waiting for XRDs... Established! ⏳ +✓ Waiting for Packages... Installed and Healthy! ⏳ +✓ Importing remaining resources... 50 resources imported! 📥 +✓ Finalizing import... Done! 🎉 +``` + +
+ +Your scenario may involve migrating resources which already exist through other automation on the platform. When executing an import in these circumstances, the importer applies the new manifests to the cluster. If the resource already exists, the restore sets fields to what's in the backup. + +The importer restores all resources in the export archive. Managed Resources get imported with the `crossplane.io/paused: "true"` annotation set. Use the `--unpause-after-import` CLI argument to automatically un-pause resources that got +paused during backup, or remove the annotation manually. + +### Restore order + +The importer restores based on Kubernetes types. The restore order doesn't include parent/child relationships. + +Because Crossplane Composites create new Managed Resources if not present on the cluster, all +Claims, Composites and Managed Resources get imported in a paused state. You can un-pause them after the restore completes. + +The first step of import is installing Base Resources into the cluster. These resources (such has +packages and XRDs) must be ready before proceeding with the import. +Base Resources are: + +- Kubernetes Resources + - ConfigMaps + - Namespaces + - Secrets +- Crossplane Resources + - ControllerConfigs: `controllerconfigs.pkg.crossplane.io` + - DeploymentRuntimeConfigs: `deploymentruntimeconfigs.pkg.crossplane.io` + - StoreConfigs: `storeconfigs.secrets.crossplane.io` +- Crossplane Packages + - Providers: `providers.pkg.crossplane.io` + - Functions: `functions.pkg.crossplane.io` + - Configurations: `configurations.pkg.crossplane.io` + +Restore waits for the base resources to be `Ready` before moving on to the next step. Next, restore walks through the archive and restores all the manifests present. + +During import, the `crossplane.io/paused` annotation gets added to Managed Resources, Claims +and Composites. + +To manually un-pause managed resources after an import, remove the annotation by running: + +```bash +kubectl annotate managed --all crossplane.io/paused- +``` + +You can also run import again with the `--unpause-after-import` flag to remove the annotations. + +```bash +up controlplane migration import --unpause-after-import +``` + +### Restoring resource status + +The importer applies the status of all resources during import. The importer determines if the CRD version has a status field defined based on the stored CRD version. + + +[cli-command]: /reference/cli-reference +[up-cli]: /reference/cli-reference +[up-cli-1]: /manuals/cli/overview +[create-command]: /reference/cli-reference +[up-ctx]: /reference/cli-reference +[configuration-aws-network]: https://marketplace.upbound.io/configurations/upbound/configuration-aws-network +[spaces]: /self-hosted-spaces/overview +[uxp]: /manuals/uxp/overview diff --git a/self-hosted-spaces_versioned_docs/version-1.13/howtos/spaces-management.md b/self-hosted-spaces_versioned_docs/version-1.13/howtos/spaces-management.md index e4d504c65..55de9b3b3 100644 --- a/self-hosted-spaces_versioned_docs/version-1.13/howtos/spaces-management.md +++ b/self-hosted-spaces_versioned_docs/version-1.13/howtos/spaces-management.md @@ -201,7 +201,7 @@ kubectl delete controlplane ctp1 [up-space-init]: /reference/cli-reference -[quickstart]: / +[quickstart]: /self-hosted-spaces/self-hosted-spaces-quickstart [aws]: /self-hosted-spaces/howtos/self-hosted-spaces-deployment [azure]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment [gcp]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment diff --git a/self-hosted-spaces_versioned_docs/version-1.14/howtos/spaces-management.md b/self-hosted-spaces_versioned_docs/version-1.14/howtos/spaces-management.md index 824f9ee89..58acccd85 100644 --- a/self-hosted-spaces_versioned_docs/version-1.14/howtos/spaces-management.md +++ b/self-hosted-spaces_versioned_docs/version-1.14/howtos/spaces-management.md @@ -196,7 +196,7 @@ kubectl delete controlplane ctp1 [up-space-init]: /reference/cli-reference -[quickstart]: / +[quickstart]: /self-hosted-spaces/self-hosted-spaces-quickstart [aws]: /self-hosted-spaces/howtos/self-hosted-spaces-deployment [azure]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment [gcp]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment diff --git a/self-hosted-spaces_versioned_docs/version-1.15/howtos/spaces-management.md b/self-hosted-spaces_versioned_docs/version-1.15/howtos/spaces-management.md index 42501abf5..6444a1e13 100644 --- a/self-hosted-spaces_versioned_docs/version-1.15/howtos/spaces-management.md +++ b/self-hosted-spaces_versioned_docs/version-1.15/howtos/spaces-management.md @@ -195,7 +195,7 @@ kubectl delete controlplane ctp1 [up-space-init]: /reference/cli-reference -[quickstart]: / +[quickstart]: /self-hosted-spaces/self-hosted-spaces-quickstart [aws]: /self-hosted-spaces/howtos/self-hosted-spaces-deployment [azure]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment [gcp]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment diff --git a/self-hosted-spaces_versioned_docs/version-1.16/howtos/spaces-management.md b/self-hosted-spaces_versioned_docs/version-1.16/howtos/spaces-management.md index 67fee932e..057ffbe9e 100644 --- a/self-hosted-spaces_versioned_docs/version-1.16/howtos/spaces-management.md +++ b/self-hosted-spaces_versioned_docs/version-1.16/howtos/spaces-management.md @@ -199,7 +199,7 @@ kubectl delete controlplane ctp1 [up-space-init]: /reference/cli-reference -[quickstart]: / +[quickstart]: /self-hosted-spaces/self-hosted-spaces-quickstart [aws]: /self-hosted-spaces/howtos/self-hosted-spaces-deployment [azure]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment [gcp]:/self-hosted-spaces/howtos/self-hosted-spaces-deployment diff --git a/src/components/CardGrid.js b/src/components/CardGrid.js new file mode 100644 index 000000000..f983c3327 --- /dev/null +++ b/src/components/CardGrid.js @@ -0,0 +1,14 @@ +import React from 'react'; + +const CardGrid = ({ sections }) => ( +
+ {sections.map((section, index) => ( + +

{section.title}

+

{section.description}

+
+ ))} +
+); + +export default CardGrid; diff --git a/src/components/CopyPageMenu.js b/src/components/CopyPageMenu.js new file mode 100644 index 000000000..6476c3389 --- /dev/null +++ b/src/components/CopyPageMenu.js @@ -0,0 +1,124 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { ArrowUpRight, Bot, Check, ChevronDown, Code2, Copy, Sparkles } from 'lucide-react'; +import styles from './CopyPageMenu.module.css'; + +const RAW_SOURCE_URL = 'https://raw.githubusercontent.com/upbound/docs/main/'; + +const AI_TARGETS = [ + { + label: 'Open in ChatGPT', + description: 'Ask ChatGPT about this page', + icon: Bot, + urlFor: (prompt) => `https://chatgpt.com/?q=${encodeURIComponent(prompt)}`, + }, + { + label: 'Open in Claude', + description: 'Ask Claude about this page', + icon: Sparkles, + urlFor: (prompt) => `https://claude.ai/new?q=${encodeURIComponent(prompt)}`, + }, + { + label: 'Open in Cursor', + description: 'Add this page as context in Cursor', + icon: Code2, + urlFor: (prompt) => `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(prompt)}`, + }, +]; + +export default function CopyPageMenu({ source, getText }) { + const [open, setOpen] = useState(false); + const [copied, setCopied] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + if (!open) return undefined; + + const handlePointerDown = (event) => { + if (containerRef.current && !containerRef.current.contains(event.target)) { + setOpen(false); + } + }; + const handleKeyDown = (event) => { + if (event.key === 'Escape') setOpen(false); + }; + + document.addEventListener('mousedown', handlePointerDown); + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('mousedown', handlePointerDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open]); + + const handleCopy = () => { + const text = getText?.(); + if (!text) return; + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setOpen(false); + setTimeout(() => setCopied(false), 2000); + }); + }; + + const sourcePath = source?.replace(/^@site\//, ''); + const rawUrl = sourcePath && `${RAW_SOURCE_URL}${sourcePath}`; + const prompt = rawUrl && `Read ${rawUrl} so you can answer questions about it. Rely only on that page.`; + + return ( +
+
+ + +
+ + {open && ( +
+ + {prompt && + AI_TARGETS.map((target) => ( + setOpen(false)} + > + + + + + + {target.label} + + + {target.description} + + + ))} +
+ )} +
+ ); +} diff --git a/src/components/CopyPageMenu.module.css b/src/components/CopyPageMenu.module.css new file mode 100644 index 000000000..962f75a85 --- /dev/null +++ b/src/components/CopyPageMenu.module.css @@ -0,0 +1,182 @@ +.container { + position: relative; + flex-shrink: 0; +} + +.trigger { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--upbound-border-color); + border-radius: 0.375rem; + color: var(--brand-purple-600); + transition: border-color 0.15s ease, box-shadow 0.2s ease; +} + +.trigger:hover, +.trigger:focus-within { + border-color: var(--brand-purple-300); + box-shadow: 0 0 8px rgba(154, 94, 252, 0.25); +} + +.triggerCopy, +.triggerChevron { + display: inline-flex; + align-items: center; + border: none; + background: none; + color: inherit; + font-family: var(--ifm-font-family-base); + font-size: 0.8125rem; + font-weight: 500; + cursor: pointer; + transition: color 0.15s ease; +} + +.triggerCopy { + gap: 0.375rem; + padding: 0.375rem 0.625rem; + border-radius: 0.375rem 0 0 0.375rem; +} + +.triggerCopy:hover { + color: var(--brand-purple-700); +} + +.triggerChevron { + padding: 0.375rem 0.5rem; + border-left: 1px solid var(--upbound-border-color); + border-radius: 0 0.375rem 0.375rem 0; +} + +.triggerCopy:focus-visible, +.triggerChevron:focus-visible { + outline: 2px solid var(--brand-purple-500); + outline-offset: -2px; +} + +.chevron { + color: var(--brand-purple-400); +} + +.menu { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + z-index: 10; + min-width: 16rem; + padding: 0.5rem; + border: 1px solid var(--upbound-border-color); + border-radius: 0.75rem; + background-color: var(--ifm-background-color); +} + +.menuItem { + display: flex; + align-items: center; + gap: 0.625rem; + width: 100%; + padding: 0.5rem 0.625rem; + border: 1px solid transparent; + border-radius: 0.625rem; + background: none; + color: var(--font-color-dark); + font-family: var(--ifm-font-family-base); + text-align: left; + text-decoration: none; + cursor: pointer; + transition: border-color 0.15s ease; +} + +.menuItem + .menuItem { + margin-top: 0.125rem; +} + +.iconBadge { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 1.75rem; + height: 1.75rem; + border: 1px solid var(--upbound-border-color); + border-radius: 0.375rem; + background-color: var(--upbound-light-bg); + color: var(--font-color-medium); +} + +.itemText { + display: flex; + flex-direction: column; + gap: 0.125rem; + min-width: 0; +} + +.itemTitle { + display: inline-flex; + align-items: center; + gap: 0.25rem; + color: var(--font-color-dark); + font-size: 0.8125rem; + font-weight: 600; +} + +.externalIcon { + color: var(--font-color-light); +} + +.menuItem small { + color: var(--font-color-light); + font-size: 0.75rem; + font-weight: 400; +} + +.menuItem:hover { + border-color: var(--brand-purple-200); +} + +.menuItem:hover .itemTitle { + color: var(--brand-purple-600); +} + +.menuItem:hover .iconBadge { + border-color: var(--brand-purple-200); + color: var(--brand-purple-500); +} + +html[data-theme="dark"] .trigger { + color: var(--brand-purple-200); +} + +html[data-theme="dark"] .trigger:hover, +html[data-theme="dark"] .trigger:focus-within { + box-shadow: 0 0 8px rgba(196, 167, 255, 0.3); +} + +html[data-theme="dark"] .triggerCopy:hover { + color: var(--brand-purple-100); +} + +html[data-theme="dark"] .chevron { + color: var(--brand-purple-300); +} + +html[data-theme="dark"] .menu { + background-color: var(--brand-neutral-800); +} + +html[data-theme="dark"] .iconBadge { + background-color: var(--brand-neutral-700); +} + +html[data-theme="dark"] .menuItem:hover { + border-color: var(--brand-neutral-700); +} + +html[data-theme="dark"] .menuItem:hover .itemTitle { + color: var(--brand-purple-300); +} + +html[data-theme="dark"] .menuItem:hover .iconBadge { + border-color: var(--brand-purple-300); + color: var(--brand-purple-300); +} diff --git a/src/components/GetStartedCallout.js b/src/components/GetStartedCallout.js deleted file mode 100644 index ce383a1e0..000000000 --- a/src/components/GetStartedCallout.js +++ /dev/null @@ -1,249 +0,0 @@ -// src/components/GetStartedCallout.js -import React, { useState, useEffect } from 'react'; - -const styles = ` -.get-started-section { - background: linear-gradient(135deg, var(--brand-neutral-700) 0%, var(--brand-neutral-800) 100%); - color: white; - padding: 2rem; - border-radius: 12px; - margin: 2rem 0; - box-shadow: var(--upbound-card-shadow-hover); -} -.get-started-section h2 { - margin-top: 0; - font-size: 1.8rem; - font-weight: var(--ifm-heading-font-weight); - font-family: var(--ifm-heading-font-family); - color: white; -} -.get-started-section ul { - font-size: 1.1rem; - margin: 1.5rem 0; - font-family: var(--ifm-font-family-base); -} -.get-started-section li { - margin-bottom: 0.8rem; - color: white; -} -.get-started-section a { - color: var(--brand-purple-200); - text-decoration: underline; - transition: all 0.2s ease; -} -.get-started-section a:hover { - color: var(--brand-purple-100); - text-decoration: none; -} -.code-block { - background: var(--brand-purple-900); - border: 1px solid var(--brand-neutral-700); - border-radius: 8px; - padding: 0; - margin: 1rem 0; - font-family: var(--ifm-font-family-monospace); - font-size: var(--ifm-code-font-size); - position: relative; - overflow: hidden; -} -.code-content { - padding: 1rem; - margin: 0; - background: none; - border: none; - color: var(--brand-neutral-200); - font-family: inherit; - font-size: inherit; -} -.copy-button { - position: absolute; - top: 0.5rem; - right: 0.5rem; - background: var(--brand-neutral-700); - border: 1px solid var(--brand-neutral-600); - border-radius: 6px; - color: var(--brand-neutral-200); - padding: 0.375rem 0.75rem; - font-size: 0.75rem; - font-family: var(--ifm-font-family-base); - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 0.25rem; -} -.copy-button:hover { - background: var(--brand-neutral-600); - border-color: var(--brand-neutral-500); - color: white; -} -.copy-icon { - width: 12px; - height: 12px; -} -.installation-note { - font-style: italic; - opacity: 0.9; - margin-top: 0.5rem; - font-family: var(--ifm-font-family-base); - color: rgba(255,255,255,0.9); -} - -.tip-box { - background: rgba(154, 94, 252, 0.05); - border-left: 4px solid var(--brand-purple-500); - padding: 1rem; - margin: 1.5rem 0; - border-radius: .75rem; -} - -.tip-label { - font-weight: var(--ifm-font-weight-bold); - color: var(--brand-purple-600); - margin-bottom: 0.5rem; - font-family: var(--ifm-font-family-base); -} - -/* Dark mode */ -html[data-theme='dark'] .tip-box { - background: rgba(196, 167, 255, 0.1); - border-left-color: var(--brand-purple-300); -} - -html[data-theme='dark'] .tip-label { - color: var(--brand-purple-300); -}html[data-theme='light'] .get-started-section { - background: var(--brand-neutral-200); - color: var(--brand-neutral-900); -} -html[data-theme='light'] .get-started-section h2 { - color: var(--brand-neutral-900); -} -html[data-theme='light'] .get-started-section li { - color: var(--brand-neutral-900); -} -html[data-theme='light'] .get-started-section a { - color: var(--brand-purple-600); -} -html[data-theme='light'] .get-started-section a:hover { - color: var(--brand-purple-700); -} -html[data-theme='light'] .installation-note { - color: var(--brand-neutral-700); -} -html[data-theme='light'] .tip-label { - color: var(--brand-purple-600); -} -html[data-theme='light'] .code-block { - background: var(--brand-neutral-300); -} -html[data-theme='light'] .code-content { - color: var(--brand-neutral-900); -} -html[data-theme='light'] .copy-button { - background: var(--brand-neutral-500); - color: var(--brand-neutral-200); -} -html[data-theme='light'] .copy-button:hover { - background: var(--brand-neutral-600); - color: var(--brand-neutral-000); -} -@media (max-width: 768px) { - .get-started-section { - margin: 1rem 0; - padding: 1.5rem; - } - .get-started-section h2 { - font-size: 1.5rem; - } - .get-started-section ul { - font-size: 1rem; - } - .code-block { - font-size: 0.85rem; - } -} -`; - -function CopyIcon({ size = 12 }) { - return ( - - - - - ); -} - -function CheckIcon({ size = 12 }) { - return ( - - - - ); -} - -export function GetStarted() { - const [copied, setCopied] = useState(false); - - useEffect(() => { - // Inject styles into document head - const styleSheet = document.createElement("style"); - styleSheet.innerText = styles; - document.head.appendChild(styleSheet); - - return () => { - // Clean up styles when component unmounts - document.head.removeChild(styleSheet); - }; - }, []); - - const copyToClipboard = async () => { - try { - await navigator.clipboard.writeText('curl -sL "https://cli.upbound.io" | sh'); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch (err) { - console.error('Failed to copy text: ', err); - } - }; - - return ( -
-

🚀 Install the Upbound CLI

-
    -
  • Install the up CLI to gain access to all Upbound's tooling on your machine.
  • -
- -
-
- curl -sL "https://cli.upbound.io" | sh -
- -
- -
- Find more installation methods on the Up CLI installation guide. -
- -
- ); -} diff --git a/src/components/GetUpboundHero.js b/src/components/GetUpboundHero.js new file mode 100644 index 000000000..eef84db5c --- /dev/null +++ b/src/components/GetUpboundHero.js @@ -0,0 +1,48 @@ +import React from 'react'; +import HeaderSVG from '@site/static/img/header.svg'; +import styles from './GetUpboundHero.module.css'; + +const GetUpboundHero = () => { + return ( +
+
+ ); +}; + +export default GetUpboundHero; diff --git a/src/components/GetUpboundHero.module.css b/src/components/GetUpboundHero.module.css new file mode 100644 index 000000000..22a236e63 --- /dev/null +++ b/src/components/GetUpboundHero.module.css @@ -0,0 +1,141 @@ +.hero { + position: relative; + display: grid; + grid-template-columns: 1.1fr 1fr; + align-items: center; + gap: 2.5rem; + overflow: hidden; + margin: 0 0 2.5rem; + padding: 2.75rem; + border-radius: 1rem; + background: linear-gradient( + 135deg, + var(--brand-purple-900) 0%, + var(--brand-neutral-800) 100% + ); +} + +.mark { + position: absolute; + top: 50%; + right: -8%; + width: 26rem; + height: auto; + transform: translateY(-50%); + opacity: 0.16; + pointer-events: none; +} + +.text { + position: relative; + z-index: 1; +} + +.eyebrow { + margin: 0 0 0.75rem; + color: var(--brand-purple-200); + font-family: var(--ifm-font-family-monospace); + font-size: 0.8rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.title { + margin: 0 0 1rem; + color: white; + font-family: var(--ifm-heading-font-family); + font-size: 2.25rem; + font-weight: 550; + line-height: 1.15; +} + +.description { + margin: 0; + max-width: 34rem; + color: rgba(255, 255, 255, 0.85); + font-size: 1.0625rem; + line-height: 1.6; +} + +.terminal { + position: relative; + z-index: 1; + overflow: hidden; + border: 1px solid var(--brand-neutral-700); + border-radius: 0.75rem; + background-color: var(--brand-neutral-800); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35); +} + +.terminalHeader { + display: flex; + gap: 0.4rem; + padding: 0.75rem 1rem; + background-color: var(--brand-neutral-700); + border-bottom: 1px solid var(--brand-neutral-600); +} + +.dot { + width: 0.65rem; + height: 0.65rem; + border-radius: 50%; +} + +.dotRed { + background-color: var(--brand-accent-red); +} + +.dotYellow { + background-color: var(--brand-accent-yellow); +} + +.dotGreen { + background-color: var(--brand-accent-green); +} + +.terminalBody { + padding: 1.25rem; + font-family: var(--ifm-font-family-monospace); + font-size: 0.8125rem; + line-height: 1.7; +} + +.command, +.output { + margin: 0 0 0.5rem; +} + +.command { + color: var(--brand-accent-green); +} + +.output { + color: var(--brand-neutral-300); +} + +@media (max-width: 900px) { + .hero { + grid-template-columns: 1fr; + padding: 2rem; + } + + .mark { + right: -20%; + width: 20rem; + } +} + +@media (max-width: 520px) { + .hero { + padding: 1.5rem; + border-radius: 0.75rem; + } + + .title { + font-size: 1.75rem; + } + + .terminalBody { + font-size: 0.75rem; + } +} diff --git a/src/components/GlobalLanguageSelector.js b/src/components/GlobalLanguageSelector.js index a13f39dd6..2b012fa52 100644 --- a/src/components/GlobalLanguageSelector.js +++ b/src/components/GlobalLanguageSelector.js @@ -1,33 +1,91 @@ -import React, { useState, useEffect, createContext, useContext } from 'react'; +import React, { useState, useEffect, useCallback, createContext, useContext } from 'react'; // Simple context for language state const LanguageContext = createContext(); +// Display labels for values that don't look right as plain toUpperCase() +const DISPLAY_LABELS = { 'kcl': 'KCL', 'python': 'Python', 'go': 'Go', 'go-templating': 'Go Templating', 'azure' : 'Azure', }; + +function displayLabel(value) { + return DISPLAY_LABELS[value] || value.toUpperCase(); +} + +// Fixed display order for language pills; anything unlisted sorts alphabetically after. +const LANGUAGE_ORDER = ['kcl', 'python', 'go', 'go-templating']; + +function sortLanguages(values) { + return [...values].sort((a, b) => { + const ia = LANGUAGE_ORDER.indexOf(a); + const ib = LANGUAGE_ORDER.indexOf(b); + if (ia === -1 && ib === -1) return a.localeCompare(b); + if (ia === -1) return 1; + if (ib === -1) return -1; + return ia - ib; + }); +} + +// Ref-counted so an option disappears once the last CodeBlock using it +// unmounts. LanguageProvider lives at the site root (src/theme/Root.js) and +// survives client-side navigation, so a plain Set that only ever grew meant +// options from a previously visited page stuck around forever -- including a +// stale *selected* language/cloud the new page never registers, which +// silently hid every CodeBlock on the page (none can match a selection that +// doesn't exist here). +export function useRefCountedSet() { + const [counts, setCounts] = useState(new Map()); + + const register = useCallback((value) => { + setCounts(prev => { + const next = new Map(prev); + next.set(value, (next.get(value) || 0) + 1); + return next; + }); + return () => { + setCounts(prev => { + const count = prev.get(value) || 0; + const next = new Map(prev); + if (count <= 1) { + next.delete(value); + } else { + next.set(value, count - 1); + } + return next; + }); + }; + }, []); + + const values = new Set(counts.keys()); + return [values, register]; +} + export function LanguageProvider({ children }) { - const [selectedLanguage, setSelectedLanguage] = useState('kcl'); - const [selectedCloud, setSelectedCloud] = useState('aws'); - const [availableLanguages, setAvailableLanguages] = useState(new Set()); - const [availableClouds, setAvailableClouds] = useState(new Set()); + const [selectedLanguage, setSelectedLanguage] = useState(null); + const [selectedCloud, setSelectedCloud] = useState(null); + const [availableLanguages, registerLanguage] = useRefCountedSet(); + const [availableClouds, registerCloud] = useRefCountedSet(); useEffect(() => { - const savedLang = localStorage.getItem('selected-language') || 'kcl'; - const savedCloud = localStorage.getItem('selected-cloud') || 'aws'; - setSelectedLanguage(savedLang); - setSelectedCloud(savedCloud); + setSelectedLanguage(localStorage.getItem('selected-language')); + setSelectedCloud(localStorage.getItem('selected-cloud')); }, []); - // Auto-select first available if current selection isn't available + // Auto-select first available (alphabetically, matching pill order) if + // current selection isn't available on this page -- including the initial + // null state, so a first-time visitor lands on the first pill instead of a + // hardcoded default. Must still correct when only one option is + // registered -- with only one choice there's no ambiguity, so there's no + // reason to leave a mismatch uncorrected. useEffect(() => { - if (availableLanguages.size > 1 && !availableLanguages.has(selectedLanguage)) { - const firstLang = Array.from(availableLanguages)[0]; + if (availableLanguages.size > 0 && !availableLanguages.has(selectedLanguage)) { + const firstLang = sortLanguages(availableLanguages)[0]; setSelectedLanguage(firstLang); localStorage.setItem('selected-language', firstLang); } }, [availableLanguages, selectedLanguage]); useEffect(() => { - if (availableClouds.size > 1 && !availableClouds.has(selectedCloud)) { - const firstCloud = Array.from(availableClouds)[0]; + if (availableClouds.size > 0 && !availableClouds.has(selectedCloud)) { + const firstCloud = Array.from(availableClouds).sort()[0]; setSelectedCloud(firstCloud); localStorage.setItem('selected-cloud', firstCloud); } @@ -43,14 +101,6 @@ export function LanguageProvider({ children }) { localStorage.setItem('selected-cloud', cloud); }; - const registerLanguage = (language) => { - setAvailableLanguages(prev => new Set([...prev, language])); - }; - - const registerCloud = (cloud) => { - setAvailableClouds(prev => new Set([...prev, cloud])); - }; - return ( {availableClouds.size > 0 && (
- - +
)} - + {availableLanguages.size > 0 && (
- - +
)} @@ -133,10 +187,15 @@ export default function GlobalLanguageSelector() { export function CodeBlock({ cloud, language, children }) { const { selectedLanguage, selectedCloud, registerLanguage, registerCloud } = useLanguageContext(); - // Register this content's options + // Register this content's options, and unregister on unmount so switching + // pages doesn't leave stale options (or a stale selection) behind. useEffect(() => { - if (language) registerLanguage(language); - if (cloud) registerCloud(cloud); + const unregisterLanguage = language ? registerLanguage(language) : undefined; + const unregisterCloud = cloud ? registerCloud(cloud) : undefined; + return () => { + unregisterLanguage?.(); + unregisterCloud?.(); + }; }, [language, cloud, registerLanguage, registerCloud]); const shouldShow = (!cloud || cloud === selectedCloud) && diff --git a/src/components/GuidesCards.js b/src/components/GuidesCards.js index 557728b7b..117b16090 100644 --- a/src/components/GuidesCards.js +++ b/src/components/GuidesCards.js @@ -1,110 +1,24 @@ import React from 'react'; +import CardGrid from './CardGrid'; -const GuidesCards = () => { - const sections = [ - { - title: 'Intelligent Control Planes', - description: 'Advanced control plane patterns with dynamic resource composition, log analysis, and database scaling.', - link: '/guides/intelligent-control-planes/' - }, - { - title: 'Solutions', - description: 'Complete platform deployments including general IDP architecture and Upbound platform reference implementations.', - link: '/guides/solutions/get-started/' - }, - { - title: 'Use Cases', - description: 'End-to-end scenarios for applications, cloud resources, databases as a service, and managed resources.', - link: '/guides/usecases' - } - ]; +const sections = [ + { + title: 'Intelligent Control Planes', + description: 'Advanced control plane patterns with dynamic resource composition, log analysis, and database scaling.', + link: '/guides/intelligent-control-planes/' + }, + { + title: 'Solutions', + description: 'Complete platform deployments including general IDP architecture and Upbound platform reference implementations.', + link: '/guides/solutions/get-started/' + }, + { + title: 'Use Cases', + description: 'End-to-end scenarios for applications, cloud resources, databases as a service, and managed resources.', + link: '/guides/usecases' + } +]; - return ( - <> - - -
- {sections.map((section, index) => ( - -

- {section.title} -

-

- {section.description} -

-
- ))} -
- - ); -}; +const GuidesCards = () => ; export default GuidesCards; diff --git a/src/components/ManualsCards.js b/src/components/ManualsCards.js index 97fec27a9..f5be77885 100644 --- a/src/components/ManualsCards.js +++ b/src/components/ManualsCards.js @@ -1,135 +1,44 @@ import React from 'react'; +import CardGrid from './CardGrid'; -const ManualsCards = () => { - const sections = [ - { - title: 'Upbound Crossplane (UXP)', - description: 'Enterprise-grade Crossplane distribution with enhanced compositions, functions, operations, and package management.', - link: '/manuals/uxp/overview' - }, - { - title: 'Cloud Spaces', - description: 'Managed Crossplane control planes in the Upbound cloud environment.', - link: '/cloud-spaces/overview/' - }, - { - title: 'Self-hosted Spaces', - description: 'Managed Crossplane control planes as a self-hosted deployment.', - link: '/self-hosted-spaces/overview/' - }, - { - title: 'CLI', - description: 'Command-line tools for managing Upbound configurations, contexts, and project tooling.', - link: '/manuals/cli/overview' - }, - { - title: 'Console', - description: 'Web-based management interface with MCP Query API and self-service capabilities.', - link: '/manuals/console/upbound-console/' - }, - { - title: 'Official Packages', - description: 'Production-ready provider packages for cloud and infrastructure platforms with authentication and migration guides.', - link: '/manuals/packages/overview' - }, - { - title: 'Marketplace', - description: 'Package discovery, publishing, and repository management platform for internal and public distribution.', - link: '/manuals/marketplace/overview' - }, - { - title: 'Platform', - description: 'Identity management, RBAC, organizations, teams, and SSO integration for enterprise deployments.', - link: './platform/overview' - } - ]; - - return ( - <> - - -
- {sections.map((section, index) => ( - -

- {section.title} -

-

- {section.description} -

-
- ))} -
- - ); -}; +const sections = [ + { + title: 'Upbound Crossplane (UXP)', + description: 'Enterprise-grade Crossplane distribution with enhanced compositions, functions, operations, and package management.', + link: '/manuals/uxp/overview' + }, + { + title: 'Cloud Spaces', + description: 'Managed Crossplane control planes in the Upbound cloud environment.', + link: '/cloud-spaces/overview/' + }, + { + title: 'Self-hosted Spaces', + description: 'Managed Crossplane control planes as a self-hosted deployment.', + link: '/self-hosted-spaces/overview/' + }, + { + title: 'Platform', + description: 'Identity management, RBAC, organizations, teams, and SSO integration for enterprise deployments.', + link: './platform/overview' + }, + { + title: 'CLI', + description: 'Command-line tools for managing Upbound configurations, contexts, and project tooling.', + link: '/manuals/cli/overview' + }, + { + title: 'Official Packages', + description: 'Production-ready provider packages for cloud and infrastructure platforms with authentication and migration guides.', + link: '/manuals/packages/overview' + }, + { + title: 'Marketplace', + description: 'Package discovery, publishing, and repository management platform for internal and public distribution.', + link: '/manuals/marketplace/overview' + }, +]; + +const ManualsCards = () => ; export default ManualsCards; diff --git a/src/components/ReferenceCards.js b/src/components/ReferenceCards.js index 627cd6c50..17e9ebec7 100644 --- a/src/components/ReferenceCards.js +++ b/src/components/ReferenceCards.js @@ -1,130 +1,44 @@ import React from 'react'; +import CardGrid from './CardGrid'; -const ReferenceCards = () => { - const sections = [ - { - title: 'APIs', - description: 'Crossplane API, Query API, Spaces API, UXP API, and Project & Testing API with CRD specifications.', - link: '/reference/apis' - }, - { - title: 'CLI Reference', - description: 'Command-line interface documentation with complete command reference and usage examples.', - link: '/reference/cli-reference' - }, - { - title: 'Spaces Helm Reference', - description: 'Helm chart configuration and deployment reference documentation for Upbound Spaces.', - link: '/self-hosted-spaces/reference/' - }, - { - title: 'UXP Helm Reference', - description: 'Helm chart configuration and deployment reference documentation for UXP.', - link: '/reference/uxp-helm-reference' - }, - { - title: 'Release Notes', - description: 'Latest updates and changes for Spaces, Managed Control Plane Connector, and Up CLI with version history.', - link: '/reference/release-notes' - }, - { - title: 'CVE Policy', - description: 'How Upbound identifies, prioritizes, and remediates CVEs across the Upbound Platform.', - link: '/reference/cve-policy' - }, - { - title: 'Usage & Operations', - description: 'Feature lifecycle, licensing, telemetry, support information, and VS Code extensions.', - link: '/reference/usage' - } - ]; +const sections = [ + { + title: 'APIs', + description: 'Crossplane API, Query API, Spaces API, UXP API, and Project & Testing API with CRD specifications.', + link: '/reference/apis' + }, + { + title: 'CLI Reference', + description: 'Command-line interface documentation with complete command reference and usage examples.', + link: '/reference/cli-reference' + }, + { + title: 'Spaces Helm Reference', + description: 'Helm chart configuration and deployment reference documentation for Upbound Spaces.', + link: '/self-hosted-spaces/reference/' + }, + { + title: 'UXP Helm Reference', + description: 'Helm chart configuration and deployment reference documentation for UXP.', + link: '/reference/uxp-helm-reference' + }, + { + title: 'Release Notes', + description: 'Latest updates and changes for Spaces, Managed Control Plane Connector, and Up CLI with version history.', + link: '/reference/release-notes' + }, + { + title: 'CVE Policy', + description: 'How Upbound identifies, prioritizes, and remediates CVEs across the Upbound Platform.', + link: '/reference/cve-policy' + }, + { + title: 'Usage & Operations', + description: 'Feature lifecycle, licensing, telemetry, support information, and VS Code extensions.', + link: '/reference/usage' + } +]; - return ( - <> - - -
- {sections.map((section, index) => ( - -

- {section.title} -

-

- {section.description} -

-
- ))} -
- - ); -}; +const ReferenceCards = () => ; export default ReferenceCards; diff --git a/src/components/VersionSelector.js b/src/components/VersionSelector.js index b1bf4877c..09112e5c7 100644 --- a/src/components/VersionSelector.js +++ b/src/components/VersionSelector.js @@ -1,120 +1,75 @@ import React, { createContext, useState, useContext, useEffect } from 'react'; +import { useRefCountedSet } from './GlobalLanguageSelector'; -let globalSelectedVersion = 'v1'; -let globalAvailableVersions = new Set(); -let globalInitialized = false; -let globalUpdateCallbacks = new Set(); - -// Create the context const VersionContext = createContext(); -// Create a provider component export function VersionProvider({ children }) { const [selectedVersion, setSelectedVersion] = useState('v1'); - const [availableVersions, setAvailableVersions] = useState(new Set()); - const [initialized, setInitialized] = useState(false); + const [availableVersions, registerVersion] = useRefCountedSet(); + + useEffect(() => { + setSelectedVersion(localStorage.getItem('selected-version') || 'v1'); + }, []); + + useEffect(() => { + if (availableVersions.size > 0 && !availableVersions.has(selectedVersion)) { + const firstVersion = Array.from(availableVersions)[0]; + setSelectedVersion(firstVersion); + localStorage.setItem('selected-version', firstVersion); + } + }, [availableVersions, selectedVersion]); + + const updateVersion = (version) => { + setSelectedVersion(version); + localStorage.setItem('selected-version', version); + }; return ( - + {children} ); } -// Custom hook to use the version context export function useVersionContext() { return useContext(VersionContext); } - -const initializeFromStorage = () => { - if (typeof window !== 'undefined' && !globalInitialized) { - const savedVersion = localStorage.getItem('selected-version') || 'v1'; - globalSelectedVersion = savedVersion; - globalInitialized = true; - } -}; - -const updateGlobalVersion = (version) => { - globalSelectedVersion = version; - if (typeof window !== 'undefined') { - localStorage.setItem('selected-version', version); - } - globalUpdateCallbacks.forEach(callback => callback()); -}; - -const registerGlobalVersion = (version) => { - globalAvailableVersions.add(version); - globalUpdateCallbacks.forEach(callback => callback()); -}; - -// Single component that does everything export default function VersionSelector({ version, children }) { - const [, forceUpdate] = useState({}); - - // Initialize on first mount - useEffect(() => { - initializeFromStorage(); - - // Register for updates - const callback = () => forceUpdate({}); - globalUpdateCallbacks.add(callback); - - return () => globalUpdateCallbacks.delete(callback); - }, []); + const { selectedVersion, availableVersions, updateVersion, registerVersion } = useVersionContext(); - // Register this version if provided useEffect(() => { - if (version) { - registerGlobalVersion(version); - } - }, [version]); + if (version) return registerVersion(version); + }, [version, registerVersion]); - // Auto-select first available if current selection isn't available - useEffect(() => { - if (globalAvailableVersions.size > 1 && !globalAvailableVersions.has(globalSelectedVersion)) { - const firstVersion = Array.from(globalAvailableVersions)[0]; - updateGlobalVersion(firstVersion); - } - }); + if (version) { + return version === selectedVersion ? <>{children} : null; + } - // If no version prop, show selector - if (!version) { - if (globalAvailableVersions.size === 0) { - return null; - } - - return ( -
-
-
- - + if (availableVersions.size === 0) { + return null; + } + + return ( +
+
+
+ Version +
+ {Array.from(availableVersions).sort().map(v => ( + + ))}
- ); - } - - // If version prop provided, show/hide content - return version === globalSelectedVersion ? <>{children} : null; +
+ ); } diff --git a/src/css/custom.css b/src/css/custom.css index 5f585514d..affceb1e8 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -323,14 +323,14 @@ article ol:not([role]) { list-style-type: decimal; } -article a:not(.card):not(.menu__link):not(.navbar__item):not(.table-of-contents__link):not(.cta-button) { +article a:not(.card):not(.menu__link):not(.navbar__item):not(.table-of-contents__link):not(.cta-button):not(.menu-item-link) { color: var(--grape); text-decoration: none; border-bottom: 1px solid var(--grape); transition: all 0.2s ease; } -article a:not(.card):not(.menu__link):not(.navbar__item):not(.table-of-contents__link):not(.cta-button):hover { +article a:not(.card):not(.menu__link):not(.navbar__item):not(.table-of-contents__link):not(.cta-button):not(.menu-item-link):hover { color: var(--link-hover-color); border-bottom-color: var(--link-hover-color); border-bottom-width: 2px; @@ -951,666 +951,6 @@ html[data-theme="light"] .footer-links a:hover { html[data-theme="light"] .footer-copyright { color: var(--brand-neutral-500); } - -/* ============================================================ */ -/* LANDING PAGE COMPONENTS */ -/* ============================================================ */ - -/* Landing Page Layout */ -.landing-page { - min-height: calc(100vh - var(--ifm-navbar-height)); - animation: fadeInUp 0.6s ease-out; - background: linear-gradient( - 135deg, - var(--brand-neutral-100) 0%, - var(--brand-purple-50) 100% - ) !important; -} - -.landing-page .container { - max-width: 1200px; - margin: 0 auto; - padding: 0 1.5rem; -} - -/* Dark mode landing page */ -html[data-theme="dark"] .landing-page { - background: linear-gradient( - 135deg, - var(--brand-purple-900) 0%, - var(--brand-neutral-800) 100% - ) !important; -} - -/* Hero Section */ -.hero-section { - position: relative; - z-index: 1; - padding: 3rem 0; - display: flex; - align-items: center; -} - -/* Keep hero section the same in light mode */ -html[data-theme="light"] .hero-section { - background: linear-gradient( - 135deg, - var(--brand-purple-900) 0%, - var(--brand-neutral-800) 100% - ) !important; -} - -.hero-content { - max-width: 1200px; - margin: 0 auto; - padding: 0 1.5rem; - width: 100%; -} - -.hero-text { - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - gap: 1.5rem; -} - -.hero-svg-container { - margin-bottom: 0; - display: flex; - justify-content: center; -} - -.hero-svg { - width: 100%; - max-width: 600px; - height: auto; - filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); - transition: transform 0.3s ease; -} - -.hero-svg:hover { - transform: scale(1.02); -} - -.hero-title { - font-size: 44px; - font-weight: 550; - color: white !important; - margin: 0; - line-height: 1.1; - font-family: var(--ifm-heading-font-family); - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); -} - -.hero-subtitle { - display: block; - font-size: 24px; - font-weight: 700; - color: var(--brand-purple-100) !important; - margin-top: 0.5rem; -} - -.hero-description { - font-size: 1.25rem; - color: rgba(255, 255, 255, 0.9) !important; - margin: 0; - max-width: 48rem; - line-height: 1.6; -} - -.hero-buttons { - display: flex; - flex-direction: row; - gap: 1rem; - align-items: center; - justify-content: center; -} - -.hero-button { - padding: 1rem 2rem; - border-radius: 0.75rem; - font-weight: 600; - font-size: 1.125rem; - transition: all 0.3s ease; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - border: none; - cursor: pointer; - min-width: 200px; - text-decoration: none; -} - -.hero-button--primary { - background-color: white; - color: var(--brand-purple-600); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); -} - -.hero-button--primary:hover { - background-color: var(--brand-neutral-100); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); - color: var(--brand-purple-600); - text-decoration: none; -} - -.hero-button--secondary { - border: 2px solid rgba(255, 255, 255, 0.8); - color: white; - background-color: transparent; -} - -.hero-button--secondary:hover { - background-color: rgba(255, 255, 255, 0.1); - transform: translateY(-2px); - color: white; - text-decoration: none; - border-color: white; -} - -.button-icon { - width: 1.25rem; - height: 1.25rem; - transition: transform 0.2s ease; -} - -.hero-button:hover .button-icon { - transform: translateX(2px); -} - -/* Quick Start Section */ -.quickstart-section { - padding: 4rem 0; - color: white; -} - -.quickstart-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 3rem; - align-items: center; -} - -.quickstart-title { - font-size: 2.5rem; - font-weight: 500; - margin-bottom: 1.5rem; - font-family: var(--ifm-heading-font-family); - color: white !important; -} - -.quickstart-description { - font-size: 1.25rem; - color: var(--brand-neutral-200) !important; - margin-bottom: 2rem; - line-height: 1.6; -} - -.quickstart-buttons { - display: flex; - flex-direction: row; - gap: 1rem; -} - -.quickstart-button { - padding: 1rem 2rem; - border-radius: 0.75rem; - font-weight: 600; - font-size: 1.125rem; - transition: all 0.3s ease; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - border: none; - cursor: pointer; - min-width: 200px; - text-decoration: none; -} - -.quickstart-button--primary { - background-color: var(--brand-purple-500); - color: white; -} - -.quickstart-button--primary:hover { - background-color: var(--brand-purple-400); - transform: translateY(-2px); - color: white; - text-decoration: none; -} - -.quickstart-button--secondary { - border: 2px solid var(--brand-purple-300); - color: var(--brand-purple-100); - background-color: transparent; -} - -.quickstart-button--secondary:hover { - background-color: rgba(196, 167, 255, 0.1); - transform: translateY(-2px); - color: var(--brand-purple-100); - text-decoration: none; -} - -/* Light mode quickstart adjustments */ -html[data-theme="light"] .quickstart-title { - color: var(--brand-neutral-900) !important; -} - -html[data-theme="light"] .quickstart-description { - color: var(--brand-neutral-700) !important; -} - -html[data-theme="light"] .quickstart-button--secondary { - border-color: var(--brand-purple-600); - color: var(--brand-purple-700); -} - -html[data-theme="light"] .quickstart-button--secondary:hover { - background-color: rgba(154, 94, 252, 0.1); - color: var(--brand-purple-800); -} - -/* Terminal Window */ -.terminal-window { - background-color: var(--brand-neutral-800); - border-radius: 0.75rem; - border: 1px solid var(--brand-neutral-700); - overflow: hidden; - box-shadow: var(--upbound-card-shadow); -} - -.terminal-header { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 1rem 1.5rem; - background-color: var(--brand-neutral-700); - border-bottom: 1px solid var(--brand-neutral-600); -} - -.terminal-controls { - display: flex; - gap: 0.5rem; -} - -.terminal-dot { - width: 0.75rem; - height: 0.75rem; - border-radius: 50%; -} - -.terminal-dot--red { - background-color: var(--brand-accent-red); -} -.terminal-dot--yellow { - background-color: var(--brand-accent-yellow); -} -.terminal-dot--green { - background-color: var(--brand-accent-green); -} - -.terminal-title { - color: var(--brand-neutral-400); - font-size: 0.875rem; - margin-left: 0.5rem; -} - -.terminal-content { - padding: 1.5rem; - font-family: var(--ifm-font-family-monospace); - font-size: 0.875rem; - line-height: 1.6; -} - -.terminal-line { - margin-bottom: 0.5rem; -} - -.terminal-command { - color: var(--brand-accent-green); -} - -.terminal-output { - color: var(--brand-neutral-300); -} - -/* Light mode terminal */ -html[data-theme="light"] .terminal-command { - color: var(--brand-sapphire) !important; -} - -html[data-theme="light"] .terminal-output { - color: var(--brand-neutral-300) !important; -} - -/* Features Section */ -.features-section { - padding: 4rem 0; -} - -.section-header { - text-align: center; - margin-bottom: 4rem; -} - -.section-title { - font-size: 2.5rem; - font-weight: 500; - color: var(--font-color-dark); - margin-bottom: 1rem; - font-family: var(--ifm-heading-font-family); -} - -.section-description { - font-size: 1.25rem; - color: var(--font-color-medium); - max-width: 48rem; - margin: 0 auto; - line-height: 1.6; -} - -.features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 2rem; - margin-bottom: 4rem; -} - -.feature-card { - background-color: var(--ifm-background-color); - border-radius: 0.75rem; - padding: 2rem; - box-shadow: var(--upbound-card-shadow); - border: 1px solid var(--upbound-border-color); - transition: all 0.3s ease; - height: 100%; -} - -.feature-card:hover { - transform: translateY(-4px); - box-shadow: var(--upbound-card-shadow-hover); -} - -.feature-icon { - color: var(--brand-purple-500); - margin-bottom: 1rem; -} - -.feature-icon svg { - width: 1.5rem; - height: 1.5rem; -} - -.feature-title { - font-size: 1.25rem; - font-weight: 450; - color: var(--font-color-dark); - margin-bottom: 0.75rem; - font-family: var(--ifm-heading-font-family); -} - -.feature-description { - color: var(--font-color-medium); - line-height: 1.6; - margin: 0; -} - -/* Dark mode features */ -html[data-theme="dark"] .feature-card { - background-color: var(--brand-neutral-800) !important; - border-color: var(--brand-neutral-700) !important; -} - -html[data-theme="dark"] .section-title { - color: var(--brand-neutral-100) !important; -} - -html[data-theme="dark"] .section-description, -html[data-theme="dark"] .feature-description { - color: var(--brand-neutral-300) !important; -} - -html[data-theme="dark"] .feature-title { - color: var(--brand-neutral-100) !important; -} - -/* Light mode features */ -html[data-theme="light"] .feature-card { - background-color: white !important; - border-color: var(--brand-neutral-300) !important; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important; -} - -html[data-theme="light"] .section-title { - color: var(--brand-neutral-900) !important; -} - -html[data-theme="light"] .section-description, -html[data-theme="light"] .feature-description { - color: var(--brand-neutral-600) !important; -} - -html[data-theme="light"] .feature-title { - color: var(--brand-neutral-700) !important; -} - -/* Documentation Sections */ -.docs-section { - padding: 4rem 0; -} - -html[data-theme="light"] .docs-section { - background-color: var(--brand-neutral-100) !important; -} - -.docs-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 1rem; -} - -.doc-card { - background-color: var(--ifm-background-color); - border-radius: 1rem; - box-shadow: var(--upbound-card-shadow); - border: 1px solid var(--upbound-border-color); - overflow: hidden; - transition: all 0.3s ease; - height: 100%; - display: flex; - flex-direction: column; - min-width: 0; - width: 100%; -} - -.doc-card:hover { - transform: translateY(-4px); - box-shadow: var(--upbound-card-shadow-hover); -} - -.doc-card-header { - padding: 1.5rem; - color: white; -} - -.doc-card-header.getstarted { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-purple-900) 100% - ); -} - -.doc-card-header.guides { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-sapphire) 100% - ); -} - -.doc-card-header.manuals { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-magenta) 100% - ); -} - -.doc-card-header.reference { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-spruce) 100% - ); -} - -.doc-card-icon { - margin-bottom: 1rem; -} - -.doc-card-icon svg { - width: 2rem; - height: 2rem; -} - -.doc-card-title { - font-size: 1.5rem; - font-weight: 650; - margin-bottom: 0.5rem; - font-family: var(--ifm-heading-font-family); - color: var(--brand-neutral-100) !important; -} - -.doc-card-description { - opacity: 0.9; - font-weight: 600; - line-height: 1.6; - margin: 0; - color: var(--brand-neutral-200) !important; -} - -.doc-card-body { - padding: 1.5rem; - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.doc-card-list { - list-style: none; - padding: 0; - margin: 0 0 1.5rem 0; - flex-grow: 1; -} - -.doc-card-item { - display: flex; - align-items: flex-start; - gap: 0.75rem; - font-weight: 600; - margin-bottom: 0.75rem; - color: var(--font-color-medium); - line-height: 1.5; -} - -.doc-card-item-icon { - width: 1rem; - height: 1rem; - color: var(--brand-neutral-400); - margin-top: 0.125rem; - flex-shrink: 0; -} - -.doc-card-button { - width: 100%; - padding: 0.75rem 1.5rem; - border-radius: 0.5rem; - font-weight: 600; - border: none; - cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - color: white; - background: linear-gradient( - 135deg, - var(--brand-purple-500), - var(--brand-purple-600) - ); - text-decoration: none; -} - -.doc-card-button.getstarted { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-purple-900) 100% - ); -} - -.doc-card-button.guides { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-sapphire) 100% - ); -} - -.doc-card-button.manuals { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-magenta) 100% - ); -} - -.doc-card-button.reference { - background: linear-gradient( - 135deg, - var(--brand-purple-500) 0%, - var(--brand-spruce) 100% - ); -} - -.doc-card-button:hover { - transform: scale(1.02); - color: white; - text-decoration: none; -} - -.doc-card-button:hover .button-icon { - transform: translateX(2px); -} - -/* Dark mode docs */ -html[data-theme="dark"] .doc-card { - background-color: var(--brand-neutral-800) !important; - border-color: var(--brand-neutral-700) !important; -} - -html[data-theme="dark"] .doc-card-item { - color: var(--brand-neutral-300) !important; -} - -/* Light mode docs */ -html[data-theme="light"] .doc-card { - background-color: white !important; - border-color: var(--brand-neutral-300) !important; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important; -} - -html[data-theme="light"] .doc-card-item { - color: var(--brand-neutral-600) !important; -} - /* ============================================================ */ /* FORM COMPONENTS - Language and Cloud Selector */ /* ============================================================ */ @@ -1619,64 +959,70 @@ html[data-theme="light"] .doc-card-item { background-color: var(--ifm-background-color); border: 1px solid var(--upbound-border-color); border-radius: 0.75rem; - padding: 1.5rem; + padding: 1rem 1.25rem; margin: 0 0 2rem 0; - box-shadow: var(--upbound-card-shadow); } .selector-controls { display: flex; - gap: 1.5rem; - align-items: baseline; + flex-wrap: wrap; + gap: 1.5rem 2rem; + align-items: center; } .selector-group { display: flex; - flex-direction: column; - gap: 0.5rem; + align-items: center; + gap: 0.75rem; } -.selector-group label { +.selector-group-label { font-weight: 600; - font-size: 0.875rem; + font-size: 0.8rem; color: var(--font-color-medium); - line-height: 1.2; - margin: 0 0 0.5rem 0; + white-space: nowrap; } -.cloud-select, -.language-select { - padding: 0.75rem 1rem; +.selector-pills { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +.selector-pill { + appearance: none; border: 1px solid var(--upbound-border-color); - border-radius: 0.5rem; - background-color: var(--ifm-background-color); - color: var(--ifm-font-color-base); - font-size: 0.9rem; + border-radius: 0.375rem; + background: transparent; + color: var(--font-color-medium); + font-size: 0.8rem; font-family: var(--ifm-font-family-base); + font-weight: 500; + padding: 0.4rem 0.9rem; cursor: pointer; - width: 160px; - height: 44px; - box-sizing: border-box; - appearance: none; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236C6F7E' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); - background-position: right 0.75rem center; - background-repeat: no-repeat; - background-size: 1.25rem; - padding-right: 2.5rem; - transition: all 0.2s ease; + transition: border-color 0.15s ease, box-shadow 0.2s ease, color 0.15s ease; } -.cloud-select:hover, -.language-select:hover { - border-color: var(--grape); - background-color: var(--upbound-light-bg); +.selector-pill:hover { + color: var(--brand-purple-700); + border-color: var(--brand-purple-300); + box-shadow: 0 0 8px rgba(154, 94, 252, 0.25); +} + +.selector-pill[aria-pressed="true"] { + color: var(--brand-purple-600); + border-color: var(--brand-purple-300); + box-shadow: 0 0 8px rgba(154, 94, 252, 0.25); +} + +html[data-theme="dark"] .selector-pill:hover { + color: var(--brand-purple-100); + box-shadow: 0 0 8px rgba(196, 167, 255, 0.3); } -.cloud-select:focus, -.language-select:focus { - outline: none; - border-color: var(--grape); - box-shadow: 0 0 0 3px rgba(154, 94, 252, 0.1); +html[data-theme="dark"] .selector-pill[aria-pressed="true"] { + color: var(--brand-purple-200); + box-shadow: 0 0 8px rgba(196, 167, 255, 0.3); } /* ============================================================ */ @@ -1748,14 +1094,6 @@ html[data-theme="dark"] code[class*="language-"] { /* ============================================================ */ @media (max-width: 996px) { - .hero-section { - padding: 2.5rem 0; - } - - .hero-svg { - max-width: 500px; - } - .navbar { position: relative !important; backdrop-filter: none !important; @@ -1775,72 +1113,6 @@ html[data-theme="dark"] code[class*="language-"] { } @media (max-width: 768px) { - .hero-title { - font-size: 2.5rem; - } - - .hero-subtitle { - font-size: 1.75rem; - } - - .hero-description { - font-size: 1.125rem; - } - - .hero-buttons, - .quickstart-buttons { - flex-direction: column; - align-items: stretch; - } - - .hero-text { - padding: 0; - gap: 1rem; - } - - .hero-section { - padding: 2rem 0; - } - - .hero-svg { - max-width: 420px; - } - - .hero-svg-container { - margin-bottom: 0; - } - - .section-title { - font-size: 2rem; - } - - .section-description { - font-size: 1.125rem; - } - - .features-grid, - .docs-grid { - grid-template-columns: 1fr; - } - - .quickstart-grid { - grid-template-columns: 1fr; - gap: 2rem; - } - - .quickstart-title { - font-size: 2rem; - } - - .quickstart-description { - font-size: 1.125rem; - } - - .terminal-content { - font-size: 0.75rem; - padding: 1rem; - } - .footer-links { gap: 1rem; flex-direction: column; @@ -1848,22 +1120,7 @@ html[data-theme="dark"] code[class*="language-"] { .selector-controls { flex-direction: column; - align-items: stretch; - } - - .cloud-select, - .language-select { - width: 100%; - } -} - -@media (max-width: 480px) { - .hero-section { - padding: 1.5rem 0; - } - - .hero-svg { - max-width: 380px; + align-items: flex-start; } } @@ -1993,89 +1250,66 @@ a.breadcrumbs__link svg { margin: 0 auto !important; } -/* ============================================================ */ -/* COPY MARKDOWN BUTTON */ -/* ============================================================ */ - -.copy-markdown-button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - margin-bottom: 1.5rem; - border: 1px solid var(--upbound-border-color); - border-radius: 0.375rem; - background-color: var(--ifm-background-color); - color: var(--font-color-medium); - font-family: var(--ifm-font-family-base); - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.copy-markdown-button:hover { - border-color: var(--brand-purple-500); - color: var(--brand-purple-600); - background-color: var(--upbound-light-bg); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); -} - -.copy-markdown-button:active { - transform: scale(0.98); +.theme-doc-version-badge { + display: none; } -.copy-markdown-button:focus { - outline: none; - border-color: var(--brand-purple-500); - box-shadow: 0 0 0 3px rgba(154, 94, 252, 0.1); -} +/* ============================================================ */ +/* DOCUMENTATION CARD GRID (CardGrid.js) */ +/* ============================================================ */ -/* Copied state */ -.copy-markdown-button.copied { - background-color: rgba(154, 94, 252, 0.1); - border-color: var(--brand-purple-500); - color: var(--brand-purple-600); +.documentation-cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1.5rem; + margin: 2rem auto; + max-width: 1200px; } -.copy-markdown-button.copied:hover { - background-color: rgba(154, 94, 252, 0.15); - border-color: var(--brand-purple-500); - color: var(--brand-purple-600); +.documentation-card { + display: block; + padding: 1.75rem; + border: 1px solid var(--upbound-border-color) !important; + border-radius: 0.75rem; + text-decoration: none !important; + background-color: var(--ifm-background-color); + transition: all 0.3s ease; + height: 100%; } -/* Hidden state - always render but hide with CSS to avoid hydration errors */ -.copy-markdown-button.hidden { - display: none; +.documentation-card:hover { + border-color: var(--ifm-color-primary); + transform: translateY(-2px); + text-decoration: none !important; + color: inherit; } -/* Dark mode styles */ -html[data-theme="dark"] .copy-markdown-button { - border-color: var(--brand-neutral-700); - color: var(--brand-neutral-300); - background-color: var(--brand-neutral-800); +.documentation-card-title { + margin: 0 0 0.75rem 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--ifm-font-color-base); + font-family: var(--ifm-heading-font-family); + line-height: 1.3; } -html[data-theme="dark"] .copy-markdown-button:hover { - border-color: var(--brand-purple-300); - color: var(--brand-purple-300); - background-color: rgba(196, 167, 255, 0.1); +.documentation-card-description { + margin: 0; + font-size: 0.9rem; + line-height: 1.5; + color: var(--ifm-color-content-secondary); } -html[data-theme="dark"] .copy-markdown-button.copied { - background-color: rgba(196, 167, 255, 0.15); - border-color: var(--brand-purple-300); - color: var(--brand-purple-300); -} +@media (max-width: 600px) { + .documentation-cards-grid { + gap: 1rem; + } -html[data-theme="dark"] .copy-markdown-button.copied:hover { - background-color: rgba(196, 167, 255, 0.2); - border-color: var(--brand-purple-300); - color: var(--brand-purple-300); -} + .documentation-card { + padding: 1.25rem; + } -.theme-doc-version-badge { - display: none; + .documentation-card-title { + font-size: 1.125rem; + } } diff --git a/src/pages/index.js b/src/pages/index.js deleted file mode 100644 index 3bea9b20a..000000000 --- a/src/pages/index.js +++ /dev/null @@ -1,307 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import Layout from "@theme/Layout"; -import Head from "@docusaurus/Head"; -import { - ChevronRight, - Zap, - Shield, - Code, - BookOpen, - Settings, - Target, - Rocket, -} from "lucide-react"; -import HeaderSVG from "@site/static/img/header.svg"; - -const LandingPage = () => { - const sections = [ - { - id: "getstarted", - title: "Get Started", - description: - "Quick introduction to Upbound Crossplane and building your first control plane", - icon: , - items: [ - "Prerequisites and setup", - "Create a control plane project", - "Define custom resource types", - "Deploy and use resources", - ], - }, - { - id: "guides", - title: "Guides", - description: - "Step-by-step tutorials for platform engineering scenarios and integrations", - icon: , - items: [ - "Build control plane projects", - "Create an Internal Developer Platform", - "Common use cases", - ], - }, - { - id: "manuals", - title: "Manuals", - description: "Comprehensive documentation for Upbound Crossplane", - icon: , - items: [ - "Install, configure, and manage Upbound", - "Control Plane Concepts", - "How-to deploy Upbound in production", - ], - }, - { - id: "reference", - title: "Reference", - description: - "API documentation, CLI commands, Release Notes, and technical specifications", - icon: , - items: [ - "Crossplane API", - "Spaces API", - "CLI reference", - "Release notes", - ], - }, - ]; - - const features = [ - { - icon: , - title: "AI-Native Distribution", - description: - "Built for autonomous infrastructure platforms serving both humans and AI systems", - link: "/guides/intelligent-control-planes", - }, - { - icon: , - title: "Control Plane Pattern", - description: - "Software that controls other software through declarative APIs and continuous reconciliation", - link: "/manuals/cli/howtos/project", - }, - { - icon: , - title: "Custom APIs", - description: - "Build your own platform APIs with Custom Resource Definitions and composition functions", - link: "/manuals/uxp/concepts/composition/overview", - }, - ]; - - return ( -
- {/* Hero Section */} -
-
-
-
- -
-

Upbound

-

- Welcome to Upbound Crossplane, the AI-native - distribution of Crossplane. -

-

- Build autonomous infrastructure platforms ready for - the age of autonomous systems. -

-
-
-
- {/* Quick Start Section */} -
-
-
-
-

- Ready to Get Started? -

-

- Create your first control plane project in just - 10 minutes. Our quickstart guide walks you - through creating a custom resource type and - deploying it to a local Upbound Crossplane - instance. -

- -
- -
-
-
-
-
-
-
-
-
-
- $ curl -sL "https://cli.upbound.io" | sh -
-
- $ sudo mv up /usr/local/bin/ -
-
- $ up project init -t - project-template-k8s-webapp -l python - my-webapp -
- -
- ✓ Created control plane project -
-
- $ cd my-webapp && up project run --local --ingress -
-
- {" "} - 💻 Local dev control plane running in kind cluster "my-webapp" -
-
- $ up uxp web-ui open -
- - -
-
-
-
-
-
- - {/* Documentation Sections */} -
-
-
-

- Your Control Plane Journey -

-

- Get started and build your best platform. -

-
- -
- {sections.map((section) => ( -
-
-
- {section.icon} -
-

- {section.title} -

-

- {section.description} -

-
- -
-
    - {section.items.map((item, index) => ( -
  • - - {item} -
  • - ))} -
- - - Explore {section.title} - - -
-
- ))} -
-
-
- {/* Features Section */} -
-
-
-
-

- Why Control Planes? -

-

- Control planes unlock the benefits of building - custom APIs to manage the resources your users - need. -

-
-
- {features.map((feature, index) => ( - -
- {feature.icon} -
-

- {feature.title} -

-

- {feature.description} -

- - ))} -
-
-
{" "} -
-
- ); -}; - -export default function Home() { - return ( - <> - - - - - - - - - -
- -
-
- - ); -} diff --git a/src/pages/index.module.css b/src/pages/index.module.css deleted file mode 100644 index 0418ed2a2..000000000 --- a/src/pages/index.module.css +++ /dev/null @@ -1,15 +0,0 @@ -/** - * CSS styles for homepage - */ - -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.featureSvg { - height: 200px; - width: 200px; -} diff --git a/src/sidebars/main.js b/src/sidebars/main.js index 6bb9f2130..2cf00f8da 100644 --- a/src/sidebars/main.js +++ b/src/sidebars/main.js @@ -39,25 +39,91 @@ module.exports = { }, { type: "category", - label: "CLI", + label: "Platform", link: { type: "doc", - id: "manuals/cli/overview", + id: "manuals/platform/overview", }, items: [ { - type: "autogenerated", - dirName: "manuals/cli", + type: "category", + label: "Insights", + items: [ + "manuals/platform/insights/overview", + { + type: "category", + label: "Install", + link: { + type: "doc", + id: "manuals/platform/insights/install/overview", + }, + items: [ + "manuals/platform/insights/install/self-hosted", + { + type: "category", + label: "OIDC", + link: { + type: "doc", + id: "manuals/platform/insights/install/oidc/overview", + }, + items: [ + "manuals/platform/insights/install/oidc/entra-id", + "manuals/platform/insights/install/oidc/keycloak", + "manuals/platform/insights/install/oidc/generic", + ], + }, + ], + }, + "manuals/platform/insights/connections", + "manuals/platform/insights/navigating", + "manuals/platform/insights/query-api", + "manuals/platform/insights/ai", + { + type: "category", + label: "Production", + link: { + type: "doc", + id: "manuals/platform/insights/production/overview", + }, + items: [ + "manuals/platform/insights/production/scalability", + "manuals/platform/insights/production/security", + ], + }, + "manuals/platform/insights/use-cases", + "manuals/platform/insights/feature-flags", + "manuals/platform/insights/reference", + ], + }, + { + type: "category", + label: "Identity & Access", + items: [ + "manuals/platform/identity/iam", + "manuals/platform/identity/organizations", + "manuals/platform/identity/teams", + "manuals/platform/identity/users", + "manuals/platform/identity/robots", + "manuals/platform/identity/rbac", + "manuals/platform/identity/k8s-rbac", + "manuals/platform/identity/sso", + "manuals/platform/identity/oidc", + ], }, + "manuals/platform/data-residency", ], }, { type: "category", - label: "Console", + label: "CLI", + link: { + type: "doc", + id: "manuals/cli/overview", + }, items: [ { type: "autogenerated", - dirName: "manuals/console", + dirName: "manuals/cli", }, ], }, @@ -89,20 +155,6 @@ module.exports = { }, ], }, - { - type: "category", - label: "Platform Access & Accounts", - link: { - type: "doc", - id: "manuals/platform/overview", - }, - items: [ - { - type: "autogenerated", - dirName: "manuals/platform", - }, - ], - }, ], referenceSidebar: [ "reference/index", diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js new file mode 100644 index 000000000..4c3208550 --- /dev/null +++ b/src/theme/DocItem/Content/index.js @@ -0,0 +1,47 @@ +import React, { useRef } from 'react'; +import clsx from 'clsx'; +import { useLocation } from '@docusaurus/router'; +import { ThemeClassNames } from '@docusaurus/theme-common'; +import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import Heading from '@theme/Heading'; +import MDXContent from '@theme/MDXContent'; +import CopyPageMenu from '@site/src/components/CopyPageMenu'; +import styles from './styles.module.css'; + +// Card-grid index pages have little real content to copy, so skip the menu there. +const EXCLUDED_PATHS = ['/guides/', '/manuals/', '/reference/']; + +function useSyntheticTitle() { + const { metadata, frontMatter, contentTitle } = useDoc(); + const shouldRender = !frontMatter.hide_title && typeof contentTitle === 'undefined'; + if (!shouldRender) return null; + return metadata.title; +} + +export default function DocItemContent({ children }) { + const syntheticTitle = useSyntheticTitle(); + const { metadata } = useDoc(); + const location = useLocation(); + const contentRef = useRef(); + + const isHidden = EXCLUDED_PATHS.includes(location.pathname); + const menu = !isHidden && ( + contentRef.current?.innerText} /> + ); + + return ( +
+ {syntheticTitle ? ( +
+ {syntheticTitle} + {menu} +
+ ) : ( + menu &&
{menu}
+ )} +
+ {children} +
+
+ ); +} diff --git a/src/theme/DocItem/Content/styles.module.css b/src/theme/DocItem/Content/styles.module.css new file mode 100644 index 000000000..78c0a3bf6 --- /dev/null +++ b/src/theme/DocItem/Content/styles.module.css @@ -0,0 +1,17 @@ +.docHeader { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.docHeader h1 { + margin-bottom: 0; +} + +.docHeaderStandalone { + display: flex; + justify-content: flex-end; + margin-bottom: 1rem; +} diff --git a/src/theme/MDXContent/index.js b/src/theme/MDXContent/index.js deleted file mode 100644 index 9ae2c4643..000000000 --- a/src/theme/MDXContent/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState, useRef } from "react"; -import { useLocation } from "@docusaurus/router"; -import OriginalMDXContent from "@theme-original/MDXContent"; - -export default function MDXContentWrapper({ children, ...props }) { - const [copied, setCopied] = useState(false); - const contentRef = useRef(); - const location = useLocation(); - - const excludedPaths = ["/guides/", "/manuals/", "/reference/"]; - const isHidden = excludedPaths.includes(location.pathname); - - const handleCopy = () => { - if (contentRef.current) { - navigator.clipboard - .writeText(contentRef.current.innerText) - .then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); - } - }; - - // Build class names - always render button but hide with CSS to avoid hydration errors - const buttonClasses = [ - 'copy-markdown-button', - copied ? 'copied' : '', - isHidden ? 'hidden' : '' - ].filter(Boolean).join(' '); - - return ( - <> - -
- {children} -
- - ); -} diff --git a/src/theme/Root.js b/src/theme/Root.js index 5fba37f52..0e44a699e 100644 --- a/src/theme/Root.js +++ b/src/theme/Root.js @@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react'; import useIsBrowser from '@docusaurus/useIsBrowser'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { LanguageProvider } from '@site/src/components/GlobalLanguageSelector'; +import { VersionProvider } from '@site/src/components/VersionSelector'; import { SessionProvider } from '@site/src/contexts/SessionProvider'; import { FeatureFlagsProvider } from '@site/src/contexts/FeatureFlagsProvider'; @@ -143,7 +144,9 @@ export default function Root({ children }) { - {children} + + {children} + diff --git a/static/manifests/getstarted/create-configuration/aws-00-prelude.yaml.gotmpl b/static/manifests/getstarted/create-configuration/aws-00-prelude.yaml.gotmpl new file mode 100644 index 000000000..5ec7a6bb5 --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-00-prelude.yaml.gotmpl @@ -0,0 +1,2 @@ +#{{ $xr := getCompositeResource . }} +#{{ $params := $xr.spec.parameters }} diff --git a/static/manifests/getstarted/create-configuration/aws-01-bucket.yaml.gotmpl b/static/manifests/getstarted/create-configuration/aws-01-bucket.yaml.gotmpl new file mode 100644 index 000000000..b6e65f3de --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-01-bucket.yaml.gotmpl @@ -0,0 +1,23 @@ +# code: language=yaml +# yaml-language-server: $schema=../../.up/json/models/index.schema.json + +--- +apiVersion: s3.aws.upbound.io/v1beta1 +kind: Bucket +metadata: + annotations: + {{ setResourceNameAnnotation "bucket" }} +spec: + forProvider: + region: "{{ $params.region }}" + +# The desired ACL, encryption, and versioning resources all need to refer to the +# bucket by its external name, which is stored in its external name +# annotation. Fetch the external name into a variable so subsequent templates +# can use it. +# +#{{ $bucket := getComposedResource . "bucket" }} +#{{ $bucket_external_name := "" }} +#{{ if $bucket }} +#{{ $bucket_external_name = get $bucket.metadata.annotations "crossplane.io/external-name" }} +#{{ end }} diff --git a/static/manifests/getstarted/create-configuration/aws-02-acl.yaml.gotmpl b/static/manifests/getstarted/create-configuration/aws-02-acl.yaml.gotmpl new file mode 100644 index 000000000..d47dcf060 --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-02-acl.yaml.gotmpl @@ -0,0 +1,17 @@ +# code: language=yaml +# yaml-language-server: $schema=../../.up/json/models/index.schema.json + +# Don't create the ACL until the bucket name is available. +#{{ if $bucket_external_name }} +--- +apiVersion: s3.aws.upbound.io/v1beta1 +kind: BucketACL +metadata: + annotations: + {{ setResourceNameAnnotation "acl" }} +spec: + forProvider: + region: "{{ $params.region }}" + bucket: "{{ $bucket_external_name }}" + acl: "{{ $params.acl }}" +#{{ end }} diff --git a/static/manifests/getstarted/create-configuration/aws-03-versioning.yaml.gotmpl b/static/manifests/getstarted/create-configuration/aws-03-versioning.yaml.gotmpl new file mode 100644 index 000000000..7212aa4ec --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-03-versioning.yaml.gotmpl @@ -0,0 +1,21 @@ +# code: language=yaml +# yaml-language-server: $schema=../../.up/json/models/index.schema.json + +#{{ if $params.versioning }} +# Don't create the BucketVersioning until the bucket name is available. +#{{ if $bucket_external_name }} +--- +apiVersion: s3.aws.upbound.io/v1beta1 +kind: BucketVersioning +metadata: + annotations: + {{ setResourceNameAnnotation "versioning" }} +spec: + forProvider: + region: "{{ $params.region }}" + bucket: "{{ $bucket_external_name }}" + versioningConfiguration: + - status: Enabled + +#{{ end }} +#{{ end }} diff --git a/static/manifests/getstarted/create-configuration/aws-fn.go b/static/manifests/getstarted/create-configuration/aws-fn.go new file mode 100644 index 000000000..42bf5b7ae --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-fn.go @@ -0,0 +1,197 @@ +package main + +import ( + "context" + "encoding/json" + + "dev.upbound.io/models/com/example/platform/v1alpha1" + "dev.upbound.io/models/io/upbound/aws/s3/v1beta1" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/function-sdk-go/errors" + fnv1 "github.com/crossplane/function-sdk-go/proto/v1" + "github.com/crossplane/function-sdk-go/request" + "github.com/crossplane/function-sdk-go/resource" + "github.com/crossplane/function-sdk-go/resource/composed" + "github.com/crossplane/function-sdk-go/response" + "k8s.io/utils/ptr" +) + +// Function is your composition function. +type Function struct { + fnv1.UnimplementedFunctionRunnerServiceServer + + log logging.Logger +} + +// RunFunction runs the Function. +func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) { + f.log.Info("Running function", "tag", req.GetMeta().GetTag()) + rsp := response.To(req, response.DefaultTTL) + + observedComposite, err := request.GetObservedCompositeResource(req) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get xr")) + return rsp, nil + } + + observedComposed, err := request.GetObservedComposedResources(req) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get observed resources")) + return rsp, nil + } + + var xr v1alpha1.XStorageBucket + if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) + return rsp, nil + } + + params := xr.Spec.Parameters + if params.Region == nil || *params.Region == "" { + response.Fatal(rsp, errors.Wrap(err, "missing region")) + return rsp, nil + } + + // We'll collect our desired composed resources into this map, then convert + // them to the SDK's types and set them in the response when we return. + desiredComposed := make(map[resource.Name]any) + defer func() { + desiredComposedResources, err := request.GetDesiredComposedResources(req) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get desired resources")) + return + } + + for name, obj := range desiredComposed { + c := composed.New() + if err := convertViaJSON(c, obj); err != nil { + response.Fatal(rsp, errors.Wrapf(err, "cannot convert %s to unstructured", name)) + return + } + desiredComposedResources[name] = &resource.DesiredComposed{Resource: c} + } + + if err := response.SetDesiredComposedResources(rsp, desiredComposedResources); err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot set desired resources")) + return + } + }() + + bucket := &v1beta1.Bucket{ + APIVersion: ptr.To(v1beta1.BucketAPIVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketKindBucket), + Spec: &v1beta1.BucketSpec{ + ForProvider: &v1beta1.BucketSpecForProvider{ + Region: params.Region, + }, + }, + } + desiredComposed["bucket"] = bucket + + // Return early if Crossplane hasn't observed the bucket yet. This means it + // hasn't been created yet. This function will be called again after it is. + observedBucket, ok := observedComposed["bucket"] + if !ok { + response.Normal(rsp, "waiting for bucket to be created").TargetCompositeAndClaim() + return rsp, nil + } + + // The desired ACL, encryption, and versioning resources all need to refer + // to the bucket by its external name, which is stored in its external name + // annotation. Return early if the Bucket's external-name annotation isn't + // set yet. + bucketExternalName := observedBucket.Resource.GetAnnotations()["crossplane.io/external-name"] + if bucketExternalName == "" { + response.Normal(rsp, "waiting for bucket to be created").TargetCompositeAndClaim() + return rsp, nil + } + + acl := &v1beta1.BucketACL{ + APIVersion: ptr.To(v1beta1.BucketACLApiVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketACLKindBucketACL), + Spec: &v1beta1.BucketACLSpec{ + ForProvider: &v1beta1.BucketACLSpecForProvider{ + Bucket: &bucketExternalName, + Region: params.Region, + ACL: params.ACL, + }, + }, + } + desiredComposed["acl"] = acl + + boc := &v1beta1.BucketOwnershipControls{ + APIVersion: ptr.To(v1beta1.BucketOwnershipControlsAPIVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketOwnershipControlsKindBucketOwnershipControls), + Spec: &v1beta1.BucketOwnershipControlsSpec{ + ForProvider: &v1beta1.BucketOwnershipControlsSpecForProvider{ + Bucket: &bucketExternalName, + Region: params.Region, + Rule: &[]v1beta1.BucketOwnershipControlsSpecForProviderRuleItem{{ + ObjectOwnership: ptr.To("BucketOwnerPreferred"), + }}, + }, + }, + } + desiredComposed["boc"] = boc + + pab := &v1beta1.BucketPublicAccessBlock{ + APIVersion: ptr.To(v1beta1.BucketPublicAccessBlockAPIVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketPublicAccessBlockKindBucketPublicAccessBlock), + Spec: &v1beta1.BucketPublicAccessBlockSpec{ + ForProvider: &v1beta1.BucketPublicAccessBlockSpecForProvider{ + Bucket: &bucketExternalName, + Region: params.Region, + BlockPublicAcls: ptr.To(false), + RestrictPublicBuckets: ptr.To(false), + IgnorePublicAcls: ptr.To(false), + BlockPublicPolicy: ptr.To(false), + }, + }, + } + desiredComposed["pab"] = pab + + sse := &v1beta1.BucketServerSideEncryptionConfiguration{ + APIVersion: ptr.To(v1beta1.BucketServerSideEncryptionConfigurationAPIVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketServerSideEncryptionConfigurationKindBucketServerSideEncryptionConfiguration), + Spec: &v1beta1.BucketServerSideEncryptionConfigurationSpec{ + ForProvider: &v1beta1.BucketServerSideEncryptionConfigurationSpecForProvider{ + Bucket: &bucketExternalName, + Region: params.Region, + Rule: &[]v1beta1.BucketServerSideEncryptionConfigurationSpecForProviderRuleItem{{ + ApplyServerSideEncryptionByDefault: &[]v1beta1.BucketServerSideEncryptionConfigurationSpecForProviderRuleItemApplyServerSideEncryptionByDefaultItem{{ + SseAlgorithm: ptr.To("AES256"), + }}, + BucketKeyEnabled: ptr.To(true), + }}, + }, + }, + } + desiredComposed["sse"] = sse + + if params.Versioning != nil && *params.Versioning { + versioning := &v1beta1.BucketVersioning{ + APIVersion: ptr.To(v1beta1.BucketVersioningAPIVersions3AwsUpboundIoV1Beta1), + Kind: ptr.To(v1beta1.BucketVersioningKindBucketVersioning), + Spec: &v1beta1.BucketVersioningSpec{ + ForProvider: &v1beta1.BucketVersioningSpecForProvider{ + Bucket: &bucketExternalName, + Region: params.Region, + VersioningConfiguration: &[]v1beta1.BucketVersioningSpecForProviderVersioningConfigurationItem{{ + Status: ptr.To("Enabled"), + }}, + }, + }, + } + desiredComposed["versioning"] = versioning + } + + return rsp, nil +} + +func convertViaJSON(to, from any) error { + bs, err := json.Marshal(from) + if err != nil { + return err + } + return json.Unmarshal(bs, to) +} diff --git a/static/manifests/getstarted/create-configuration/aws-main.k b/static/manifests/getstarted/create-configuration/aws-main.k new file mode 100644 index 000000000..1795b165a --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-main.k @@ -0,0 +1,115 @@ +import models.io.upbound.awsm.s3.v1beta1 as awsms3v1beta1 + +oxr = option("params").oxr # observed composite resource +params = oxr.spec.parameters # extract parameter values from XR + +_metadata = lambda name: str -> any { + { + generateName = name # due to global S3 naming restrictions we'll have + # Crossplane generate a name to garauntee uniqueness + annotations = { + "krm.kcl.dev/composition-resource-name" = name + } + } +} + +_items: [any] = [ + # Create S3 Bucket + awsms3v1beta1.Bucket { + metadata: _metadata("{}-bucket".format(oxr.metadata.name)) + spec = { + forProvider = { + region = params.region + } + } + }, + + # Bucket BOC + awsms3v1beta1.BucketOwnershipControls { + metadata: _metadata("{}-boc".format(oxr.metadata.name)) + spec = { + forProvider = { + bucketSelector: { + matchControllerRef: True + } + region = params.region + rule = { + objectOwnership = "BucketOwnerPreferred" + } + } + } + }, + + # Bucket PAB + awsms3v1beta1.BucketPublicAccessBlock { + metadata: _metadata("{}-pab".format(oxr.metadata.name)) + spec = { + forProvider = { + bucketSelector: { + matchControllerRef: True + } + region = params.region + blockPublicAcls: False + ignorePublicAcls: False + restrictPublicBuckets: False + blockPublicPolicy: False + } + } + }, + + # Bucket ACL + awsms3v1beta1.BucketACL { + metadata: _metadata("{}-acl".format(oxr.metadata.name)) + spec = { + forProvider = { + bucketSelector: { + matchControllerRef: True + } + region = params.region + acl = params.acl + } + } + }, + + # Default encryption for the bucket + awsms3v1beta1.BucketServerSideEncryptionConfiguration { + metadata: _metadata("{}-encryption".format(oxr.metadata.name)) + spec = { + forProvider = { + region = params.region + bucketSelector: { + matchControllerRef: True + } + rule = [ + { + applyServerSideEncryptionByDefault = { + sseAlgorithm = "AES256" + } + bucketKeyEnabled = True + } + ] + } + } + } +] + +# Set up versioning for the bucket if desired +if params.versioning: + _items += [ + awsms3v1beta1.BucketVersioning{ + metadata: _metadata("{}-versioning".format(oxr.metadata.name)) + spec = { + forProvider = { + region = params.region + bucketSelector: { + matchControllerRef: True + } + versioningConfiguration = { + status = "Enabled" + } + } + } + } + ] + +items = _items diff --git a/static/manifests/getstarted/create-configuration/aws-main.py b/static/manifests/getstarted/create-configuration/aws-main.py new file mode 100644 index 000000000..f3c5d6b51 --- /dev/null +++ b/static/manifests/getstarted/create-configuration/aws-main.py @@ -0,0 +1,114 @@ +from crossplane.function import resource +from crossplane.function.proto.v1 import run_function_pb2 as fnv1 + +from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 +from .model.com.example.platform.storagebucket import v1alpha1 +from .model.io.upbound.m.aws.s3.bucket import v1beta1 as mbucketv1beta1 +from .model.io.upbound.m.aws.s3.bucketacl import v1beta1 as maclv1beta1 +from .model.io.upbound.m.aws.s3.bucketownershipcontrols import v1beta1 as mbocv1beta1 +from .model.io.upbound.m.aws.s3.bucketpublicaccessblock import v1beta1 as mpabv1beta1 +from .model.io.upbound.m.aws.s3.bucketversioning import v1beta1 as mverv1beta1 +from .model.io.upbound.m.aws.s3.bucketserversideencryptionconfiguration import v1beta1 as mssev1beta1 + +def resource_name(xr, resource): + return "{}-{}".format(xr.metadata.name, resource) + +def default_metadata(name): + return { + "generateName": name + } + +def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) + params = observed_xr.spec.parameters + + # Create S3 Bucket + desired_bucket = mbucketv1beta1.Bucket( + metadata = default_metadata(resource_name(observed_xr, "bucket")), + spec = mbucketv1beta1.Spec( + forProvider = mbucketv1beta1.ForProvider( + region = params.region, + ), + ), + ) + resource.update(rsp.desired.resources["bucket"], desired_bucket) + + # Bucket BOC + desired_boc = mbocv1beta1.BucketOwnershipControls( + metadata = default_metadata(resource_name(observed_xr, "boc")), + spec = mbocv1beta1.Spec( + forProvider = mbocv1beta1.ForProvider( + region = params.region, + bucketSelector = mbocv1beta1.BucketSelector(matchControllerRef = True), + rule = { + "objectOwnership": "BucketOwnerPreferred" + } + ) + ), + ) + resource.update(rsp.desired.resources["boc"], desired_boc) + + # Bucket PAB + desired_pab = mpabv1beta1.BucketPublicAccessBlock( + metadata = default_metadata(resource_name(observed_xr, "pab")), + spec=mpabv1beta1.Spec( + forProvider = mpabv1beta1.ForProvider( + region = params.region, + bucketSelector = mpabv1beta1.BucketSelector(matchControllerRef = True), + blockPublicAcls = False, + ignorePublicAcls = False, + restrictPublicBuckets = False, + blockPublicPolicy = False, + ) + ), + ) + resource.update(rsp.desired.resources["pab"], desired_pab) + + # Bucket ACL + desired_acl = maclv1beta1.BucketACL( + metadata = default_metadata(resource_name(observed_xr, "acl")), + spec = maclv1beta1.Spec( + forProvider = maclv1beta1.ForProvider( + region = params.region, + bucketSelector = maclv1beta1.BucketSelector(matchControllerRef = True), + acl = params.acl, + ), + ), + ) + resource.update(rsp.desired.resources["acl"], desired_acl) + + # Default encryption for the bucket + desired_sse = mssev1beta1.BucketServerSideEncryptionConfiguration( + metadata = default_metadata(resource_name(observed_xr, "encryption")), + spec = mssev1beta1.Spec( + forProvider = mssev1beta1.ForProvider( + region = params.region, + bucketSelector = mssev1beta1.BucketSelector(matchControllerRef = True), + rule = [ + mssev1beta1.RuleItem( + applyServerSideEncryptionByDefault = { + "sseAlgorithm": "AES256" + }, + bucketKeyEnabled = True + ) + ] + ), + ), + ) + resource.update(rsp.desired.resources["sse"], desired_sse) + + # Set up versioning for the bucket if desired + if params.versioning: + desired_versioning = mverv1beta1.BucketVersioning( + metadata = default_metadata(resource_name(observed_xr, "versioning")), + spec = mverv1beta1.Spec( + forProvider = mverv1beta1.ForProvider( + region = params.region, + bucketSelector = mverv1beta1.BucketSelector(matchControllerRef = True), + versioningConfiguration = { + "status": "Enabled" + } + ), + ), + ) + resource.update(rsp.desired.resources["versioning"], desired_versioning) diff --git a/static/manifests/getstarted/create-configuration/azure-main.k b/static/manifests/getstarted/create-configuration/azure-main.k new file mode 100644 index 000000000..dbeeb6203 --- /dev/null +++ b/static/manifests/getstarted/create-configuration/azure-main.k @@ -0,0 +1,75 @@ +import regex +import models.io.upbound.azurem.v1beta1 as azuremv1beta1 +import models.io.upbound.azurem.storage.v1beta1 as azuremstoragev1beta1 + +oxr = option("params").oxr # observed composite resource +params = oxr.spec.parameters # extract parameter values from XR +xr_metadata = oxr.metadata # store XR metadata + +_metadata = lambda name: str -> any { + { + name = name + annotations = { + "krm.kcl.dev/composition-resource-name" = name + } + } +} + +sanitize_azure_storage_account_name = lambda name: str -> str { + # Due to Azure's naming restrictions, storage account names must + # be between 3-24 characters in length and use numbers and lower-case letters only + + # lower string and remove illegal characters + sanitized = name.lower() + sanitized = regex.replace(sanitized, "[^a-z0-9]", "") + + # pad with 0s if string name less than 3 characters + if len(sanitized) < 3: + sanitized = "{:0<3}".format(sanitized) + + # trim string to 24 characters + sanitized = sanitized[:24] +} + +_items = [ + azuremv1beta1.ResourceGroup { + metadata = _metadata("{}-group".format(xr_metadata.name)) + spec = { + forProvider = { + location = params.location + } + } + }, + azuremstoragev1beta1.Account { + metadata = _metadata(sanitize_azure_storage_account_name("{}account".format(xr_metadata.name))) + spec = { + forProvider = { + accountTier = "Standard" + accountReplicationType = "LRS" + location = params.location + blobProperties = { + versioningEnabled = params.versioning + } + infrastructureEncryptionEnabled = True + resourceGroupNameSelector = { + matchControllerRef = True + } + } + } + }, + azuremstoragev1beta1.Container { + metadata: _metadata("{}-container".format(xr_metadata.name)) + spec = { + forProvider = { + if params.acl == "public": + containerAccessType = "blob" + else: + containerAccessType = "private" + storageAccountNameSelector = { + matchControllerRef = True + } + } + } + } +] +items = _items diff --git a/static/manifests/getstarted/create-configuration/azure-main.py b/static/manifests/getstarted/create-configuration/azure-main.py new file mode 100644 index 000000000..4225fc427 --- /dev/null +++ b/static/manifests/getstarted/create-configuration/azure-main.py @@ -0,0 +1,77 @@ +import re + +from crossplane.function import resource +from crossplane.function.proto.v1 import run_function_pb2 as fnv1 + +from .model.io.k8s.apimachinery.pkg.apis.meta import v1 as metav1 +from .model.io.upbound.m.azure.resourcegroup import v1beta1 as rgv1beta1 +from .model.io.upbound.m.azure.storage.account import v1beta1 as acctv1beta1 +from .model.io.upbound.m.azure.storage.container import v1beta1 as contv1beta1 +from .model.com.example.platform.storagebucket import v1alpha1 + +def resource_name(xr, resource): + return "{}-{}".format(xr.metadata.name, resource) + +def default_metadata(name): + return { + "name": name + } + +def sanitize_azure_storage_account_name(account_name): + # Due to Azure's naming restrictions, storage account names must + # be between 3-24 characters in length and use numbers and lower-case letters only + + # Convert to lowercase and remove all non-alphanumeric characters + sanitized = re.sub(r'[^a-z0-9]', '', account_name.lower()) + + # Ensure minimum length of 3 + if len(sanitized) < 3: + sanitized = sanitized.ljust(3, '0') + + # Ensure maximum length of 24 + if len(sanitized) > 24: + sanitized = sanitized[:24] + + return sanitized + +def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): + observed_xr = v1alpha1.StorageBucket(**resource.struct_to_dict(req.observed.composite.resource)) + params = observed_xr.spec.parameters + + desired_group = rgv1beta1.ResourceGroup( + metadata = default_metadata(resource_name(observed_xr, "group")), + spec = rgv1beta1.Spec( + forProvider = rgv1beta1.ForProvider( + location = params.location, + ), + ), + ) + resource.update(rsp.desired.resources["group"], desired_group) + + desired_acct = acctv1beta1.Account( + metadata = default_metadata(sanitize_azure_storage_account_name(resource_name(observed_xr, "account"))), + spec = acctv1beta1.Spec( + forProvider = acctv1beta1.ForProvider( + accountTier = "Standard", + accountReplicationType = "LRS", + location = params.location, + infrastructureEncryptionEnabled = True, + blobProperties = { + "versioningEnabled": params.versioning + }, + resourceGroupNameSelector = acctv1beta1.ResourceGroupNameSelector(matchControllerRef = True) + ), + ), + ) + resource.update(rsp.desired.resources["account"], desired_acct) + + desired_cont = contv1beta1.Container( + metadata = default_metadata(resource_name(observed_xr, "container")), + spec = contv1beta1.Spec( + forProvider = contv1beta1.ForProvider( + containerAccessType = "blob" if params.acl == "public" else "private", + storageAccountNameSelector = contv1beta1.StorageAccountNameSelector(matchControllerRef = True) + ), + ), + ) + resource.update(rsp.desired.resources["container"], desired_cont) diff --git a/static/manifests/getstarted/create-configuration/gcp-main.k b/static/manifests/getstarted/create-configuration/gcp-main.k new file mode 100644 index 000000000..d37f4723b --- /dev/null +++ b/static/manifests/getstarted/create-configuration/gcp-main.k @@ -0,0 +1,43 @@ +import models.io.upbound.gcpm.storage.v1beta1 as gcpmstoragev1beta1 + +oxr = option("params").oxr # observed composite resource +params = oxr.spec.parameters # extract parameter values from XR + +_metadata = lambda name: str -> any { + { + name = name + annotations = { + "krm.kcl.dev/composition-resource-name" = name + } + } +} + +_items: [any] = [ + # Create GCP Bucket + gcpmstoragev1beta1.Bucket { + metadata: _metadata("{}-bucket".format(oxr.metadata.name)) + spec = { + forProvider = { + location = params.location + versioning = { + enabled = params.versioning + } + } + } + }, + + # Bucket ACL + gcpmstoragev1beta1.BucketACL { + metadata: _metadata("{}-acl".format(oxr.metadata.name)) + spec = { + forProvider = { + predefinedAcl = params.acl + bucketSelector = { + matchControllerRef = True + } + } + } + } +] + +items = _items diff --git a/static/manifests/getstarted/create-configuration/gcp-main.py b/static/manifests/getstarted/create-configuration/gcp-main.py new file mode 100644 index 000000000..35192600c --- /dev/null +++ b/static/manifests/getstarted/create-configuration/gcp-main.py @@ -0,0 +1,44 @@ +from crossplane.function import resource +from crossplane.function.proto.v1 import run_function_pb2 as fnv1 + +from .model.io.upbound.m.gcp.storage.bucket import v1beta1 as mbucketv1beta1 +from .model.io.upbound.m.gcp.storage.bucketacl import v1beta1 as maclv1beta1 +from .model.com.example.platform.storagebucket import v1alpha1 + +def resource_name(xr, resource): + return "{}-{}".format(xr.metadata.name, resource) + +def default_metadata(name): + return { + "name": name + } + +def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse): + observed_xr = v1alpha1.StorageBucket(**req.observed.composite.resource) + params = observed_xr.spec.parameters + + # Create GCP Bucket + desired_bucket = mbucketv1beta1.Bucket( + metadata = default_metadata(resource_name(observed_xr, "bucket")), + spec = mbucketv1beta1.Spec( + forProvider = mbucketv1beta1.ForProvider( + location = params.location, + versioning = { + "enabled": params.versioning + } + ), + ), + ) + resource.update(rsp.desired.resources["bucket"], desired_bucket) + + # Bucket ACL + desired_acl = maclv1beta1.BucketACL( + metadata = default_metadata(resource_name(observed_xr, "acl")), + spec = maclv1beta1.Spec( + forProvider = maclv1beta1.ForProvider( + predefinedAcl=params.acl, + bucketSelector = maclv1beta1.BucketSelector(matchControllerRef = True) + ), + ), + ) + resource.update(rsp.desired.resources["acl"], desired_acl) diff --git a/utils/requirements.txt b/utils/requirements.txt deleted file mode 100644 index a00a6636c..000000000 --- a/utils/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -cryptography -selenium == 4.2.0 -webdriver-manager == 3.7.0 -pillow == 12.3.0 -Selenium-Screenshot == 1.7.0 -robotframework == 5.0.1 -robotframework-seleniumlibrary == 6.0.0 -webdrivermanager == 0.10.0 -# webdrivermanager firefox chrome --linkpath /usr/local/bin diff --git a/utils/robot/resources/common.robot b/utils/robot/resources/common.robot deleted file mode 100644 index 39e7796fc..000000000 --- a/utils/robot/resources/common.robot +++ /dev/null @@ -1,55 +0,0 @@ -*** Settings *** -Documentation Resource file providing common methods for Upbound -Library SeleniumLibrary -Library OperatingSystem - -*** Keywords *** -Login to Upbound - [Arguments] ${username} ${password} ${size}=1700,2050 - [Documentation] Pass login information to the Upbound login page and login. Username and password is required - Open Browser To Login Page ${size} - Input Login Credentials ${username} ${password} - Click Login - -Open Browser To Login Page - [Arguments] ${size}=1700,2050 - [Documentation] Create a browser, go to Upbound login and wait until it's loaded. - Create a Browser ${size} - Go To https://accounts.upbound.io/register - # Wait for the whole page to load - Wait Until Element Is Visible id:signup_button-email - -Input Login Credentials - [Arguments] ${username} ${password} - [Documentation] Input the username and password to the Upbound login page. - Get Environment Variables - Input Text id ${username} - Input Text password ${password} - -Click Login - [Documentation] Click the "login" button on the Upbound login page. - Click Button auth_button-login - Wait Until Element Contains class:ehhnx060 Create New Control Plane - Wait Until Element Is Visible class:e1qqtum60 - Run Keyword And Ignore Error Click Button class:css-10twlew - -Create a Browser - [Arguments] ${size}=1700,2050 - [Documentation] Create a Chrome browswer set to screenshot standards. Custom window sizes are optionally supported. - # To make this portable we need a custom Chrome driver location - # Also set the window size and use headless mode - ${chrome_options} = Evaluate selenium.webdriver.ChromeOptions() - Call Method ${chrome_options} add_argument --binary-location\=""../venv/bin/chromedriver" - Call Method ${chrome_options} add_argument --window-size\=${size} - # Comment out the next line to view the windows and operations. - Call Method ${chrome_options} add_argument --headless - Create WebDriver Chrome chrome_options=${chrome_options} - Set Selenium Speed 0 - # Assumes robot framework is being run from the root of docs/ - Set Screenshot Directory ./static/images - # Don't have Selenium capture screenshots on failure. - Register Keyword To Run On Failure NONE - -Click User Menu - # Click the round thingy in the bottom left - Click Element user-btn \ No newline at end of file diff --git a/utils/robot/resources/orgainzation.robot b/utils/robot/resources/orgainzation.robot deleted file mode 100644 index d99bd71b1..000000000 --- a/utils/robot/resources/orgainzation.robot +++ /dev/null @@ -1,47 +0,0 @@ -*** Settings *** -Documentation Resource file providing common methods for Upbound -Library SeleniumLibrary -Library String -Resource common.robot - -*** Variables *** -${hash} -${org} - -*** Keywords *** -Capture Create Organization - ${hash} = Generate Random String chars=[LETTERS] - ${org} = Catenate SEPARATOR= docs ${hash} - # Capture the left nav without the Teams and Robots options - Capture Element Screenshot class:css-111qlur accounts/left-menu-personal.png - Click User Menu - # Without the sleep the menu doesn't load completely before the screenshot is taken - Sleep 1 - # Capture the pop up menu showing account options - Capture Element Screenshot class:react-tiny-popover-container accounts/account-menu-no-org.png - # Click "Create New Organization" - Click Element xpath://*[contains(text(), "Create New Organization")] - # Wait until the create org screen is visible and fill it in - Wait Until Element Is Visible name - Input Text name ExampleOrg - Input Text displayName Example Organization - # Capture the org screen - Capture Element Screenshot class:css-12yosia accounts/create-new-org.png - -Capture Organization Left Menu - Click User Menu - Sleep 1 - Capture Element Screenshot class:react-tiny-popover-container accounts/account-menu-with-org.png - # Click "Example Organization" - Click Element org-link-docsb118edafc - # Click "Admin Console" - Click Element //span[text()='Admin Console'] - # Wait until "Robots" icon is visible - Wait Until Element Is Visible //p[text()='Robots'] - Capture Element Screenshot class:css-111qlur accounts/left-menu-organization.png - # Hide the "Upbound Inc" org - # Execute Javascript document.getElementById("org-link-upbound").remove() - # # Rename the "Upbound Docs" org - # Execute Javascript document.evaluate("//*[@id='org-link-upbound-docs']/div/div[2]/span", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent="Example Organization" - # # Replace "Pete Lumbis" with "A. User" - # Execute Javascript document.querySelector('.css-1k81wk5').textContent=("A. User") diff --git a/utils/robot/resources/register.robot b/utils/robot/resources/register.robot deleted file mode 100644 index f297e5022..000000000 --- a/utils/robot/resources/register.robot +++ /dev/null @@ -1,65 +0,0 @@ -*** Settings *** -Documentation Resource file providing the methods to capture cloud.upbound account registration -Library SeleniumLibrary -Library String -Resource common.robot - -*** Variables *** -${hash} -${username} -${email} - -*** Keywords *** - -Capture Sign Up - # Continue from the Sign Up page and take a screenshot - [Arguments] ${img} - Capture Element Screenshot class:e13dycp82 ${img} - -Generate Email - # Helper function to create a random string to create a docs+@upbound.io email - # Username and password for the account will also be set to - ${hash} = Generate Random String chars=[LETTERS] - ${email} = Catenate SEPARATOR= docs+ ${hash} @upbound.io - ${username} = Catenate SEPARATOR= docs ${hash} - Set Suite Variable ${hash} ${hash} - Set Suite Variable ${email} ${email} - Set Suite Variable ${username} ${username} - -Capture Sign Up Form - # Fill out the signup form and take a screenshot. - [Arguments] ${img} - Click Button id:signup_button-email - Input Text firstName A. - Input Text lastName User - Generate Email - # Input a fake email for the screenshot - Input Text email a.user@upbound.io - Input Text password ${hash} - Select Checkbox data:testid:register-terms - Scroll Element Into View class:css-7fowp2 - Capture Element Screenshot class:css-1pc0v7v ${img} - # Clear the fake email and put in the docs+@upbound.io email - Execute Javascript document.querySelector('#email').value="" - Input Text email ${email} - Submit Form - -Capture Choose Username - [Arguments] ${img} - Wait Until Element Is Visible username - # Input a fake user for the screenshot - Input Text username auser - Wait Until Element Is Enabled class:css-2zwxg9 - Capture Element Screenshot class:css-uft8mm ${img} - # Clear the username and input the docs username - Execute Javascript document.querySelector('#username').value="" - Input Text username ${username} - Submit Form - Wait Until Element Is Visible data:testid:pin-container - -Capture Confirm Pin - # There is no way to programatically capture the PIN value - # so the screenshot is without the PIN completed - [Arguments] ${img} - Execute Javascript document.querySelector('.css-e51ts8').textContent= document.querySelector('.css-e51ts8').textContent.replace("${email}", "a.user@upbound.io") - Capture Element Screenshot class:css-1pc0v7v ${img} diff --git a/utils/robot/resources/robot-tokens.robot b/utils/robot/resources/robot-tokens.robot deleted file mode 100644 index f5e35994f..000000000 --- a/utils/robot/resources/robot-tokens.robot +++ /dev/null @@ -1,59 +0,0 @@ -*** Settings *** -Documentation Resource file providing common methods for Upbound -Library SeleniumLibrary -Resource common.robot - -*** Keywords *** -Create Robot Account - [Documentation] From the inital user page, click the Robot buttom and create a new Robot account. - Click User Menu - # Click "Example Organization" - Click Element org-link-docsb118edafc - # Click "Admin Console" - Click Element //span[text()='Admin Console'] - # Wait until "Robots" icon is visible - Wait Until Element Is Visible //p[text()='Robots'] - # Click the "Robots" left-nav button - Click Element //p[text()='Robots'] - # Wait for the "Create Robot Account Button" - Run Keyword And Ignore Error Delete Robot User - Wait Until Element Is Visible //a[text()='Create Robot Account'] - Capture Element Screenshot //*[@id="root"]/div[2]/main/div/section/div accounts/create-robot-account.png - # Skip to the create robot screen - Click Link //a[text()='Create Robot Account'] - Input Text name ExampleRobot - Input Text description This is a description for an example robot account - # Capture the form with inputs - Capture Element Screenshot xpath://*[@id="root"]/div[2]/main/div/div[2] accounts/robot-account-create-screen.png - Click Button class:css-1x2ywxm - -Create Robot Token - [Documentation] Creates a new robot token. Assumes a robot account already exists. - # Wait for and capture "Create your first token" div - Wait Until Element Is Visible //button[text()='Create Token'] - Capture Element Screenshot class:css-16y9f05 robots/create-first-robot-token.png - # Click "Create Token" button - Click Button //button[text()='Create Token'] - # Wait for the box to load and input a token name - Wait Until Element Is Visible name - Input Text name exampletoken - # Sleep is required to let the element fully render and not capture a partially transparent image - Capture Element Screenshot class:css-12yosia robots/create-token-name.png - # Submit the form - Submit Form - Wait Until Element Is Visible //button[text()='Close'] - # Let the element fully render - Sleep 1 - Capture Element Screenshot class:css-12yosia robots/token-credentials.png - # Click "Close" on token creds box - Click Element //button[text()='Close'] - -Delete Robot User - [Documentation] Delete the first robot account. May not work with multiple robot accounts. - # Click on the Robot Settings Tab and not the left-menu Settings - Wait Until Element Is Visible //h5[text()='Settings'] - Click Element //h5[text()='Settings'] - # Click "I understand, delete this robot" - Click Element //button[text()='I understand, delete this robot'] - # Click "Delete Robot Account" - Click Button //button[text()='Delete Robot Account'] diff --git a/utils/robot/tests/register.robot b/utils/robot/tests/register.robot deleted file mode 100644 index 5892c564c..000000000 --- a/utils/robot/tests/register.robot +++ /dev/null @@ -1,14 +0,0 @@ -*** Settings *** -Documentation Capture screenshots related to cloud.upbound.io account creation. -Resource ../resources/register.robot -Resource ../resources/common.robot - -*** Test Cases *** -Capture Create Account - [Documentation] Capture the screenshots involved in creating a user account - Open Browser To Login Page - Capture Sign Up register/signup.png - Capture Sign Up Form register/completed_form.png - Capture Choose Username register/choose_username.png - Capture Confirm Pin register/confirm_pin.png - [Teardown] Close All Browsers \ No newline at end of file diff --git a/utils/robot/tests/robot-tokens.robot b/utils/robot/tests/robot-tokens.robot deleted file mode 100644 index c966e1b56..000000000 --- a/utils/robot/tests/robot-tokens.robot +++ /dev/null @@ -1,25 +0,0 @@ -*** Settings *** -Documentation Capture screenshots related to generating robot tokens -Resource ../resources/common.robot -Resource ../resources/orgainzation.robot -Resource ../resources/robot-tokens.robot - -*** Test Cases *** -Create Organization - [Documentation] Login as a user with no organization and capture the workflow to create one. - Login to Upbound %{UP_NO_ORG_USERNAME} %{UP_NO_ORG_PASSWORD} 800x600 - Capture Create Organization - [Teardown] Close All Browsers - -Get Org Menu - [Documentation] Login as a user with organizations and capture menu screenshots. - Login to Upbound %{UP_ORG_USERNAME} %{UP_ORG_PASSWORD} 800x600 - Capture Organization Left Menu - -Capture Robot Token - [Documentation] Capture the flow of creating a new robot token. - Login to Upbound %{UP_ORG_USERNAME} %{UP_ORG_PASSWORD} - Create Robot Account - Create Robot Token - Delete Robot User - [Teardown] Close All Browsers \ No newline at end of file diff --git a/utils/vagrant/Vagrantfile b/utils/vagrant/Vagrantfile deleted file mode 100644 index 2327dab87..000000000 --- a/utils/vagrant/Vagrantfile +++ /dev/null @@ -1,481 +0,0 @@ -ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt' - -# Check required plugins -REQUIRED_PLUGINS_LIBVIRT = %w(vagrant-libvirt) -exit unless REQUIRED_PLUGINS_LIBVIRT.all? do |plugin| - Vagrant.has_plugin?(plugin) || ( - puts "The #{plugin} plugin is required. Please install it with:" - puts "$ vagrant plugin install #{plugin}" - false - ) -end - -Vagrant.require_version ">= 2.0.2" - -# NUM_CONTROLLER_NODE = 1 -NUM_WORKER_NODE = 2 -HOST_INTERFACE = "enp36s0f0" - -Vagrant.configure("2") do |config| - config.ssh.forward_agent = true - config.vm.box = "generic/ubuntu2004" - - # (1..NUM_CONTROLLER_NODE).each do |i| - (1..1).each do |i| - config.vm.define "kubecontroller-0#{i}" do |node| - node.vm.hostname = "kubecontroller-0#{i}" - node.vm.synced_folder ".", "/vagrant", disabled: true - node.vm.network "public_network", bridge: HOST_INTERFACE , dev: HOST_INTERFACE, ip: "172.16.1.80" - - node.vm.provider :libvirt do |v| - v.memory = 4096 - v.cpus = 4 - end - - node.vm.provision :shell, privileged: true, inline: $configure_linux - node.vm.provision :shell, privileged: true, inline: $install_tools - if ARGV.include? '--provision-with' - node.vm.provision "eks", type: 'shell', env: {"AWS_ACCESS_KEY_ID"=>ENV['AWS_ACCESS_KEY_ID'], "AWS_SECRET_ACCESS_KEY"=>ENV['AWS_SECRET_ACCESS_KEY'], "AWS_DEFAULT_REGION"=>ENV['AWS_DEFAULT_REGION']}, inline: <<-SHELL - echo "Installing AWS CLI" - curl --silent "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" - unzip -q awscliv2.zip - sudo ./aws/install - echo "Installing eksctl" - curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp - sudo mv /tmp/eksctl /usr/local/bin - echo "Applying AWS credentials" - mkdir -p /home/vagrant/.aws/ - echo "[default]" >> /home/vagrant/.aws/credentials - echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> /home/vagrant/.aws/credentials - echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> /home/vagrant/.aws/credentials - echo "region=$AWS_DEFAULT_REGION" >> /home/vagrant/.aws/credentials - chown vagrant:vagrant /home/vagrant/.aws/credentials - echo "complete -C '/usr/local/bin/aws_completer' aws" >> /home/vagrant/.bashrc - echo ". <(eksctl completion bash)" >> /home/vagrant/.bashrc - chown vagrant:vagrant /home/vagrant/.bashrc - echo "export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" > /home/vagrant/.profile - echo "export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" >> /home/vagrant/.profile - echo "export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> /home/vagrant/.profile - cat /home/vagrant/.profile - SHELL - end - node.vm.provision :shell, privileged: true, inline: $install_cri - node.vm.provision :shell, privileged: true, inline: <<-SCRIPT - until [ -f /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf ] - do - sleep 5 - done - cat << 'EOF' > /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf -# Note: This dropin only works with kubeadm and kubelet v1.11+ -[Service] -Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" -Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=172.16.1.80" -# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically -EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env -# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use -# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. -EnvironmentFile=-/etc/default/kubelet -ExecStart= -ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS -EOF - sudo systemctl daemon-reload - sudo systemctl restart kubelet.service - SCRIPT - node.vm.provision :shell, privileged: true, inline: $configure_kube_controller - node.vm.provision :shell, privileged: true, inline: $install_up_cli - node.vm.provision :shell, privileged: true, inline: $add_crossplane_script - node.vm.provision :shell, privileged: true, inline: $glcoud_cli_script - - end - end - - # Provision Worker Nodes - (1..NUM_WORKER_NODE).each do |i| - config.vm.define "kubenode-0#{i}" do |node| - node.vm.hostname = "kubenode-0#{i}" - node.vm.synced_folder ".", "/vagrant", disabled: true - node.vm.network "public_network", bridge: HOST_INTERFACE , dev: HOST_INTERFACE, ip: "172.16.1.8#{i}" - - node.vm.provider :libvirt do |v| - v.memory = 4096 - v.cpus = 4 - end - - node.vm.provision :shell, privileged: true, inline: $configure_linux - node.vm.provision :shell, privileged: true, inline: $install_cri - node.vm.provision :shell, privileged: true, inline: <<-SCRIPT - until [ -f /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf ] - do - sleep 5 - done - cat << 'EOF' > /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf -# Note: This dropin only works with kubeadm and kubelet v1.11+ -[Service] -Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" -Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=172.16.1.8#{i}" -# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically -EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env -# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use -# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. -EnvironmentFile=-/etc/default/kubelet -ExecStart= -ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS -EOF - sudo systemctl daemon-reload - sudo systemctl restart kubelet.service - SCRIPT - node.vm.provision :shell, privileged: true, inline: $configure_kube_worker - node.vm.provision :shell, privileged: true, inline: $install_tools - - end - end -end - -$configure_linux = <<-SCRIPT -echo "Applying Linux system settings" - -# Fixes "stdin: is not a tty" and "mesg: ttyname failed : Inappropriate ioctl for device" messages --> https://github.com/mitchellh/vagrant/issues/1673 -(sudo grep -q 'mesg n' /root/.profile 2>/dev/null && sudo sed -i '/mesg n/d' /root/.profile 2>/dev/null) || true; - -# Disable AAAA records; speeds up APT for v4 only networks -echo -e "precedence ::ffff:0:0/96 100" >> /etc/gai.conf - -# Disable swap to prevent kubeadm warnings -swapoff -a -sed -i '/ swap / s/^/#/' /etc/fstab - -# Enable k8s related networking settings -modprobe br_netfilter -echo -e "br_netfilter" > /etc/modules-load.d/k8s.conf -echo -e "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.d/k8s.conf -echo -e "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.d/k8s.conf -sed -i 's/^#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.d/99-sysctl.conf - -# Revert systemd change that breaks sysctl command (commit https://github.com/systemd/systemd-stable/commit/5d4fc0e665a3639f92ac880896c56f9533441307#) -# Fix is in procps https://gitlab.com/procps-ng/procps/-/issues/191 -sed 's/net.ipv4.conf.\*.promote_secondaries = 1//' /usr/lib/sysctl.d/50-default.conf > /usr/lib/sysctl.d/50-default.conf -sed 's/-net.ipv4.conf.all.promote_secondaries//' /usr/lib/sysctl.d/50-default.conf > /usr/lib/sysctl.d/50-default.conf - -sudo sysctl --system > /dev/null 2>&1 - - -SCRIPT - -$install_tools = <<-SCRIPT -echo "Installing additional software packages" -DEBIAN_FRONTEND=noninteractive apt-get update > /dev/null 2>&1 -DEBIAN_FRONTEND=noninteractive apt-get install -y git htop chrony tree jq bat apt-transport-https ca-certificates curl gpg > /dev/null 2>&1 -SCRIPT - -$install_cri = <<-SCRIPT -echo "Installing containerd and Kubeadm" - - -# Docker Key -curl --retry 30 -fsSL https://download.docker.com/linux/ubuntu/gpg | DEBIAN_FRONTEND=noninteractive apt-key add - > /dev/null 2>&1 - -# Docker Repo -echo deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable > /etc/apt/sources.list.d/docker.list - -# Helm Key -curl --retry 30 -fsSL https://baltocdn.com/helm/signing.asc | DEBIAN_FRONTEND=noninteractive sudo apt-key add - > /dev/null 2>&1 - -# Helm Repo -echo "deb https://baltocdn.com/helm/stable/debian/ all main" > /etc/apt/sources.list.d/helm.list - -# Kubernetes key directory -sudo mkdir -p -m 755 /etc/apt/keyrings - -# Kubernetes keys -curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | DEBIAN_FRONTEND=noninteractive sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg - -# Kubernetes repo -echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null 2>&1 - -DEBIAN_FRONTEND=noninteractive apt-get update > /dev/null 2>&1 -DEBIAN_FRONTEND=noninteractive apt-get install -y kubeadm=1.29.2-1.1 kubelet=1.29.2-1.1 kubectl=1.29.2-1.1 containerd.io helm > /dev/null 2>&1 -DEBIAN_FRONTEND=noninteractive apt-mark hold kubelet kubeadm kubectl > /dev/null 2>&1 - -# Remove the stock containerd config file and let it auto generate -rm -f /etc/containerd/config.toml -systemctl restart containerd.service > /dev/null 2>&1 - - -echo -e "set ts=2 sw=2 expandtab smarttab ruler" >> /home/vagrant/.vimrc -chown vagrant:vagrant /home/vagrant/.vimrc -SCRIPT - -$configure_kube_controller = <<-SCRIPT -echo "Configuring Kube Controller" -# CLI magic to pull the IP address for future multi-controller setups ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/\1/p' -kubeadm init --apiserver-advertise-address 172.16.1.80 --pod-network-cidr 10.244.0.0/16 > /dev/null 2>&1 - -# Generate a known token for workers to join the cluster. -kubeadm token create 123456.abcdefghijklmnop > /dev/null 2>&1 - -# Enable kubectl for root and vagrant users -mkdir -p /root/.kube -sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config -sudo chown root:root /root/.kube/config -mkdir -p /home/vagrant/.kube -cp -i /etc/kubernetes/admin.conf /home/vagrant/.kube/config -chown -R vagrant:vagrant /home/vagrant/.kube/ - -# Install Flannel -kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml > /dev/null 2>&1 - -echo "source <(kubectl completion bash)" >> /root/.bashrc # add autocomplete permanently to your bash shell. -echo "source <(kubectl completion bash)" >> /home/vagrant/.bashrc -echo "alias k=kubectl" >> /root/.bashrc -echo "alias k=kubectl" >> /home/vagrant/.bashrc -echo "complete -F __start_kubectl k" >> /root/.bashrc -echo "complete -F __start_kubectl k" >> /home/vagrant/.bashrc - - -SCRIPT - -$configure_kube_worker = <<-SCRIPT -echo "Kube Worker joining the cluster" -# Join the worker to the cluster -kubeadm join 172.16.1.80:6443 --token 123456.abcdefghijklmnop --discovery-token-unsafe-skip-ca-verification > /dev/null 2>&1 - -SCRIPT - -$install_up_cli = <<-SCRIPT -echo "Installing Up CLI" -curl -sL "https://cli.upbound.io" | sh > /dev/null 2>&1 -mv up /usr/local/bin/ > /dev/null 2>&1 -SCRIPT - -$enable_metrics = <<-SCRIPT -echo "Removing API Server taint" -kubectl patch node kubecontroller-01 -p '{"spec":{"taints":[]}}' - -echo "Enabling Metrics Server" -# https://github.com/kubernetes-sigs/metrics-server/issues/917#issuecomment-1002337025 -cat << 'EOF' > /home/vagrant/metrics.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - k8s-app: metrics-server - name: metrics-server - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - k8s-app: metrics-server - rbac.authorization.k8s.io/aggregate-to-admin: "true" - rbac.authorization.k8s.io/aggregate-to-edit: "true" - rbac.authorization.k8s.io/aggregate-to-view: "true" - name: system:aggregated-metrics-reader -rules: -- apiGroups: - - metrics.k8s.io - resources: - - pods - - nodes - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - k8s-app: metrics-server - name: system:metrics-server -rules: -- apiGroups: - - "" - resources: - - nodes/metrics - verbs: - - get -- apiGroups: - - "" - resources: - - pods - - nodes - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - k8s-app: metrics-server - name: metrics-server-auth-reader - namespace: kube-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: extension-apiserver-authentication-reader -subjects: -- kind: ServiceAccount - name: metrics-server - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - k8s-app: metrics-server - name: metrics-server:system:auth-delegator -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:auth-delegator -subjects: -- kind: ServiceAccount - name: metrics-server - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - k8s-app: metrics-server - name: system:metrics-server -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:metrics-server -subjects: -- kind: ServiceAccount - name: metrics-server - namespace: kube-system ---- -apiVersion: v1 -kind: Service -metadata: - labels: - k8s-app: metrics-server - name: metrics-server - namespace: kube-system -spec: - ports: - - name: https - port: 443 - protocol: TCP - targetPort: https - selector: - k8s-app: metrics-server ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - k8s-app: metrics-server - name: metrics-server - namespace: kube-system -spec: - selector: - matchLabels: - k8s-app: metrics-server - strategy: - rollingUpdate: - maxUnavailable: 0 - template: - metadata: - labels: - k8s-app: metrics-server - spec: - containers: - - args: - - --cert-dir=/tmp - - --secure-port=4443 - - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - - --kubelet-use-node-status-port - - --metric-resolution=3s - - --kubelet-insecure-tls - image: k8s.gcr.io/metrics-server/metrics-server:v0.6.2 - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 3 - httpGet: - path: /livez - port: https - scheme: HTTPS - periodSeconds: 10 - name: metrics-server - ports: - - containerPort: 4443 - name: https - protocol: TCP - readinessProbe: - failureThreshold: 3 - httpGet: - path: /readyz - port: https - scheme: HTTPS - initialDelaySeconds: 20 - periodSeconds: 10 - resources: - requests: - cpu: 100m - memory: 200Mi - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 1000 - volumeMounts: - - mountPath: /tmp - name: tmp-dir - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-cluster-critical - serviceAccountName: metrics-server - volumes: - - emptyDir: {} - name: tmp-dir ---- -apiVersion: apiregistration.k8s.io/v1 -kind: APIService -metadata: - labels: - k8s-app: metrics-server - name: v1beta1.metrics.k8s.io -spec: - group: metrics.k8s.io - groupPriorityMinimum: 100 - insecureSkipTLSVerify: true - service: - name: metrics-server - namespace: kube-system - version: v1beta1 - versionPriority: 100 -EOF -SCRIPT - -$add_crossplane_script = <<-SCRIPT -cat << 'EOF' > /home/vagrant/xp.sh -helm repo add \ -crossplane-stable https://charts.crossplane.io/stable -helm repo update -helm install crossplane \ -crossplane-stable/crossplane \ ---namespace crossplane-system \ ---create-namespace -EOF -chown vagrant:vagrant /home/vagrant/xp.sh -chmod +x /home/vagrant/xp.sh -SCRIPT - -$glcoud_cli_script = <<-SCRIPT -cat << 'EOF' > /home/vagrant/gcp.sh -sudo apt-get install curl sudo -echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list -curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - -sudo apt-get update && sudo apt-get install google-cloud-cli google-cloud-sdk-gke-gcloud-auth-plugin -gcloud init -EOF -chown vagrant:vagrant /home/vagrant/gcp.sh -chmod +x /home/vagrant/gcp.sh -SCRIPT \ No newline at end of file diff --git a/utils/webpack/package-lock.json b/utils/webpack/package-lock.json deleted file mode 100644 index 605060e46..000000000 --- a/utils/webpack/package-lock.json +++ /dev/null @@ -1,1543 +0,0 @@ -{ - "name": "webpack", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@cfworker/json-schema": "^1.12.5", - "flexsearch": "^0.7.31", - "lodash": "^4.18.1" - }, - "devDependencies": { - "bootstrap": "^5.3.3", - "clipboard": "^2.0.11", - "instant.page": "^5.2.0", - "jquery": "^3.6.4", - "lightbox2": "^2.11.5", - "webpack": "^5.105.2", - "webpack-cli": "^5.1.4", - "webpack-manifest-plugin": "^5.0.0" - } - }, - "node_modules/@cfworker/json-schema": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-1.12.5.tgz", - "integrity": "sha512-k+ungOs1TxSfNfmfOxkwaiNL2EgRQjhk2SRweMYoKkBJZUYsGsY+XYpe2KpuS9EyjZWjZhq8QvVM+gwi3g1+hQ==" - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", - "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "license": "MIT", - "peerDependencies": { - "@popperjs/core": "^2.11.8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clipboard": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", - "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", - "dev": true, - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flexsearch": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.31.tgz", - "integrity": "sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", - "dev": true, - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/instant.page": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/instant.page/-/instant.page-5.2.0.tgz", - "integrity": "sha512-DUSwWyoHFOQnmEwJtg9vzDx8Ef8uNNvTxTmHjd0vN9/XEIb5EQkm/itpZMypoH3dJLJvtkrD97WOCKuMqDdMHQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jquery": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz", - "integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lightbox2": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/lightbox2/-/lightbox2-2.11.5.tgz", - "integrity": "sha512-IsDqv/D9pjgh7GvwTNvmHF98+nrIcOD17fraXgtx8ivq469y95l5ycLi6SeZAZHdeyD3cGLjYwbDX8SRfWx5fA==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", - "dev": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", - "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", - "dev": true - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.105.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", - "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", - "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", - "dev": true, - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "webpack": "^5.47.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - } - } -} diff --git a/utils/webpack/package.json b/utils/webpack/package.json deleted file mode 100644 index 0a690370c..000000000 --- a/utils/webpack/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "scripts": { - "prod": "webpack --mode=production", - "dev": "webpack --mode=development" - }, - "devDependencies": { - "bootstrap": "^5.3.3", - "clipboard": "^2.0.11", - "instant.page": "^5.2.0", - "jquery": "^3.6.4", - "lightbox2": "^2.11.5", - "webpack": "^5.105.2", - "webpack-cli": "^5.1.4", - "webpack-manifest-plugin": "^5.0.0" - }, - "dependencies": { - "@cfworker/json-schema": "^1.12.5", - "flexsearch": "^0.7.31", - "lodash": "^4.18.1" - } -} diff --git a/utils/webpack/src/LICENSE b/utils/webpack/src/LICENSE deleted file mode 100644 index dda75ca9a..000000000 --- a/utils/webpack/src/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2011-2022 Twitter, Inc. -Copyright (c) 2011-2022 The Bootstrap Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/utils/webpack/src/js/colorMode.js b/utils/webpack/src/js/colorMode.js deleted file mode 100644 index 749934519..000000000 --- a/utils/webpack/src/js/colorMode.js +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * Dark Mode Switch v1.0.1 (https://github.com/coliff/dark-mode-switch) - * Copyright 2021 C.Oliff - * Licensed under MIT (https://github.com/coliff/dark-mode-switch/blob/main/LICENSE) - */ - -(() => { - var darkSwitch = document.getElementById("darkSwitch"); - - window.addEventListener("load", function () { - if (darkSwitch) { - initTheme(); - darkSwitch.addEventListener("change", function () { - resetTheme(); - }); - } - }); -})(); - -/** - * Summary: function that adds or removes the attribute 'color-theme' depending if - * the switch is 'on' or 'off'. - * - * Description: initTheme is a function that uses localStorage from JavaScript DOM, - * to store the value of the HTML switch. If the switch was already switched to - * 'on' it will set an HTML attribute to the body named: 'color-theme' to a 'dark' - * value. If it is the first time opening the page, or if the switch was off the - * 'color-theme' attribute will not be set. - * @return {void} - */ -function initTheme() { - - /****** - * Changes to this function likely need to be copied to - * themes/geekboot/layouts/partials/stylesheet-cached.html - ******/ - - // Is the system set to dark mode? - var darkPrefered = window.matchMedia('(prefers-color-scheme: dark)').matches - - // The DOM element for the color mode toggle - var darkSwitch = document.getElementById("darkSwitch"); - - // Do we have a dark mode cookie saved? - var darkThemeSelected = - localStorage.getItem("darkSwitch") !== null && - localStorage.getItem("darkSwitch") === "dark"; - - // Do we have a light mode cookie saved? - var lightThemeSelected = - localStorage.getItem("darkSwitch") !== null && - localStorage.getItem("darkSwitch") === "light"; - - // if a light mode cookie is saved ignore the system dark mode - if (lightThemeSelected) { - darkPrefered = false; - } - - // Set the document color theme based on the user's system or cookie preference - if (darkThemeSelected || darkPrefered) { - document.documentElement.setAttribute("color-theme", "dark") - darkSwitch.checked = true; - } - else { - document.documentElement.setAttribute("color-theme", "light") - darkSwitch.checked = false; - } -} - -/** - * Summary: resetTheme checks if the switch is 'on' or 'off' and if it is toggled - * on it will set the HTML attribute 'color-theme' to dark so the dark-theme CSS is - * applied. - * @return {void} - */ -function resetTheme() { - - var darkSwitch = document.getElementById("darkSwitch"); - - if (darkSwitch.checked) { - document.documentElement.setAttribute("color-theme", "dark"); - localStorage.setItem("darkSwitch", "dark"); - } else { - document.documentElement.setAttribute("color-theme", "light"); - localStorage.setItem("darkSwitch", "light"); - } -} \ No newline at end of file diff --git a/utils/webpack/src/js/customClipboard.js b/utils/webpack/src/js/customClipboard.js deleted file mode 100644 index f406d6f10..000000000 --- a/utils/webpack/src/js/customClipboard.js +++ /dev/null @@ -1,161 +0,0 @@ -// Customize clipboard.js -import * as ClipboardJS from 'clipboard'; - -// Most of the following is directly from the Bootstrap Website. -// https://github.com/twbs/bootstrap/blob/main/site/assets/js/code-examples.js -const btnHtml = [ - '
', - ' ', - '
' -].join('') - -// wrap programmatically code blocks and add copy btn. -var codeBlocks = document.querySelectorAll('.highlight') - -for (var i = 0; i < codeBlocks.length; i++){ - var copyLines = getLines(codeBlocks[i]) - // Skip adding button and highlighting if copy-lines="none" - if(copyLines[0] === 0 && copyLines[1] === 0){ - continue - } - setHighlight(codeBlocks[i], copyLines) - codeBlocks[i].insertAdjacentHTML('beforeend', btnHtml) -} - -// use .parentNode.parentNode because the trigger is the