Skip to content

Commit

Permalink
Merge pull request #465 from weaveworks/api-resources-loader
Browse files Browse the repository at this point in the history
Api resources loader
  • Loading branch information
Kingdon Barrett authored Aug 29, 2023
2 parents b9e22a4 + 6432347 commit f293cb3
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 176 deletions.
83 changes: 17 additions & 66 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,36 +163,36 @@
"command": "gitops.createKustomization",
"title": "Create Kustomization from Path",
"icon": "$(add)",
"enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled",
"enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled",
"category": "GitOps"
},
{
"command": "gitops.addSource",
"title": "Add Source",
"category": "GitOps",
"icon": "$(add)",
"enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled"
"enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled"
},
{
"command": "gitops.addKustomization",
"title": "Add Kustomization",
"category": "GitOps",
"icon": "$(add)",
"enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled"
"enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled"
},
{
"command": "gitops.views.expandAllSources",
"title": "Expand All",
"category": "GitOps",
"icon": "$(expand-all)",
"enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && !gitops:noSources"
"enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled"
},
{
"command": "gitops.views.expandAllWorkloads",
"title": "Expand All",
"category": "GitOps",
"icon": "$(expand-all)",
"enablement": "!gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && !gitops:noWorkloads"
"enablement": "!gitops:clusterUnreachable && !gitops:currentClusterGitOpsNotEnabled"
},
{
"command": "gitops.views.deleteWorkload",
Expand All @@ -217,7 +217,12 @@
},
{
"command": "gitops.editor.openResource",
"title": "View Config",
"title": "Open Resource",
"category": "GitOps"
},
{
"command": "gitops.editor.openKubeconfig",
"title": "Open Kubeconfig",
"category": "GitOps"
},
{
Expand Down Expand Up @@ -306,74 +311,15 @@
}
},
"viewsWelcome": [
{
"view": "gitops.views.clusters",
"contents": "Loading Clusters ...",
"when": "gitops:loadingClusters"
},
{
"view": "gitops.views.clusters",
"contents": "No clusters.",
"when": "!gitops:loadingClusters && gitops:noClusters"
},
{
"view": "gitops.views.clusters",
"contents": "Failed to load cluster contexts.",
"when": "!gitops:loadingClusters && gitops:failedToLoadClusterContexts"
},
{
"view": "gitops.views.sources",
"contents": "[Enable GitOps](command:gitops.flux.install) for the selected Cluster to view Sources.",
"when": "gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.sources",
"contents": "Loading Sources ...",
"when": "gitops:loadingSources && !gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.sources",
"contents": "No sources.",
"when": "!gitops:loadingSources && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && gitops:noSources && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.sources",
"contents": "Cluster unreachable",
"when": "gitops:clusterUnreachable"
},
{
"view": "gitops.views.sources",
"contents": "Select GitOps Cluster to view Sources.",
"when": "gitops:noClusterSelected"
"when": "gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.workloads",
"contents": "[Enable GitOps](command:gitops.flux.install) for the selected Cluster to view Workloads.",
"when": "gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.workloads",
"contents": "Loading Workloads ...",
"when": "gitops:loadingWorkloads && !gitops:noClusterSelected && !gitops:currentClusterGitOpsNotEnabled && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.workloads",
"contents": "No workloads.",
"when": "!gitops:loadingWorkloads && !gitops:currentClusterGitOpsNotEnabled && !gitops:noClusterSelected && gitops:noWorkloads && !gitops:clusterUnreachable"
},
{
"view": "gitops.views.workloads",
"contents": "Cluster unreachable",
"when": "gitops:clusterUnreachable"
},
{
"view": "gitops.views.workloads",
"contents": "Select GitOps Cluster to view Workloads.",
"when": "gitops:noClusterSelected"
},
{
"view": "gitops.views.documentation",
"contents": "Loading Topics ..."
}
],
"menus": {
Expand All @@ -388,6 +334,11 @@
"group": "1",
"when": "view == gitops.views.clusters"
},
{
"command": "gitops.editor.openKubeconfig",
"group": "1",
"when": "view == gitops.views.clusters"
},
{
"command": "gitops.addSource",
"group": "navigation@0",
Expand Down
11 changes: 8 additions & 3 deletions src/cli/flux/fluxTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ class FluxTools {
if (!enabledFluxChecks()) {
return undefined;
}
console.warn('NOOOOO flux check');
const result = await shell.execWithOutput(safesh`flux check --context ${context}`, { revealOutputView: false });

if (result.code !== 0) {
Expand Down Expand Up @@ -134,11 +133,17 @@ class FluxTools {
*/
async tree(name: string, namespace: string): Promise<undefined | FluxTreeResources> {

const treeShellResult = await shell.exec(`flux tree kustomization ${name} -n ${namespace} -o json`);
const cmd = `flux tree kustomization ${name} -n ${namespace} -o json`;
const treeShellResult = await shell.exec(cmd);

if (treeShellResult.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_RUN_FLUX_TREE);
window.showErrorMessage(`Failed to get resources created by the kustomization ${name}. ERROR: ${treeShellResult?.stderr}`);
let errorData = treeShellResult.stderr;
if (treeShellResult.code === null) {
errorData += `Command '${cmd}' timed out`;
}
// + (treeShellResult.code === null ? 'Command timed out' : '';
window.showErrorMessage(`Failed to get resources created by the kustomization ${name}. ERROR: ${errorData}`);
return;
}

Expand Down
25 changes: 24 additions & 1 deletion src/cli/kubernetes/apiResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@ import { invokeKubectlCommand } from './kubernetesToolsKubectl';
import { Kind } from 'types/kubernetes/kubernetesTypes';
import { createK8sClients } from 'k8s/client';
import { ContextId } from 'types/extensionIds';

import { refreshAllTreeViews, refreshResourcesTreeViews } from 'commands/refreshTreeViews';
import { restartKubeProxy } from './kubectlProxy';
import { clusterDataProvider } from 'ui/treeviews/treeViews';

export enum ApiState {
Loading,
Loaded,
ClusterUnreachable,
}

type KindApiParams = {
plural: string; // configmaps, deployments, gitrepositories, ...
group: string; // '', apps, source.toolkit.fluxcd.io, ...
version: string; // v1, v1beta2, ...
};

export let apiState: ApiState = ApiState.Loading;

/*
* Current cluster supported kubernetes resource kinds.
*/
Expand Down Expand Up @@ -41,12 +51,16 @@ export function getAPIParams(kind: Kind): KindApiParams | undefined {

export async function loadAvailableResourceKinds() {
apiResources = undefined;
apiState = ApiState.Loading;

const kindsShellResult = await invokeKubectlCommand('api-resources --verbs=list -o wide');
if (kindsShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_AVAILABLE_RESOURCE_KINDS);
console.warn(`Failed to get resource kinds: ${kindsShellResult?.stderr}`);
apiState = ApiState.ClusterUnreachable;
setVSCodeContext(ContextId.ClusterUnreachable, true);
clusterDataProvider.updateCurrentContextChildNodes();
refreshResourcesTreeViews();
return;
}

Expand Down Expand Up @@ -77,6 +91,15 @@ export async function loadAvailableResourceKinds() {

console.log('apiResources loaded');

apiState = ApiState.Loaded;
setVSCodeContext(ContextId.ClusterUnreachable, false);
clusterDataProvider.updateCurrentContextChildNodes();

createK8sClients();
await restartKubeProxy();

// give proxy init callbacks time to fire
setTimeout(() => {
refreshResourcesTreeViews();
}, 100);
}
58 changes: 37 additions & 21 deletions src/cli/kubernetes/kubernetesConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,81 @@ import { ContextId } from 'types/extensionIds';
import { TelemetryError } from 'types/telemetryEventNames';
import { refreshClustersTreeView } from 'ui/treeviews/treeViews';
import { kcContextsListChanged, kcCurrentContextChanged, kcTextChanged } from 'utils/kubeConfigCompare';
import { loadAvailableResourceKinds } from './apiResources';
import { loadAvailableResourceKinds as loadApiResources } from './apiResources';
import { restartKubeProxy } from './kubectlProxy';
import { loadKubeConfigPath } from './kubernetesConfigWatcher';
import { invokeKubectlCommand } from './kubernetesToolsKubectl';

export enum KubeConfigState {
/* effectively KubeConfigState.Loading has meaning obnly at the extension init
* because subsequent kubeconfig updates are swapped-in atomically. but we keep track of it anyway
*/
Loading,
Loaded,
Failed,
NoContextSelected,
}

export let kubeConfigState: KubeConfigState = KubeConfigState.Loading;

export const kubeConfig: k8s.KubeConfig = new k8s.KubeConfig();
export const kubeConfig: k8s.KubeConfig = new k8s.KubeConfig();

// reload the kubeconfig via kubernetes-tools. fire events if things have changed
export async function syncKubeConfig(forceReloadResourceKinds = false) {
console.log('syncKubeConfig');

kubeConfigState = KubeConfigState.Loading;
const configShellResult = await invokeKubectlCommand('config view');

if (configShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_KUBECTL_CONFIG);
const path = await loadKubeConfigPath();
window.showErrorMessage(`Failed to load kubeconfig: ${path} ${shellCodeError(configShellResult)}`);
kubeConfigState = KubeConfigState.Failed;
return;
}

const newKubeConfig = new k8s.KubeConfig();
newKubeConfig.loadFromString(configShellResult.stdout, {onInvalidEntry: ActionOnInvalid.FILTER});

kubeConfigState = KubeConfigState.Loaded;

if (kcTextChanged(kubeConfig, newKubeConfig)) {
await kubeconfigChanged(newKubeConfig, forceReloadResourceKinds);
} else if(forceReloadResourceKinds) {
loadAvailableResourceKinds();
loadApiResources();
}
}

async function kubeconfigChanged(newKubeConfig: k8s.KubeConfig, forceReloadResourceKinds: boolean) {


async function kubeconfigChanged(newKubeConfig: k8s.KubeConfig, forceReloadResourceKinds: boolean) {
const contextsListChanged = kcContextsListChanged(kubeConfig, newKubeConfig);
const contextChanged = kcCurrentContextChanged(kubeConfig, newKubeConfig);

// load the changed kubeconfig globally so that the following code use the new config
kubeConfig.loadFromString(newKubeConfig.exportConfig(), {onInvalidEntry: ActionOnInvalid.FILTER});

if (contextChanged || forceReloadResourceKinds) {
loadAvailableResourceKinds();
console.log(kubeConfig.currentContext);
if(!currentContextExists()) {
kubeConfigState = KubeConfigState.NoContextSelected;
}


if (contextChanged) {
console.log('currentContext changed', kubeConfig.getCurrentContext());
vscodeOnCurrentContextChanged();
await restartKubeProxy();
// give proxy a chance to start
setTimeout(() => {
refreshAllTreeViews();
}, 100);
setVSCodeContext(ContextId.CurrentClusterGitOpsNotEnabled, false);
setVSCodeContext(ContextId.ClusterUnreachable, false);
}

if (contextChanged || forceReloadResourceKinds) {
refreshClustersTreeView();
loadApiResources();
} else if (contextsListChanged) {
refreshClustersTreeView();
}
}

async function vscodeOnCurrentContextChanged() {
setVSCodeContext(ContextId.NoClusterSelected, false);
setVSCodeContext(ContextId.CurrentClusterGitOpsNotEnabled, false);
setVSCodeContext(ContextId.NoSources, false);
setVSCodeContext(ContextId.NoWorkloads, false);
setVSCodeContext(ContextId.FailedToLoadClusterContexts, false);
setVSCodeContext(ContextId.ClusterUnreachable, false);
}

/**
* Sets current kubectl context.
* @param contextName Kubectl context name to use.
Expand All @@ -100,3 +110,9 @@ export async function setCurrentContext(contextName: string): Promise<undefined
};
}


function currentContextExists() {
const name = kubeConfig.currentContext;
return !!kubeConfig.getContexts().find(context => context.name === name);
}

2 changes: 1 addition & 1 deletion src/cli/kubernetes/kubernetesConfigWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { syncKubeConfig } from './kubernetesConfig';


let fsWacher: vscode.FileSystemWatcher | undefined;
let kubeConfigPath: string | undefined;
export let kubeConfigPath: string | undefined;

export async function loadKubeConfigPath(): Promise<string | undefined> {
const configuration = await kubernetes.extension.configuration.v1_1;
Expand Down
3 changes: 2 additions & 1 deletion src/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { fluxReconcileRepositoryForPath } from './fluxReconcileGitRepositoryForP
import { fluxReconcileSourceCommand } from './fluxReconcileSource';
import { fluxReconcileWorkload, fluxReconcileWorkloadWithSource } from './fluxReconcileWorkload';
import { installFluxCli } from './installFluxCli';
import { openResource } from './openResource';
import { openResource, openKubeconfig } from './openResource';
import { pullGitRepository } from './pullGitRepository';
import { resume } from './resume';
import { setClusterProvider } from './setClusterProvider';
Expand Down Expand Up @@ -82,6 +82,7 @@ export function registerCommands(context: ExtensionContext) {

// editor
registerCommand(CommandId.EditorOpenResource, openResource);
registerCommand(CommandId.EditorOpenKubeconfig, openKubeconfig);

// webview
registerCommand(CommandId.ShowLogs, showLogs);
Expand Down
8 changes: 8 additions & 0 deletions src/commands/openResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Uri, window, workspace } from 'vscode';

import { telemetry } from 'extension';
import { TelemetryError } from 'types/telemetryEventNames';
import { kubeConfig } from 'cli/kubernetes/kubernetesConfig';
import { kubeConfigPath } from 'cli/kubernetes/kubernetesConfigWatcher';

/**
* Open resource in the editor
Expand All @@ -18,3 +20,9 @@ export async function openResource(uri: Uri): Promise<void> {
telemetry.sendError(TelemetryError.FAILED_TO_OPEN_RESOURCE);
});
}

export async function openKubeconfig() {
if (kubeConfigPath) {
openResource(Uri.file(kubeConfigPath));
}
}
Loading

0 comments on commit f293cb3

Please sign in to comment.