From f59da860287102483f60c4c8be6765a68abee24a Mon Sep 17 00:00:00 2001 From: Marcel Mueller Date: Mon, 20 Mar 2023 12:55:12 +0100 Subject: [PATCH] Update existing dev content from intranet --- .../adding_app_to_appcatalog.md | 1 + .../app-developer-processes/app_bundles.md | 100 ++++++-------- .../app-developer-processes/apps_in_happa.md | 41 ++++-- .../cordoning_app_and_chart_crs.md | 5 +- .../creating_app_catalog.md | 1 + .../handle_crds_with_helm_3.md | 123 +++++++++++++++++- ...how-to-resolve-resources-exists-in-helm.md | 7 +- .../matchlabels-are-immutable.md | 1 + .../{ => resources}/app-example.png | Bin .../resources/app-icon-bad-example.png | Bin 0 -> 5751 bytes .../resources/app-icon-good-example.png | Bin 0 -> 10235 bytes .../resources/app-logo-example.png | Bin 0 -> 15883 bytes .../routing_app_alerts.md | 23 +++- ..._multiple_k8s_versions_in_helm_template.md | 5 +- .../dev-and-releng/code-signing/_index.md | 1 + content/docs/dev-and-releng/helm/_index.md | 1 + .../how-to-release-a-project/_index.md | 1 + .../test-environments/_index.md | 1 + .../specs/registry-mirror.md | 1 + .../dev-experience/git-subtree.md | 72 ++++++---- 20 files changed, 280 insertions(+), 104 deletions(-) rename content/docs/dev-and-releng/app-developer-processes/{ => resources}/app-example.png (100%) create mode 100644 content/docs/dev-and-releng/app-developer-processes/resources/app-icon-bad-example.png create mode 100644 content/docs/dev-and-releng/app-developer-processes/resources/app-icon-good-example.png create mode 100644 content/docs/dev-and-releng/app-developer-processes/resources/app-logo-example.png diff --git a/content/docs/dev-and-releng/app-developer-processes/adding_app_to_appcatalog.md b/content/docs/dev-and-releng/app-developer-processes/adding_app_to_appcatalog.md index 91be6ec..6eba768 100644 --- a/content/docs/dev-and-releng/app-developer-processes/adding_app_to_appcatalog.md +++ b/content/docs/dev-and-releng/app-developer-processes/adding_app_to_appcatalog.md @@ -3,6 +3,7 @@ title: "How to add and maintain App in an App Catalog" weight: 30 description: > How to create, manage and release an App into an App Catalog +confidentiality: public --- ## Table of Contents diff --git a/content/docs/dev-and-releng/app-developer-processes/app_bundles.md b/content/docs/dev-and-releng/app-developer-processes/app_bundles.md index 7df5c98..4cfea70 100644 --- a/content/docs/dev-and-releng/app-developer-processes/app_bundles.md +++ b/content/docs/dev-and-releng/app-developer-processes/app_bundles.md @@ -1,22 +1,34 @@ --- title: "App Bundles" weight: 20 +confidentiality: public --- ## Overview -For CAPI we have decided to deploy default apps via App Bundles. -See [ADR](https://intranet.giantswarm.io/docs/product/architecture-specs-adrs/adr/016-capi-releases/) +The App Bundle is a Helm Chart that instead of providing a regular resources, that normally represents +an application, it provides App CRs, optionally accompanied by ConfigMaps and Secrets. In other words, it is a +way to represent a group of apps as a single app, to the user and to the system. Check out the [public docs](https://docs.giantswarm.io/app-platform/app-bundle/#app-bundle-definition) if you look for a more detailed explanation. -An App Bundle is an App CR that is created in the management cluster and groups -multiple workload cluster apps. This makes installation simpler and means we can -retire the [Release](https://docs.giantswarm.io/ui-api/management-api/crd/releases.release.giantswarm.io/) -CRD used in vintage clusters. +The App Bundle usage is currently well established in the company. -A `default-apps-PROVIDER` app will exist for each CAPI provider. +For example, for CAPI it has been decided to deploy default apps in this way, see [ADR](https://intranet.giantswarm.io/docs/product/architecture-specs-adrs/adr/016-capi-releases.md). By bundling a group of default apps we make the installation +simpler and also means we can retire the [Release](https://docs.giantswarm.io/ui-api/management-api/crd/releases.release.giantswarm.io/) CRD used in vintage clusters. Hence, a `default-apps-PROVIDER` app bundle will +exist for each CAPI provider. -We also provide App Bundles that group apps related to a topic -e.g. `security-pack`. +Another example is grouping apps by the topic, creating specialized bundles, like for example the `security-pack` app. + +## Naming Convention + +It has been decided for App Bundles to carry the `-bundle` prefix in order to distinguish them from regular apps, see +the [PDR](https://intranet.giantswarm.io/docs/product/pdr/008_app_bundle_naming.md). + +**Note**, as you may notice the `security-pack` referenced in this doc, for whatever reason, is not compliant with +these rules yet, do not be suggested by it and please stick to the PDR demands. + +What may seem as another exception are `default-apps-PROVIDER` apps. These however are subject to a bit different rules, +as being used in a different ways than usual apps, no matter bundled or not. See the reasoning behind these apps in +the previous paragraph. ## Creating a new App Bundle @@ -27,51 +39,17 @@ and the app should be published to the [cluster-catalog](https://github.com/gian For other bundles this is [security-pack](https://github.com/giantswarm/security-pack). -## Installing an App Bundle - -An App Bundle can be installed via `kubectl gs template app` using the -`--in-cluster` flag. - -```nohighlight -$ kubectl gs template app \ ---catalog giantswarm \ ---name security-pack \ ---in-cluster \ ---namespace demo1 \ ---version 0.1.0 \ ---user-configmap user-values.yaml -``` - -We wish to automate the setting of the `--in-cluster` flag by adding an annotation -to the bundle apps Chart.yaml that will be added to its AppCatalogEntry CR where -it can be accessed by front end components. However this is not yet implemented. +Note, app bundle beyond its fancy name is nothing more than a regular Helm Chart. Whatever the Helm +offers for Charts creation can be used when creating a bundle. Some demands are however posed on the +configuration, yet not by the bundle construction, but by the way how App Platform works. Find more +about in the next paragraphs. -```yaml -apiVersion: application.giantswarm.io/v1alpha1 -kind: App -metadata: - labels: - app-operator.giantswarm.io/version: 0.0.0 - giantswarm.io/managed-by: flux - name: security-pack - namespace: demo1 -spec: - catalog: giantswarm - kubeConfig: - inCluster: true - name: security-pack - namespace: demo1 - version: 0.1.0 -``` +## Installing an App Bundle -- The `app-operator.giantswarm.io/version` label must have the value `0.0.0`. -- `.spec.kubeConfig.inCluster` must be `true`. -- `.spec.namespace` must match the namespace used for workload cluster apps. -This is the org namespace for CAPI and the cluster namespace for vintage. +The installation process for bundles can be found in the [public docs](https://docs.giantswarm.io/app-platform/app-bundle/). Go there to understand: -Note: Any other value for `.spec.namespace` will be blocked by `app-admission-controller` -this is a security requirement to prevent the user escaping their Management API -permissions. +- the components the installation involves +- the configurational demands by these components ## Child Apps @@ -92,7 +70,7 @@ apps: Each child app should have the `giantswarm.io/managed-by` label set to the name of the parent app e.g. `default-apps-openstack`. This identifies the parent app -CR and means the install [is not blocked](https://docs.giantswarm.io/app-platform/defaulting-validation/#gitops-support) +CR and means the install [is not blocked](https://docs.giantswarm.io/app-platform/defaulting-validation/#gitops-support) by `app-admission-controller`. ```nohighlight @@ -120,18 +98,18 @@ When apps are created in the org namespace add a cluster prefix. {{- end -}} {{- end -}} -# templates/apps.yaml +# templates/apps.yaml {{- $appName := include "app.name" (dict "app" .appName "cluster" $.Values.clusterName "ns" $.Release.Namespace) }} ``` ## User Values Each child app in the bundle needs to be configurable. This is done via the -`values.yaml` of the app bundle's helm chart which needs to pass values to the +`values.yaml` of the app bundle's Helm Chart which needs to pass values to the child apps. -This relies heavily on helm templating so care needs to be taken and ideally -there should be test coverage for this. +This relies heavily on Helm templating so care needs to be taken and ideally +there should be test coverage for this. Find an example of such `values.yaml` below. ```yaml userConfig: @@ -145,9 +123,11 @@ userConfig: ## GitOps Support -There is a problem with using GitOps and the app bundles concept. The user values -are passed via a `values` key and a single block of YAML. This prevents using -bases and overrides in Flux. +There is a problem with using GitOps and managed apps in general, affecting the +bundles as well. The user values are passed via the `values` key of either a +ConfigMap or a Secret, and must be a single block of YAML. This prevents using +bases and overrides in Flux. This is because [kustomize](https://github.com/kubernetes-sigs/kustomize) +cannot patch strings. The proposal in [RFC#29](https://github.com/giantswarm/rfc/pull/29) is to use both `.spec.config` and `.spec.userConfig` and the values will be merged by @@ -169,3 +149,5 @@ spec: name: flux01-userconfig namespace: org-some ``` + +This approach has been adapted and explained in our [GitOps Template repository](https://github.com/giantswarm/gitops-template), that represents our GitOps offering. diff --git a/content/docs/dev-and-releng/app-developer-processes/apps_in_happa.md b/content/docs/dev-and-releng/app-developer-processes/apps_in_happa.md index 2b46bb1..56e33b1 100644 --- a/content/docs/dev-and-releng/app-developer-processes/apps_in_happa.md +++ b/content/docs/dev-and-releng/app-developer-processes/apps_in_happa.md @@ -2,13 +2,14 @@ title: Adding READMEs and Icons to Apps in Happa linkTitle: README and icon weight: 20 +confidentiality: public --- Your beautiful app deserves to look beautiful in Happa as well. Here are some tips and tricks on how to set the Chart.yaml values of your app so that the frontend can show relevant information in a pleasing way. -![Sample of an App in Happa](app-example.png) +![Sample of an App in Happa](../resources/app-example.png) ## It's in the Chart.yaml @@ -35,22 +36,44 @@ sources: annotations: application.giantswarm.io/readme: https://raw.githubusercontent.com/giantswarm/jaeger-operator-app/v[[ .Version ]]/README.md application.giantswarm.io/team: team-cabbage + ui.giantswarm.io/logo: https://s.giantswarm.io/app-icons/jaeger-operator/1/dark.svg ``` [View Chart.yaml in Github](https://github.com/giantswarm/jaeger-operator-app/blob/master/helm/jaeger-operator-app/Chart.yaml) -### Icon +### Icon and logo -If you add a URL to a PNG or SVG file in the `icon` field, then -happa will attempt to fetch it and use it when showing -your app. +Happa is designed to work with 2 types of image assets for apps: +#### 1. Icon -We publish app icons through [web-assets](https://github.com/giantswarm/web-assets). Find them in the [`assets/app-icons`](https://github.com/giantswarm/web-assets/tree/master/assets/app-icons) folder. +Every app should have an icon, and it must fit a square space. + +A good example: + +![Good example of an app icon](../resources/app-icon-good-example.png) + +A bad example: + +![Bad example of an app icon](../resources/app-icon-bad-example.png) + +While the icon in the second example also technically fits into a square space, it is not designed for it, as evidenced by the unused vertical space. The resulting icon is also difficult to discern when displayed in a space-constrained area. + +Icons can be made available to happa by adding a URL to a PNG or SVG file in the Chart.yaml's `icon` field. -An example icon url would be `https://s.giantswarm.io/app-icons/1/png/jaeger-operator-app-dark.png` +#### 2. Logo -#### Providing an app icon for light and dark backgrounds +An app may also have a logo for the catalog, to optimize its display in a rectangular space. Logos should only be provided if they have a different shape than the app's icon. The ideal width:height ratio of logos is 2:1. + +For example: + +![Example of an app logo displayed in the app catalog list in happa](../resources/app-logo-example.png) + +Logos can be made available to happa using the `ui.giantswarm.io/logo` annotation. + +#### Providing app icons for light and dark backgrounds + +We publish app icons through [web-assets](https://github.com/giantswarm/web-assets). Find them in the [`assets/app-icons`](https://github.com/giantswarm/web-assets/tree/master/assets/app-icons) folder. Since our app catalog UI in happa (our web UI) shows app icons on a dark background, there is a chance that an icon/logo would end up invisible because it uses black on a dark blue background. To avoid this, you can provide two versions of the icon file and name the files following a specific pattern. @@ -77,7 +100,7 @@ Apps packaged using CircleCI job `push-to-app-catalog` with [`executor: "app-bui automatically include an annotation with key `application.giantswarm.io/readme` which points to a versioned copy of the apps README residing in the app catalog. -You can find more information about app metadata in spec [Representation of apps and their metadata for app catalog entries](https://intranet.giantswarm.io/docs/product/architecture-specs-adrs/specs/managed-apps/2020-05-05-app-versions-representation/). +You can find more information about app metadata in spec [Representation of apps and their metadata for app catalog entries](https://intranet.giantswarm.io/docs/product/architecture-specs-adrs/specs/managed-apps/2020-05-05-app-versions-representation.md). In case you're not using [`app-build-suite`](https://github.com/giantswarm/app-build-suite), it is possible to manually add an annotation called `application.giantswarm.io/readme` to `Chart.yaml`. diff --git a/content/docs/dev-and-releng/app-developer-processes/cordoning_app_and_chart_crs.md b/content/docs/dev-and-releng/app-developer-processes/cordoning_app_and_chart_crs.md index 29c8f2c..38967fb 100644 --- a/content/docs/dev-and-releng/app-developer-processes/cordoning_app_and_chart_crs.md +++ b/content/docs/dev-and-releng/app-developer-processes/cordoning_app_and_chart_crs.md @@ -1,6 +1,7 @@ --- title: "Cordoning app and chart CRs" weight: 20 +confidentiality: public --- # Overview @@ -65,7 +66,7 @@ metadata: ```bash kubectl -n giantswarm annotate chart chart-operator-unique 'chart-operator.giantswarm.io/cordon-reason'='Maintenance in progress' -kubectl -n giantswarm annotate chart chart-operator-unique 'chart-operator.giantswarm.io/cordon-until'=$(date -v +1d '+%Y-%m-%dT%H:%M:%S) +kubectl -n giantswarm annotate chart chart-operator-unique 'chart-operator.giantswarm.io/cordon-until'=$(date -v +1d '+%Y-%m-%dT%H:%M:%S') ``` - You can remove the annotations with these commands. @@ -73,4 +74,4 @@ kubectl -n giantswarm annotate chart chart-operator-unique 'chart-operator.giant ```bash kubectl -n giantswarm annotate chart chart-operator-unique chart-operator.giantswarm.io/cordon-reason- kubectl -n giantswarm annotate chart chart-operator-unique chart-operator.giantswarm.io/cordon-until- -``` \ No newline at end of file +``` diff --git a/content/docs/dev-and-releng/app-developer-processes/creating_app_catalog.md b/content/docs/dev-and-releng/app-developer-processes/creating_app_catalog.md index aa99628..5bb02d0 100644 --- a/content/docs/dev-and-releng/app-developer-processes/creating_app_catalog.md +++ b/content/docs/dev-and-releng/app-developer-processes/creating_app_catalog.md @@ -1,6 +1,7 @@ --- title: "Creating an App Catalog" weight: 20 +confidentiality: public --- # App catalog diff --git a/content/docs/dev-and-releng/app-developer-processes/handle_crds_with_helm_3.md b/content/docs/dev-and-releng/app-developer-processes/handle_crds_with_helm_3.md index e7f6397..497f894 100644 --- a/content/docs/dev-and-releng/app-developer-processes/handle_crds_with_helm_3.md +++ b/content/docs/dev-and-releng/app-developer-processes/handle_crds_with_helm_3.md @@ -1,6 +1,7 @@ --- title: "Handle CRD installation and updates with Helm 3" description: "This page documents a method for installing and updating CRDs using Helm 3" +confidentiality: public --- Helm 3 introduced the `crds/` directory inside a helm chart. ([Reference](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#install-a-crd-declaration-before-using-the-resource)) @@ -48,5 +49,125 @@ Along with additional required NetworkPolicy, PodSecurityPolicy, ServiceAccount - If the CRD definitions are huge then it's possible that the chart will fail to install with this method. Helm releases are stored as Secrets which are limited to 1MB in size, and using this method means the CRDs are included in the Secret twice (as CRDs from `crds/` and also in the Job configMaps). - Because helm does not support templating in the `crds/` directory, one cannot use any templating for CRDs. A solution to this is to not use `crds/` at all and to place the CRD manifests in `files/` instead. - - This however comes with its own additional caveats - if the Helm chart creates resources which consume these CRDs, then they _must_ exist in `crds/` because Helm will fail initially to install the chart as it doesn't know about the CRDs (upgrades to existing charts will work though). This means this method cannot be used to install CRDs which require templating, and which are also used in the main chart itself - this is a chicken-and-egg problem which cannot be solved with this method. + - This however comes with its own additional caveats - if the Helm chart creates resources which consume these CRDs, then they _must_ exist in `crds/` because Helm will fail initially to install the chart as it doesn't know about the CRDs (upgrades to existing charts will work though). This means this method cannot be used to install CRDs which require templating, and which are also used in the main chart itself - this is a chicken-and-egg problem which cannot be solved with this method. To slightly improve this situation, the App Platform offers a workaround allowing to install both, CRDs and CRs of these CRDs kinds, in a semi-single step. See the next paragraph for more details. - CRD deletion should never be managed by the Chart hooks in order to avoid accidental removal. + +## Installing CRDs and CRs in a semi-single run + +### Chart Operator + +Starting with the `2.31.0` version of Chart Operator it supports doing a two-step installation of an application. + +What changes in comparison to a normal installation is that, right after it, the Chart Operator executes the +upgrade. This upgrade is internal, meaning it is not shown to the user, yet observative user may spot signs of it +being done, by for example Helm release revision being `2` instead of `1`. + +In addition, the internal upgrade is not done until explicitly requested. App owner may request it for its app +by annotating its `Chart.yaml` with: + +```yaml +annotations: + application.giantswarm.io/two-step-install: "true" +``` + +To sum up, starting with the aforementioned version Chart Operator does install the app, and then when explicitly +asked to, it also runs the upgrade internally, immediately after the first step. + +### Accounting for two-step installation in Helm Chart + +With a two-step installation in place, the app owners can now configure their apps to install both CRDs and CRs +when apps are configured for the cluster for the first time. Obviously the rule from [caveats](#caveats) still +applies, Helm Chart however can be configured to skip the CRs on the installation, when CRDs do not yet exist, +and then to provide them on upgrade, when CRDs are already in place. This is exactly what the internal upgrade is +for. + +There are two things to take care when configuring Helm Chart for it: +- skipping CRs installation when CRDs do not exist +- retaining faulty behaviour for installation outside the App Platform, or for old Chart Operators inside the App +Platform + +#### Check for CRDs + +In the most basic form, CRs can be conditioned on the CRDs existance with the Helm's `lookup` function. Result +can be then used in the IF conditions enclosing the CRs in question. + +Below is the example of `lookup` used in [Flux app](https://github.com/giantswarm/flux-app/blob/master/helm/flux-app/templates/source.yaml) for checking the `GitRepository` CRD, note some fields have been removed for brevity. + +```yaml +{{- $is_gitrepository_crd := (lookup "apiextensions.k8s.io/v1" "CustomResourceDefinition" "" "gitrepositories.source.toolkit.fluxcd.io") -}} +{{- if $is_gitrepository_crd }} +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: GitRepository +metadata: + name: "{{ .name }}" + namespace: "{{ $.Release.Namespace }}" +... +{{- end }} +``` + +Unfortunately, relying solely on the `lookup`'s result "breaks" the Helm Chart in some ways. It now does not fail +when both, CRDs and CRs, are requested, but it neither install the latter. It neither informs the user of +skipping them, and even if it was it would take us back to the square one because user would need to perform the +update manually, as he must do now. + +So except skipping CRs, the Helm Chart must know when to do it, i.e. when Chart Operator can do the internal +upgrade. + +#### Check for App Platform + +As stated, the Helm Chart must know when to skip the CRs. We know it can do it only when: +- it is being installed with the App Platform, and +- App Platform runs the Chart Operator that supports the internal upgrade. + +Now, the check for that is not the prettiest thing to see: + +```yaml +{{- define "appPlatform.twoStepInstall" -}} +{{- $is_chart_operator := lookup "application.giantswarm.io/v1alpha1" "Chart" "giantswarm" "chart-operator" -}} +{{- $is_chart_operator_bad := true }} +{{- if $is_chart_operator }} +{{- $is_chart_operator_bad = (semverCompare "< 2.31.0-0" $is_chart_operator.spec.version) }} +{{- end }} + +{{- $is_this_chart_cr := lookup "application.giantswarm.io/v1alpha1" "Chart" "giantswarm" . -}} +{{- $is_outside_app_platform := true }} +{{- if $is_this_chart_cr }} +{{- $is_outside_app_platform = false }} +{{- end }} + +{{- if or $is_chart_operator_bad $is_outside_app_platform }} +{{- print "unsupported: true" -}} +{{- else -}} +{{- print "unsupported: false" -}} +{{- end -}} +{{- end -}} +``` + +The first part checks for Chart Operator Chart CR in the `giantswarm` namespace. Then, it checks the version +supports the internal upgrade, by comparing it to the version that first introduced this. + +The second part looks for this app's Chart CR since its existence means the app is installed inside the App +Platform. + +When Chart Operator exists and supports the upgrade, and when app is being installed as a managed app, then the +function renders two-step installation as supported. + +Now this piece of code can be used into Helm Chart's `_helpers.tpl`, and used to enrich the condition from +previous paragraph, see below. + +```yaml +{{- $two_step_upgrade := include "appPlatform.twoStepInstall" .Release.Name | fromYaml }} +... +{{- $is_gitrepository_crd := (lookup "apiextensions.k8s.io/v1" "CustomResourceDefinition" "" "gitrepositories.source.toolkit.fluxcd.io") -}} +{{- if or $two_step_upgrade.unsupported $is_gitrepository_crd }} +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: GitRepository +metadata: + name: "{{ .name }}" + namespace: "{{ $.Release.Namespace }}" +... +{{- end }} +``` + +With this, the CR should be skipped when app is being installed as a managed app by a conformant Chart Operator +for a cluster that does not yet have the CRDs in place, and rendered in other cases. diff --git a/content/docs/dev-and-releng/app-developer-processes/how-to-resolve-resources-exists-in-helm.md b/content/docs/dev-and-releng/app-developer-processes/how-to-resolve-resources-exists-in-helm.md index f987845..0207b2f 100644 --- a/content/docs/dev-and-releng/app-developer-processes/how-to-resolve-resources-exists-in-helm.md +++ b/content/docs/dev-and-releng/app-developer-processes/how-to-resolve-resources-exists-in-helm.md @@ -1,20 +1,21 @@ --- title: "How to resolve resources already exists in helm manifest" weight: 20 +confidentiality: public --- -### Problem +### Problem - In some cases, the user could not install/upgrade helm release because some resources exist in clusters already. ``` -helm install helm/cert-manager-app --name cert-manager-app --namespace cert-manager-app --values helm/cert-manager-app/values.yaml +helm install helm/cert-manager-app --name cert-manager-app --namespace cert-manager-app --values helm/cert-manager-app/values.yaml Error: serviceaccounts "cert-manager-giantswarm-clusterissuer" already exists ``` ### How to solve it - From helm `3.2.0`, you could add helm label and annotations into dangling objects and helm would adopt them as one of their manifests. -To solve the above example, we could put the following label, annotations as below. +To solve the above example, we could put the following label, annotations as below. ``` KIND=serviceaccount NAME=cert-manager-giantswarm-clusterissuer diff --git a/content/docs/dev-and-releng/app-developer-processes/matchlabels-are-immutable.md b/content/docs/dev-and-releng/app-developer-processes/matchlabels-are-immutable.md index e76efd0..1749fab 100644 --- a/content/docs/dev-and-releng/app-developer-processes/matchlabels-are-immutable.md +++ b/content/docs/dev-and-releng/app-developer-processes/matchlabels-are-immutable.md @@ -3,6 +3,7 @@ title: "LabelSelector MatchLabels are immutable" description: | LabelSelector MatchLabels of a Kubernetes resource are immutable. This page describes the error and gives tips how to resolve the issue. weight: 70 +confidentiality: public --- # spec.selector.matchLabels are immutable in Daemonsets, Deployments etc diff --git a/content/docs/dev-and-releng/app-developer-processes/app-example.png b/content/docs/dev-and-releng/app-developer-processes/resources/app-example.png similarity index 100% rename from content/docs/dev-and-releng/app-developer-processes/app-example.png rename to content/docs/dev-and-releng/app-developer-processes/resources/app-example.png diff --git a/content/docs/dev-and-releng/app-developer-processes/resources/app-icon-bad-example.png b/content/docs/dev-and-releng/app-developer-processes/resources/app-icon-bad-example.png new file mode 100644 index 0000000000000000000000000000000000000000..41e7dc8b59680e2c9c665ed61254afce3b6000e6 GIT binary patch literal 5751 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 o2|XqWv&|srfQxts_ChgDO-pA2TEO2g8%>k literal 0 HcmV?d00001 diff --git a/content/docs/dev-and-releng/app-developer-processes/resources/app-icon-good-example.png b/content/docs/dev-and-releng/app-developer-processes/resources/app-icon-good-example.png new file mode 100644 index 0000000000000000000000000000000000000000..cd207d3449f4466eb6b01eb4942a2d03b4ad90f6 GIT binary patch literal 10235 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 literal 0 HcmV?d00001 diff --git a/content/docs/dev-and-releng/app-developer-processes/resources/app-logo-example.png b/content/docs/dev-and-releng/app-developer-processes/resources/app-logo-example.png new file mode 100644 index 0000000000000000000000000000000000000000..54d753794b1e445dbe3955532da53b26d9051bcb GIT binary patch literal 15883 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 OBPAv$S}Cmm^M3%I%z48A literal 0 HcmV?d00001 diff --git a/content/docs/dev-and-releng/app-developer-processes/routing_app_alerts.md b/content/docs/dev-and-releng/app-developer-processes/routing_app_alerts.md index bb50e6e..1d4c1eb 100644 --- a/content/docs/dev-and-releng/app-developer-processes/routing_app_alerts.md +++ b/content/docs/dev-and-releng/app-developer-processes/routing_app_alerts.md @@ -1,8 +1,9 @@ --- title: "How to route app alerts to a team" description: | - Routing app alerts via annotations in Chart.yaml or app CRs. + Routing app alerts via annotations in Chart.yaml or app CRs. weight: 70 +confidentiality: public --- ## App CR Annotation @@ -13,7 +14,7 @@ this annotation takes precedence. ```sh kubectl -n giantswarm annotate app app-exporter-unique application.giantswarm.io/team=halo -# port forwarding to the app-exporter service. +# port forwarding to the app-exporter service. # kubectl -n giantswarm port-forward svc/app-exporter-unique 8000:8000 # Forwarding from 127.0.0.1:8000 -> 8000 # Forwarding from [::1]:8000 -> 8000 @@ -42,10 +43,8 @@ annotations: - The annotation is added to the AppCatalogEntry CRs by `app-operator-unique`. ```sh -kubectl get appcatalogentry -l app.kubernetes.io/name=app-exporter,latest=true -o yaml | yq '.items[1].metadata.annotations' -{ - "application.giantswarm.io/team": "batman" -} +kubectl get appcatalogentry -A -l app.kubernetes.io/name=app-exporter,latest=true -o yaml | yq '.items[].metadata.annotations' +"application.giantswarm.io/team": "batman" ``` - It is used by [app-exporter] to set the team label in app info metrics. @@ -55,6 +54,18 @@ curl -s http://localhost:8000/metrics | grep app-exporter app_operator_app_info{app="app-exporter",catalog="control-plane-catalog",name="app-exporter-unique",namespace="giantswarm",status="deployed",team="batman",version="0.4.0"} 1 ``` +### Team label in resources + +Team chart annotation should be propagated to a label for generated resources, because some alerts rely on the specific resource's labels. + +The recommended solution is to define a list of common labels in a `_helpers` template, containing this: +``` +application.giantswarm.io/team: {{ index .Chart.Annotations "application.giantswarm.io/team" | default "batman" | quote }} +``` + +See [grafana app](https://github.com/giantswarm/grafana-app/blob/master/helm/grafana/templates/_helpers.tpl#L14) for reference. + + ## Owners Annotation - When a component is owned by multiple teams you can set the `application.giantswarm.io/owners` diff --git a/content/docs/dev-and-releng/app-developer-processes/support_multiple_k8s_versions_in_helm_template.md b/content/docs/dev-and-releng/app-developer-processes/support_multiple_k8s_versions_in_helm_template.md index a9d1f1b..17b6e2c 100644 --- a/content/docs/dev-and-releng/app-developer-processes/support_multiple_k8s_versions_in_helm_template.md +++ b/content/docs/dev-and-releng/app-developer-processes/support_multiple_k8s_versions_in_helm_template.md @@ -3,13 +3,14 @@ title: "How to support multiple k8s versions in a Helm template" description: | Support multiple k8s versions using Helm capabilities API weight: 80 +confidentiality: public --- -# Supporting multiple Kubernetes versions in a Helm template +## Rationale - Sometimes we need to support multiple Kubernetes versions with API versions that have been removed or not exist yet. -- Helm was a [built in](https://helm.sh/docs/chart_template_guide/builtin_objects/) +- Helm has a [built in](https://helm.sh/docs/chart_template_guide/builtin_objects/) Capabilities API that helps with this. ## Ingress example diff --git a/content/docs/dev-and-releng/code-signing/_index.md b/content/docs/dev-and-releng/code-signing/_index.md index 07c3fe0..5d880f2 100644 --- a/content/docs/dev-and-releng/code-signing/_index.md +++ b/content/docs/dev-and-releng/code-signing/_index.md @@ -3,6 +3,7 @@ title: Codesigning for Windows binaries linkTitle: Codesigning description: > We distribute signed CLI binaries (.exe) for Windows. Here is how to configure CI and the CLI repository, and how to update the certificate once it expires. +confidentiality: public --- ## Ensure creation of signed binaries diff --git a/content/docs/dev-and-releng/helm/_index.md b/content/docs/dev-and-releng/helm/_index.md index 00571a6..e8463a5 100644 --- a/content/docs/dev-and-releng/helm/_index.md +++ b/content/docs/dev-and-releng/helm/_index.md @@ -4,6 +4,7 @@ linkTitle: Helm description: > We use Helm to deploy our App Platform. This page is for local setup and tips and tricks. +confidentiality: public --- ## Aliases diff --git a/content/docs/dev-and-releng/releases/how-to-release-a-project/_index.md b/content/docs/dev-and-releng/releases/how-to-release-a-project/_index.md index 4fc4de8..99d3bbc 100644 --- a/content/docs/dev-and-releng/releases/how-to-release-a-project/_index.md +++ b/content/docs/dev-and-releng/releases/how-to-release-a-project/_index.md @@ -4,6 +4,7 @@ description: | This document describes the different steps required in order to release a new version of a project. weight: 20 +confidentiality: public --- ## Recording diff --git a/content/docs/dev-and-releng/test-environments/_index.md b/content/docs/dev-and-releng/test-environments/_index.md index e40ceea..4917321 100644 --- a/content/docs/dev-and-releng/test-environments/_index.md +++ b/content/docs/dev-and-releng/test-environments/_index.md @@ -2,6 +2,7 @@ title: "Test Environments" description: > When working with test environments there are a couple of things we should try to stick to process wise in order to make everyones life easier and avoid cost. +confidentiality: public --- ## Avoid any avoidable cost diff --git a/content/docs/product/architecture-specs-adrs/specs/registry-mirror.md b/content/docs/product/architecture-specs-adrs/specs/registry-mirror.md index 59537ec..427a980 100644 --- a/content/docs/product/architecture-specs-adrs/specs/registry-mirror.md +++ b/content/docs/product/architecture-specs-adrs/specs/registry-mirror.md @@ -1,5 +1,6 @@ --- title: "Registry Mirrors" +confidentiality: public --- ## Why do we need to use registry mirrors? diff --git a/content/docs/product/managed-apps/dev-experience/git-subtree.md b/content/docs/product/managed-apps/dev-experience/git-subtree.md index ba60007..ba9b437 100644 --- a/content/docs/product/managed-apps/dev-experience/git-subtree.md +++ b/content/docs/product/managed-apps/dev-experience/git-subtree.md @@ -3,13 +3,14 @@ title: "Git subtree for tracking changes in upstream apps" weight: 10 description: > This page describes how to use the git-subtree command to easily track upstream projects without leaving git. +confidentiality: public --- # Git repo setup for tracking upstream changes ## Notes -- github recently changed the default name of the default branch from `master` to `main`; be careful to +- GitHub recently changed the default name of the default branch from `master` to `main`; be careful to not fall for that - some git knowledge is required below - if the upstream repo is a repo that has a single chart only, you can skip the 2nd repo described below @@ -22,6 +23,13 @@ You can watch them here: - [git-subtree intro](https://drive.google.com/file/d/11BWAOIm6Mr04DRPf-E4RHf_y7QSee5f8/view) - [migrating to git-subtree workflow](https://drive.google.com/file/d/1xb8MGhv5OiBgwL5KMCZCRG3cw59xYOtT/view) +## Usages + +The following apps - not limited to - are managed with this method: + +- https://github.com/giantswarm/crossplane/ +- https://github.com/giantswarm/external-secrets + ## Assumptions As an example, I'm @@ -30,10 +38,10 @@ which is one of the charts held in upstream `helm-charts` repo. I want to easily but also be able to submit patches to upstream. I also want to make some specific changes, that I never want to go to upstream. -## Repositories types and setup +## Repository types and setup We will be working with 3 git repositories per project. This covers the most complex scenario, -where we track upstream repo that hosts multiple charts in a single repeository and we want to track +where we track upstream repo that hosts multiple charts in a single repository, and we want to track all of them, but finally get just a single chart from there. Please note, that for a simpler scenario, where you just want to track a subdirectory of an upstream @@ -44,7 +52,7 @@ and "chart repo". The three repos we're going to use are: - Original upstream repo . - Every time I say "upstream" repo, I mean this one. It is read-only for us and maintaned by external + Every time I say "upstream" repo, I mean this one. It is read-only for us and maintained by external organization. - (Optional, but recommended) Our copy for tracking the "upstream". We will fork the upstream to create what we call "upstream copy" @@ -61,12 +69,12 @@ The three repos we're going to use are: ## Setting up repos Everything here will be shown as an example based on the -[grafana's helm charts repo](https://github.com/grafana/helm-charts/). Please make +[grafana helm charts repo](https://github.com/grafana/helm-charts/). Please make sure to go there and have a look at how the repo is organized before you read on. ### Upstream copy -Let's start with creating "upstream copy". Go on github to the "upstream" repo and fork it. +Let's start with creating "upstream copy". Go on GitHub to the "upstream" repo and fork it. Make sure to change the default repo name into something meaningful and ending with "-upstream". In my example, the default repo name was `giantswarm/helm-charts`, but I changed it to @@ -150,15 +158,19 @@ Now, we add code from "upstream-copy" as subtree. We have 2 options here: ```bash # Create a work branch git checkout -b tmp + # Add remote in a subdir # git subtree add --prefix [target directory] [git remote] [remote branch] --squash git subtree add --prefix upstream-tmp upstream-copy main --squash + # create a new branch with only the contents of a path # git subtree split -P [path] -b [target branch] git subtree split -P charts/helm-distributed -b temp-split-branch + # create a branch where you will actually update the remote git checkout master git checkout -b updates + # Put the extracted path in [path] in your new branch # git subtree add --squash -P [path] [source branch] git subtree add --squash -P helm/loki-distributed temp-split-branch @@ -200,11 +212,11 @@ git log --grep="^git-subtree-dir: $dir/*\$" ``` As a result, you must work carefully to never delete such a commit messages, as then `git subtree` will -loose any track of your previous `subtree` command. Pay special attention when you merge a branch +lose any track of your previous `subtree` command. Pay special attention when you merge a branch that includes subtree work, as in this case you often edit a long set of messages to something shorter, so it's easy to remove `git subtree` comments. -Example of how it looks like in github: +Example of how it looks like in GitHub: ![Sample git-subtree merge](subtree-msg.png). @@ -212,7 +224,7 @@ Example of how it looks like in github: ## Workflows -### I want to setup my local repos after they were already created for the first time +### I want to set up my local repos after they were already created for the first time - to setup "upstream-copy": @@ -223,11 +235,12 @@ Example of how it looks like in github: git remote add -f upstream https://github.com/grafana/helm-charts.git ``` -- to setup "chart repo" +- to set up "chart repo" ``` git clone git@github.com:giantswarm/loki-app.git cd loki-app + git remote add -f --no-tags upstream-copy git@github.com:giantswarm/grafana-helm-charts-upstream.git # add remote ``` @@ -238,7 +251,7 @@ replace `upstream/main` with any other branch or just tag: `vX.Y.Z` (to see upst skip the `--skip-tags` flag, as explained [above in set up instructions](#chart-repo)). 1. In "upstream-copy" repo - - make sure your local "main" branch is up to date with origin "main" + - make sure your local "main" branch is up-to-date with origin "main" - checkout "upstream-main" branch - fetch changes from "upstream/main", merge them with "upstream-main" - checkout "main", merge "upstream-main" to it, push "master" @@ -247,13 +260,19 @@ skip the `--skip-tags` flag, as explained [above in set up instructions](#chart- ``` git checkout main git pull origin main + git checkout upstream-main git fetch upstream + git merge upstream/main + git push origin upstream-main # push upstream changes to GitHub git push origin [latest-tag-from-upstream] + git checkout main + git merge upstream-main + git push origin main git push origin [latest-tag-from-upstream] ``` @@ -269,13 +288,22 @@ skip the `--skip-tags` flag, as explained [above in set up instructions](#chart- - if the subtree is tracking a subdir of "upstream copy": ``` - git fetch upstream-copy refs/tags/:refs/tags/ # fetch the most recent state from "upstream " - git checkout # it's OK to be in detached head, we won't change anything + # Fetch the upstream tags as `upstream-` + git fetch upstream-copy refs/tags/:refs/tags/upstream- + + # It's OK to be in detached head, we won't change anything + git checkout upstream- + git subtree split -P charts/loki-distributed -b temp-split-branch + git checkout master git subtree merge --squash -P helm/loki temp-split-branch + git push + + # Clean up temporary split branch and upstream tag git branch -D temp-split-branch + git tag -d upstream- ``` ### I want to send non-urgent patch for upstream @@ -286,7 +314,7 @@ is accepted by upstream (so, you'll get your patch applied and then get it from - go to "upstream copy", update remote "upstream" and fetch changes into the "upstream-main" branch - create a branch "my-feature" from "upstream-main" - when ready, create a PR for "upstream" -- when PR is merged, remove local "my-feature" branch and update our dependencies as in [normal upstream update](#I-want-to-update-to-the-latest-version-from-upstream) +- when PR is merged, remove local "my-feature" branch and update our dependencies as in [normal upstream update](#i-want-to-update-to-the-latest-version-from-upstream) ### I want to send urgent patch for upstream and use it already @@ -294,20 +322,20 @@ Do this if you want to submit a patch for "upstream" and you need to use it righ for being accepted by upstream: {{% alert title="Pay attention" color="warning" %}} -GitHub automatically closes the Pull Request in the upstream repository once you merge the subtree PR in the apps repository. You can simply re-open it. +GitHub automatically closes the Pull Request in the upstream repository once you merge the subtree PR in the app's repository. You can simply re-open it. {{% /alert %}} -- go to "upstream copy" repository, update remote "upstream" and fetch changes into the "upstream-main" branch (Step 1 of [I want to update to the latest version from upstream](i-want-to-update-to-the-latest-version-from-upstream)) +- go to "upstream copy" repository, update remote "upstream" and fetch changes into the "upstream-main" branch (Step 1 of [I want to update to the latest version from upstream](i-want-to-update-to-the-latest-version-from-upstreami-want-to-update-to-the-latest-version-from-upstream)) - create a branch "my-feature" from "upstream-main" - when ready, create a PR (PR1) for "upstream" - create another PR (PR2) to merge "my-feature" into "main" -- when PR2 is merged, update "chart repo" dependency on "upstream-copy/master" as in point 2 in [normal upstream update](#I-want-to-update-to-the-latest-version-from-upstream) -- when PR1 is merged, remove local "my-feature" branch and update our dependencies as in [normal upstream update](#I-want-to-update-to-the-latest-version-from-upstream) +- when PR2 is merged, update "chart repo" dependency on "upstream-copy/master" as in point 2 in [normal upstream update](#i-want-to-update-to-the-latest-version-from-upstream) +- when PR1 is merged, remove local "my-feature" branch and update our dependencies as in [normal upstream update](#i-want-to-update-to-the-latest-version-from-upstream) ### I want to make changes that I don't want to be ever sent to upstream Do this if you want to make any Giant Swarm specific changes to the chart. -We have two options about where to do that and it's up to you to think where it makes the most +We have two options about where to do that, and it's up to you to think where it makes the most sense. 1. In the "chart repo" - this should be your default. @@ -316,12 +344,12 @@ sense. changes won't be lost when you update it. 2. In the "upstream copy" repo - makes sense for cases where multiple charts include some shared - sub-chart and you want to patch it. + sub-chart, and you want to patch it. - go to "upstream copy", checkout and update "main" branch - create a branch "my-feature" from "main" - when ready, create a PR from "my-feature" to "main" - - when PR is merged, update "chart repo" dependency on "upstream-copy/master" as in point 2 in [normal upstream update](#I-want-to-update-to-the-latest-version-from-upstream) + - when PR is merged, update "chart repo" dependency on "upstream-copy/master" as in point 2 in [normal upstream update](#i-want-to-update-to-the-latest-version-from-upstream) ### I want to switch from another way of tracking upstream to the git-subtree way @@ -330,7 +358,7 @@ In general, we have two options here: 1) Git-supported. It works like this: we start by figuring our a commit (tag, branch, anything) in our current repo that was an exact copy of a known upstream version. Let's say this is - reperesented by the `vX.Y.Z` tag. Now, I can save a diff between that clean state (a state of + represented by the `vX.Y.Z` tag. Now, I can save a diff between that clean state (a state of my repo when I got it from the "upstream" but before I applied any custom changes) and my current most recent state. The result should include everything we've changed since `vX.Y.Z` comparing to upstream.