Add getDockerImageSpec and cleanDockerObjects to API (#19900)

* Add getDockerImageSpec and cleanDockerObjects to API

* docs
This commit is contained in:
Charles Gagnon
2022-07-01 12:57:40 -07:00
committed by GitHub
parent c211fb981c
commit 60026a39f9
5 changed files with 94 additions and 49 deletions

View File

@@ -73,7 +73,7 @@ export class ProjectsController {
private buildHelper: BuildHelper;
private buildInfo: DashboardData[] = [];
private publishInfo: PublishData[] = [];
private deployService: DeployService;
public deployService: DeployService;
private connectionService: ConnectionService;
private azureSqlClient: AzureSqlClient;
private autorestHelper: AutorestHelper;

View File

@@ -12,13 +12,8 @@ import * as vscode from 'vscode';
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
import { AzureSqlClient } from './azureSqlClient';
import { ConnectionService } from '../connections/connectionService';
import { IDockerSettings, IPublishToDockerSettings } from 'sqldbproj';
import { DockerImageSpec, IDockerSettings, IPublishToDockerSettings } from 'sqldbproj';
interface DockerImageSpec {
label: string;
containerName: string;
tag: string
}
export class DeployService {
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
@@ -38,28 +33,6 @@ export class DeployService {
}
}
public getDockerImageSpec(projectName: string, baseImage: string, imageUniqueId?: string): DockerImageSpec {
imageUniqueId = imageUniqueId ?? UUID.generateUuid();
// Remove unsupported characters
//
// docker image name and tag can only include letters, digits, underscore, period and dash
const regexForDockerImageName = /[^a-zA-Z0-9_,\-]/g;
let imageProjectName = projectName.replace(regexForDockerImageName, '');
const tagMaxLength = 128;
const tag = baseImage.replace(':', '-').replace(constants.sqlServerDockerRegistry, '').replace(regexForDockerImageName, '');
// cut the name if it's too long
//
imageProjectName = imageProjectName.substring(0, tagMaxLength - (constants.dockerImageNamePrefix.length + tag.length + 2));
const imageLabel = `${constants.dockerImageLabelPrefix}-${imageProjectName}`.toLocaleLowerCase();
const imageTag = `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`.toLocaleLowerCase();
const dockerName = `${constants.dockerImageNamePrefix}-${imageProjectName}-${imageUniqueId}`.toLocaleLowerCase();
return { label: imageLabel, tag: imageTag, containerName: dockerName };
}
/**
* Creates a new Azure Sql server and tries to connect to the new server. If connection fails because of firewall rule, it prompts user to add firewall rule settings
* @param profile Azure Sql server settings
@@ -99,23 +72,14 @@ export class DeployService {
this.logToOutput(constants.dockerImageEulaMessage);
this.logToOutput(profile.dockerSettings.dockerBaseImageEula);
const imageSpec = this.getDockerImageSpec(project.projectFileName, profile.dockerSettings.dockerBaseImage);
const imageSpec = getDockerImageSpec(project.projectFileName, profile.dockerSettings.dockerBaseImage);
// If profile name is not set use the docker name to have a unique name
if (!profile.dockerSettings.profileName) {
profile.dockerSettings.profileName = imageSpec.containerName;
}
this.logToOutput(constants.cleaningDockerImagesMessage);
// Clean up existing docker image
const containerIds = await this.getCurrentDockerContainer(imageSpec.label);
if (containerIds.length > 0) {
const result = await vscode.window.showWarningMessage(constants.containerAlreadyExistForProject, constants.yesString, constants.noString);
if (result === constants.yesString) {
this.logToOutput(constants.cleaningDockerImagesMessage);
await this.cleanDockerObjects(containerIds, ['docker stop', 'docker rm']);
}
}
await this.cleanDockerObjectsIfNeeded(imageSpec.label);
this.logToOutput(constants.creatingDeploymentSettingsMessage);
// Create commands
@@ -208,6 +172,23 @@ export class DeployService {
return currentIds ? currentIds.split(/\r?\n/) : [];
}
/**
* Checks if any containers with the specified label already exist, and if they do prompt the user whether they want to clean them up
* @param imageLabel The label of the container to search for
*/
public async cleanDockerObjectsIfNeeded(imageLabel: string): Promise<void> {
this.logToOutput(constants.cleaningDockerImagesMessage);
// Clean up existing docker image
const containerIds = await this.getCurrentDockerContainer(imageLabel);
if (containerIds.length > 0) {
const result = await vscode.window.showWarningMessage(constants.containerAlreadyExistForProject, constants.yesString, constants.noString);
if (result === constants.yesString) {
this.logToOutput(constants.cleaningDockerImagesMessage);
await this.cleanDockerObjects(containerIds, ['docker stop', 'docker rm']);
}
}
}
public async cleanDockerObjects(ids: string[], commandsToClean: string[]): Promise<void> {
for (let index = 0; index < ids.length; index++) {
const id = ids[index];
@@ -220,3 +201,25 @@ export class DeployService {
}
}
}
export function getDockerImageSpec(projectName: string, baseImage: string, imageUniqueId?: string): DockerImageSpec {
imageUniqueId = imageUniqueId ?? UUID.generateUuid();
// Remove unsupported characters
//
// docker image name and tag can only include letters, digits, underscore, period and dash
const regexForDockerImageName = /[^a-zA-Z0-9_,\-]/g;
let imageProjectName = projectName.replace(regexForDockerImageName, '');
const tagMaxLength = 128;
const tag = baseImage.replace(':', '-').replace(constants.sqlServerDockerRegistry, '').replace(regexForDockerImageName, '');
// cut the name if it's too long
//
imageProjectName = imageProjectName.substring(0, tagMaxLength - (constants.dockerImageNamePrefix.length + tag.length + 2));
const imageLabel = `${constants.dockerImageLabelPrefix}-${imageProjectName}`.toLocaleLowerCase();
const imageTag = `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`.toLocaleLowerCase();
const dockerName = `${constants.dockerImageNamePrefix}-${imageProjectName}-${imageUniqueId}`.toLocaleLowerCase();
return { label: imageLabel, tag: imageTag, containerName: dockerName };
}

View File

@@ -15,6 +15,7 @@ import { ProjectsController } from '../controllers/projectController';
import { Project } from '../models/project';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { getPublishToDockerSettings } from '../dialogs/deployDatabaseQuickpick';
import { getDockerImageSpec } from '../models/deploy/deployService';
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider, sqldbproj.IExtension {
constructor(private projectController: ProjectsController) {
@@ -223,4 +224,12 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
public getPublishToDockerSettings(project: sqldbproj.ISqlProject): Promise<sqldbproj.IPublishToDockerSettings | undefined> {
return getPublishToDockerSettings(project);
}
public getDockerImageSpec(projectName: string, baseImage: string, imageUniqueId?: string): sqldbproj.DockerImageSpec {
return getDockerImageSpec(projectName, baseImage, imageUniqueId);
}
public cleanDockerObjectsIfNeeded(imageLabel: string): Promise<void> {
return this.projectController.deployService.cleanDockerObjectsIfNeeded(imageLabel);
}
}

View File

@@ -66,8 +66,25 @@ declare module 'sqldbproj' {
*/
addItemPrompt(project: ISqlProject, relativeFilePath: string, options?: AddItemOptions): Promise<void>;
/**
* Gathers information required for publishing a project to a docker container, prompting the user as necessary
* @param project The Project being published
*/
getPublishToDockerSettings(project: ISqlProject): Promise<IPublishToDockerSettings | undefined>;
/**
* Gets the information required to start a docker container for publishing to
* @param projectName The name of the project being published
* @param baseImage The base docker image being deployed
* @param imageUniqueId The unique ID to use in the name, default is a random GUID
*/
getDockerImageSpec(projectName: string, baseImage: string, imageUniqueId?: string): DockerImageSpec;
/**
* Checks if any containers with the specified label already exist, and if they do prompt the user whether they want to clean them up
* @param imageLabel The label of the container to search for
*/
cleanDockerObjectsIfNeeded(imageLabel: string): Promise<void>;
}
export interface AddItemOptions {
@@ -333,4 +350,22 @@ declare module 'sqldbproj' {
deploymentOptions?: DeploymentOptions;
profileUsed?: boolean;
}
/**
* Information for deploying a new docker container
*/
interface DockerImageSpec {
/**
* The label to apply to the container
*/
label: string;
/**
* The full name to give the container
*/
containerName: string;
/**
* The tag to apply to the container
*/
tag: string
}
}

View File

@@ -7,7 +7,7 @@ import * as should from 'should';
import * as sinon from 'sinon';
import * as baselines from '../baselines/baselines';
import * as testUtils from '../testUtils';
import { DeployService } from '../../models/deploy/deployService';
import { DeployService, getDockerImageSpec } from '../../models/deploy/deployService';
import { Project } from '../../models/project';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
@@ -175,33 +175,31 @@ describe('deploy service', function (): void {
});
it('Should create docker image info correctly', () => {
const testContext = createContext();
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel);
const id = UUID.generateUuid().toLocaleLowerCase();
const baseImage = 'baseImage:latest';
const tag = baseImage.replace(':', '-').replace(constants.sqlServerDockerRegistry, '').replace(/[^a-zA-Z0-9_,\-]/g, '').toLocaleLowerCase();
should(deployService.getDockerImageSpec('project-name123_test', baseImage, id)).deepEqual({
should(getDockerImageSpec('project-name123_test', baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-project-name123_test`,
containerName: `${constants.dockerImageNamePrefix}-project-name123_test-${id}`,
tag: `${constants.dockerImageNamePrefix}-project-name123_test-${tag}`
});
should(deployService.getDockerImageSpec('project-name1', baseImage, id)).deepEqual({
should(getDockerImageSpec('project-name1', baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-project-name1`,
containerName: `${constants.dockerImageNamePrefix}-project-name1-${id}`,
tag: `${constants.dockerImageNamePrefix}-project-name1-${tag}`
});
should(deployService.getDockerImageSpec('project-name2$#', baseImage, id)).deepEqual({
should(getDockerImageSpec('project-name2$#', baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-project-name2`,
containerName: `${constants.dockerImageNamePrefix}-project-name2-${id}`,
tag: `${constants.dockerImageNamePrefix}-project-name2-${tag}`
});
should(deployService.getDockerImageSpec('project - name3', baseImage, id)).deepEqual({
should(getDockerImageSpec('project - name3', baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-project-name3`,
containerName: `${constants.dockerImageNamePrefix}-project-name3-${id}`,
tag: `${constants.dockerImageNamePrefix}-project-name3-${tag}`
});
should(deployService.getDockerImageSpec('project_name4', baseImage, id)).deepEqual({
should(getDockerImageSpec('project_name4', baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-project_name4`,
containerName: `${constants.dockerImageNamePrefix}-project_name4-${id}`,
tag: `${constants.dockerImageNamePrefix}-project_name4-${tag}`
@@ -210,7 +208,7 @@ describe('deploy service', function (): void {
const reallyLongName = new Array(128 + 1).join('a').replace(/[^a-zA-Z0-9_,\-]/g, '');
const imageProjectName = reallyLongName.substring(0, 128 - (constants.dockerImageNamePrefix.length + tag.length + 2));
should(deployService.getDockerImageSpec(reallyLongName, baseImage, id)).deepEqual({
should(getDockerImageSpec(reallyLongName, baseImage, id)).deepEqual({
label: `${constants.dockerImageLabelPrefix}-${imageProjectName}`,
containerName: `${constants.dockerImageNamePrefix}-${imageProjectName}-${id}`,
tag: `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`