Skip to content

Commit

Permalink
merge WGE support into main (#496)
Browse files Browse the repository at this point in the history
Support for Template, Canary, Pipeline and GitOpsSet resources.

Integration with WGE web portal and documentation.

TreeNode code improvements

---------

Co-authored-by: gitops-release-bot <kingdon+github-bot@weave.works>
Co-authored-by: Kingdon Barrett <kingdon@weave.works>
  • Loading branch information
3 people authored Dec 6, 2023
1 parent 1f09314 commit 80a94db
Show file tree
Hide file tree
Showing 77 changed files with 2,019 additions and 650 deletions.
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,37 @@ The GitOps Tools Extension depends on the [Kubernetes Tools](https://marketplace
- Make sure you have [successfully authenticated](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli) on your `az` CLI and have access to the [correct subscription](https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_set) for your AKS or ARC cluster.
- The easiest way to get your AKS or Arc cluster visible by the GitOps and Kubernetes Extensions, is to use the `az` CLI to merge the kubeconfig for accessing your cluster onto the default `kubectl` config. Use `get-credentials` as shown in the [official CLI documentation](https://docs.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az_aks_get_credentials). In order to enable GitOps in a cluster you will likely need the `--admin` credentials.

## Weave GitOps Enterprise (WGE) Templates
## Weave GitOps Enterprise (WGE) Integration

WGE users can access GitOpsTemplates directly from this extensions. Templates are provided by cluster administrators (Platform Teams) and can be used to quickly create cluster and configure applications with GitOps.
WGE users can access GitOpsTemplates directly from this extensions. WGE integration adds another treeview that shows `GitOpsTemplate`, `Canary`, `Pipeline`, and `GitOpsSet` resources and adds new interactions to each type of resource. All WGE resources have a right-click action to open them in WGE portal.

Templates are an opt-in feature that must be enabled in setting:
GitOpsTemplates are provided by cluster administrators (Platform Teams) and can be used to quickly create cluster and configure applications with GitOps. Flagger Canaries status can be visualized and their progress tracked. Pipelines are listed with their targets and each `GitopsCluster` attached to a Pipeline can be set as the current selected cluster for quick navigation between clusters.

![Enable GitOpsTemplates](docs/images/vscode-templates-config.png)
WGE integration is an opt-in feature that must be enabled in settings:

After that they can be seen in a new 'Templates' view. Right-click a template to use it:
![Enable WGE Features](docs/images/config-enable-wge.png)

![Weave GitOps Treeview](docs/images/weave-gitops-treeview.png)

![Create GitOps Template](docs/images/vscode-templates-view.png)

### WGE Configuration

For the integration to work, this extension needs a ConfigMap that provides WGE information and settings:

`kubectl create configmap weave-gitops-interop --from-literal=portalUrl='https://WGE-CLUSTER-HOST' --from-literal=wgeClusterName='WGE-CLUSTER-NAME' -n flux-system``

```
apiVersion: v1
kind: ConfigMap
metadata:
namespace: flux-system
name: weave-gitops-interop
data:
portalUrl: https://mccp.howard.moomboo.space
wgeClusterName: howard-moomboo-space
```

![Use GitOpsTemplates](docs/images/vscode-templates-view.png)



Expand Down
Binary file added docs/images/config-enable-wge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/images/vscode-templates-config.png
Binary file not shown.
Binary file added docs/images/weave-gitops-treeview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 70 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-gitops-tools",
"displayName": "GitOps Tools for Flux",
"description": "GitOps automation tools for continuous delivery of Kubernetes and Cloud Native applications",
"version": "0.25.4",
"version": "0.25.5-edge.0",
"author": "Kingdon Barrett <kingdon@weave.works>",
"contributors": [
"Kingdon Barrett <kingdon@weave.works>",
Expand Down Expand Up @@ -98,6 +98,16 @@
"title": "Resume",
"category": "GitOps"
},
{
"command": "gitops.manualPromotion",
"title": "Disable Automatic Promotion",
"category": "GitOps"
},
{
"command": "gitops.autoPromotion",
"title": "Enable Automatic Promotion",
"category": "GitOps"
},
{
"command": "gitops.flux.checkPrerequisites",
"title": "Flux Check Prerequisites",
Expand Down Expand Up @@ -259,6 +269,16 @@
"command": "gitops.views.createFromTemplate",
"title": "Create from Template",
"category": "GitOps"
},
{
"command": "gitops.views.openInWgePortal",
"title": "Open in Weave Gitops Enterprise...",
"category": "GitOps"
},
{
"command": "gitops.views.setContextToGitopsCluster",
"title": "Set as Current Context",
"category": "GitOps"
}
],
"viewsContainers": {
Expand All @@ -285,8 +305,8 @@
"name": "Workloads"
},
{
"id": "gitops.views.templates",
"name": "Templates",
"id": "gitops.views.wge",
"name": "Weave GitOps",
"when": "config.gitops.weaveGitopsEnterprise"
},
{
Expand All @@ -311,7 +331,7 @@
"gitops.weaveGitopsEnterprise": {
"type": "boolean",
"default": false,
"description": "Enable WGE GitOpsTemplates feature"
"description": "Enable WGE features"
},
"gitops.kubectlRequestTimeout": {
"type": "string",
Expand Down Expand Up @@ -388,7 +408,7 @@
{
"command": "gitops.views.refreshResourcesTreeView",
"group": "navigation@1",
"when": "view == gitops.views.templates"
"when": "view == gitops.views.wge"
},
{
"command": "gitops.views.showWorkloadsHelpMessage",
Expand Down Expand Up @@ -434,57 +454,67 @@
},
{
"command": "gitops.flux.reconcileSource",
"when": "view == gitops.views.sources && viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/",
"when": "viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/",
"group": "navigation@0"
},
{
"command": "gitops.flux.reconcileWorkloadWithSource",
"when": "view == gitops.views.workloads && viewItem =~ /(Kustomization;|HelmRelease;)/",
"when": "viewItem =~ /(Kustomization;|HelmRelease;)/",
"group": "navigation@0"
},
{
"command": "gitops.flux.reconcileWorkload",
"when": "view == gitops.views.workloads && viewItem =~ /(Kustomization;|HelmRelease;)/",
"when": "viewItem =~ /(Kustomization;|HelmRelease;)/",
"group": "navigation@1"
},
{
"command": "gitops.suspend",
"when": "view =~ /(gitops.views.sources|gitops.views.workloads)/ && viewItem =~ /(GitRepository;|OCIRepository;|Kustomization;|HelmRelease;|HelmRepository;)/ && viewItem =~ /notSuspend;/",
"when": "viewItem =~ /notSuspend;/",
"group": "navigation@1"
},
{
"command": "gitops.resume",
"when": "view =~ /(gitops.views.sources|gitops.views.workloads)/ && viewItem =~ /(GitRepository;|OCIRepository;|Kustomization;|HelmRelease;|HelmRepository;)/ && viewItem =~ /suspend;/",
"when": "viewItem =~ /suspend;/",
"group": "navigation@1"
},
{
"command": "gitops.manualPromotion",
"when": "viewItem =~ /autoPromotion;/",
"group": "1"
},
{
"command": "gitops.autoPromotion",
"when": "viewItem =~ /manualPromotion;/",
"group": "1"
},
{
"command": "gitops.views.deleteWorkload",
"when": "view == gitops.views.workloads && viewItem =~ /(Kustomization;|HelmRelease;)/",
"when": "viewItem =~ /(Kustomization;|HelmRelease;)/",
"group": "navigation@2"
},
{
"command": "gitops.views.deleteSource",
"when": "view == gitops.views.sources && viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/",
"when": "viewItem =~ /(GitRepository;|OCIRepository;|HelmRepository;|Bucket;)/",
"group": "navigation@2"
},
{
"command": "gitops.views.pullGitRepository",
"when": "view == gitops.views.sources && viewItem =~ /GitRepository;/",
"when": "viewItem =~ /GitRepository;/",
"group": "navigation@3"
},
{
"command": "gitops.addKustomization",
"when": "view == gitops.views.sources && viewItem =~ /GitRepository;|OCIRepository;|Bucket;/",
"when": "viewItem =~ /GitRepository;|OCIRepository;|Bucket;/",
"group": "navigation@3"
},
{
"command": "gitops.editor.showLogs",
"when": "view =~ /^(gitops.views.clusters)$/ && viewItem =~ /(Deployment;)/"
"when": "viewItem =~ /(Deployment;)/"
},
{
"command": "gitops.copyResourceName",
"when": "view =~ /^(gitops.views.sources|gitops.views.workloads)$/",
"group": "navigation@9"
"when": "view =~ /^(gitops.views.sources|gitops.views.workloads||gitops.views.wge)$/ && !(viewItem =~ /Container;/)",
"group": "9"
},
{
"command": "gitops.flux.trace",
Expand All @@ -494,7 +524,17 @@
{
"command": "gitops.views.createFromTemplate",
"group": "1",
"when": "view == gitops.views.templates"
"when": "viewItem =~ /GitOpsTemplate;/"
},
{
"command": "gitops.views.openInWgePortal",
"group": "1",
"when": "viewItem =~ /hasWgePortal;/"
},
{
"command": "gitops.views.setContextToGitopsCluster",
"group": "1",
"when": "viewItem =~ /GitopsCluster;/"
}
],
"gitops.explorer": [
Expand Down Expand Up @@ -543,6 +583,14 @@
"command": "gitops.resume",
"when": "never"
},
{
"command": "gitops.manualPromotion",
"when": "never"
},
{
"command": "gitops.autoPromotion",
"when": "never"
},
{
"command": "gitops.flux.check",
"when": "never"
Expand Down Expand Up @@ -614,6 +662,10 @@
{
"command": "gitops.dev.showGlobalState",
"when": "gitops:isDev"
},
{
"command": "gitops.views.setContextToGitopsCluster",
"when": "never"
}
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/kubernetes/apiResources.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { redrawResourcesTreeViews, refreshResourcesTreeViews } from 'commands/refreshTreeViews';
import { currentContextData } from 'data/contextData';
import { currentContextData, loadContextData } from 'data/contextData';
import { setVSCodeContext, telemetry } from 'extension';
import { ContextId } from 'types/extensionIds';
import { Kind } from 'types/kubernetes/kubernetesTypes';
Expand Down Expand Up @@ -102,5 +102,6 @@ export async function loadAvailableResourceKinds() {
// give proxy init callbacks time to fire
setTimeout(() => {
refreshResourcesTreeViews();
loadContextData();
}, 100);
}
63 changes: 54 additions & 9 deletions src/cli/kubernetes/kubectlGet.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import safesh from 'shell-escape-tag';

import { telemetry } from 'extension';
import { k8sList } from 'k8s/list';
import { k8sGet, k8sList } from 'k8s/list';
import { Bucket } from 'types/flux/bucket';
import { Canary } from 'types/flux/canary';
import { GitOpsTemplate } from 'types/flux/gitOpsTemplate';
import { GitRepository } from 'types/flux/gitRepository';
import { GitOpsSet } from 'types/flux/gitopsset';
import { HelmRelease } from 'types/flux/helmRelease';
import { HelmRepository } from 'types/flux/helmRepository';
import { Kustomization } from 'types/flux/kustomization';
import { OCIRepository } from 'types/flux/ociRepository';
import { Pipeline } from 'types/flux/pipeline';
import { Deployment, Kind, KubernetesObject, Pod, qualifyToolkitKind } from 'types/kubernetes/kubernetesTypes';
import { TelemetryError } from 'types/telemetryEventNames';
import { parseJson, parseJsonItems } from 'utils/jsonUtils';
Expand All @@ -29,14 +32,21 @@ export const notAnErrorServerNotRunning = /no connection could be made because t
* @param namespace namespace of the target resource
* @param kind kind of the target resource
*/
export async function getResource(name: string, namespace: string, kind: string): Promise<undefined | KubernetesObject> {
const shellResult = await invokeKubectlCommand(`get ${kind}/${name} --namespace=${namespace} -o json`);
export async function getResource<T extends KubernetesObject>(name: string, namespace: string, kind: Kind): Promise<undefined | T> {
const item = await k8sGet(name, namespace, kind);
if(item) {
return item as T;
}

let fqKind = qualifyToolkitKind(kind);

const shellResult = await invokeKubectlCommand(`get ${fqKind}/${name} --namespace=${namespace} -o json`);
if (shellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_RESOURCE);
return;
}

return parseJson(shellResult.stdout);
return parseJson(shellResult.stdout) as T;
}

export async function getResourcesAllNamespaces<T extends KubernetesObject>(kind: Kind, telemetryError: TelemetryError): Promise<T[]> {
Expand Down Expand Up @@ -89,6 +99,18 @@ export async function getGitOpsTemplates(): Promise<GitOpsTemplate[]> {
return getResourcesAllNamespaces(Kind.GitOpsTemplate, TelemetryError.FAILED_TO_GET_GITOPSTEMPLATES);
}

export async function getCanaries(): Promise<Canary[]> {
return getResourcesAllNamespaces(Kind.Canary, TelemetryError.FAILED_TO_GET_HELM_RELEASES);
}

export async function getPipelines(): Promise<Pipeline[]> {
return getResourcesAllNamespaces(Kind.Pipeline, TelemetryError.FAILED_TO_GET_HELM_RELEASES);
}

export async function getGitOpsSet(): Promise<GitOpsSet[]> {
return getResourcesAllNamespaces(Kind.GitOpsSet, TelemetryError.FAILED_TO_GET_HELM_RELEASES);
}


/**
* Get all flux system deployments.
Expand Down Expand Up @@ -118,8 +140,7 @@ export async function getFluxControllers(context?: string): Promise<Deployment[]
* @param name name of the kustomize/helmRelease object
* @param namespace namespace of the kustomize/helmRelease object
*/
export async function getChildrenOfWorkload(
workload: 'kustomize' | 'helm',
export async function getHelmReleaseChildren(
name: string,
namespace: string,
): Promise<KubernetesObject[] | undefined> {
Expand All @@ -129,22 +150,46 @@ export async function getChildrenOfWorkload(
return;
}

const labelNameSelector = `-l ${workload}.toolkit.fluxcd.io/name=${name}`;
const labelNamespaceSelector = `-l ${workload}.toolkit.fluxcd.io/namespace=${namespace}`;
const labelNameSelector = `-l helm.toolkit.fluxcd.io/name=${name}`;
const labelNamespaceSelector = `-l helm.toolkit.fluxcd.io/namespace=${namespace}`;

const query = `get ${resourceKinds.join(',')} ${labelNameSelector} ${labelNamespaceSelector} -A -o json`;
const shellResult = await invokeKubectlCommand(query);

if (!shellResult || shellResult.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD);
window.showErrorMessage(`Failed to get ${workload} created resources: ${shellResult?.stderr}`);
window.showErrorMessage(`Failed to get HelmRelease created resources: ${shellResult?.stderr}`);
return;
}

return parseJsonItems(shellResult.stdout);
}


export async function getCanaryChildren(
name: string,
): Promise<KubernetesObject[]> {
// return [];
const resourceKinds = getAvailableResourcePlurals();
if (!resourceKinds) {
return [];
}

const labelNameSelector = `-l app=${name}`;

const query = `get ${resourceKinds.join(',')} ${labelNameSelector} -A -o json`;
const shellResult = await invokeKubectlCommand(query);

if (!shellResult || shellResult.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD);
window.showErrorMessage(`Failed to get HelmRelease created resources: ${shellResult?.stderr}`);
return [];
}

return parseJsonItems(shellResult.stdout);
}


/**
* Get pods by a deployment name.
* @param name pod target name
Expand Down
Loading

0 comments on commit 80a94db

Please sign in to comment.