diff --git a/package.json b/package.json index 08adb608f..8d8c87032 100644 --- a/package.json +++ b/package.json @@ -807,6 +807,11 @@ "title": "S2I Build", "category": "OpenShift" }, + { + "command": "openshift.Serverless.onClusterBuild", + "title": "On Cluster Build", + "category": "OpenShift" + }, { "command": "openshift.Serverless.deploy", "title": "Deploy", @@ -1223,6 +1228,10 @@ "command": "openshift.Serverless.s2iBuild", "when": "false" }, + { + "command": "openshift.Serverless.onClusterBuild", + "when": "false" + }, { "command": "openshift.Serverless.deploy", "when": "false" @@ -1406,6 +1415,11 @@ "command": "openshift.Serverless.s2iBuild", "when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild|localDeployFunctions)$/", "group": "c1@1" + }, + { + "command": "openshift.Serverless.onClusterBuild", + "when": "view == openshiftServerlessFunctionsView && viewItem =~ /^(localFunctions|localFunctionsWithBuild|localDeployFunctions)$/ && isLoggedIn", + "group": "c1@2" } ], "view/item/context": [ diff --git a/src/@types/git.d.ts b/src/@types/git.d.ts new file mode 100644 index 000000000..1eb99ff03 --- /dev/null +++ b/src/@types/git.d.ts @@ -0,0 +1,400 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken, ThemeIcon } from 'vscode'; +export { ProviderResult } from 'vscode'; + +export interface Git { + readonly path: string; +} + +export interface InputBox { + value: string; +} + +export const enum ForcePushMode { + Force, + ForceWithLease, + ForceWithLeaseIfIncludes, +} + +export const enum RefType { + Head, + RemoteHead, + Tag +} + +export interface Ref { + readonly type: RefType; + readonly name?: string; + readonly commit?: string; + readonly remote?: string; +} + +export interface UpstreamRef { + readonly remote: string; + readonly name: string; +} + +export interface Branch extends Ref { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface Commit { + readonly hash: string; + readonly message: string; + readonly parents: string[]; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; + readonly commitDate?: Date; +} + +export interface Submodule { + readonly name: string; + readonly path: string; + readonly url: string; +} + +export interface Remote { + readonly name: string; + readonly fetchUrl?: string; + readonly pushUrl?: string; + readonly isReadOnly: boolean; +} + +export const enum Status { + INDEX_MODIFIED, + INDEX_ADDED, + INDEX_DELETED, + INDEX_RENAMED, + INDEX_COPIED, + + MODIFIED, + DELETED, + UNTRACKED, + IGNORED, + INTENT_TO_ADD, + INTENT_TO_RENAME, + TYPE_CHANGED, + + ADDED_BY_US, + ADDED_BY_THEM, + DELETED_BY_US, + DELETED_BY_THEM, + BOTH_ADDED, + BOTH_DELETED, + BOTH_MODIFIED +} + +export interface Change { + + /** + * Returns either `originalUri` or `renameUri`, depending + * on whether this change is a rename change. When + * in doubt always use `uri` over the other two alternatives. + */ + readonly uri: Uri; + readonly originalUri: Uri; + readonly renameUri: Uri | undefined; + readonly status: Status; +} + +export interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly refs: Ref[]; + readonly remotes: Remote[]; + readonly submodules: Submodule[]; + readonly rebaseCommit: Commit | undefined; + + readonly mergeChanges: Change[]; + readonly indexChanges: Change[]; + readonly workingTreeChanges: Change[]; + + readonly onDidChange: Event; +} + +export interface RepositoryUIState { + readonly selected: boolean; + readonly onDidChange: Event; +} + +/** + * Log options. + */ +export interface LogOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; + readonly path?: string; + /** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */ + readonly range?: string; + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; +} + +export interface CommitOptions { + all?: boolean | 'tracked'; + amend?: boolean; + signoff?: boolean; + signCommit?: boolean; + empty?: boolean; + noVerify?: boolean; + requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; +} + +export interface FetchOptions { + remote?: string; + ref?: string; + all?: boolean; + prune?: boolean; + depth?: number; +} + +export interface InitOptions { + defaultBranch?: string; +} + +export interface RefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string; + readonly sort?: 'alphabetically' | 'committerdate'; +} + +export interface BranchQuery extends RefQuery { + readonly remote?: boolean; +} + +export interface Repository { + + readonly rootUri: Uri; + readonly inputBox: InputBox; + readonly state: RepositoryState; + readonly ui: RepositoryUIState; + + getConfigs(): Promise<{ key: string; value: string; }[]>; + getConfig(key: string): Promise; + setConfig(key: string, value: string): Promise; + getGlobalConfig(key: string): Promise; + + getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>; + detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>; + buffer(ref: string, path: string): Promise; + show(ref: string, path: string): Promise; + getCommit(ref: string): Promise; + + add(paths: string[]): Promise; + revert(paths: string[]): Promise; + clean(paths: string[]): Promise; + + apply(patch: string, reverse?: boolean): Promise; + diff(cached?: boolean): Promise; + diffWithHEAD(): Promise; + diffWithHEAD(path: string): Promise; + diffWith(ref: string): Promise; + diffWith(ref: string, path: string): Promise; + diffIndexWithHEAD(): Promise; + diffIndexWithHEAD(path: string): Promise; + diffIndexWith(ref: string): Promise; + diffIndexWith(ref: string, path: string): Promise; + diffBlobs(object1: string, object2: string): Promise; + diffBetween(ref1: string, ref2: string): Promise; + diffBetween(ref1: string, ref2: string, path: string): Promise; + + hashObject(data: string): Promise; + + createBranch(name: string, checkout: boolean, ref?: string): Promise; + deleteBranch(name: string, force?: boolean): Promise; + getBranch(name: string): Promise; + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; + setBranchUpstream(name: string, upstream: string): Promise; + + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; + + getMergeBase(ref1: string, ref2: string): Promise; + + tag(name: string, upstream: string): Promise; + deleteTag(name: string): Promise; + + status(): Promise; + checkout(treeish: string): Promise; + + addRemote(name: string, url: string): Promise; + removeRemote(name: string): Promise; + renameRemote(name: string, newName: string): Promise; + + fetch(options?: FetchOptions): Promise; + fetch(remote?: string, ref?: string, depth?: number): Promise; + pull(unshallow?: boolean): Promise; + push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; + + blame(path: string): Promise; + log(options?: LogOptions): Promise; + + commit(message: string, opts?: CommitOptions): Promise; +} + +export interface RemoteSource { + readonly name: string; + readonly description?: string; + readonly url: string | string[]; +} + +export interface RemoteSourceProvider { + readonly name: string; + readonly icon?: string; // codicon name + readonly supportsQuery?: boolean; + getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; + publishRepository?(repository: Repository): Promise; +} + +export interface RemoteSourcePublisher { + readonly name: string; + readonly icon?: string; // codicon name + publishRepository(repository: Repository): Promise; +} + +export interface Credentials { + readonly username: string; + readonly password: string; +} + +export interface CredentialsProvider { + getCredentials(host: Uri): ProviderResult; +} + +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + +export interface PushErrorHandler { + handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; +} + +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; +} + +export interface CommitMessageProvider { + readonly title: string; + readonly icon?: Uri | { light: Uri, dark: Uri } | ThemeIcon; + provideCommitMessage(repository: Repository, changes: string[], cancellationToken?: CancellationToken): Promise; +} + +export type APIState = 'uninitialized' | 'initialized'; + +export interface PublishEvent { + repository: Repository; + branch?: string; +} + +export interface API { + readonly state: APIState; + readonly onDidChangeState: Event; + readonly onDidPublish: Event; + readonly git: Git; + readonly repositories: Repository[]; + readonly onDidOpenRepository: Event; + readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; + getRepository(uri: Uri): Repository | null; + init(root: Uri, options?: InitOptions): Promise; + openRepository(root: Uri): Promise + + registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; + registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; + registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; + registerCommitMessageProvider(provider: CommitMessageProvider): Disposable; +} + +export interface GitExtension { + + readonly enabled: boolean; + readonly onDidChangeEnablement: Event; + + /** + * Returns a specific API version. + * + * Throws error if git extension is disabled. You can listen to the + * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event + * to know when the extension becomes enabled/disabled. + * + * @param version Version number. + * @returns API instance + */ + getAPI(version: 1): API; +} + +export const enum GitErrorCodes { + BadConfigFile = 'BadConfigFile', + AuthenticationFailed = 'AuthenticationFailed', + NoUserNameConfigured = 'NoUserNameConfigured', + NoUserEmailConfigured = 'NoUserEmailConfigured', + NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified', + NotAGitRepository = 'NotAGitRepository', + NotAtRepositoryRoot = 'NotAtRepositoryRoot', + Conflict = 'Conflict', + StashConflict = 'StashConflict', + UnmergedChanges = 'UnmergedChanges', + PushRejected = 'PushRejected', + ForcePushWithLeaseRejected = 'ForcePushWithLeaseRejected', + ForcePushWithLeaseIfIncludesRejected = 'ForcePushWithLeaseIfIncludesRejected', + RemoteConnectionError = 'RemoteConnectionError', + DirtyWorkTree = 'DirtyWorkTree', + CantOpenResource = 'CantOpenResource', + GitNotFound = 'GitNotFound', + CantCreatePipe = 'CantCreatePipe', + PermissionDenied = 'PermissionDenied', + CantAccessRemote = 'CantAccessRemote', + RepositoryNotFound = 'RepositoryNotFound', + RepositoryIsLocked = 'RepositoryIsLocked', + BranchNotFullyMerged = 'BranchNotFullyMerged', + NoRemoteReference = 'NoRemoteReference', + InvalidBranchName = 'InvalidBranchName', + BranchAlreadyExists = 'BranchAlreadyExists', + NoLocalChanges = 'NoLocalChanges', + NoStashFound = 'NoStashFound', + LocalChangesOverwritten = 'LocalChangesOverwritten', + NoUpstreamBranch = 'NoUpstreamBranch', + IsInSubmodule = 'IsInSubmodule', + WrongCase = 'WrongCase', + CantLockRef = 'CantLockRef', + CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', + PatchDoesNotApply = 'PatchDoesNotApply', + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage', + BranchFastForwardRejected = 'BranchFastForwardRejected', + BranchNotYetBorn = 'BranchNotYetBorn', + TagConflict = 'TagConflict' +} diff --git a/src/serverlessFunction/commands.ts b/src/serverlessFunction/commands.ts index c450b3ede..ec0ed921b 100644 --- a/src/serverlessFunction/commands.ts +++ b/src/serverlessFunction/commands.ts @@ -8,6 +8,7 @@ import { load } from 'js-yaml'; import * as path from 'path'; import { CommandOption, CommandText } from '../base/command'; import { FunctionContent, InvokeFunction } from './types'; +import { GitModel } from './git/git'; export class Utils { static async getFuncYamlContent(dir: string): Promise { @@ -80,6 +81,22 @@ export class ServerlessCommand { return commandText } + static onClusterBuildFunction(fsPath: string, namespace: string, buildImage: string, gitModel: GitModel, isOpenShiftCluster: boolean): CommandText { + const commandText = new CommandText('func', 'deploy', [ + new CommandOption('--path', fsPath), + new CommandOption('-i', buildImage), + new CommandOption('-n', namespace), + new CommandOption('-v'), + new CommandOption('--remote'), + new CommandOption('--git-url', gitModel.remoteUrl), + new CommandOption('--git-branch', gitModel.branchName) + ]); + if (isOpenShiftCluster) { + commandText.addOption(new CommandOption('-r', '""')) + } + return commandText + } + static runFunction(location: string, runBuild: boolean): CommandText { const commandText = new CommandText('func', 'run', [ new CommandOption('-p', location), diff --git a/src/serverlessFunction/functions.ts b/src/serverlessFunction/functions.ts index 6ce253a90..c243de5e5 100644 --- a/src/serverlessFunction/functions.ts +++ b/src/serverlessFunction/functions.ts @@ -8,10 +8,13 @@ import { Uri, commands, window } from 'vscode'; import { CliChannel } from '../cli'; import { Oc } from '../oc/ocWrapper'; import { Odo } from '../odo/odoWrapper'; +import { isTektonAware } from '../tekton/tekton'; import { Platform } from '../util/platform'; import { Progress } from '../util/progress'; import { OpenShiftTerminalApi, OpenShiftTerminalManager } from '../webview/openshift-terminal/openShiftTerminal'; import { ServerlessCommand, Utils } from './commands'; +import { GitModel, getGitBranchInteractively, getGitRepoInteractively, getGitStateByPath } from './git/git'; +import { isKnativeServingAware } from './knative'; import { multiStep } from './multiStepInput'; import { FunctionContent, FunctionObject, InvokeFunction } from './types'; @@ -47,6 +50,78 @@ export class Functions { } } + private async getGitModel(fsPath?: string): Promise { + const gitState = await getGitStateByPath(fsPath); + + const gitRemote = await getGitRepoInteractively(gitState); + if (!gitRemote) { + return null; + } + + const gitBranch = await getGitBranchInteractively(gitState, gitRemote); + if (!gitBranch) { + return null; + } + return { + remoteUrl: gitState.remotes.filter((r) => r.name === gitRemote).map((r) => r.fetchUrl)[0], + branchName: gitBranch, + }; + } + + public async onClusterBuild(context: FunctionObject): Promise { + + if (!await isTektonAware()) { + await window.showWarningMessage( + 'This action requires Tekton to be installed on the cluster. Please install it and then proceed to build the function on the cluster.', + ); + return null; + } + + if (!await isKnativeServingAware()) { + await window.showWarningMessage( + 'This action requires Knative Serving to be installed on the cluster. Please install it and then proceed to build the function on the cluster.', + ); + return null; + } + + const gitModel = await this.getGitModel(context.folderURI?.fsPath); + if (!gitModel) { + return null; + } + + const buildImage = await this.getImage(context.folderURI); + if (!buildImage) { + return null; + } + + const currentNamespace: string = await Odo.Instance.getActiveProject(); + const yamlContent = await Utils.getFuncYamlContent(context.folderURI.fsPath); + if (!yamlContent) { + return null; + } + + const deployedNamespace = yamlContent.deploy?.namespace || undefined; + if (deployedNamespace && deployedNamespace !== currentNamespace) { + const response = await window.showInformationMessage(`Function namespace (declared in func.yaml) is different from the current active namespace. Deploy function ${context.name} to current namespace ${currentNamespace}?`, + 'Ok', + 'Cancel'); + if (response !== 'Ok') { + return null; + } + } + await this.clustrBuildTerminal(context, currentNamespace, buildImage, gitModel); + } + + private async clustrBuildTerminal(context: FunctionObject, namespace: string, buildImage: string, gitModel: GitModel) { + const isOpenShiftCluster = await Oc.Instance.isOpenShiftCluster(); + await OpenShiftTerminalManager.getInstance().createTerminal( + ServerlessCommand.onClusterBuildFunction(context.folderURI.fsPath, namespace, buildImage, gitModel, isOpenShiftCluster), + `On Cluster Build - ${context.name}`, + context.folderURI.fsPath, + process.env + ); + } + public async build(context: FunctionObject, s2iBuild: boolean): Promise { const existingTerminal: OpenShiftTerminalApi = this.buildTerminalMap.get(`build-${context.folderURI.fsPath}`); diff --git a/src/serverlessFunction/git/git.ts b/src/serverlessFunction/git/git.ts new file mode 100644 index 000000000..66af6958c --- /dev/null +++ b/src/serverlessFunction/git/git.ts @@ -0,0 +1,161 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import type { API, Branch, Ref, Remote } from '../../@types/git'; + +const GIT_EXTENSION_ID = 'vscode.git'; + +export interface GitState { + readonly remotes: Remote[]; + readonly refs: Ref[]; + readonly remote: Remote; + readonly branch: Branch; + readonly isGit: boolean; +} + +export interface GitModel { + readonly remoteUrl: string; + readonly branchName: string; +} + +export function getGitAPI(): API { + const extension = vscode.extensions.getExtension(GIT_EXTENSION_ID); + if (!extension) { + return null; + } + const gitExtension = extension.exports; + if (!gitExtension) { + return null; + } + return gitExtension.getAPI(1); +} + +function getRemoteByCommit(refs: Ref[], remotes: Remote[], branch: Branch): Remote { + const refsByCommit = refs + .map((r) => { + if (r.remote && r.name) { + return { + ...r, + name: r.name.replace(`${r.remote}/`, ''), + }; + } + return r; + }) + .filter((r) => r.commit === branch.commit && r.name === branch.name) + .sort((a, b) => { + if (!a.remote) { + return 1; + } + if (!b.remote) { + return -1; + } + return a.remote.localeCompare(b.remote); + }); + const remoteNameByCommit = refsByCommit[0]?.remote; + if (remoteNameByCommit) { + return remotes.filter((r) => r.name === remoteNameByCommit)[0]; + } + return undefined; +} + +export async function getGitStateByPath(rootPath?: string): Promise { + let remotes: Remote[] = []; + let refs: Ref[] = []; + let remote: Remote; + let branch: Branch; + let isGit = false; + + const api = getGitAPI(); + if (api) { + const repositories = api.repositories.filter((r) => r.rootUri.fsPath === rootPath); + isGit = repositories.length > 0; + if (isGit) { + const repo = repositories[0]; + remotes = repo.state.remotes; + refs = await repo.getRefs({}); + branch = repo.state.HEAD; + if (branch.commit) { + remote = getRemoteByCommit(refs, remotes, branch); + } + } + } + + return { + remotes, + refs, + remote, + branch, + isGit, + }; +} + +function showWarningByState(gitState: GitState) { + if (!gitState.isGit) { + void vscode.window.showWarningMessage( + 'This project is not a git repository. Please git initialise it and then proceed to build it on the cluster.', + ); + } + + if (!gitState.remote && gitState.branch) { + void vscode.window.showWarningMessage( + 'The local branch is not present remotely. Push it to remote and then proceed to build it on cluster.', + ); + } +} + +function showGitQuickPick( + gitState: GitState, + title: string, + value: string, + items: vscode.QuickPickItem[], +): Promise { + showWarningByState(gitState); + return new Promise((resolve, _reject) => { + const quickPick = vscode.window.createQuickPick(); + quickPick.items = items; + quickPick.value = value; + quickPick.onDidHide(() => { + resolve(undefined); + quickPick.dispose(); + }); + quickPick.onDidChangeSelection((e) => { + quickPick.value = e[0].label; + }); + quickPick.onDidAccept(() => { + resolve(quickPick.value); + quickPick.dispose(); + }); + quickPick.canSelectMany = false; + quickPick.ignoreFocusOut = true; + quickPick.title = title; + quickPick.show(); + }); +} + +export async function getGitRepoInteractively(gitState: GitState): Promise { + return showGitQuickPick( + gitState, + 'Provide the git repository with the function source code', + gitState.remote?.name, + gitState.remotes.map((r) => ({ + label: r.name, + description: r.fetchUrl, + })), + ); +} + +export async function getGitBranchInteractively(gitState: GitState, repository: string): Promise { + return showGitQuickPick( + gitState, + 'Git revision to be used (branch, tag, commit).', + gitState.branch?.name, + gitState.refs + .filter((r) => repository === r.remote) + .map((r) => ({ + label: r.name?.replace(`${repository}/`, ''), + })), + ); +} diff --git a/src/serverlessFunction/knative.ts b/src/serverlessFunction/knative.ts new file mode 100644 index 000000000..c677fc086 --- /dev/null +++ b/src/serverlessFunction/knative.ts @@ -0,0 +1,26 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import * as k8s from 'vscode-kubernetes-tools-api'; + +/** + * Returns true if the cluster has the Knative Serving CRDs, and false otherwise. + * + * @returns true if the cluster has the Knative Serving CRDs, and false otherwise + */ +export async function isKnativeServingAware(): Promise { + const kubectl = await k8s.extension.kubectl.v1; + let isKnative = false; + if (kubectl.available) { + const sr = await kubectl.api.invokeCommand('api-versions'); + isKnative = + sr && + sr.code === 0 && + (sr.stdout.includes('serving.knative.dev/v1') || + sr.stdout.includes('serving.knative.dev/v1alpha1') || + sr.stdout.includes('serving.knative.dev/v1beta1')); + } + return isKnative; +} diff --git a/src/serverlessFunction/view.ts b/src/serverlessFunction/view.ts index d2014b939..c6ed3939a 100644 --- a/src/serverlessFunction/view.ts +++ b/src/serverlessFunction/view.ts @@ -173,6 +173,11 @@ export class ServerlessFunctionView implements TreeDataProvider, D await Functions.getInstance().build(context, true); } + @vsCommand('openshift.Serverless.onClusterBuild') + static async onClusterBuild(context: FunctionObject) { + await Functions.getInstance().onClusterBuild(context); + } + @vsCommand('openshift.Serverless.buildAndRun') static async buildAndRunFunction(context: FunctionObject) { await Functions.getInstance().run(context, true); diff --git a/src/tekton/tekton.ts b/src/tekton/tekton.ts new file mode 100644 index 000000000..a738d5431 --- /dev/null +++ b/src/tekton/tekton.ts @@ -0,0 +1,21 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import * as k8s from 'vscode-kubernetes-tools-api'; + +/** + * Returns true if the cluster has the Tekton CRDs, and false otherwise. + * + * @returns true if the cluster has the Tekton CRDs, and false otherwise + */ +export async function isTektonAware(): Promise { + const kubectl = await k8s.extension.kubectl.v1; + let isTekton = false; + if (kubectl.available) { + const sr = await kubectl.api.invokeCommand('api-versions'); + isTekton = sr && sr.code === 0 && sr.stdout.includes('tekton.dev/v1beta1'); + } + return isTekton; +} diff --git a/src/tools.json b/src/tools.json index e9cc8b017..a6b480896 100644 --- a/src/tools.json +++ b/src/tools.json @@ -63,40 +63,40 @@ "description": "Function CLI tool", "vendor": "Red Hat, Inc.", "name": "func", - "version": "1.10.0", - "versionRange": "1.10.0", - "versionRangeLabel": "v1.10.0", + "version": "1.12.0", + "versionRange": "1.12.0", + "versionRangeLabel": "v1.12.0", "dlFileName": "func", "cmdFileName": "func", "filePrefix": "", "platform": { "win32": { - "url": "https://github.com/knative/func/releases/download/knative-v1.10.0/func_windows_amd64.exe", - "sha256sum": "0d1ac065f67b2461c709a1cdbd477d26eb57a53635892d1726b945fc9e582409", + "url": "https://github.com/knative/func/releases/download/knative-v1.12.0/func_windows_amd64.exe", + "sha256sum": "eac88926c302bc00fa7d8838294b6adf638a6f2b844198336a23007d0679ae25", "dlFileName": "func_windows_amd64.exe", "cmdFileName": "func.exe" }, "darwin": { - "url": "https://github.com/knative/func/releases/download/knative-v1.10.0/func_darwin_amd64", - "sha256sum": "de953a9167f28af5c1f0acfe67e2c851e999aa1f6efed5a326083ba569cc2381", + "url": "https://github.com/knative/func/releases/download/knative-v1.12.0/func_darwin_amd64", + "sha256sum": "46a3a7dada63f9285d93b24f393ccff99125b4e3980139e43aa04b0f17e2dfd6", "dlFileName": "func_darwin_amd64", "cmdFileName": "func" }, "darwin-arm64": { - "url": "https://github.com/knative/func/releases/download/knative-v1.10.0/func_darwin_arm64", - "sha256sum": "740bef3723a30861149e86ea0505e32be74112bafa892a8d0a86d2526c41cf71", + "url": "https://github.com/knative/func/releases/download/knative-v1.12.0/func_darwin_arm64", + "sha256sum": "84682105d562611f515dc8cd83f0761bfe0b35dab5a1961b48b1414b1d6a191b", "dlFileName": "func_darwin_arm64", "cmdFileName": "func" }, "linux": { - "url": "https://github.com/knative/func/releases/download/knative-v1.10.0/func_linux_amd64", - "sha256sum": "5069e5fb8d1b3742c4df8e8bbbeb7737f3bf7aab175bd8e76a900b86bc28239a", + "url": "https://github.com/knative/func/releases/download/knative-v1.12.0/func_linux_amd64", + "sha256sum": "cb9cfe6772273a0b0c2d0736b7366ec515819a8a0a0cfeb2c7f50aa4ac845ca1", "dlFileName": "func_linux_amd64", "cmdFileName": "func" }, "linux-arm64": { - "url": "https://github.com/knative/func/releases/download/knative-v1.10.0/func_linux_arm64", - "sha256sum": "9004e811ad5cfe042e4e2009735447c9e1a60ea8b1a3c2c824dbf117eead7c22", + "url": "https://github.com/knative/func/releases/download/knative-v1.12.0/func_linux_arm64", + "sha256sum": "fff8871edbe4667f095df0f242e7cc52ecd92350197a066fb1a62fdcaf4e7ed1", "dlFileName": "func_linux_arm64", "cmdFileName": "func" } diff --git a/src/tools.ts b/src/tools.ts index f81a34207..24ae0fc9a 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -92,10 +92,10 @@ export class ToolsConfig { public static async selectTool(locations: string[], versionRange: string): Promise { let result: string; // Array.find cannot be used here because of async calls - // eslint-disable-next-line no-restricted-syntax for (const location of locations) { + // FIXME: see https://github.com/knative/func/issues/2067 // eslint-disable-next-line no-await-in-loop - if (location && semver.satisfies(await ToolsConfig.getVersion(location), versionRange)) { + if (location && (location.endsWith('func') || semver.satisfies(await ToolsConfig.getVersion(location), versionRange))) { result = location; break; }