Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Source/configure/activate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as vscode from 'vscode';
import { AzureTreeItem, createApiProvider, IActionContext, registerCommand } from 'vscode-azureextensionui';
import { AzureExtensionApi, AzureExtensionApiProvider } from 'vscode-azureextensionui/api';
import { browsePipeline } from './browse';
import { configurePipeline } from './configure';
import { telemetryHelper } from './helper/telemetryHelper';
import { AzureAccountExtensionExports, extensionVariables, IResourceNode } from './model/models';
import { Messages } from './resources/messages';

export async function activateConfigurePipeline(): Promise<AzureExtensionApiProvider> {
let azureAccountExtension = vscode.extensions.getExtension("ms-vscode.azure-account");
if (!azureAccountExtension) {
throw new Error(Messages.azureAccountExntesionUnavailable);
}

if (!azureAccountExtension.isActive) {
await azureAccountExtension.activate();
}

extensionVariables.azureAccountExtensionApi = <AzureAccountExtensionExports>azureAccountExtension.exports;

// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
registerCommand('configure-cicd-pipeline', async (actionContext: IActionContext, node: IResourceNode | vscode.Uri) => {
// The code you place here will be executed every time your command is executed
telemetryHelper.initialize(actionContext, 'configure-cicd-pipeline');
await configurePipeline(node);
});

registerCommand('browse-cicd-pipeline', async (actionContext: IActionContext, node: AzureTreeItem) => {
// The code you place here will be executed every time your command is executed
telemetryHelper.initialize(actionContext, 'browse-cicd-pipeline');
await browsePipeline(node);
});

return createApiProvider([<AzureExtensionApi>
{
configurePipelineApi: configurePipeline,
browsePipeline: browsePipeline,
apiVersion: "0.0.1"
}]);
}
100 changes: 100 additions & 0 deletions Source/configure/browse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as vscode from 'vscode';
import { AzureTreeItem, UserCancelledError } from 'vscode-azureextensionui';
import { AppServiceClient, ScmType } from './clients/azure/appServiceClient';
import { getSubscriptionSession } from './helper/azureSessionHelper';
import { ControlProvider } from './helper/controlProvider';
import { Result, telemetryHelper } from './helper/telemetryHelper';
import { AzureSession, extensionVariables, ParsedAzureResourceId } from './model/models';
import * as constants from './resources/constants';
import { Messages } from './resources/messages';
import { TelemetryKeys } from './resources/telemetryKeys';
import { TracePoints } from './resources/tracePoints';

const Layer = 'browsePipeline';

export async function browsePipeline(node: AzureTreeItem): Promise<void> {
await telemetryHelper.executeFunctionWithTimeTelemetry(async () => {
try {
if (!!node && !!node.fullId) {
let parsedAzureResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(node.fullId);
let session: AzureSession = await getSubscriptionSession(parsedAzureResourceId.subscriptionId);
let appServiceClient = new AppServiceClient(session.credentials, session.environment, session.tenantId, parsedAzureResourceId.subscriptionId);
await browsePipelineInternal(node.fullId, appServiceClient);
}
else {
throw new Error(Messages.didNotRecieveAzureResourceNodeToProcess);
}
}
catch (error) {
if (!(error instanceof UserCancelledError)) {
extensionVariables.outputChannel.appendLine(error.message);
vscode.window.showErrorMessage(error.message);
telemetryHelper.setResult(Result.Failed, error);
}
else {
telemetryHelper.setResult(Result.Canceled, error);
}
}
}, TelemetryKeys.CommandExecutionDuration);
}

async function browsePipelineInternal(resourceId: string, appServiceClient: AppServiceClient): Promise<void> {
let siteConfig = await appServiceClient.getAppServiceConfig(resourceId);
let scmType = !!siteConfig && !!siteConfig.scmType && siteConfig.scmType.toLowerCase();
telemetryHelper.setTelemetry(TelemetryKeys.ScmType, scmType);

if (scmType === ScmType.VSTSRM.toLowerCase()) {
await browseAzurePipeline(resourceId, appServiceClient);
}
else if (scmType === ScmType.GITHUBACTION.toLowerCase() && extensionVariables.enableGitHubWorkflow) {
await browseGitHubWorkflow(resourceId, appServiceClient);
}
else if (scmType === '' || scmType === ScmType.NONE.toLowerCase()) {
let deployToAzureAction = 'Deploy to Azure';
let controlProvider = new ControlProvider();
let result = await controlProvider.showInformationBox(
constants.BrowseNotAvailableConfigurePipeline,
Messages.browseNotAvailableConfigurePipeline,
deployToAzureAction);

if (result === deployToAzureAction) {
vscode.commands.executeCommand('configure-pipeline', { fullId: resourceId });
telemetryHelper.setTelemetry(TelemetryKeys.ClickedConfigurePipeline, 'true');
}
}
else {
await openDeploymentCenter(resourceId, appServiceClient);
}
}

async function browseAzurePipeline(resourceId: string, appServiceClient: AppServiceClient): Promise<void> {
try {
let pipelineUrl = await appServiceClient.getAzurePipelineUrl(resourceId);
vscode.env.openExternal(vscode.Uri.parse(pipelineUrl));
telemetryHelper.setTelemetry(TelemetryKeys.BrowsedExistingPipeline, 'true');
}
catch (ex) {
telemetryHelper.logError(Layer, TracePoints.CorruptMetadataForVstsRmScmType, ex);
await openDeploymentCenter(resourceId, appServiceClient);
}
}

async function browseGitHubWorkflow(resourceId: string, appServiceClient: AppServiceClient): Promise<void> {
let webAppSourceControl = await appServiceClient.getSourceControl(resourceId);
let webAppMetaData = await appServiceClient.getAppServiceMetadata(resourceId);

if (!!webAppSourceControl && !!webAppSourceControl.properties && webAppSourceControl.properties.isGitHubAction) {
let url = `${webAppSourceControl.properties.repoUrl}/actions?query=${encodeURI("workflow:\"" + (!!webAppMetaData.properties.configName ? webAppMetaData.properties.configName : webAppMetaData.properties.configPath) + "\"")}`;
await vscode.env.openExternal(vscode.Uri.parse(url));
telemetryHelper.setTelemetry(TelemetryKeys.BrowsedExistingPipeline, 'true');
}
else {
await openDeploymentCenter(resourceId, appServiceClient);
}
}

async function openDeploymentCenter(resourceId: string, appServiceClient: AppServiceClient): Promise<void> {
let deploymentCenterUrl: string = await appServiceClient.getDeploymentCenterUrl(resourceId);
await vscode.env.openExternal(vscode.Uri.parse(deploymentCenterUrl));
telemetryHelper.setTelemetry(TelemetryKeys.BrowsedDeploymentCenter, 'true');
}
6 changes: 6 additions & 0 deletions Source/configure/clients/IProvisioningServiceClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ProvisioningConfiguration } from "../model/provisioningConfiguration";

export interface IProvisioningServiceClient {
createProvisioningConfiguration(provisioningConfiguration: ProvisioningConfiguration, githubOrg: string, repositoryId: string): Promise<ProvisioningConfiguration>;
getProvisioningConfiguration(jobId: string, githubOrg: string, repositoryId: string): Promise<ProvisioningConfiguration>;
}
12 changes: 12 additions & 0 deletions Source/configure/clients/ITemplateServiceClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RepositoryAnalysis } from "azureintegration-repoanalysis-client-internal";
import { ExtendedPipelineTemplate } from "../model/Contracts";
import { StringMap } from "../model/models";
import { TemplateInfo } from "../model/templateModels";

export interface ITemplateServiceClient {
getTemplates(body: RepositoryAnalysis): Promise<TemplateInfo[]>;
getTemplateParameters(templateId: string): Promise<ExtendedPipelineTemplate>;
getTemplateConfiguration(templateId: string, inputs: StringMap<string>): Promise<ExtendedPipelineTemplate>;
getTemplateFile(templateId: string, fileName: string): Promise<{ id: string, content: string }[]>;
getTemplatesInfoByFilter(language: string, deployTargetFilter?: string, buildTargetFilter?: string): Promise<TemplateInfo[]>;
}
52 changes: 52 additions & 0 deletions Source/configure/clients/ProvisioningServiceClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ServiceClientCredentials, UrlBasedRequestPrepareOptions } from "ms-rest";
import { IServiceUrlDefinition, ServiceFramework } from "../helper/remoteServiceUrlHelper";
import { ProvisioningConfiguration } from "../model/provisioningConfiguration";
import { IProvisioningServiceClient } from "./IProvisioningServiceClient";
import { RestClient } from "./restClient";

export class ProvisioningServiceClient implements IProvisioningServiceClient {
private restClient: RestClient;
private serviceDefinition: IServiceUrlDefinition;
private defaultHeaders: { [propertyName: string]: string };
private defaultParameters: { [propertyName: string]: string };
private readonly pipelineProvisioningJob = "PipelineProvisioningJob";
private readonly PEProvisioningServiceAPIVersion = "6.1-preview.1";

constructor(serviceDefinition: IServiceUrlDefinition, headers: { [propertyName: string]: string }, credentials: ServiceClientCredentials) {
this.restClient = new RestClient(credentials);
this.serviceDefinition = serviceDefinition;
this.defaultHeaders = headers;
if (this.serviceDefinition.serviceFramework === ServiceFramework.Vssf) {
this.defaultParameters = { "api-version": this.PEProvisioningServiceAPIVersion };
}
}

public async createProvisioningConfiguration(provisioningConfiguration: ProvisioningConfiguration, githubOrg: string, repositoryId: string): Promise<ProvisioningConfiguration> {
const requestUrl = this.serviceDefinition.serviceUrl + githubOrg + "/" + repositoryId + "/" + this.pipelineProvisioningJob;

return this.restClient.sendRequest(<UrlBasedRequestPrepareOptions>{
url: requestUrl,
method: "POST",
headers: this.defaultHeaders,
queryParameters: this.defaultParameters,
body: provisioningConfiguration,
serializationMapper: null,
deserializationMapper: null,
}
);
}

public async getProvisioningConfiguration(jobId: string, githubOrg: string, repositoryId: string): Promise<ProvisioningConfiguration> {
const requestUrl = this.serviceDefinition.serviceUrl + githubOrg + "/" + repositoryId + "/" + this.pipelineProvisioningJob + "/" + jobId;

return this.restClient.sendRequest(<UrlBasedRequestPrepareOptions>{
url: requestUrl,
method: "GET",
headers: this.defaultHeaders,
queryParameters: this.defaultParameters,
serializationMapper: null,
deserializationMapper: null,
}
);
}
}
43 changes: 43 additions & 0 deletions Source/configure/clients/TemplateServiceClientFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ServiceClientCredentials, TokenCredentials } from "ms-rest";
import { RemoteServiceUrlHelper, ServiceFramework } from "../helper/remoteServiceUrlHelper";
import { Messages } from '../resources/messages';
import { TemplateServiceClient } from "./github/TemplateServiceClient";
import { ITemplateServiceClient } from "./ITemplateServiceClient";
const UserAgent = "deploy-to-azure-vscode";

export class TemplateServiceClientFactory {

public static async getClient(): Promise<ITemplateServiceClient> {
if (!!this.client) {
return this.client;
}

if (!this.githubPatToken || !this.credentials) {
throw new Error(Messages.UndefinedClientCredentials);
}

const serviceDefinition = await RemoteServiceUrlHelper.getTemplateServiceDefinition();
if (serviceDefinition.serviceFramework === ServiceFramework.Vssf) {
this.client = new TemplateServiceClient(serviceDefinition.serviceUrl, this.credentials, {
"Content-Type": "application/json; charset=utf-8"
});
} else {
this.client = new TemplateServiceClient(serviceDefinition.serviceUrl, new TokenCredentials(this.githubPatToken, "token"), {
"User-Agent": UserAgent,
"Content-Type": "application/json; charset=utf-8"
});
}

return this.client;
}

public static initialize(credentials: ServiceClientCredentials, githubPatToken: string): void {
this.client = null;
this.credentials = credentials;
this.githubPatToken = githubPatToken
}

private static client: ITemplateServiceClient;
private static credentials: ServiceClientCredentials;
private static githubPatToken: string;
}
Loading