import { KubernetesListObject , KubernetesObject } from '@kubernetes/client-node' ; import { Uri , window } from 'vscode' ; import * as kubernetes from 'vscode-kubernetes-tools-api' ; import { AzureConstants } from '../azure/azureTools' ; import { ContextTypes , setVSCodeContext } from '../context' ; import { checkIfOpenedFolderGitRepositorySourceExists } from '../git/checkIfOpenedFolderGitRepositorySourceExists' ; import { output } from '../output' ; import { telemetry } from '../telemetry' ; import { parseJson } from '../utils/jsonUtils' ; import { BucketResult } from './bucket' ; import { GitRepositoryResult } from './gitRepository' ; import { HelmReleaseResult } from './helmRelease' ; import { HelmRepositoryResult } from './helmRepository' ; import { KubernetesConfig , KubernetesContextWithCluster } from './kubernetesConfig' ; import { KubernetesFileSchemes } from './kubernetesFileSchemes' ; import { ClusterProvider , ConfigMap , DeploymentResult , KubectlVersionResult , NamespaceResult , NodeResult , PodResult } from './kubernetesTypes' ; import { KustomizeResult } from './kustomize' ; see https see https class KubernetesTools { /**
     * Keep a reference to the Kubernetes extension api.
     */ private kubectlApi ? : kubernetes . KubectlV1 ; /**
     * Current cluster supported kubernetes resource kinds.
     */ private clusterSupportedResourceKinds ? : string [ ] ; see https async getKubectlApi ( ) { if ( this . kubectlApi ) { return this . kubectlApi ; } const kubectl = await kubernetes . extension . kubectl . v1 ; if ( ! kubectl . available ) { window . showErrorMessage ( `Kubernetes Tools Kubectl API is unavailable: ${ kubectl . reason }` ) ; telemetry . sendError ( 'KUBERNETES_TOOLS_API_UNAVAILABLE' , new Error ( kubectl . reason ) ) ; return ; } this . kubectlApi = kubectl . api ; return this . kubectlApi ; } param command returns async invokeKubectlCommand ( command ) { const kubectl = await this . getKubectlApi ( ) ; if ( ! kubectl ) { return ; } const kubectlShellResult = await kubectl . invokeCommand ( command ) ; output . send ( `> ${ command }\n` , { channelName : 'GitOps: kubectl' , addNewline : false , revealOutputView : false , } ) ; if ( kubectlShellResult ?. code === 0 ) { output . send ( kubectlShellResult . stdout , { channelName : 'GitOps: kubectl' , revealOutputView : false , } ) ; } else { output . send ( kubectlShellResult ?. stderr || '' , { channelName : 'GitOps: kubectl' , revealOutputView : false , logLevel : 'error' , } ) ; } return kubectlShellResult ; } /**
     * Gets current kubectl config with available contexts and clusters.
     */ async getKubectlConfig ( ) { const configShellResult = await this . invokeKubectlCommand ( 'config view -o json' ) ; if ( ! configShellResult || configShellResult . stderr ) { telemetry . sendError ( 'FAILED_TO_GET_KUBECTL_CONFIG' ) ; console . warn ( `Failed to get kubectl config: ${ configShellResult ?. stderr }` ) ; return ; } return parseJson ( configShellResult . stdout ) ; } /**
     * Gets current kubectl context name.
     */ async getCurrentContext ( ) { const currentContextShellResult = await this . invokeKubectlCommand ( 'config current-context' ) ; if ( ! currentContextShellResult || currentContextShellResult . stderr ) { telemetry . sendError ( 'FAILED_TO_GET_CURRENT_KUBERNETES_CONTEXT' ) ; console . warn ( `Failed to get current kubectl context: ${ currentContextShellResult ?. stderr }` ) ; setVSCodeContext ( ContextTypes . NoClusterSelected , true ) ; return ; } const currentContext = currentContextShellResult . stdout . trim ( ) ; setVSCodeContext ( ContextTypes . NoClusterSelected , ! currentContext ) ; return currentContext ; } param contextName returns async setCurrentContext ( contextName ) { const currentContext = await this . getCurrentContext ( ) ; if ( currentContext && currentContext === contextName ) { return { isChanged : false , } ; } const setContextShellResult = await this . invokeKubectlCommand ( `config use-context ${ contextName }` ) ; if ( setContextShellResult ?. stderr ) { telemetry . sendError ( 'FAILED_TO_SET_CURRENT_KUBERNETES_CONTEXT' ) ; window . showErrorMessage ( `Failed to set kubectl context to ${ contextName }: ${ setContextShellResult ?. stderr }` ) ; return ; } setVSCodeContext ( ContextTypes . NoClusterSelected , false ) ; setVSCodeContext ( ContextTypes . CurrentClusterGitOpsNotEnabled , false ) ; setVSCodeContext ( ContextTypes . NoSources , false ) ; setVSCodeContext ( ContextTypes . NoWorkloads , false ) ; this . clusterSupportedResourceKinds = undefined ; checkIfOpenedFolderGitRepositorySourceExists ( ) ; return { isChanged : true , } ; } /**
     * Get a list of contexts from kubeconfig.
     * Also add cluster info to the context objects.
     */ async getContexts ( ) { const kubectlConfig = await this . getKubectlConfig ( ) ; if ( ! kubectlConfig || ! kubectlConfig . contexts ) { return ; } return kubectlConfig . contexts . map ( ( context ) => { const clusterInfo = kubectlConfig . clusters ?. find ( cluster => cluster . name === context . context . cluster ) ; if ( clusterInfo ) { context . context . clusterInfo = clusterInfo ; } return context ; } ) ; } param name param namespace async getPodsOfADeployment ( name = '' , namespace = '' ) { let nameArg ; if ( name === 'fluxconfig-agent' || name === 'fluxconfig-controller' ) { nameArg = name ? `-l app.kubernetes.io/component=${ name }` : '' ; } else { nameArg = name ? `-l app=${ name }` : '' ; } let namespaceArg = '' ; if ( namespace === 'all' ) { namespaceArg = '--all-namespaces' ; } else if ( namespace . length > 0 ) { namespaceArg = `--namespace=${ namespace }` ; } const podResult = await this . invokeKubectlCommand ( `get pod ${ nameArg } ${ namespaceArg } -o json` ) ; if ( podResult ?. code !== 0 ) { console . warn ( `Failed to get pods: ${ podResult ?. stderr }` ) ; return ; } return parseJson ( podResult ?. stdout ) ; } /**
     * Gets all kustomizations for the current kubectl context.
     */ async getKustomizations ( ) { const kustomizationShellResult = await this . invokeKubectlCommand ( 'get Kustomization -A -o json' ) ; if ( ! kustomizationShellResult || kustomizationShellResult . stderr ) { console . warn ( `Failed to get kubectl kustomizations: ${ kustomizationShellResult ?. stderr }` ) ; return ; } return parseJson ( kustomizationShellResult . stdout ) ; } /**
     * Gets all helm releases from the current kubectl context.
     */ async getHelmReleases ( ) { const helmReleaseShellResult = await this . invokeKubectlCommand ( 'get HelmRelease -A -o json' ) ; if ( ! helmReleaseShellResult || helmReleaseShellResult . stderr ) { console . warn ( `Failed to get kubectl helm releases: ${ helmReleaseShellResult ?. stderr }` ) ; return ; } return parseJson ( helmReleaseShellResult . stdout ) ; } /**
     * Gets all git repositories for the current kubectl context.
     */ async getGitRepositories ( ) { const gitRepositoryShellResult = await this . invokeKubectlCommand ( 'get GitRepository -A -o json' ) ; if ( ! gitRepositoryShellResult || gitRepositoryShellResult . stderr ) { console . warn ( `Failed to get kubectl git repositories: ${ gitRepositoryShellResult ?. stderr }` ) ; return ; } return parseJson ( gitRepositoryShellResult . stdout ) ; } /**
     * Gets all helm repositories for the current kubectl context.
     */ async getHelmRepositories ( ) { const helmRepositoryShellResult = await this . invokeKubectlCommand ( 'get HelmRepository -A -o json' ) ; if ( ! helmRepositoryShellResult || helmRepositoryShellResult . stderr ) { console . warn ( `Failed to get kubectl helm repositories: ${ helmRepositoryShellResult ?. stderr }` ) ; return ; } return parseJson ( helmRepositoryShellResult . stdout ) ; } /**
     * Gets all buckets for the current kubectl context.
     */ async getBuckets ( ) { const bucketShellResult = await this . invokeKubectlCommand ( 'get Bucket -A -o json' ) ; if ( ! bucketShellResult || bucketShellResult . stderr ) { console . warn ( `Failed to get kubectl buckets: ${ bucketShellResult ?. stderr }` ) ; return ; } return parseJson ( bucketShellResult . stdout ) ; } /**
     * Get all flux system deployments.
     */ async getFluxControllers ( context ? ) { const contextArg = context ? `--context ${ context }` : '' ; const fluxDeploymentShellResult = await this . invokeKubectlCommand ( `get deployment --namespace=flux-system ${ contextArg } -o json` ) ; if ( fluxDeploymentShellResult ?. code !== 0 ) { console . warn ( `Failed to get flux controllers: ${ fluxDeploymentShellResult ?. stderr }` ) ; return ; } return parseJson ( fluxDeploymentShellResult . stdout ) ; } param contextName async isGitOpsEnabled ( contextName ) { const fluxControllers = await this . getFluxControllers ( contextName ) ; if ( ! fluxControllers ) { return ; } return fluxControllers . items . length !== 0 ; } /**
     * Return all available kubernetes resource kinds.
     */ async getAvailableResourceKinds ( ) { if ( this . clusterSupportedResourceKinds ) { return this . clusterSupportedResourceKinds ; } const kindsShellResult = await this . invokeKubectlCommand ( 'api-resources --verbs=list -o name' ) ; if ( ! kindsShellResult || kindsShellResult . stderr ) { this . clusterSupportedResourceKinds = undefined ; console . warn ( `Failed to get resource kinds: ${ kindsShellResult ?. stderr }` ) ; return ; } const kinds = kindsShellResult . stdout . split ( '\n' ) . filter ( kind => kind . length ) ; this . clusterSupportedResourceKinds = kinds ; return kinds ; } param name param namespace async getChildrenOfWorkload ( workload , name , namespace ) { const resourceKinds = await this . getAvailableResourceKinds ( ) ; if ( ! resourceKinds ) { return ; } const query = `get ${ resourceKinds . join ( ',' ) } -l ${ workload }.toolkit.fluxcd.io/name=${ name } -n ${ namespace } -o json` ; const resourcesShellResult = await this . invokeKubectlCommand ( query ) ; if ( ! resourcesShellResult || resourcesShellResult . code !== 0 ) { telemetry . sendError ( 'FAILED_TO_GET_CHILDREN_OF_A_WORKLOAD' ) ; window . showErrorMessage ( `Failed to get ${ workload } created resources: ${ resourcesShellResult ?. stderr }` ) ; return undefined ; } return parseJson ( resourcesShellResult . stdout ) ; } /**
     * Get namespaces from current context.
     */ async getNamespaces ( ) { const namespacesShellResult = await this . invokeKubectlCommand ( 'get ns -o json' ) ; if ( namespacesShellResult ?. code !== 0 ) { window . showErrorMessage ( `Failed to get namespaces ${ namespacesShellResult ?. stderr }` ) ; return ; } return parseJson ( namespacesShellResult . stdout ) ; } param context async detectClusterProvider ( context ) { const tryProviderAKS = await this . isClusterAKS ( context ) ; if ( tryProviderAKS === ClusterProvider . AKS ) { return ClusterProvider . AKS ; } const tryProviderAzureARC = await this . isClusterAzureARC ( context ) ; if ( tryProviderAzureARC === ClusterProvider . AzureARC ) { return ClusterProvider . AzureARC ; } if ( tryProviderAKS === ClusterProvider . Unknown || tryProviderAzureARC === ClusterProvider . Unknown ) { return ClusterProvider . Unknown ; } else { return ClusterProvider . Generic ; } } param context async isClusterAKS ( context ) { const nodesShellResult = await this . invokeKubectlCommand ( `get nodes --context=${ context } -o json` ) ; if ( nodesShellResult ?. code !== 0 ) { telemetry . sendError ( 'FAILED_TO_GET_NODES_TO_DETECT_AKS_CLUSTER' ) ; console . warn ( `Failed to get nodes from "${ context }" context to determine the cluster type.` ) ; return ClusterProvider . Unknown ; } const nodes = parseJson ( nodesShellResult . stdout ) ; if ( ! nodes ) { return ClusterProvider . Unknown ; } const firstNode = nodes . items [ 0 ] ; if ( ! firstNode ) { console . warn ( `No nodes in the "${ context }" context to determine the cluster type.` ) ; return ClusterProvider . Unknown ; } const providerID = firstNode . spec . providerID ; if ( providerID ?. startsWith ( 'azure:///' ) ) { return ClusterProvider . AKS ; } else { return ClusterProvider . Generic ; } } param context async isClusterAzureARC ( context ) { const configmapShellResult = await this . invokeKubectlCommand ( `get configmaps azure-clusterconfig -n ${ AzureConstants . ArcNamespace } --context=${ context } --ignore-not-found -o json` ) ; if ( configmapShellResult ?. code !== 0 ) { telemetry . sendError ( 'FAILED_TO_GET_CONFIGMAPS_TO_DETECT_ARC_CLUSTER' ) ; console . warn ( `Failed to get configmaps from "${ context }" context to determine the cluster type.` ) ; return ClusterProvider . Unknown ; } const stdout = configmapShellResult . stdout ; if ( stdout . length ) { const azureClusterconfigConfigMap = parseJson ( stdout ) ; if ( azureClusterconfigConfigMap === undefined ) { return ClusterProvider . Unknown ; } else { return ClusterProvider . AzureARC ; } } return ClusterProvider . Generic ; } see https param namespace param resourceName param documentFormat param action returns getResourceUri ( namespace , resourceName , documentFormat , action ? ) { let fileExtension = '' ; if ( documentFormat !== '' ) { fileExtension = `.${ documentFormat }` ; } const documentName = `${ resourceName ?. replace ( '/' , '-' ) }${ fileExtension }` ; let scheme = KubernetesFileSchemes . Resource ; if ( action === 'describe' ) { scheme = KubernetesFileSchemes . ReadonlyResource ; } let authority = KubernetesFileSchemes . KubectlResource ; if ( action === 'describe' ) { authority = KubernetesFileSchemes . DescribeResource ; } let namespaceQuery = '' ; if ( namespace ) { namespaceQuery = `ns=${ namespace }&` ; } const nonce = new Date ( ) . getTime ( ) ; const url = `${ scheme }://${ authority }/${ documentName }?${ namespaceQuery }value=${ resourceName }&_=${ nonce }` ; return Uri . parse ( url ) ; } param name param namespace param kind async getResource ( name , namespace , kind ) { const resourceShellResult = await this . invokeKubectlCommand ( `get ${ kind }/${ name } --namespace=${ namespace } -o json` ) ; if ( resourceShellResult ?. code !== 0 ) { return ; } return parseJson ( resourceShellResult . stdout ) ; } } export const kubernetesTools = new KubernetesTools ( ) ;