mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 09:35:38 -05:00
sqlproj - publish to container - fix the image name, tag and added validation (#17295)
This commit is contained in:
@@ -15,6 +15,11 @@ import * as os from 'os';
|
||||
import { ConnectionResult } from 'azdata';
|
||||
import * as templates from '../../templates/templates';
|
||||
|
||||
interface DockerImageSpec {
|
||||
label: string;
|
||||
containerName: string;
|
||||
tag: string
|
||||
}
|
||||
export class DeployService {
|
||||
|
||||
constructor(private _outputChannel: vscode.OutputChannel) {
|
||||
@@ -94,6 +99,28 @@ 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 };
|
||||
}
|
||||
|
||||
public async deploy(profile: IDeployProfile, project: Project): Promise<string | undefined> {
|
||||
return await this.executeTask(constants.deployDbTaskName, async () => {
|
||||
if (!profile.localDbSetting) {
|
||||
@@ -102,9 +129,8 @@ export class DeployService {
|
||||
|
||||
await this.verifyDocker();
|
||||
|
||||
const projectName = project.projectFileName;
|
||||
const imageLabel = `${constants.dockerImageLabelPrefix}_${projectName}`.toLocaleLowerCase();
|
||||
const imageName = `${constants.dockerImageNamePrefix}-${projectName}-${UUID.generateUuid()}`.toLocaleLowerCase();
|
||||
const imageSpec = this.getDockerImageSpec(project.projectFileName, profile.localDbSetting.dockerBaseImage);
|
||||
|
||||
const root = project.projectFolderPath;
|
||||
const mssqlFolderPath = path.join(root, constants.mssqlFolderName);
|
||||
const commandsFolderPath = path.join(mssqlFolderPath, constants.commandsFolderName);
|
||||
@@ -112,8 +138,8 @@ export class DeployService {
|
||||
const startFilePath = path.join(commandsFolderPath, constants.startCommandName);
|
||||
|
||||
// Clean up existing docker image
|
||||
const containerIds = await this.getCurrentDockerContainer(imageLabel);
|
||||
if (containerIds && containerIds.length > 0) {
|
||||
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);
|
||||
@@ -125,19 +151,19 @@ export class DeployService {
|
||||
// Create commands
|
||||
//
|
||||
|
||||
await this.createCommands(mssqlFolderPath, commandsFolderPath, dockerFilePath, startFilePath, imageLabel, profile.localDbSetting.dockerBaseImage);
|
||||
await this.createCommands(mssqlFolderPath, commandsFolderPath, dockerFilePath, startFilePath, imageSpec.label, profile.localDbSetting.dockerBaseImage);
|
||||
|
||||
this.logToOutput(constants.runningDockerMessage);
|
||||
// Building the image and running the docker
|
||||
//
|
||||
const createdDockerId: string | undefined = await this.buildAndRunDockerContainer(dockerFilePath, imageName, root, profile.localDbSetting, imageLabel);
|
||||
const createdDockerId: string | undefined = await this.buildAndRunDockerContainer(dockerFilePath, imageSpec, root, profile.localDbSetting);
|
||||
this.logToOutput(`Docker container created. Id: ${createdDockerId}`);
|
||||
|
||||
|
||||
// Waiting a bit to make sure docker container doesn't crash
|
||||
//
|
||||
const runningDockerId = await utils.retry('Validating the docker container', async () => {
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${imageLabel} -q`, this._outputChannel);
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${imageSpec.label} -q`, this._outputChannel);
|
||||
}, (dockerId) => {
|
||||
return Promise.resolve({ validated: dockerId !== undefined, errorMessage: constants.dockerContainerNotRunningErrorMessage });
|
||||
}, (dockerId) => {
|
||||
@@ -161,7 +187,7 @@ export class DeployService {
|
||||
});
|
||||
}
|
||||
|
||||
private async buildAndRunDockerContainer(dockerFilePath: string, imageName: string, root: string, profile: ILocalDbSetting, imageLabel: string): Promise<string | undefined> {
|
||||
private async buildAndRunDockerContainer(dockerFilePath: string, dockerImageSpec: DockerImageSpec, root: string, profile: ILocalDbSetting): Promise<string | undefined> {
|
||||
|
||||
// Sensitive data to remove from output console
|
||||
const sensitiveData = [profile.password];
|
||||
@@ -169,12 +195,12 @@ export class DeployService {
|
||||
// Running commands to build the docker image
|
||||
this.logToOutput('Building docker image ...');
|
||||
await utils.executeCommand(`docker pull ${profile.dockerBaseImage}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker build -f ${dockerFilePath} -t ${imageName} ${root}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker images --filter label=${imageLabel}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker build -f ${dockerFilePath} -t ${dockerImageSpec.tag} ${root}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker images --filter label=${dockerImageSpec.label}`, this._outputChannel);
|
||||
|
||||
this.logToOutput('Running docker container ...');
|
||||
await utils.executeCommand(`docker run -p ${profile.port}:1433 -e "MSSQL_SA_PASSWORD=${profile.password}" -d ${imageName}`, this._outputChannel, sensitiveData);
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${imageLabel} -q`, this._outputChannel);
|
||||
await utils.executeCommand(`docker run -p ${profile.port}:1433 -e "MSSQL_SA_PASSWORD=${profile.password}" -d --name ${dockerImageSpec.containerName} ${dockerImageSpec.tag}`, this._outputChannel, sensitiveData);
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${dockerImageSpec.label} -q`, this._outputChannel);
|
||||
}
|
||||
|
||||
private async getConnectionString(connectionUri: string): Promise<string | undefined> {
|
||||
@@ -345,7 +371,7 @@ export class DeployService {
|
||||
//
|
||||
await this.createFile(startFilePath, 'echo starting the container!');
|
||||
if (os.platform() !== 'win32') {
|
||||
await utils.executeCommand(`chmod +x ${startFilePath}`, this._outputChannel);
|
||||
await utils.executeCommand(`chmod +x '${startFilePath}'`, this._outputChannel);
|
||||
}
|
||||
|
||||
// Create the Dockerfile
|
||||
|
||||
@@ -13,8 +13,9 @@ import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as childProcess from 'child_process';
|
||||
import { AppSettingType, IDeployProfile } from '../../models/deploy/deployProfile';
|
||||
let fse = require('fs-extra');
|
||||
let path = require('path');
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export interface TestContext {
|
||||
@@ -90,7 +91,7 @@ describe('deploy service', function (): void {
|
||||
|
||||
});
|
||||
|
||||
it('Should deploy fails if docker is not running', async function (): Promise<void> {
|
||||
it('Should fail the deploy if docker is not running', async function (): Promise<void> {
|
||||
const testContext = createContext();
|
||||
const deployProfile: IDeployProfile = {
|
||||
localDbSetting: {
|
||||
@@ -248,4 +249,47 @@ describe('deploy service', function (): void {
|
||||
await deployService.cleanDockerObjects(ids, ['docker stop', 'docker rm']);
|
||||
should(process.calledThrice);
|
||||
});
|
||||
|
||||
it('Should create docker image info correctly', () => {
|
||||
const testContext = createContext();
|
||||
const deployService = new DeployService(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({
|
||||
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({
|
||||
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({
|
||||
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({
|
||||
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({
|
||||
label: `${constants.dockerImageLabelPrefix}-project_name4`,
|
||||
containerName: `${constants.dockerImageNamePrefix}-project_name4-${id}`,
|
||||
tag: `${constants.dockerImageNamePrefix}-project_name4-${tag}`
|
||||
});
|
||||
|
||||
|
||||
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({
|
||||
label: `${constants.dockerImageLabelPrefix}-${imageProjectName}`,
|
||||
containerName: `${constants.dockerImageNamePrefix}-${imageProjectName}-${id}`,
|
||||
tag: `${constants.dockerImageNamePrefix}-${imageProjectName}-${tag}`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user