mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-16 09:35:36 -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
|
||||
|
||||
Reference in New Issue
Block a user