mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add getDockerImageSpec and cleanDockerObjects to API (#19900)
* Add getDockerImageSpec and cleanDockerObjects to API * docs
This commit is contained in:
@@ -73,7 +73,7 @@ export class ProjectsController {
|
|||||||
private buildHelper: BuildHelper;
|
private buildHelper: BuildHelper;
|
||||||
private buildInfo: DashboardData[] = [];
|
private buildInfo: DashboardData[] = [];
|
||||||
private publishInfo: PublishData[] = [];
|
private publishInfo: PublishData[] = [];
|
||||||
private deployService: DeployService;
|
public deployService: DeployService;
|
||||||
private connectionService: ConnectionService;
|
private connectionService: ConnectionService;
|
||||||
private azureSqlClient: AzureSqlClient;
|
private azureSqlClient: AzureSqlClient;
|
||||||
private autorestHelper: AutorestHelper;
|
private autorestHelper: AutorestHelper;
|
||||||
|
|||||||
@@ -12,13 +12,8 @@ import * as vscode from 'vscode';
|
|||||||
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
||||||
import { AzureSqlClient } from './azureSqlClient';
|
import { AzureSqlClient } from './azureSqlClient';
|
||||||
import { ConnectionService } from '../connections/connectionService';
|
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 {
|
export class DeployService {
|
||||||
|
|
||||||
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
|
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
|
* 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
|
* @param profile Azure Sql server settings
|
||||||
@@ -99,23 +72,14 @@ export class DeployService {
|
|||||||
this.logToOutput(constants.dockerImageEulaMessage);
|
this.logToOutput(constants.dockerImageEulaMessage);
|
||||||
this.logToOutput(profile.dockerSettings.dockerBaseImageEula);
|
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 name is not set use the docker name to have a unique name
|
||||||
if (!profile.dockerSettings.profileName) {
|
if (!profile.dockerSettings.profileName) {
|
||||||
profile.dockerSettings.profileName = imageSpec.containerName;
|
profile.dockerSettings.profileName = imageSpec.containerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logToOutput(constants.cleaningDockerImagesMessage);
|
await this.cleanDockerObjectsIfNeeded(imageSpec.label);
|
||||||
// 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']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logToOutput(constants.creatingDeploymentSettingsMessage);
|
this.logToOutput(constants.creatingDeploymentSettingsMessage);
|
||||||
// Create commands
|
// Create commands
|
||||||
@@ -208,6 +172,23 @@ export class DeployService {
|
|||||||
return currentIds ? currentIds.split(/\r?\n/) : [];
|
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> {
|
public async cleanDockerObjects(ids: string[], commandsToClean: string[]): Promise<void> {
|
||||||
for (let index = 0; index < ids.length; index++) {
|
for (let index = 0; index < ids.length; index++) {
|
||||||
const id = ids[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 };
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { ProjectsController } from '../controllers/projectController';
|
|||||||
import { Project } from '../models/project';
|
import { Project } from '../models/project';
|
||||||
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
|
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
|
||||||
import { getPublishToDockerSettings } from '../dialogs/deployDatabaseQuickpick';
|
import { getPublishToDockerSettings } from '../dialogs/deployDatabaseQuickpick';
|
||||||
|
import { getDockerImageSpec } from '../models/deploy/deployService';
|
||||||
|
|
||||||
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider, sqldbproj.IExtension {
|
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider, sqldbproj.IExtension {
|
||||||
constructor(private projectController: ProjectsController) {
|
constructor(private projectController: ProjectsController) {
|
||||||
@@ -223,4 +224,12 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
|
|||||||
public getPublishToDockerSettings(project: sqldbproj.ISqlProject): Promise<sqldbproj.IPublishToDockerSettings | undefined> {
|
public getPublishToDockerSettings(project: sqldbproj.ISqlProject): Promise<sqldbproj.IPublishToDockerSettings | undefined> {
|
||||||
return getPublishToDockerSettings(project);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,25 @@ declare module 'sqldbproj' {
|
|||||||
*/
|
*/
|
||||||
addItemPrompt(project: ISqlProject, relativeFilePath: string, options?: AddItemOptions): Promise<void>;
|
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>;
|
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 {
|
export interface AddItemOptions {
|
||||||
@@ -333,4 +350,22 @@ declare module 'sqldbproj' {
|
|||||||
deploymentOptions?: DeploymentOptions;
|
deploymentOptions?: DeploymentOptions;
|
||||||
profileUsed?: boolean;
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as should from 'should';
|
|||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as baselines from '../baselines/baselines';
|
import * as baselines from '../baselines/baselines';
|
||||||
import * as testUtils from '../testUtils';
|
import * as testUtils from '../testUtils';
|
||||||
import { DeployService } from '../../models/deploy/deployService';
|
import { DeployService, getDockerImageSpec } from '../../models/deploy/deployService';
|
||||||
import { Project } from '../../models/project';
|
import { Project } from '../../models/project';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
@@ -175,33 +175,31 @@ describe('deploy service', function (): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should create docker image info correctly', () => {
|
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 id = UUID.generateUuid().toLocaleLowerCase();
|
||||||
const baseImage = 'baseImage:latest';
|
const baseImage = 'baseImage:latest';
|
||||||
const tag = baseImage.replace(':', '-').replace(constants.sqlServerDockerRegistry, '').replace(/[^a-zA-Z0-9_,\-]/g, '').toLocaleLowerCase();
|
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`,
|
label: `${constants.dockerImageLabelPrefix}-project-name123_test`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-project-name123_test-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-project-name123_test-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-project-name123_test-${tag}`
|
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`,
|
label: `${constants.dockerImageLabelPrefix}-project-name1`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-project-name1-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-project-name1-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-project-name1-${tag}`
|
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`,
|
label: `${constants.dockerImageLabelPrefix}-project-name2`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-project-name2-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-project-name2-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-project-name2-${tag}`
|
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`,
|
label: `${constants.dockerImageLabelPrefix}-project-name3`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-project-name3-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-project-name3-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-project-name3-${tag}`
|
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`,
|
label: `${constants.dockerImageLabelPrefix}-project_name4`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-project_name4-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-project_name4-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-project_name4-${tag}`
|
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 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));
|
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}`,
|
label: `${constants.dockerImageLabelPrefix}-${imageProjectName}`,
|
||||||
containerName: `${constants.dockerImageNamePrefix}-${imageProjectName}-${id}`,
|
containerName: `${constants.dockerImageNamePrefix}-${imageProjectName}-${id}`,
|
||||||
tag: `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`
|
tag: `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`
|
||||||
|
|||||||
Reference in New Issue
Block a user