sqlproj - publish to container - fix the image name, tag and added validation (#17295)

This commit is contained in:
Leila Lali
2021-10-07 14:39:05 -07:00
committed by GitHub
parent 8c7c491581
commit 3d810e8e20
2 changed files with 87 additions and 17 deletions

View File

@@ -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