Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions src/deployment/dockerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -989,72 +989,6 @@ export async function getAllSqlServerContainerTags(): Promise<string[]> {
}
}

/**
* Retrieves and filters SQL Server container tags based on project target version.
* Used by Publish Project dialog to provide granular tag selection.
*
* @param targetVersion - The SQL Server version (e.g., "160" for SQL Server 2022)
* @returns Sorted array of tags filtered by version and organized by year
*/
export async function getSqlServerContainerTagsForTargetVersion(
targetVersion?: string,
): Promise<FormItemOptions[]> {
try {
const allTags = await getAllSqlServerContainerTags();
if (!allTags || allTags.length === 0) {
return [];
}

// Extract year from tag (e.g., "2025-RC1-ubuntu-24.04" -> "2025")
const extractYear = (tag: string): string | undefined => {
const year = tag.slice(0, 4);
return /^\d{4}$/.test(year) ? year : undefined;
};

// Extract version number from tag (e.g., "2025-RC1-ubuntu-24.04" -> 2025)
const extractVersionNumber = (tag: string): number | undefined => {
const year = extractYear(tag);
return year ? parseInt(year, 10) : undefined;
};

// Determine minimum version based on target
let minVersion = 2017; // Default to SQL Server 2017
if (targetVersion) {
const versionNum = parseInt(targetVersion, 10);
if (versionNum >= 160)
minVersion = 2022; // SQL Server 2022
else if (versionNum >= 150)
minVersion = 2019; // SQL Server 2019
else if (versionNum >= 140) minVersion = 2017; // SQL Server 2017
}

// Filter and sort tags
const sortedTags = allTags
.filter((tag) => {
const version = extractVersionNumber(tag);
return version === undefined || version >= minVersion;
})
.sort((a, b) => (a.indexOf("latest") > 0 ? -1 : a.localeCompare(b)));

// Move "latest" to the very first position (as default selection)
const latestIndex = sortedTags.indexOf("latest");
if (latestIndex > 0) {
// Only move if it's not already at position 0
sortedTags.splice(latestIndex, 1);
sortedTags.unshift("latest");
}

// Convert to FormItemOptions format
return sortedTags.map((tag) => ({
value: tag,
displayName: tag,
}));
} catch (e) {
dockerLogger.appendLine(`Error filtering SQL Server container tags: ${getErrorMessage(e)}`);
return [];
}
}

/**
* Retrieves the SQL Server container versions from the Microsoft Container Registry.
* Returns a simplified year-based list (2025, 2022, 2019, 2017) for the deployment UI.
Expand Down
76 changes: 76 additions & 0 deletions src/publishProject/projectUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import * as constants from "../constants/constants";
import { SqlProjectsService } from "../services/sqlProjectsService";
import { promises as fs } from "fs";
import { DOMParser } from "@xmldom/xmldom";
import { getSqlServerContainerVersions, dockerLogger } from "../deployment/dockerUtils";
import { FormItemOptions } from "../sharedInterfaces/form";
import { getErrorMessage } from "../utils/utils";

/**
* Checks if preview features are enabled in VS Code settings for SQL Database Projects.
Expand All @@ -33,6 +36,7 @@ export const enum SqlTargetPlatform {
sqlServer2017 = "SQL Server 2017",
sqlServer2019 = "SQL Server 2019",
sqlServer2022 = "SQL Server 2022",
sqlServer2025 = "SQL Server 2025",
sqlAzure = "Azure SQL Database",
sqlDW = "Azure Synapse SQL Pool",
sqlDwServerless = "Azure Synapse Serverless SQL Pool",
Expand All @@ -51,13 +55,28 @@ export const targetPlatformToVersion: Map<string, string> = new Map<string, stri
[SqlTargetPlatform.sqlServer2017, "140"],
[SqlTargetPlatform.sqlServer2019, "150"],
[SqlTargetPlatform.sqlServer2022, "160"],
[SqlTargetPlatform.sqlServer2025, "170"],
[SqlTargetPlatform.sqlAzure, "AzureV12"],
[SqlTargetPlatform.sqlDW, "Dw"],
[SqlTargetPlatform.sqlDwServerless, "Serverless"],
[SqlTargetPlatform.sqlDwUnified, "DwUnified"],
[SqlTargetPlatform.sqlDbFabric, "DbFabric"],
]);

/**
* Maps DSP version numbers to SQL Server release years.
* Add new versions here as they become available.
*/
const DSP_VERSION_TO_YEAR: Map<number, number> = new Map([
[170, 2025], // SQL Server 2025
[160, 2022], // SQL Server 2022
[150, 2019], // SQL Server 2019
[140, 2017], // SQL Server 2017
[130, 2016], // SQL Server 2016
[120, 2014], // SQL Server 2014
[110, 2012], // SQL Server 2012
]);

/**
* Get project properties from the tools service and extract the target platform version portion
* of the DatabaseSchemaProvider (e.g. 130, 150, 160, AzureV12, AzureDw, etc.).
Expand Down Expand Up @@ -143,6 +162,63 @@ export function validateSqlServerPortNumber(port: number): boolean {
return Number.isInteger(port) && port >= 1 && port <= constants.MAX_PORT_NUMBER;
}

/**
* Retrieves and filters SQL Server container tags based on project target version.
* Used by Publish Project dialog to provide granular tag selection.
*
* This follows the same filtering logic as ADS (Azure Data Studio):
* - If target version is known: filter to show versions >= target version year
* - If target version is unknown: fallback to max available version year (like ADS)
*
* @param targetVersion - The SQL Server version (e.g., "160" for SQL Server 2022)
* @returns Sorted array of tags filtered by version from deployment UI versions
*/
export async function getSqlServerContainerTagsForTargetVersion(
targetVersion?: string,
): Promise<FormItemOptions[]> {
try {
// Get the deployment UI versions first
const deploymentVersions = await getSqlServerContainerVersions();
if (!deploymentVersions || deploymentVersions.length === 0) {
return [];
}

// Extract years from all available versions to find the maximum
const availableYears = deploymentVersions
.map((option) => parseInt(option.displayName.match(/\d{4}/)?.[0] || "0", 10))
.filter((year) => year > 0);

if (availableYears.length === 0) {
return deploymentVersions; // No years found, return all
}

const maxYear = Math.max(...availableYears);

// Determine minimum year based on target version
let minYear: number;
if (targetVersion) {
const versionNum = parseInt(targetVersion, 10);
const mappedYear = DSP_VERSION_TO_YEAR.get(versionNum);
// If we can map the version to a year, use it; otherwise fallback to max available year
minYear = mappedYear ?? maxYear;
} else {
// No target version provided, fallback to max available year
minYear = maxYear;
}

// Filter deployment versions based on minimum year
const filteredVersions = deploymentVersions.filter((option) => {
const year = parseInt(option.displayName.match(/\d{4}/)?.[0] || "0", 10);
return year >= minYear;
});

return filteredVersions;
} catch (e) {
dockerLogger.appendLine(`Error filtering SQL Server container tags: ${getErrorMessage(e)}`);
return [];
}
}

/**
* Read SQLCMD variables from publish profile text
* @param profileText Publish profile XML text
Expand Down
22 changes: 7 additions & 15 deletions src/publishProject/publishProjectWebViewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { parsePublishProfileXml, readProjectProperties } from "./projectUtils";
import { SqlProjectsService } from "../services/sqlProjectsService";
import { Deferred } from "../protocol";
import { TelemetryViews, TelemetryActions } from "../sharedInterfaces/telemetry";
import { getSqlServerContainerTagsForTargetVersion } from "../deployment/dockerUtils";
import { getSqlServerContainerTagsForTargetVersion } from "../publishProject/projectUtils";
import { hasAnyMissingRequiredValues } from "../utils/utils";

export class PublishProjectWebViewController extends FormWebviewController<
Expand Down Expand Up @@ -131,7 +131,7 @@ export class PublishProjectWebViewController extends FormWebviewController<
}
} catch (error) {
// Log error and send telemetry, but keep dialog resilient
console.error("Failed to read project properties:", error);
this.logger.error("Failed to read project properties:", error);
sendErrorEvent(
TelemetryViews.SqlProjects,
TelemetryActions.PublishProjectChanges,
Expand All @@ -142,28 +142,20 @@ export class PublishProjectWebViewController extends FormWebviewController<

// Load publish form components
this.state.formComponents = generatePublishFormComponents(projectTargetVersion);

// Update state to notify UI of the project properties and form components
this.updateState();

// Fetch Docker tags for the container image dropdown
// Use the deployment UI function with target version filtering
// Use deployment UI method to get filtered image tags
const tagComponent = this.state.formComponents[PublishFormFields.ContainerImageTag];
if (tagComponent) {
try {
const tagOptions =
await getSqlServerContainerTagsForTargetVersion(projectTargetVersion);
if (tagOptions && tagOptions.length > 0) {
tagComponent.options = tagOptions;

// Set default to first option (most recent -latest) if not already set
if (!this.state.formState.containerImageTag && tagOptions[0]) {
this.state.formState.containerImageTag = tagOptions[0].value;
}
tagComponent.options = tagOptions;
if (!this.state.formState.containerImageTag && tagOptions.length > 0) {
this.state.formState.containerImageTag = tagOptions[0].value;
}
} catch (error) {
console.error("Failed to fetch Docker container tags:", error);
// Keep dialog resilient - don't block if Docker tags fail to load
this.logger.error("Failed to fetch Docker container tags:", error);
}
}

Expand Down