Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Prerelease) WGE integration #495

Merged
merged 30 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3cf029e
canary api type
juozasg Sep 20, 2023
ea1f6f8
canary node
juozasg Sep 20, 2023
98c2e9e
canary visualization
juozasg Sep 20, 2023
eb8593e
canary markdown
juozasg Sep 20, 2023
d49faf7
live node children wip
juozasg Sep 20, 2023
cf819f9
quick and cheap canary children
juozasg Sep 20, 2023
eb41a09
canary percent
juozasg Sep 20, 2023
895f200
open WGE context
juozasg Sep 20, 2023
458b4b9
gitops.openWge command
juozasg Sep 20, 2023
262e341
rename templates view to wge view
juozasg Oct 16, 2023
c17a4e4
factor out icons
juozasg Oct 16, 2023
df8e64b
wge types
juozasg Oct 19, 2023
9fe71e2
load helm release to lookup wge portal url
juozasg Oct 19, 2023
612bfbd
open in wge portal wip
juozasg Oct 19, 2023
120fba6
icons
juozasg Oct 23, 2023
94ffc4d
clearer vscode contexts
juozasg Oct 23, 2023
bf94086
open in wge portal links
juozasg Oct 23, 2023
a29dd47
more wge portal
juozasg Oct 23, 2023
5a1f823
pipeline details wip
juozasg Oct 25, 2023
ebd2d00
key collapse states uniq
juozasg Oct 30, 2023
714f250
breakdown TreeNode
juozasg Oct 30, 2023
67188d7
more precise kubernetes types
juozasg Oct 30, 2023
7178b56
fix contexts again
juozasg Oct 30, 2023
f94b8a7
pipelines details wip
juozasg Oct 31, 2023
72d5c53
pipelines ui
juozasg Oct 31, 2023
82b5cb5
two level collapse states
juozasg Oct 31, 2023
50e8e86
openInWge urls
juozasg Oct 31, 2023
caf4ba1
pipeline markdown
juozasg Oct 31, 2023
a4adcfd
kubectl patch for resume/suspend and pipeline manual/auto
juozasg Oct 31, 2023
6b3b3cd
README
juozasg Oct 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 setting:

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.
86 changes: 69 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
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
14 changes: 14 additions & 0 deletions src/cli/kubernetes/kubernetesToolsKubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as kubernetes from 'vscode-kubernetes-tools-api';
import * as shell from 'cli/shell/exec';
import { output } from 'cli/shell/output';
import { telemetry } from 'extension';
import { KubernetesObject, qualifyToolkitKind } from 'types/kubernetes/kubernetesTypes';
import { TelemetryError } from 'types/telemetryEventNames';

/**
Expand Down Expand Up @@ -73,6 +74,19 @@ export async function invokeKubectlCommand(command: string, printOutput = true):
return kubectlShellResult;
}

export async function kubectlPatchNamespacedResource(resource: KubernetesObject, patch: string) {
const namespace = resource.metadata.namespace;
if(!namespace) {
return;
}

const name = resource.metadata.name;
const kind = qualifyToolkitKind(resource.kind);

const cmd = `kubectl patch ${kind} ${name} -n ${namespace} -p '${patch}' --type=merge`;
return shell.execWithOutput(cmd);
}


function getRequestTimeout(): string {
return workspace.getConfiguration('gitops').get('kubectlRequestTimeout') || '20s';
Expand Down
Loading
Loading