import * as path from 'path' ; import * as crypto from 'crypto' ; import * as download from '../download/download' ; import { Shell , shell } from "../../shell" ; import { Dictionary } from '../../utils/dictionary' ; import { fs } from '../../fs' ; import { Kubectl , createOnBinary as kubectlCreateOnBinary } from '../../kubectl' ; import { getCurrentContext } from '../../kubectlUtils' ; import { succeeded } from '../../errorable' ; import { FileBacked } from '../../utils/filebacked' ; import { getKubeconfigPath } from '../kubectl/kubeconfig' ; import { getToolPath } from '../config/config' ; import { Host } from '../../host' ; import { mkdirpAsync } from '../../utils/mkdirp' ; import { platformUrlString , formatBin , platformArch } from '../installer/installationlayout' ; import * as installer from '../installer/installer' ; import { ExecResult } from '../../binutilplusplus' ; const AUTO_VERSION_CACHE_FILE = getCachePath ( shell ) ; const AUTO_VERSION_CACHE = new FileBacked < ClusterVersionCache > ( fs , AUTO_VERSION_CACHE_FILE , defaultClusterVersionCache ) ; const OPERATION_ID_DOWNLOAD_KC_FOR_BOOTSTRAP = 'autoversion:download_kubectl_for_bootstrap' ; interface ClusterVersionCache { readonly derivedFromKubeconfig : string | undefined ; readonly versions : Dictionary < string > ; } export async function ensureSuitableKubectl ( kubectl , shell , host ) { if ( ! ( await fs . existsAsync ( getBasePath ( shell ) ) ) ) { await mkdirpAsync ( getBasePath ( shell ) ) ; } const bootstrapperKubectl = await ensureBootstrapperKubectl ( kubectl , shell , host ) ; if ( ! bootstrapperKubectl ) { return undefined ; } const context = await getCurrentContext ( bootstrapperKubectl , { silent : true } ) ; if ( ! context ) { return undefined ; } const serverVersion = await getServerVersion ( bootstrapperKubectl , context . contextName ) ; if ( ! serverVersion ) { return undefined ; } if ( ! ( await gotKubectlVersion ( shell , serverVersion ) ) ) { const downloaded = await downloadKubectlVersion ( shell , host , serverVersion ) ; if ( ! downloaded ) { return undefined ; } } return kubectlVersionPath ( shell , serverVersion ) ; } function getBasePath ( shell ) { return path . join ( shell . home ( ) , `.vs-kubernetes/tools/kubectl/autoversion` ) ; } function getCachePath ( shell ) { return path . join ( getBasePath ( shell ) , `cache.json` ) ; } async function gotKubectlVersion ( shell , serverVersion ) { const binPath = kubectlVersionPath ( shell , serverVersion ) ; if ( ! binPath ) { return true ; } return await fs . existsAsync ( binPath ) ; } async function downloadKubectlVersion ( shell , host , serverVersion ) { const binPath = kubectlVersionPath ( shell , serverVersion ) ; if ( ! binPath ) { return false ; } const os = platformUrlString ( shell . platform ( ) ) ! ; const arch = platformArch ( os ) ; const binFile = ( shell . isUnix ( ) ) ? 'kubectl' : 'kubectl.exe' ; const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${ serverVersion }/bin/${ os }/${ arch }/${ binFile }` ; const downloadResult = await host . longRunning ( { title : `Downloading kubectl ${ serverVersion }` , operationKey : binPath } , ( ) => download . once ( kubectlUrl , binPath ) ) ; if ( shell . isUnix ( ) ) { await fs . chmod ( binPath , '0755' ) ; } return succeeded ( downloadResult ) ; } function kubectlVersionPath ( shell , serverVersion ) { const platform = shell . platform ( ) ; const binPath = formatBin ( 'kubectl' , platform ) ; if ( ! binPath ) { return undefined ; } return path . join ( getBasePath ( shell ) , serverVersion , binPath ) ; } async function ensureCacheIsForCurrentKubeconfig ( ) { const kubeconfigHashText = await getKubeconfigPathHash ( ) ; if ( ! kubeconfigHashText ) { AUTO_VERSION_CACHE . update ( { derivedFromKubeconfig : undefined , versions : { } } ) ; return ; } const cacheKubeconfigHashText = ( await AUTO_VERSION_CACHE . get ( ) ) . derivedFromKubeconfig ; if ( kubeconfigHashText !== cacheKubeconfigHashText ) { AUTO_VERSION_CACHE . update ( { derivedFromKubeconfig : kubeconfigHashText , versions : { } } ) ; } } async function getKubeconfigPathHash ( ) { const kubeconfigPath = getKubeconfigPath ( ) ; const kubeconfigFilePath = kubeconfigPath . pathType === "host" ? kubeconfigPath . hostPath : kubeconfigPath . wslPath ; if ( ! await fs . existsAsync ( kubeconfigFilePath ) ) { return undefined ; } const kubeconfigHashText = crypto . createHash ( 'sha256' ) . update ( Buffer . from ( kubeconfigFilePath ) ) . digest ( 'hex' ) ; return kubeconfigHashText ; } async function getServerVersion ( kubectl , context ) { await ensureCacheIsForCurrentKubeconfig ( ) ; const cachedVersions = await AUTO_VERSION_CACHE . get ( ) ; if ( cachedVersions . versions [ context ] ) { return cachedVersions . versions [ context ] ; } const versionInfo = await kubectl . readJSON < any > ( 'version -o json' ) ; if ( ExecResult . failed ( versionInfo ) ) { return undefined ; } if ( versionInfo . result && versionInfo . result . serverVersion ) { const serverVersion = versionInfo . result . serverVersion . gitVersion ; cachedVersions . versions [ context ] = serverVersion ; await AUTO_VERSION_CACHE . update ( cachedVersions ) ; return serverVersion ; } return undefined ; } function defaultClusterVersionCache ( ) { return { derivedFromKubeconfig : undefined , versions : { } } ; } async function ensureBootstrapperKubectl ( naiveKubectl , shell , host ) { if ( await naiveKubectl . ensurePresent ( { silent : true } ) ) { return naiveKubectl ; } const existingKubectls = getExistingAutoversionKubectls ( shell ) ; if ( existingKubectls . length > 0 ) { const existingKubectl = existingKubectls [ 0 ] ; return kubectlCreateOnBinary ( host , fs , shell , existingKubectl ) ; } const installationResult = await host . longRunning ( { title : 'Downloading default version of kubectl' , operationKey : OPERATION_ID_DOWNLOAD_KC_FOR_BOOTSTRAP } , ( ) => installer . installKubectl ( shell ) ) ; if ( ! installationResult . succeeded ) { return undefined ; } const installedToolPath = getToolPath ( host , shell , 'kubectl' ) ; if ( ! installedToolPath ) { return undefined ; } return kubectlCreateOnBinary ( host , fs , shell , installedToolPath ) ; } function getExistingAutoversionKubectls ( shell ) { const baseFolder = getBasePath ( shell ) ; const downloadedFolders = fs . dirSync ( baseFolder ) . map ( ( p ) => path . join ( baseFolder , p ) ) . filter ( ( p ) => fs . statSync ( p ) . isDirectory ) ; const binName = formatBin ( 'kubectl' , shell . platform ( ) ) ; if ( ! binName ) { return [ ] ; } const kubectls = downloadedFolders . map ( ( f ) => path . join ( f , binName ) ) . filter ( ( p ) => fs . existsSync ( p ) ) ; return kubectls ; }