@iflow-mcp/alibabacloud-devops-mcp-server
Version:
MCP Server for using the alibabacloud-devops API: allows AI assistants to directly participate in development collaboration, helping teams optimize development processes and improve efficiency.
531 lines (530 loc) • 22.6 kB
JavaScript
import * as utils from "../../common/utils.js";
import { PipelineDetailSchema, PipelineListItemSchema, PipelineRunSchema, PipelineRunListItemSchema } from "../../common/types.js";
import { generateModularPipeline } from "../../common/modularTemplates.js";
import { listServiceConnectionsFunc } from "./serviceConnection.js";
import { listHostGroupsFunc } from "./hostGroup.js";
/**
* 获取流水线详情
* @param organizationId 组织ID
* @param pipelineId 流水线ID
* @returns 流水线详情
*/
export async function getPipelineFunc(organizationId, pipelineId) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}`;
const response = await utils.yunxiaoRequest(url, {
method: "GET",
});
return PipelineDetailSchema.parse(response);
}
/**
* 获取流水线列表
* @param organizationId 组织ID
* @param options 查询选项
* @returns 流水线列表
*/
export async function listPipelinesFunc(organizationId, options) {
const baseUrl = `/oapi/v1/flow/organizations/${organizationId}/pipelines`;
// 构建查询参数
const queryParams = {};
// 处理时间戳参数
// 如果传入的是日期字符串或Date对象,自动转换为毫秒时间戳
if (options?.createStartTime !== undefined) {
queryParams.createStartTime = utils.convertToTimestamp(options.createStartTime);
}
if (options?.createEndTime !== undefined) {
queryParams.createEndTime = utils.convertToTimestamp(options.createEndTime);
}
if (options?.executeStartTime !== undefined) {
queryParams.executeStartTime = utils.convertToTimestamp(options.executeStartTime);
}
if (options?.executeEndTime !== undefined) {
queryParams.executeEndTime = utils.convertToTimestamp(options.executeEndTime);
}
if (options?.pipelineName !== undefined) {
queryParams.pipelineName = options.pipelineName;
}
if (options?.statusList !== undefined) {
queryParams.statusList = options.statusList;
}
if (options?.perPage !== undefined) {
queryParams.perPage = options.perPage;
}
if (options?.page !== undefined) {
queryParams.page = options.page;
}
const url = utils.buildUrl(baseUrl, queryParams);
const response = await utils.yunxiaoRequest(url, {
method: "GET",
});
const pagination = {
nextPage: null,
page: 1,
perPage: 10,
prevPage: null,
total: 0,
totalPages: 0
};
let items = [];
if (Array.isArray(response)) {
items = response.map(item => PipelineListItemSchema.parse(item));
}
return {
items,
pagination
};
}
/**
* 智能查询流水线列表,能够解析自然语言中的时间表达
* @param organizationId 组织ID
* @param timeReference 自然语言时间引用,如"今天"、"本周"、"上个月"
* @param options 其他查询选项
* @returns 流水线列表
*/
export async function smartListPipelinesFunc(organizationId, timeReference, options) {
// 解析时间引用获取开始和结束时间戳
const { startTime, endTime } = utils.parseDateReference(timeReference);
// 合并选项
const fullOptions = {
...options,
executeStartTime: startTime,
executeEndTime: endTime
};
return listPipelinesFunc(organizationId, fullOptions);
}
/**
* 运行流水线
* @param organizationId 组织ID
* @param pipelineId 流水线ID
* @param options 运行选项,可以是直接的JSON字符串或者自然语言描述的选项
* @returns 流水线运行ID
*/
export async function createPipelineRunFunc(organizationId, pipelineId, options) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs`;
// 如果用户已经提供了格式化的params,直接使用
if (options?.params) {
const body = {
params: options.params
};
const response = await utils.yunxiaoRequest(url, {
method: "POST",
body: body,
});
return Number(response);
}
// 否则,基于用户提供的自然语言参数构建params
const paramsObject = {};
// 处理分支模式相关参数
if (options?.branchMode && options?.branches && options.branches.length > 0) {
paramsObject.branchModeBranchs = options.branches;
}
// 处理Release分支相关参数
if (options?.createReleaseBranch !== undefined) {
paramsObject.needCreateBranch = options.createReleaseBranch;
}
if (options?.releaseBranch) {
paramsObject.releaseBranch = options.releaseBranch;
}
// 处理环境变量
if (options?.environmentVariables && Object.keys(options.environmentVariables).length > 0) {
paramsObject.envs = options.environmentVariables;
}
// 处理特定仓库配置
if (options?.repositories && options.repositories.length > 0) {
// 初始化runningBranchs和runningTags对象
const runningBranchs = {};
const runningTags = {};
// 填充分支和标签信息
options.repositories.forEach(repo => {
if (repo.branch) {
runningBranchs[repo.url] = repo.branch;
}
if (repo.tag) {
runningTags[repo.url] = repo.tag;
}
});
// 只有在有内容时才添加到params对象
if (Object.keys(runningBranchs).length > 0) {
paramsObject.runningBranchs = runningBranchs;
}
if (Object.keys(runningTags).length > 0) {
paramsObject.runningTags = runningTags;
}
}
// 如果有自然语言描述,尝试解析它
if (options?.description) {
// 此处可以添加更复杂的自然语言处理逻辑
// 当前实现是简单的关键词匹配
const description = options.description.toLowerCase();
// 检测分支模式
if ((description.includes('branch mode') || description.includes('分支模式')) &&
!paramsObject.branchModeBranchs &&
options?.branches?.length) {
paramsObject.branchModeBranchs = options.branches;
}
// 检测是否需要创建release分支
if ((description.includes('create release') || description.includes('创建release')) &&
paramsObject.needCreateBranch === undefined) {
paramsObject.needCreateBranch = true;
}
// 如果提到特定release分支但没有指定
if ((description.includes('release branch') || description.includes('release分支')) &&
!paramsObject.releaseBranch &&
options?.branches?.length) {
// 假设第一个分支就是release分支
paramsObject.releaseBranch = options.branches[0];
}
}
const body = {};
if (Object.keys(paramsObject).length > 0) {
body.params = JSON.stringify(paramsObject);
}
const response = await utils.yunxiaoRequest(url, {
method: "POST",
body: body,
});
return Number(response);
}
/**
* 获取最近一次流水线运行信息
* @param organizationId 组织ID
* @param pipelineId 流水线ID
* @returns 最近一次流水线运行信息
*/
export async function getLatestPipelineRunFunc(organizationId, pipelineId) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs/latestPipelineRun`;
const response = await utils.yunxiaoRequest(url, {
method: "GET",
});
return PipelineRunSchema.parse(response);
}
/**
* 获取特定流水线运行实例
* @param organizationId 组织ID
* @param pipelineId 流水线ID
* @param pipelineRunId 流水线运行ID
* @returns 流水线运行实例信息
*/
export async function getPipelineRunFunc(organizationId, pipelineId, pipelineRunId) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs/${pipelineRunId}`;
const response = await utils.yunxiaoRequest(url, {
method: "GET",
});
return PipelineRunSchema.parse(response);
}
/**
* 获取流水线运行实例列表
* @param organizationId 组织ID
* @param pipelineId 流水线ID
* @param options 查询选项
* @returns 流水线运行实例列表和分页信息
*/
export async function listPipelineRunsFunc(organizationId, pipelineId, options) {
const baseUrl = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}/runs`;
// 构建查询参数
const queryParams = {};
if (options?.perPage !== undefined) {
queryParams.perPage = options.perPage;
}
if (options?.page !== undefined) {
queryParams.page = options.page;
}
if (options?.startTime !== undefined) {
queryParams.startTime = utils.convertToTimestamp(options.startTime);
}
if (options?.endTime !== undefined) {
queryParams.endTme = utils.convertToTimestamp(options.endTime);
}
if (options?.status !== undefined) {
queryParams.status = options.status;
}
if (options?.triggerMode !== undefined) {
queryParams.triggerMode = options.triggerMode;
}
const url = utils.buildUrl(baseUrl, queryParams);
const response = await utils.yunxiaoRequest(url, {
method: "GET",
});
const pagination = {
nextPage: null,
page: options?.page ?? 1,
perPage: options?.perPage ?? 10,
prevPage: null,
total: 0,
totalPages: 0
};
let items = [];
if (Array.isArray(response)) {
items = response.map(item => PipelineRunListItemSchema.parse(item));
}
return {
items,
pagination
};
}
/**
* 创建流水线
* @param organizationId 组织ID
* @param name 流水线名称
* @param content 流水线YAML描述
* @returns 流水线ID
*/
export async function createPipelineFunc(organizationId, name, content) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines`;
const body = {
name: name,
content: content
};
const response = await utils.yunxiaoRequest(url, {
method: "POST",
body: body,
});
return Number(response);
}
/**
* 基于结构化参数生成流水线YAML(不创建流水线)
* @param options 结构化的流水线配置选项
* @returns 生成的YAML字符串
*/
export async function generatePipelineYamlFunc(options) {
// 自动从repoUrl解析serviceName(如果用户没有明确指定)
let derivedServiceName = options.serviceName;
if (!derivedServiceName && options.repoUrl) {
// 从Git URL中提取项目名称
// 支持格式: git@codeup.aliyun.com:org/repo.git 或 https://codeup.aliyun.com/org/repo.git
const repoUrlMatch = options.repoUrl.match(/[\/:]([^\/]+)\.git$/);
if (repoUrlMatch) {
derivedServiceName = repoUrlMatch[1];
}
}
// 准备变量,确保版本号有双引号
const variables = {
// 基础配置
...(options.repoUrl && { repoUrl: options.repoUrl }),
...(options.branch && { branch: options.branch }),
...(derivedServiceName && { serviceName: derivedServiceName }),
...(options.serviceConnectionId && { serviceConnectionId: options.serviceConnectionId }),
...(options.packagesServiceConnection && { packagesServiceConnection: options.packagesServiceConnection }),
...(options.machineGroupId && { machineGroupId: options.machineGroupId }),
...(options.namespace && { namespace: options.namespace }),
...(options.dockerImage && { dockerImage: options.dockerImage }),
// 版本相关(确保双引号)
...(options.jdkVersion && { jdkVersion: `"${options.jdkVersion}"` }),
...(options.mavenVersion && { mavenVersion: `"${options.mavenVersion}"` }),
...(options.nodeVersion && { nodeVersion: `"${options.nodeVersion}"` }),
...(options.pythonVersion && { pythonVersion: `"${options.pythonVersion}"` }),
...(options.goVersion && { goVersion: `"${options.goVersion}"` }),
...(options.kubectlVersion && { kubectlVersion: `"${options.kubectlVersion}"` }),
// 构建物上传相关
...(options.uploadType && { uploadType: options.uploadType }),
...(options.artifactName && { artifactName: options.artifactName }),
...(options.artifactVersion && { artifactVersion: options.artifactVersion }),
...(options.packagesRepoId && { packagesRepoId: options.packagesRepoId }),
...(options.includePathInArtifact !== undefined && { includePathInArtifact: options.includePathInArtifact }),
// 部署相关
...(options.executeUser && { executeUser: options.executeUser }),
...(options.artifactDownloadPath && { artifactDownloadPath: options.artifactDownloadPath }),
...(options.kubernetesClusterId && { kubernetesClusterId: options.kubernetesClusterId }),
...(options.yamlPath && { yamlPath: options.yamlPath }),
// 命令
...(options.buildCommand && { buildCommand: options.buildCommand }),
...(options.testCommand && { testCommand: options.testCommand }),
...(options.deployCommand && { deployCommand: options.deployCommand }),
};
// 转换为模块化流水线选项
const deployTargets = options.deployTarget ? [options.deployTarget] : [];
// 使用模块化架构生成YAML
return generateModularPipeline({
keywords: [options.buildLanguage, options.buildTool],
buildLanguages: [options.buildLanguage],
buildTools: [options.buildTool],
deployTargets: deployTargets,
uploadType: options.uploadType || 'packages',
variables: variables
});
}
/**
* 基于结构化参数创建流水线
* @param organizationId 组织ID
* @param options 结构化的流水线配置选项
* @returns 创建结果,包含流水线ID和生成的YAML
*/
export async function createPipelineWithOptionsFunc(organizationId, options) {
// 获取默认服务连接ID(如果用户没有明确指定)
let defaultServiceConnectionId = null;
const hasServiceConnectionId = options.serviceConnectionId;
if (!hasServiceConnectionId) {
defaultServiceConnectionId = await getDefaultServiceConnectionId(organizationId);
}
// 获取默认Packages服务连接ID(如果用户没有明确指定且需要packages上传)
let defaultPackagesServiceConnectionId = null;
const hasPackagesServiceConnectionId = options.packagesServiceConnection;
const needsPackagesUpload = !options.uploadType || options.uploadType === 'packages';
if (!hasPackagesServiceConnectionId && needsPackagesUpload) {
defaultPackagesServiceConnectionId = await getDefaultPackagesServiceConnectionId(organizationId);
}
// 获取默认主机组ID(如果用户没有明确指定且需要VM部署)
let defaultMachineGroupId = null;
const hasMachineGroupId = options.machineGroupId;
const needsVMDeploy = options.deployTarget === 'vm';
if (!hasMachineGroupId && needsVMDeploy) {
defaultMachineGroupId = await getDefaultHostGroupId(organizationId);
}
// 自动从repoUrl解析serviceName(如果用户没有明确指定)
let derivedServiceName = options.serviceName;
if (!derivedServiceName && options.repoUrl) {
// 从Git URL中提取项目名称
// 支持格式: git@codeup.aliyun.com:org/repo.git 或 https://codeup.aliyun.com/org/repo.git
const repoUrlMatch = options.repoUrl.match(/[\/:]([^\/]+)\.git$/);
if (repoUrlMatch) {
derivedServiceName = repoUrlMatch[1];
}
}
// 准备模块化流水线生成的变量
const finalVariables = {
// 基础配置(直接使用用户提供的值)
...(options.repoUrl && { repoUrl: options.repoUrl }),
...(options.branch && { branch: options.branch }),
...(derivedServiceName && { serviceName: derivedServiceName }),
// 使用获取到的默认服务连接ID
...(defaultServiceConnectionId && !hasServiceConnectionId && { serviceConnectionId: defaultServiceConnectionId }),
// 使用获取到的默认Packages服务连接ID
...(defaultPackagesServiceConnectionId && !hasPackagesServiceConnectionId && { packagesServiceConnection: defaultPackagesServiceConnectionId }),
// 使用获取到的默认主机组ID
...(defaultMachineGroupId && !hasMachineGroupId && { machineGroupId: defaultMachineGroupId }),
// 用户明确指定的值优先级最高
...(options.serviceConnectionId && { serviceConnectionId: options.serviceConnectionId }),
...(options.packagesServiceConnection && { packagesServiceConnection: options.packagesServiceConnection }),
...(options.machineGroupId && { machineGroupId: options.machineGroupId }),
...(options.namespace && { namespace: options.namespace }),
...(options.dockerImage && { dockerImage: options.dockerImage }),
// 版本相关(确保双引号)
...(options.jdkVersion && { jdkVersion: `"${options.jdkVersion}"` }),
...(options.mavenVersion && { mavenVersion: `"${options.mavenVersion}"` }),
...(options.nodeVersion && { nodeVersion: `"${options.nodeVersion}"` }),
...(options.pythonVersion && { pythonVersion: `"${options.pythonVersion}"` }),
...(options.goVersion && { goVersion: `"${options.goVersion}"` }),
...(options.kubectlVersion && { kubectlVersion: `"${options.kubectlVersion}"` }),
// 构建物上传相关
...(options.uploadType && { uploadType: options.uploadType }),
...(options.artifactName && { artifactName: options.artifactName }),
...(options.artifactVersion && { artifactVersion: options.artifactVersion }),
...(options.packagesRepoId && { packagesRepoId: options.packagesRepoId }),
...(options.includePathInArtifact !== undefined && { includePathInArtifact: options.includePathInArtifact }),
// 部署相关
...(options.executeUser && { executeUser: options.executeUser }),
...(options.artifactDownloadPath && { artifactDownloadPath: options.artifactDownloadPath }),
...(options.kubernetesClusterId && { kubernetesClusterId: options.kubernetesClusterId }),
...(options.yamlPath && { yamlPath: options.yamlPath }),
// 命令
...(options.buildCommand && { buildCommand: options.buildCommand }),
...(options.testCommand && { testCommand: options.testCommand }),
...(options.deployCommand && { deployCommand: options.deployCommand }),
};
// 转换为模块化流水线选项
const deployTargets = options.deployTarget ? [options.deployTarget] : [];
// 使用模块化架构生成YAML
const generatedYaml = generateModularPipeline({
keywords: [options.buildLanguage, options.buildTool],
buildLanguages: [options.buildLanguage],
buildTools: [options.buildTool],
deployTargets: deployTargets,
uploadType: options.uploadType || 'packages',
variables: finalVariables
});
// 创建流水线
try {
const pipelineId = await createPipelineFunc(organizationId, options.name, generatedYaml);
return {
pipelineId,
generatedYaml
};
}
catch (error) {
// 如果是YAML校验失败或其他流水线创建错误,将详细信息透出给用户
console.error('Create pipeline failed:', error);
// 构造包含生成YAML的错误信息,方便用户排查
const errorMessage = error instanceof Error ? error.message : String(error);
const enhancedError = new Error(`Create pipeline failed: ${errorMessage}\n\n` +
`YAML content:\n${generatedYaml}\n\n` +
`Suggestions:\n` +
`1. Check whether the YAML format is correct.\n` +
`2. Verify whether the serviceConnectionID、machineGroupID、kubernetesClusterID and other parameters are existed and valid.`);
// 保持原始错误的堆栈信息
if (error instanceof Error && error.stack) {
enhancedError.stack = error.stack;
}
throw enhancedError;
}
}
/**
* 获取默认的服务连接ID(用于代码源配置)
* @param organizationId 组织ID
* @returns 服务连接ID
*/
async function getDefaultServiceConnectionId(organizationId) {
try {
// 获取Codeup类型的服务连接(代码源最常用)
const serviceConnections = await listServiceConnectionsFunc(organizationId, 'codeup');
if (serviceConnections && serviceConnections.length > 0) {
return serviceConnections[0].uuid || null;
}
return null;
}
catch (error) {
console.error('获取Codeup服务连接失败:', error);
return null;
}
}
/**
* 获取默认的Packages服务连接ID(用于制品上传配置)
* @param organizationId 组织ID
* @returns Packages服务连接ID
*/
async function getDefaultPackagesServiceConnectionId(organizationId) {
try {
// 获取packages类型的服务连接
const serviceConnections = await listServiceConnectionsFunc(organizationId, 'packages');
if (serviceConnections && serviceConnections.length > 0) {
return serviceConnections[0].uuid || null;
}
return null;
}
catch (error) {
console.error('获取Packages服务连接失败:', error);
return null;
}
}
/**
* 获取默认的主机组UUID(用于VM部署配置)
* @param organizationId 组织ID
* @returns null(暂不自动获取)
*/
async function getDefaultHostGroupId(organizationId) {
try {
const hostGroups = await listHostGroupsFunc(organizationId);
if (hostGroups && hostGroups.length > 0) {
return hostGroups[0].uuid || null;
}
return null;
}
catch (error) {
console.error('获取主机组失败:', error);
return null;
}
}
/**
* 更新流水线内容(YAML)
* @param organizationId
* @param pipelineId
* @param name
* @param content
*/
export async function updatePipelineFunc(organizationId, pipelineId, name, content) {
const url = `/oapi/v1/flow/organizations/${organizationId}/pipelines/${pipelineId}`;
const body = { name, content };
const response = await utils.yunxiaoRequest(url, {
method: "PUT",
body
});
return Boolean(response);
}