From 0e37c048a64aaee9ea77cbc8dc2182e7f6f8474c Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Mon, 30 Oct 2023 12:11:00 +0530 Subject: [PATCH 1/5] remote deployment functionality added Signed-off-by: msivasubramaniaan --- package.json | 14 ++ src/extension.ts | 14 ++ src/serverlessFunction/commands.ts | 17 ++ src/serverlessFunction/functions.ts | 67 +++++ src/serverlessFunction/git/git.d.ts | 363 ++++++++++++++++++++++++++++ src/serverlessFunction/git/git.ts | 161 ++++++++++++ src/serverlessFunction/view.ts | 5 + 7 files changed, 641 insertions(+) create mode 100644 src/serverlessFunction/git/git.d.ts create mode 100644 src/serverlessFunction/git/git.ts 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/extension.ts b/src/extension.ts index d782acce9..e2e504197 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,6 +34,8 @@ import { WelcomePage } from './welcomePage'; import fsx = require('fs-extra'); +export let contextGlobalState: ExtensionContext; + // eslint-disable-next-line @typescript-eslint/no-empty-function // this method is called when your extension is deactivated export function deactivate(): void { @@ -67,6 +69,8 @@ async function registerKubernetesCloudProvider(): Promise { export async function activate(extensionContext: ExtensionContext): Promise { void WelcomePage.createOrShow(); + contextGlobalState = extensionContext; + await contextGlobalState.globalState.update('hasTekton', await isTektonAware()); await commands.executeCommand('setContext', 'isVSCode', env.uiKind); // UIKind.Desktop ==1 & UIKind.Web ==2. These conditions are checked for browser based & electron based IDE. migrateFromOdo018(); @@ -208,3 +212,13 @@ export async function activate(extensionContext: ExtensionContext): 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/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..1b0dab47d 100644 --- a/src/serverlessFunction/functions.ts +++ b/src/serverlessFunction/functions.ts @@ -14,6 +14,8 @@ import { OpenShiftTerminalApi, OpenShiftTerminalManager } from '../webview/opens import { ServerlessCommand, Utils } from './commands'; import { multiStep } from './multiStepInput'; import { FunctionContent, FunctionObject, InvokeFunction } from './types'; +import { GitModel, getGitBranchInteractively, getGitRepoInteractively, getGitStateByPath } from './git/git'; +import { contextGlobalState } from '../extension'; export class Functions { @@ -47,6 +49,71 @@ export class Functions { } } + private async getGitModel(fsPath?: string): Promise { + const gitState = 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 (!contextGlobalState.globalState.get('hasTekton')) { + 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; + } + + 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.d.ts b/src/serverlessFunction/git/git.d.ts new file mode 100644 index 000000000..b1877413e --- /dev/null +++ b/src/serverlessFunction/git/git.d.ts @@ -0,0 +1,363 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +/* eslint-disable import/newline-after-import */ +/*--------------------------------------------------------------------------------------------- + * 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 } from 'vscode'; +export { ProviderResult } from 'vscode'; + +export interface Git { + readonly path: string; +} + +export interface InputBox { + value: string; +} + +export const enum ForcePushMode { + Force, + ForceWithLease, +} + +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, + + 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; +} + +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 BranchQuery { + readonly remote?: boolean; + readonly pattern?: string; + readonly count?: number; + readonly contains?: string; +} + +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): Promise; + setBranchUpstream(name: string, upstream: string): 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 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): 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; +} + +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', + 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', +} diff --git a/src/serverlessFunction/git/git.ts b/src/serverlessFunction/git/git.ts new file mode 100644 index 000000000..17ad20d75 --- /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 { API, Branch, Ref, Remote } from './git.d'; + +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 function getGitStateByPath(rootPath?: string): GitState { + 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 = repo.state.refs; + 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/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); From b1a78cee38b97c9ff12b99142f46b5f21d5da85b Mon Sep 17 00:00:00 2001 From: msivasubramaniaan Date: Tue, 31 Oct 2023 16:49:41 +0530 Subject: [PATCH 2/5] fixed cycle dependency Signed-off-by: msivasubramaniaan --- src/extension.ts | 7 +++---- src/serverlessFunction/functions.ts | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index e2e504197..e53b4887e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -33,8 +33,7 @@ import { OpenShiftTerminalManager } from './webview/openshift-terminal/openShift import { WelcomePage } from './welcomePage'; import fsx = require('fs-extra'); - -export let contextGlobalState: ExtensionContext; +import { Functions } from './serverlessFunction/functions'; // eslint-disable-next-line @typescript-eslint/no-empty-function // this method is called when your extension is deactivated @@ -69,12 +68,12 @@ async function registerKubernetesCloudProvider(): Promise { export async function activate(extensionContext: ExtensionContext): Promise { void WelcomePage.createOrShow(); - contextGlobalState = extensionContext; - await contextGlobalState.globalState.update('hasTekton', await isTektonAware()); await commands.executeCommand('setContext', 'isVSCode', env.uiKind); // UIKind.Desktop ==1 & UIKind.Web ==2. These conditions are checked for browser based & electron based IDE. migrateFromOdo018(); + void extensionContext.globalState.update('hasTekton', await isTektonAware()); Cluster.extensionContext = extensionContext; + Functions.extensionContext = extensionContext; TokenStore.extensionContext = extensionContext; // pick kube config in case multiple are configured diff --git a/src/serverlessFunction/functions.ts b/src/serverlessFunction/functions.ts index 1b0dab47d..d12b804bf 100644 --- a/src/serverlessFunction/functions.ts +++ b/src/serverlessFunction/functions.ts @@ -4,7 +4,7 @@ *-----------------------------------------------------------------------------------------------*/ import validator from 'validator'; -import { Uri, commands, window } from 'vscode'; +import { ExtensionContext, Uri, commands, window } from 'vscode'; import { CliChannel } from '../cli'; import { Oc } from '../oc/ocWrapper'; import { Odo } from '../odo/odoWrapper'; @@ -15,12 +15,13 @@ import { ServerlessCommand, Utils } from './commands'; import { multiStep } from './multiStepInput'; import { FunctionContent, FunctionObject, InvokeFunction } from './types'; import { GitModel, getGitBranchInteractively, getGitRepoInteractively, getGitStateByPath } from './git/git'; -import { contextGlobalState } from '../extension'; export class Functions { private static instance: Functions; + public static extensionContext: ExtensionContext; + protected static readonly cli = CliChannel.getInstance(); private buildTerminalMap = new Map(); @@ -69,7 +70,7 @@ export class Functions { public async onClusterBuild(context: FunctionObject): Promise { - if (!contextGlobalState.globalState.get('hasTekton')) { + if (!Functions.extensionContext.globalState.get('hasTekton')) { 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.', ); From 02faa34ccb67610f2c944a6bb5b785f4a675ebcc Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 2 Nov 2023 14:42:44 -0400 Subject: [PATCH 3/5] Fix some bugs - Update `func` CLI to the latest version - Adjust to API change for fetching refs in the vscode-git extension API Signed-off-by: David Thompson --- src/@types/git.d.ts | 400 ++++++++++++++++++++++++++++ src/serverlessFunction/functions.ts | 4 +- src/serverlessFunction/git/git.d.ts | 363 ------------------------- src/serverlessFunction/git/git.ts | 6 +- src/tools.json | 26 +- src/tools.ts | 4 +- 6 files changed, 420 insertions(+), 383 deletions(-) create mode 100644 src/@types/git.d.ts delete mode 100644 src/serverlessFunction/git/git.d.ts 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/functions.ts b/src/serverlessFunction/functions.ts index d12b804bf..406fae682 100644 --- a/src/serverlessFunction/functions.ts +++ b/src/serverlessFunction/functions.ts @@ -12,9 +12,9 @@ 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 { multiStep } from './multiStepInput'; import { FunctionContent, FunctionObject, InvokeFunction } from './types'; -import { GitModel, getGitBranchInteractively, getGitRepoInteractively, getGitStateByPath } from './git/git'; export class Functions { @@ -51,7 +51,7 @@ export class Functions { } private async getGitModel(fsPath?: string): Promise { - const gitState = getGitStateByPath(fsPath); + const gitState = await getGitStateByPath(fsPath); const gitRemote = await getGitRepoInteractively(gitState); if (!gitRemote) { diff --git a/src/serverlessFunction/git/git.d.ts b/src/serverlessFunction/git/git.d.ts deleted file mode 100644 index b1877413e..000000000 --- a/src/serverlessFunction/git/git.d.ts +++ /dev/null @@ -1,363 +0,0 @@ -/*----------------------------------------------------------------------------------------------- - * Copyright (c) Red Hat, Inc. All rights reserved. - * Licensed under the MIT License. See LICENSE file in the project root for license information. - *-----------------------------------------------------------------------------------------------*/ - -/* eslint-disable import/newline-after-import */ -/*--------------------------------------------------------------------------------------------- - * 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 } from 'vscode'; -export { ProviderResult } from 'vscode'; - -export interface Git { - readonly path: string; -} - -export interface InputBox { - value: string; -} - -export const enum ForcePushMode { - Force, - ForceWithLease, -} - -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, - - 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; -} - -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 BranchQuery { - readonly remote?: boolean; - readonly pattern?: string; - readonly count?: number; - readonly contains?: string; -} - -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): Promise; - setBranchUpstream(name: string, upstream: string): 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 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): 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; -} - -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', - 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', -} diff --git a/src/serverlessFunction/git/git.ts b/src/serverlessFunction/git/git.ts index 17ad20d75..66af6958c 100644 --- a/src/serverlessFunction/git/git.ts +++ b/src/serverlessFunction/git/git.ts @@ -4,7 +4,7 @@ *-----------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { API, Branch, Ref, Remote } from './git.d'; +import type { API, Branch, Ref, Remote } from '../../@types/git'; const GIT_EXTENSION_ID = 'vscode.git'; @@ -61,7 +61,7 @@ function getRemoteByCommit(refs: Ref[], remotes: Remote[], branch: Branch): Remo return undefined; } -export function getGitStateByPath(rootPath?: string): GitState { +export async function getGitStateByPath(rootPath?: string): Promise { let remotes: Remote[] = []; let refs: Ref[] = []; let remote: Remote; @@ -75,7 +75,7 @@ export function getGitStateByPath(rootPath?: string): GitState { if (isGit) { const repo = repositories[0]; remotes = repo.state.remotes; - refs = repo.state.refs; + refs = await repo.getRefs({}); branch = repo.state.HEAD; if (branch.commit) { remote = getRemoteByCommit(refs, remotes, branch); 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; } From 09b6310f4f3c89798b0b20e692f2436dbda3dd40 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 2 Nov 2023 15:45:14 -0400 Subject: [PATCH 4/5] Check for Tekton and Knative Serving every time Signed-off-by: David Thompson --- src/extension.ts | 13 +------------ src/serverlessFunction/functions.ts | 11 ++++++++++- src/serverlessFunction/knative.ts | 25 +++++++++++++++++++++++++ src/tekton/tekton.ts | 21 +++++++++++++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 src/serverlessFunction/knative.ts create mode 100644 src/tekton/tekton.ts diff --git a/src/extension.ts b/src/extension.ts index e53b4887e..19483155a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,7 @@ import { extendClusterExplorer } from './k8s/clusterExplorer'; import { Cluster } from './openshift/cluster'; import { Component } from './openshift/component'; import { ComponentTypesView } from './registriesView'; +import { Functions } from './serverlessFunction/functions'; import { ServerlessFunctionView } from './serverlessFunction/view'; import { startTelemetry } from './telemetry'; import { ToolsConfig } from './tools'; @@ -33,7 +34,6 @@ import { OpenShiftTerminalManager } from './webview/openshift-terminal/openShift import { WelcomePage } from './welcomePage'; import fsx = require('fs-extra'); -import { Functions } from './serverlessFunction/functions'; // eslint-disable-next-line @typescript-eslint/no-empty-function // this method is called when your extension is deactivated @@ -71,7 +71,6 @@ export async function activate(extensionContext: ExtensionContext): 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/serverlessFunction/functions.ts b/src/serverlessFunction/functions.ts index 406fae682..69e46065c 100644 --- a/src/serverlessFunction/functions.ts +++ b/src/serverlessFunction/functions.ts @@ -8,11 +8,13 @@ import { ExtensionContext, 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'; @@ -70,13 +72,20 @@ export class Functions { public async onClusterBuild(context: FunctionObject): Promise { - if (!Functions.extensionContext.globalState.get('hasTekton')) { + 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; diff --git a/src/serverlessFunction/knative.ts b/src/serverlessFunction/knative.ts new file mode 100644 index 000000000..d86d4c412 --- /dev/null +++ b/src/serverlessFunction/knative.ts @@ -0,0 +1,25 @@ +/*----------------------------------------------------------------------------------------------- + * 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/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; +} From 1afa70ea23af2133eee29ba929a3cdc422915b4e Mon Sep 17 00:00:00 2001 From: David Thompson Date: Thu, 2 Nov 2023 15:56:11 -0400 Subject: [PATCH 5/5] Code cleanup Signed-off-by: David Thompson --- src/extension.ts | 2 -- src/serverlessFunction/functions.ts | 4 +--- src/serverlessFunction/knative.ts | 13 +++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 19483155a..d782acce9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,6 @@ import { extendClusterExplorer } from './k8s/clusterExplorer'; import { Cluster } from './openshift/cluster'; import { Component } from './openshift/component'; import { ComponentTypesView } from './registriesView'; -import { Functions } from './serverlessFunction/functions'; import { ServerlessFunctionView } from './serverlessFunction/view'; import { startTelemetry } from './telemetry'; import { ToolsConfig } from './tools'; @@ -72,7 +71,6 @@ export async function activate(extensionContext: ExtensionContext): Promise(); diff --git a/src/serverlessFunction/knative.ts b/src/serverlessFunction/knative.ts index d86d4c412..c677fc086 100644 --- a/src/serverlessFunction/knative.ts +++ b/src/serverlessFunction/knative.ts @@ -14,12 +14,13 @@ 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') - ); + 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; }