@mcp-consultant-tools/azure-devops 24.0.0 → 25.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/AzureDevOpsService.d.ts +593 -0
- package/build/AzureDevOpsService.d.ts.map +1 -1
- package/build/AzureDevOpsService.js +1195 -3
- package/build/AzureDevOpsService.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +846 -1
- package/build/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,21 @@ export class AzureDevOpsService {
|
|
|
11
11
|
apiVersion: config.apiVersion || '7.1',
|
|
12
12
|
enableWorkItemWrite: config.enableWorkItemWrite ?? false,
|
|
13
13
|
enableWorkItemDelete: config.enableWorkItemDelete ?? false,
|
|
14
|
-
enableWikiWrite: config.enableWikiWrite ?? false
|
|
14
|
+
enableWikiWrite: config.enableWikiWrite ?? false,
|
|
15
|
+
// Tier 1: Read-only (master switch)
|
|
16
|
+
showDevOpsAdminReadonly: config.showDevOpsAdminReadonly ?? false,
|
|
17
|
+
// Tier 2: Upsert (create + update)
|
|
18
|
+
enablePipelineUpsert: config.enablePipelineUpsert ?? false,
|
|
19
|
+
enableServiceConnUpsert: config.enableServiceConnUpsert ?? false,
|
|
20
|
+
enableVariableGroupUpsert: config.enableVariableGroupUpsert ?? false,
|
|
21
|
+
enableAgentPoolUpsert: config.enableAgentPoolUpsert ?? false,
|
|
22
|
+
enableEnvironmentUpsert: config.enableEnvironmentUpsert ?? false,
|
|
23
|
+
// Tier 3: Delete/Disable (destructive operations)
|
|
24
|
+
enablePipelineDelete: config.enablePipelineDelete ?? false,
|
|
25
|
+
enableServiceConnDelete: config.enableServiceConnDelete ?? false,
|
|
26
|
+
enableVariableGroupDelete: config.enableVariableGroupDelete ?? false,
|
|
27
|
+
enableAgentPoolDisable: config.enableAgentPoolDisable ?? false,
|
|
28
|
+
enableEnvironmentDelete: config.enableEnvironmentDelete ?? false,
|
|
15
29
|
};
|
|
16
30
|
this.baseUrl = `https://dev.azure.com/${this.config.organization}`;
|
|
17
31
|
this.searchUrl = `https://almsearch.dev.azure.com/${this.config.organization}`;
|
|
@@ -512,11 +526,13 @@ export class AzureDevOpsService {
|
|
|
512
526
|
this.validateProject(project);
|
|
513
527
|
// Comments API requires -preview suffix (not GA in 7.1)
|
|
514
528
|
const response = await this.makeRequest(`${project}/_apis/wit/workItems/${workItemId}/comments?api-version=7.1-preview`);
|
|
529
|
+
// Handle case where response.value may be undefined or empty
|
|
530
|
+
const comments = response.value || [];
|
|
515
531
|
return {
|
|
516
532
|
workItemId,
|
|
517
533
|
project,
|
|
518
|
-
totalCount: response.totalCount
|
|
519
|
-
comments:
|
|
534
|
+
totalCount: response.totalCount ?? comments.length,
|
|
535
|
+
comments: comments.map((comment) => ({
|
|
520
536
|
id: comment.id,
|
|
521
537
|
text: comment.text,
|
|
522
538
|
createdBy: comment.createdBy?.displayName,
|
|
@@ -713,5 +729,1181 @@ export class AzureDevOpsService {
|
|
|
713
729
|
}, {})
|
|
714
730
|
};
|
|
715
731
|
}
|
|
732
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
733
|
+
// PIPELINE/BUILD OPERATIONS (DevOps Admin Tools)
|
|
734
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
735
|
+
/**
|
|
736
|
+
* List all pipeline definitions in a project
|
|
737
|
+
* @param project The project name
|
|
738
|
+
* @returns List of pipeline definitions
|
|
739
|
+
*/
|
|
740
|
+
async listPipelineDefinitions(project) {
|
|
741
|
+
this.validateProject(project);
|
|
742
|
+
const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`);
|
|
743
|
+
return {
|
|
744
|
+
project,
|
|
745
|
+
totalCount: response.value.length,
|
|
746
|
+
pipelines: response.value.map((def) => ({
|
|
747
|
+
id: def.id,
|
|
748
|
+
name: def.name,
|
|
749
|
+
path: def.path,
|
|
750
|
+
revision: def.revision,
|
|
751
|
+
type: def.type,
|
|
752
|
+
queueStatus: def.queueStatus,
|
|
753
|
+
createdDate: def.createdDate,
|
|
754
|
+
authoredBy: def.authoredBy?.displayName,
|
|
755
|
+
repository: def.repository ? {
|
|
756
|
+
id: def.repository.id,
|
|
757
|
+
name: def.repository.name,
|
|
758
|
+
type: def.repository.type
|
|
759
|
+
} : null,
|
|
760
|
+
process: def.process ? {
|
|
761
|
+
type: def.process.type,
|
|
762
|
+
yamlFilename: def.process.yamlFilename
|
|
763
|
+
} : null,
|
|
764
|
+
url: def._links?.web?.href
|
|
765
|
+
}))
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Get a specific pipeline definition with full details
|
|
770
|
+
* @param project The project name
|
|
771
|
+
* @param definitionId The pipeline definition ID
|
|
772
|
+
* @returns Full pipeline definition
|
|
773
|
+
*/
|
|
774
|
+
async getPipelineDefinition(project, definitionId) {
|
|
775
|
+
this.validateProject(project);
|
|
776
|
+
const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
|
|
777
|
+
return {
|
|
778
|
+
id: response.id,
|
|
779
|
+
name: response.name,
|
|
780
|
+
path: response.path,
|
|
781
|
+
revision: response.revision,
|
|
782
|
+
type: response.type,
|
|
783
|
+
queueStatus: response.queueStatus,
|
|
784
|
+
quality: response.quality,
|
|
785
|
+
createdDate: response.createdDate,
|
|
786
|
+
authoredBy: response.authoredBy?.displayName,
|
|
787
|
+
project: response.project?.name,
|
|
788
|
+
repository: response.repository ? {
|
|
789
|
+
id: response.repository.id,
|
|
790
|
+
name: response.repository.name,
|
|
791
|
+
type: response.repository.type,
|
|
792
|
+
defaultBranch: response.repository.defaultBranch,
|
|
793
|
+
url: response.repository.url
|
|
794
|
+
} : null,
|
|
795
|
+
process: response.process ? {
|
|
796
|
+
type: response.process.type,
|
|
797
|
+
yamlFilename: response.process.yamlFilename
|
|
798
|
+
} : null,
|
|
799
|
+
triggers: response.triggers || [],
|
|
800
|
+
variables: response.variables ? Object.keys(response.variables).reduce((acc, key) => {
|
|
801
|
+
const variable = response.variables[key];
|
|
802
|
+
acc[key] = {
|
|
803
|
+
value: variable.isSecret ? '***SECRET***' : variable.value,
|
|
804
|
+
isSecret: variable.isSecret || false,
|
|
805
|
+
allowOverride: variable.allowOverride || false
|
|
806
|
+
};
|
|
807
|
+
return acc;
|
|
808
|
+
}, {}) : {},
|
|
809
|
+
queue: response.queue ? {
|
|
810
|
+
id: response.queue.id,
|
|
811
|
+
name: response.queue.name,
|
|
812
|
+
pool: response.queue.pool?.name
|
|
813
|
+
} : null,
|
|
814
|
+
url: response._links?.web?.href
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Get the YAML content for a pipeline definition
|
|
819
|
+
* @param project The project name
|
|
820
|
+
* @param definitionId The pipeline definition ID
|
|
821
|
+
* @returns YAML content or location info for external repos
|
|
822
|
+
*/
|
|
823
|
+
async getPipelineYaml(project, definitionId) {
|
|
824
|
+
this.validateProject(project);
|
|
825
|
+
// First get the pipeline definition to check repo type
|
|
826
|
+
const definition = await this.getPipelineDefinition(project, definitionId);
|
|
827
|
+
// If external repo (GitHub, Bitbucket, etc.), return helpful info instead of failing
|
|
828
|
+
if (definition.repository?.type && definition.repository.type !== 'TfsGit') {
|
|
829
|
+
return {
|
|
830
|
+
definitionId,
|
|
831
|
+
project,
|
|
832
|
+
pipelineName: definition.name,
|
|
833
|
+
yamlLocation: 'external',
|
|
834
|
+
repositoryType: definition.repository.type,
|
|
835
|
+
repositoryName: definition.repository.name,
|
|
836
|
+
repositoryUrl: definition.repository.url,
|
|
837
|
+
defaultBranch: definition.repository.defaultBranch,
|
|
838
|
+
yamlFilename: definition.process?.yamlFilename,
|
|
839
|
+
message: `YAML is stored in external ${definition.repository.type} repository. ` +
|
|
840
|
+
`To view the YAML content, access the file "${definition.process?.yamlFilename}" ` +
|
|
841
|
+
`at ${definition.repository.url}`
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
// For Azure Repos (TfsGit), fetch YAML content directly
|
|
845
|
+
const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}/yaml?api-version=${this.apiVersion}`);
|
|
846
|
+
return {
|
|
847
|
+
definitionId,
|
|
848
|
+
project,
|
|
849
|
+
pipelineName: definition.name,
|
|
850
|
+
yamlLocation: 'azureRepos',
|
|
851
|
+
yaml: response.yaml || response
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* List recent pipeline runs for a definition
|
|
856
|
+
* @param project The project name
|
|
857
|
+
* @param definitionId The pipeline definition ID
|
|
858
|
+
* @param top Maximum number of results (default: 10)
|
|
859
|
+
* @returns List of builds/runs
|
|
860
|
+
*/
|
|
861
|
+
async listPipelineRuns(project, definitionId, top = 10) {
|
|
862
|
+
this.validateProject(project);
|
|
863
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds?definitions=${definitionId}&$top=${top}&api-version=${this.apiVersion}`);
|
|
864
|
+
return {
|
|
865
|
+
project,
|
|
866
|
+
definitionId,
|
|
867
|
+
totalCount: response.value.length,
|
|
868
|
+
builds: response.value.map((build) => ({
|
|
869
|
+
id: build.id,
|
|
870
|
+
buildNumber: build.buildNumber,
|
|
871
|
+
status: build.status,
|
|
872
|
+
result: build.result,
|
|
873
|
+
queueTime: build.queueTime,
|
|
874
|
+
startTime: build.startTime,
|
|
875
|
+
finishTime: build.finishTime,
|
|
876
|
+
sourceBranch: build.sourceBranch,
|
|
877
|
+
sourceVersion: build.sourceVersion,
|
|
878
|
+
requestedBy: build.requestedBy?.displayName,
|
|
879
|
+
requestedFor: build.requestedFor?.displayName,
|
|
880
|
+
reason: build.reason,
|
|
881
|
+
priority: build.priority,
|
|
882
|
+
url: build._links?.web?.href
|
|
883
|
+
}))
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Get build status and details
|
|
888
|
+
* @param project The project name
|
|
889
|
+
* @param buildId The build ID
|
|
890
|
+
* @param detail Level of detail: "summary" | "timeline" | "full"
|
|
891
|
+
* @returns Build details
|
|
892
|
+
*/
|
|
893
|
+
async getBuildStatus(project, buildId, detail = 'summary') {
|
|
894
|
+
this.validateProject(project);
|
|
895
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
|
|
896
|
+
const result = {
|
|
897
|
+
id: response.id,
|
|
898
|
+
buildNumber: response.buildNumber,
|
|
899
|
+
status: response.status,
|
|
900
|
+
result: response.result,
|
|
901
|
+
queueTime: response.queueTime,
|
|
902
|
+
startTime: response.startTime,
|
|
903
|
+
finishTime: response.finishTime,
|
|
904
|
+
sourceBranch: response.sourceBranch,
|
|
905
|
+
sourceVersion: response.sourceVersion,
|
|
906
|
+
definition: response.definition ? {
|
|
907
|
+
id: response.definition.id,
|
|
908
|
+
name: response.definition.name
|
|
909
|
+
} : null,
|
|
910
|
+
requestedBy: response.requestedBy?.displayName,
|
|
911
|
+
requestedFor: response.requestedFor?.displayName,
|
|
912
|
+
reason: response.reason,
|
|
913
|
+
priority: response.priority,
|
|
914
|
+
project: response.project?.name,
|
|
915
|
+
url: response._links?.web?.href
|
|
916
|
+
};
|
|
917
|
+
// Add timeline if requested
|
|
918
|
+
if (detail === 'timeline' || detail === 'full') {
|
|
919
|
+
const timeline = await this.getBuildTimeline(project, buildId);
|
|
920
|
+
result.timeline = timeline;
|
|
921
|
+
}
|
|
922
|
+
// Add logs if full detail requested
|
|
923
|
+
if (detail === 'full') {
|
|
924
|
+
const logs = await this.getBuildLogs(project, buildId);
|
|
925
|
+
result.logs = logs;
|
|
926
|
+
}
|
|
927
|
+
return result;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Get build timeline (step-by-step breakdown)
|
|
931
|
+
* @param project The project name
|
|
932
|
+
* @param buildId The build ID
|
|
933
|
+
* @returns Build timeline with records
|
|
934
|
+
*/
|
|
935
|
+
async getBuildTimeline(project, buildId) {
|
|
936
|
+
this.validateProject(project);
|
|
937
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
|
|
938
|
+
return {
|
|
939
|
+
buildId,
|
|
940
|
+
project,
|
|
941
|
+
changeId: response.changeId,
|
|
942
|
+
lastChangedBy: response.lastChangedBy,
|
|
943
|
+
lastChangedOn: response.lastChangedOn,
|
|
944
|
+
records: (response.records || []).map((record) => ({
|
|
945
|
+
id: record.id,
|
|
946
|
+
parentId: record.parentId,
|
|
947
|
+
type: record.type,
|
|
948
|
+
name: record.name,
|
|
949
|
+
state: record.state,
|
|
950
|
+
result: record.result,
|
|
951
|
+
startTime: record.startTime,
|
|
952
|
+
finishTime: record.finishTime,
|
|
953
|
+
order: record.order,
|
|
954
|
+
errorCount: record.errorCount,
|
|
955
|
+
warningCount: record.warningCount,
|
|
956
|
+
log: record.log ? { id: record.log.id, url: record.log.url } : null,
|
|
957
|
+
issues: record.issues || []
|
|
958
|
+
}))
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Get build logs
|
|
963
|
+
* @param project The project name
|
|
964
|
+
* @param buildId The build ID
|
|
965
|
+
* @param logId Optional specific log ID
|
|
966
|
+
* @returns Build logs
|
|
967
|
+
*/
|
|
968
|
+
async getBuildLogs(project, buildId, logId) {
|
|
969
|
+
this.validateProject(project);
|
|
970
|
+
if (logId !== undefined) {
|
|
971
|
+
// Get specific log content
|
|
972
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs/${logId}?api-version=${this.apiVersion}`);
|
|
973
|
+
return {
|
|
974
|
+
buildId,
|
|
975
|
+
logId,
|
|
976
|
+
project,
|
|
977
|
+
content: response
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
// Get list of all logs
|
|
981
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs?api-version=${this.apiVersion}`);
|
|
982
|
+
return {
|
|
983
|
+
buildId,
|
|
984
|
+
project,
|
|
985
|
+
totalCount: response.value.length,
|
|
986
|
+
logs: response.value.map((log) => ({
|
|
987
|
+
id: log.id,
|
|
988
|
+
type: log.type,
|
|
989
|
+
lineCount: log.lineCount,
|
|
990
|
+
createdOn: log.createdOn,
|
|
991
|
+
lastChangedOn: log.lastChangedOn,
|
|
992
|
+
url: log.url
|
|
993
|
+
}))
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Create a new pipeline definition
|
|
998
|
+
* @param project The project name
|
|
999
|
+
* @param name Pipeline name
|
|
1000
|
+
* @param repositoryId Repository ID
|
|
1001
|
+
* @param yamlPath Path to YAML file in repository
|
|
1002
|
+
* @param folder Optional folder path
|
|
1003
|
+
* @returns Created pipeline definition
|
|
1004
|
+
*/
|
|
1005
|
+
async createPipelineDefinition(project, name, repositoryId, yamlPath, folder) {
|
|
1006
|
+
this.validateProject(project);
|
|
1007
|
+
if (!this.config.enablePipelineUpsert) {
|
|
1008
|
+
throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
|
|
1009
|
+
}
|
|
1010
|
+
const definition = {
|
|
1011
|
+
name,
|
|
1012
|
+
path: folder || '\\',
|
|
1013
|
+
type: 'build',
|
|
1014
|
+
queueStatus: 'enabled',
|
|
1015
|
+
process: {
|
|
1016
|
+
type: 2, // YAML process type
|
|
1017
|
+
yamlFilename: yamlPath
|
|
1018
|
+
},
|
|
1019
|
+
repository: {
|
|
1020
|
+
id: repositoryId,
|
|
1021
|
+
type: 'TfsGit'
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`, 'POST', definition);
|
|
1025
|
+
return {
|
|
1026
|
+
id: response.id,
|
|
1027
|
+
name: response.name,
|
|
1028
|
+
path: response.path,
|
|
1029
|
+
revision: response.revision,
|
|
1030
|
+
project,
|
|
1031
|
+
url: response._links?.web?.href
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Update a pipeline definition
|
|
1036
|
+
* @param project The project name
|
|
1037
|
+
* @param definitionId The pipeline definition ID
|
|
1038
|
+
* @param updates Object with updated properties
|
|
1039
|
+
* @returns Updated pipeline definition
|
|
1040
|
+
*/
|
|
1041
|
+
async updatePipelineDefinition(project, definitionId, updates) {
|
|
1042
|
+
this.validateProject(project);
|
|
1043
|
+
if (!this.config.enablePipelineUpsert) {
|
|
1044
|
+
throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
|
|
1045
|
+
}
|
|
1046
|
+
// Get current definition first
|
|
1047
|
+
const current = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
|
|
1048
|
+
// Merge updates
|
|
1049
|
+
const updated = {
|
|
1050
|
+
...current,
|
|
1051
|
+
...updates,
|
|
1052
|
+
revision: current.revision // Must include current revision
|
|
1053
|
+
};
|
|
1054
|
+
const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'PUT', updated);
|
|
1055
|
+
return {
|
|
1056
|
+
id: response.id,
|
|
1057
|
+
name: response.name,
|
|
1058
|
+
path: response.path,
|
|
1059
|
+
revision: response.revision,
|
|
1060
|
+
project,
|
|
1061
|
+
url: response._links?.web?.href
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Rename a pipeline definition
|
|
1066
|
+
* @param project The project name
|
|
1067
|
+
* @param definitionId The pipeline definition ID
|
|
1068
|
+
* @param newName The new name
|
|
1069
|
+
* @returns Updated pipeline definition
|
|
1070
|
+
*/
|
|
1071
|
+
async renamePipelineDefinition(project, definitionId, newName) {
|
|
1072
|
+
return this.updatePipelineDefinition(project, definitionId, { name: newName });
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Queue a new build
|
|
1076
|
+
* @param project The project name
|
|
1077
|
+
* @param definitionId The pipeline definition ID
|
|
1078
|
+
* @param branch Optional source branch
|
|
1079
|
+
* @param variables Optional variables to pass
|
|
1080
|
+
* @param parameters Optional pipeline parameters
|
|
1081
|
+
* @returns Queued build
|
|
1082
|
+
*/
|
|
1083
|
+
async queueBuild(project, definitionId, branch, variables, parameters) {
|
|
1084
|
+
this.validateProject(project);
|
|
1085
|
+
if (!this.config.enablePipelineUpsert) {
|
|
1086
|
+
throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
|
|
1087
|
+
}
|
|
1088
|
+
const buildRequest = {
|
|
1089
|
+
definition: { id: definitionId }
|
|
1090
|
+
};
|
|
1091
|
+
if (branch) {
|
|
1092
|
+
buildRequest.sourceBranch = branch;
|
|
1093
|
+
}
|
|
1094
|
+
if (variables) {
|
|
1095
|
+
buildRequest.parameters = JSON.stringify(variables);
|
|
1096
|
+
}
|
|
1097
|
+
if (parameters) {
|
|
1098
|
+
buildRequest.templateParameters = parameters;
|
|
1099
|
+
}
|
|
1100
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds?api-version=${this.apiVersion}`, 'POST', buildRequest);
|
|
1101
|
+
return {
|
|
1102
|
+
id: response.id,
|
|
1103
|
+
buildNumber: response.buildNumber,
|
|
1104
|
+
status: response.status,
|
|
1105
|
+
queueTime: response.queueTime,
|
|
1106
|
+
definition: response.definition?.name,
|
|
1107
|
+
sourceBranch: response.sourceBranch,
|
|
1108
|
+
project,
|
|
1109
|
+
url: response._links?.web?.href
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Cancel a running build
|
|
1114
|
+
* @param project The project name
|
|
1115
|
+
* @param buildId The build ID
|
|
1116
|
+
* @returns Cancelled build
|
|
1117
|
+
*/
|
|
1118
|
+
async cancelBuild(project, buildId) {
|
|
1119
|
+
this.validateProject(project);
|
|
1120
|
+
if (!this.config.enablePipelineUpsert) {
|
|
1121
|
+
throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
|
|
1122
|
+
}
|
|
1123
|
+
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`, 'PATCH', { status: 'cancelling' });
|
|
1124
|
+
return {
|
|
1125
|
+
id: response.id,
|
|
1126
|
+
buildNumber: response.buildNumber,
|
|
1127
|
+
status: response.status,
|
|
1128
|
+
project,
|
|
1129
|
+
message: 'Build cancellation requested'
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Retry a failed build
|
|
1134
|
+
* @param project The project name
|
|
1135
|
+
* @param buildId The build ID to retry
|
|
1136
|
+
* @returns New build
|
|
1137
|
+
*/
|
|
1138
|
+
async retryBuild(project, buildId) {
|
|
1139
|
+
this.validateProject(project);
|
|
1140
|
+
if (!this.config.enablePipelineUpsert) {
|
|
1141
|
+
throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
|
|
1142
|
+
}
|
|
1143
|
+
// Get the original build to get definition and source info
|
|
1144
|
+
const original = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
|
|
1145
|
+
// Queue a new build with the same parameters
|
|
1146
|
+
return this.queueBuild(project, original.definition.id, original.sourceBranch);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Delete a pipeline definition
|
|
1150
|
+
* @param project The project name
|
|
1151
|
+
* @param definitionId The pipeline definition ID
|
|
1152
|
+
* @returns Deletion confirmation
|
|
1153
|
+
*/
|
|
1154
|
+
async deletePipelineDefinition(project, definitionId) {
|
|
1155
|
+
this.validateProject(project);
|
|
1156
|
+
if (!this.config.enablePipelineDelete) {
|
|
1157
|
+
throw new Error('Pipeline delete operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_DELETE=true to enable.');
|
|
1158
|
+
}
|
|
1159
|
+
await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'DELETE');
|
|
1160
|
+
return {
|
|
1161
|
+
definitionId,
|
|
1162
|
+
project,
|
|
1163
|
+
deleted: true
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1167
|
+
// SERVICE CONNECTION OPERATIONS (DevOps Admin Tools)
|
|
1168
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1169
|
+
/**
|
|
1170
|
+
* List all service connections in a project
|
|
1171
|
+
* @param project The project name
|
|
1172
|
+
* @returns List of service connections
|
|
1173
|
+
*/
|
|
1174
|
+
async listServiceConnections(project) {
|
|
1175
|
+
this.validateProject(project);
|
|
1176
|
+
const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`);
|
|
1177
|
+
return {
|
|
1178
|
+
project,
|
|
1179
|
+
totalCount: response.value.length,
|
|
1180
|
+
serviceConnections: response.value.map((conn) => ({
|
|
1181
|
+
id: conn.id,
|
|
1182
|
+
name: conn.name,
|
|
1183
|
+
type: conn.type,
|
|
1184
|
+
url: conn.url,
|
|
1185
|
+
description: conn.description,
|
|
1186
|
+
isShared: conn.isShared,
|
|
1187
|
+
isReady: conn.isReady,
|
|
1188
|
+
owner: conn.owner,
|
|
1189
|
+
createdBy: conn.createdBy?.displayName,
|
|
1190
|
+
authorization: conn.authorization ? {
|
|
1191
|
+
scheme: conn.authorization.scheme,
|
|
1192
|
+
// Mask any sensitive parameters
|
|
1193
|
+
parameters: conn.authorization.parameters ?
|
|
1194
|
+
Object.keys(conn.authorization.parameters).reduce((acc, key) => {
|
|
1195
|
+
acc[key] = key.toLowerCase().includes('password') ||
|
|
1196
|
+
key.toLowerCase().includes('secret') ||
|
|
1197
|
+
key.toLowerCase().includes('key') ||
|
|
1198
|
+
key.toLowerCase().includes('token')
|
|
1199
|
+
? '***SECRET***'
|
|
1200
|
+
: conn.authorization.parameters[key];
|
|
1201
|
+
return acc;
|
|
1202
|
+
}, {}) : {}
|
|
1203
|
+
} : null
|
|
1204
|
+
}))
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Get a specific service connection
|
|
1209
|
+
* @param project The project name
|
|
1210
|
+
* @param connectionId The service connection ID
|
|
1211
|
+
* @returns Service connection details
|
|
1212
|
+
*/
|
|
1213
|
+
async getServiceConnection(project, connectionId) {
|
|
1214
|
+
this.validateProject(project);
|
|
1215
|
+
const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
|
|
1216
|
+
return {
|
|
1217
|
+
id: response.id,
|
|
1218
|
+
name: response.name,
|
|
1219
|
+
type: response.type,
|
|
1220
|
+
url: response.url,
|
|
1221
|
+
description: response.description,
|
|
1222
|
+
isShared: response.isShared,
|
|
1223
|
+
isReady: response.isReady,
|
|
1224
|
+
owner: response.owner,
|
|
1225
|
+
createdBy: response.createdBy?.displayName,
|
|
1226
|
+
project,
|
|
1227
|
+
authorization: response.authorization ? {
|
|
1228
|
+
scheme: response.authorization.scheme,
|
|
1229
|
+
// Mask any sensitive parameters
|
|
1230
|
+
parameters: response.authorization.parameters ?
|
|
1231
|
+
Object.keys(response.authorization.parameters).reduce((acc, key) => {
|
|
1232
|
+
acc[key] = key.toLowerCase().includes('password') ||
|
|
1233
|
+
key.toLowerCase().includes('secret') ||
|
|
1234
|
+
key.toLowerCase().includes('key') ||
|
|
1235
|
+
key.toLowerCase().includes('token')
|
|
1236
|
+
? '***SECRET***'
|
|
1237
|
+
: response.authorization.parameters[key];
|
|
1238
|
+
return acc;
|
|
1239
|
+
}, {}) : {}
|
|
1240
|
+
} : null,
|
|
1241
|
+
data: response.data || {},
|
|
1242
|
+
serviceEndpointProjectReferences: response.serviceEndpointProjectReferences
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Get available service connection types
|
|
1247
|
+
* @returns List of service connection types
|
|
1248
|
+
*/
|
|
1249
|
+
async getServiceConnectionTypes() {
|
|
1250
|
+
const response = await this.makeRequest(`_apis/serviceendpoint/types?api-version=${this.apiVersion}`);
|
|
1251
|
+
return {
|
|
1252
|
+
totalCount: response.value.length,
|
|
1253
|
+
types: response.value.map((type) => ({
|
|
1254
|
+
name: type.name,
|
|
1255
|
+
displayName: type.displayName,
|
|
1256
|
+
description: type.description,
|
|
1257
|
+
helpMarkDown: type.helpMarkDown,
|
|
1258
|
+
authenticationSchemes: type.authenticationSchemes?.map((scheme) => ({
|
|
1259
|
+
scheme: scheme.scheme,
|
|
1260
|
+
displayName: scheme.displayName
|
|
1261
|
+
})) || []
|
|
1262
|
+
}))
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Create a new service connection
|
|
1267
|
+
* @param project The project name
|
|
1268
|
+
* @param name Connection name
|
|
1269
|
+
* @param type Connection type
|
|
1270
|
+
* @param configuration Type-specific configuration
|
|
1271
|
+
* @returns Created service connection
|
|
1272
|
+
*/
|
|
1273
|
+
async createServiceConnection(project, name, type, configuration) {
|
|
1274
|
+
this.validateProject(project);
|
|
1275
|
+
if (!this.config.enableServiceConnUpsert) {
|
|
1276
|
+
throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
|
|
1277
|
+
}
|
|
1278
|
+
const endpoint = {
|
|
1279
|
+
name,
|
|
1280
|
+
type,
|
|
1281
|
+
url: configuration.url || '',
|
|
1282
|
+
description: configuration.description || '',
|
|
1283
|
+
authorization: configuration.authorization,
|
|
1284
|
+
data: configuration.data || {},
|
|
1285
|
+
isShared: false,
|
|
1286
|
+
isReady: true
|
|
1287
|
+
};
|
|
1288
|
+
const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`, 'POST', endpoint);
|
|
1289
|
+
return {
|
|
1290
|
+
id: response.id,
|
|
1291
|
+
name: response.name,
|
|
1292
|
+
type: response.type,
|
|
1293
|
+
isReady: response.isReady,
|
|
1294
|
+
project
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Update a service connection
|
|
1299
|
+
* @param project The project name
|
|
1300
|
+
* @param connectionId The service connection ID
|
|
1301
|
+
* @param updates Updated properties (not credentials)
|
|
1302
|
+
* @returns Updated service connection
|
|
1303
|
+
*/
|
|
1304
|
+
async updateServiceConnection(project, connectionId, updates) {
|
|
1305
|
+
this.validateProject(project);
|
|
1306
|
+
if (!this.config.enableServiceConnUpsert) {
|
|
1307
|
+
throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
|
|
1308
|
+
}
|
|
1309
|
+
// Get current connection first
|
|
1310
|
+
const current = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
|
|
1311
|
+
// Merge updates (but don't allow overwriting authorization for security)
|
|
1312
|
+
const updated = {
|
|
1313
|
+
...current,
|
|
1314
|
+
name: updates.name || current.name,
|
|
1315
|
+
description: updates.description || current.description,
|
|
1316
|
+
url: updates.url || current.url,
|
|
1317
|
+
data: updates.data ? { ...current.data, ...updates.data } : current.data
|
|
1318
|
+
};
|
|
1319
|
+
const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PUT', updated);
|
|
1320
|
+
return {
|
|
1321
|
+
id: response.id,
|
|
1322
|
+
name: response.name,
|
|
1323
|
+
type: response.type,
|
|
1324
|
+
isReady: response.isReady,
|
|
1325
|
+
project
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Share a service connection across projects
|
|
1330
|
+
* @param connectionId The service connection ID
|
|
1331
|
+
* @param projectIds Array of project IDs to share with
|
|
1332
|
+
* @returns Updated service connection
|
|
1333
|
+
*/
|
|
1334
|
+
async shareServiceConnection(connectionId, projectIds) {
|
|
1335
|
+
if (!this.config.enableServiceConnUpsert) {
|
|
1336
|
+
throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
|
|
1337
|
+
}
|
|
1338
|
+
// This requires fetching the current endpoint and updating its project references
|
|
1339
|
+
// Note: This is a simplified implementation - full implementation would need project name lookup
|
|
1340
|
+
const shareRequest = projectIds.map(projectId => ({
|
|
1341
|
+
projectReference: { id: projectId },
|
|
1342
|
+
name: '' // Will be set by API
|
|
1343
|
+
}));
|
|
1344
|
+
const response = await this.makeRequest(`_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PATCH', { serviceEndpointProjectReferences: shareRequest });
|
|
1345
|
+
return {
|
|
1346
|
+
id: response.id,
|
|
1347
|
+
name: response.name,
|
|
1348
|
+
sharedWith: projectIds
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Delete a service connection
|
|
1353
|
+
* @param project The project name
|
|
1354
|
+
* @param connectionId The service connection ID
|
|
1355
|
+
* @returns Deletion confirmation
|
|
1356
|
+
*/
|
|
1357
|
+
async deleteServiceConnection(project, connectionId) {
|
|
1358
|
+
this.validateProject(project);
|
|
1359
|
+
if (!this.config.enableServiceConnDelete) {
|
|
1360
|
+
throw new Error('Service connection delete operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_DELETE=true to enable.');
|
|
1361
|
+
}
|
|
1362
|
+
await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'DELETE');
|
|
1363
|
+
return {
|
|
1364
|
+
connectionId,
|
|
1365
|
+
project,
|
|
1366
|
+
deleted: true
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1370
|
+
// VARIABLE GROUP UPSERT/DELETE OPERATIONS (DevOps Admin Tools)
|
|
1371
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1372
|
+
/**
|
|
1373
|
+
* Create a new variable group
|
|
1374
|
+
* @param project The project name
|
|
1375
|
+
* @param name Variable group name
|
|
1376
|
+
* @param description Optional description
|
|
1377
|
+
* @param variables Initial variables
|
|
1378
|
+
* @returns Created variable group
|
|
1379
|
+
*/
|
|
1380
|
+
async createVariableGroup(project, name, description, variables) {
|
|
1381
|
+
this.validateProject(project);
|
|
1382
|
+
if (!this.config.enableVariableGroupUpsert) {
|
|
1383
|
+
throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
|
|
1384
|
+
}
|
|
1385
|
+
const variableGroup = {
|
|
1386
|
+
name,
|
|
1387
|
+
description: description || '',
|
|
1388
|
+
type: 'Vsts',
|
|
1389
|
+
variables: variables || {},
|
|
1390
|
+
variableGroupProjectReferences: [{
|
|
1391
|
+
projectReference: { name: project },
|
|
1392
|
+
name: name
|
|
1393
|
+
}]
|
|
1394
|
+
};
|
|
1395
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups?api-version=${this.apiVersion}`, 'POST', variableGroup);
|
|
1396
|
+
return {
|
|
1397
|
+
id: response.id,
|
|
1398
|
+
name: response.name,
|
|
1399
|
+
project,
|
|
1400
|
+
variableCount: Object.keys(response.variables || {}).length
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Update a variable group's metadata
|
|
1405
|
+
* @param project The project name
|
|
1406
|
+
* @param groupId The variable group ID
|
|
1407
|
+
* @param updates Updated properties
|
|
1408
|
+
* @returns Updated variable group
|
|
1409
|
+
*/
|
|
1410
|
+
async updateVariableGroupMetadata(project, groupId, updates) {
|
|
1411
|
+
this.validateProject(project);
|
|
1412
|
+
if (!this.config.enableVariableGroupUpsert) {
|
|
1413
|
+
throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
|
|
1414
|
+
}
|
|
1415
|
+
// Get current group first
|
|
1416
|
+
const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
|
|
1417
|
+
const updated = {
|
|
1418
|
+
...current,
|
|
1419
|
+
name: updates.name || current.name,
|
|
1420
|
+
description: updates.description || current.description
|
|
1421
|
+
};
|
|
1422
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', updated);
|
|
1423
|
+
return {
|
|
1424
|
+
id: response.id,
|
|
1425
|
+
name: response.name,
|
|
1426
|
+
description: response.description,
|
|
1427
|
+
project
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Set or update a variable in a variable group
|
|
1432
|
+
* @param project The project name
|
|
1433
|
+
* @param groupId The variable group ID
|
|
1434
|
+
* @param variableName Variable name
|
|
1435
|
+
* @param value Variable value
|
|
1436
|
+
* @param isSecret Whether the variable is secret
|
|
1437
|
+
* @returns Updated variable group
|
|
1438
|
+
*/
|
|
1439
|
+
async setVariable(project, groupId, variableName, value, isSecret = false) {
|
|
1440
|
+
this.validateProject(project);
|
|
1441
|
+
if (!this.config.enableVariableGroupUpsert) {
|
|
1442
|
+
throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
|
|
1443
|
+
}
|
|
1444
|
+
// Get current group first
|
|
1445
|
+
const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
|
|
1446
|
+
// Update the variable
|
|
1447
|
+
current.variables[variableName] = { value, isSecret };
|
|
1448
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
|
|
1449
|
+
return {
|
|
1450
|
+
id: response.id,
|
|
1451
|
+
name: response.name,
|
|
1452
|
+
project,
|
|
1453
|
+
variableSet: variableName,
|
|
1454
|
+
isSecret
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Remove a variable from a variable group
|
|
1459
|
+
* @param project The project name
|
|
1460
|
+
* @param groupId The variable group ID
|
|
1461
|
+
* @param variableName Variable name to remove
|
|
1462
|
+
* @returns Updated variable group
|
|
1463
|
+
*/
|
|
1464
|
+
async removeVariable(project, groupId, variableName) {
|
|
1465
|
+
this.validateProject(project);
|
|
1466
|
+
if (!this.config.enableVariableGroupDelete) {
|
|
1467
|
+
throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
|
|
1468
|
+
}
|
|
1469
|
+
// Get current group first
|
|
1470
|
+
const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
|
|
1471
|
+
// Remove the variable
|
|
1472
|
+
if (current.variables[variableName]) {
|
|
1473
|
+
delete current.variables[variableName];
|
|
1474
|
+
}
|
|
1475
|
+
else {
|
|
1476
|
+
throw new Error(`Variable '${variableName}' not found in group ${groupId}`);
|
|
1477
|
+
}
|
|
1478
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
|
|
1479
|
+
return {
|
|
1480
|
+
id: response.id,
|
|
1481
|
+
name: response.name,
|
|
1482
|
+
project,
|
|
1483
|
+
variableRemoved: variableName
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Delete a variable group
|
|
1488
|
+
* @param project The project name
|
|
1489
|
+
* @param groupId The variable group ID
|
|
1490
|
+
* @returns Deletion confirmation
|
|
1491
|
+
*/
|
|
1492
|
+
async deleteVariableGroup(project, groupId) {
|
|
1493
|
+
this.validateProject(project);
|
|
1494
|
+
if (!this.config.enableVariableGroupDelete) {
|
|
1495
|
+
throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
|
|
1496
|
+
}
|
|
1497
|
+
await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'DELETE');
|
|
1498
|
+
return {
|
|
1499
|
+
groupId,
|
|
1500
|
+
project,
|
|
1501
|
+
deleted: true
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1505
|
+
// AGENT POOL OPERATIONS (DevOps Admin Tools)
|
|
1506
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1507
|
+
/**
|
|
1508
|
+
* List all agent pools
|
|
1509
|
+
* @param poolType Optional filter: "automation" or "deployment"
|
|
1510
|
+
* @returns List of agent pools
|
|
1511
|
+
*/
|
|
1512
|
+
async listAgentPools(poolType) {
|
|
1513
|
+
let url = `_apis/distributedtask/pools?api-version=${this.apiVersion}`;
|
|
1514
|
+
if (poolType) {
|
|
1515
|
+
url += `&poolType=${poolType}`;
|
|
1516
|
+
}
|
|
1517
|
+
const response = await this.makeRequest(url);
|
|
1518
|
+
return {
|
|
1519
|
+
totalCount: response.value.length,
|
|
1520
|
+
pools: response.value.map((pool) => ({
|
|
1521
|
+
id: pool.id,
|
|
1522
|
+
name: pool.name,
|
|
1523
|
+
size: pool.size,
|
|
1524
|
+
isHosted: pool.isHosted,
|
|
1525
|
+
poolType: pool.poolType,
|
|
1526
|
+
createdBy: pool.createdBy?.displayName,
|
|
1527
|
+
createdOn: pool.createdOn,
|
|
1528
|
+
autoProvision: pool.autoProvision,
|
|
1529
|
+
autoUpdate: pool.autoUpdate,
|
|
1530
|
+
autoSize: pool.autoSize,
|
|
1531
|
+
targetSize: pool.targetSize
|
|
1532
|
+
}))
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Get a specific agent pool
|
|
1537
|
+
* @param poolId The pool ID
|
|
1538
|
+
* @returns Agent pool details
|
|
1539
|
+
*/
|
|
1540
|
+
async getAgentPool(poolId) {
|
|
1541
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`);
|
|
1542
|
+
return {
|
|
1543
|
+
id: response.id,
|
|
1544
|
+
name: response.name,
|
|
1545
|
+
size: response.size,
|
|
1546
|
+
isHosted: response.isHosted,
|
|
1547
|
+
poolType: response.poolType,
|
|
1548
|
+
createdBy: response.createdBy?.displayName,
|
|
1549
|
+
createdOn: response.createdOn,
|
|
1550
|
+
autoProvision: response.autoProvision,
|
|
1551
|
+
autoUpdate: response.autoUpdate,
|
|
1552
|
+
autoSize: response.autoSize,
|
|
1553
|
+
targetSize: response.targetSize,
|
|
1554
|
+
owner: response.owner?.displayName,
|
|
1555
|
+
agentCloudId: response.agentCloudId,
|
|
1556
|
+
properties: response.properties
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* List agents in a pool
|
|
1561
|
+
* @param poolId The pool ID
|
|
1562
|
+
* @param includeCapabilities Include agent capabilities
|
|
1563
|
+
* @returns List of agents
|
|
1564
|
+
*/
|
|
1565
|
+
async listAgents(poolId, includeCapabilities = false) {
|
|
1566
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents?includeCapabilities=${includeCapabilities}&api-version=${this.apiVersion}`);
|
|
1567
|
+
return {
|
|
1568
|
+
poolId,
|
|
1569
|
+
totalCount: response.value.length,
|
|
1570
|
+
agents: response.value.map((agent) => ({
|
|
1571
|
+
id: agent.id,
|
|
1572
|
+
name: agent.name,
|
|
1573
|
+
version: agent.version,
|
|
1574
|
+
osDescription: agent.osDescription,
|
|
1575
|
+
enabled: agent.enabled,
|
|
1576
|
+
status: agent.status,
|
|
1577
|
+
provisioningState: agent.provisioningState,
|
|
1578
|
+
createdOn: agent.createdOn,
|
|
1579
|
+
maxParallelism: agent.maxParallelism,
|
|
1580
|
+
systemCapabilities: includeCapabilities ? agent.systemCapabilities : undefined,
|
|
1581
|
+
userCapabilities: includeCapabilities ? agent.userCapabilities : undefined
|
|
1582
|
+
}))
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get a specific agent
|
|
1587
|
+
* @param poolId The pool ID
|
|
1588
|
+
* @param agentId The agent ID
|
|
1589
|
+
* @returns Agent details
|
|
1590
|
+
*/
|
|
1591
|
+
async getAgent(poolId, agentId) {
|
|
1592
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?includeCapabilities=true&api-version=${this.apiVersion}`);
|
|
1593
|
+
return {
|
|
1594
|
+
id: response.id,
|
|
1595
|
+
name: response.name,
|
|
1596
|
+
version: response.version,
|
|
1597
|
+
osDescription: response.osDescription,
|
|
1598
|
+
enabled: response.enabled,
|
|
1599
|
+
status: response.status,
|
|
1600
|
+
provisioningState: response.provisioningState,
|
|
1601
|
+
accessPoint: response.accessPoint,
|
|
1602
|
+
createdOn: response.createdOn,
|
|
1603
|
+
maxParallelism: response.maxParallelism,
|
|
1604
|
+
systemCapabilities: response.systemCapabilities,
|
|
1605
|
+
userCapabilities: response.userCapabilities,
|
|
1606
|
+
assignedRequest: response.assignedRequest,
|
|
1607
|
+
lastCompletedRequest: response.lastCompletedRequest
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Update an agent pool
|
|
1612
|
+
* @param poolId The pool ID
|
|
1613
|
+
* @param updates Updated settings
|
|
1614
|
+
* @returns Updated agent pool
|
|
1615
|
+
*/
|
|
1616
|
+
async updateAgentPool(poolId, updates) {
|
|
1617
|
+
if (!this.config.enableAgentPoolUpsert) {
|
|
1618
|
+
throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
|
|
1619
|
+
}
|
|
1620
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`, 'PATCH', updates);
|
|
1621
|
+
return {
|
|
1622
|
+
id: response.id,
|
|
1623
|
+
name: response.name,
|
|
1624
|
+
autoProvision: response.autoProvision,
|
|
1625
|
+
autoUpdate: response.autoUpdate,
|
|
1626
|
+
autoSize: response.autoSize,
|
|
1627
|
+
targetSize: response.targetSize
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Enable a disabled agent
|
|
1632
|
+
* @param poolId The pool ID
|
|
1633
|
+
* @param agentId The agent ID
|
|
1634
|
+
* @returns Updated agent
|
|
1635
|
+
*/
|
|
1636
|
+
async enableAgent(poolId, agentId) {
|
|
1637
|
+
if (!this.config.enableAgentPoolUpsert) {
|
|
1638
|
+
throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
|
|
1639
|
+
}
|
|
1640
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: true });
|
|
1641
|
+
return {
|
|
1642
|
+
id: response.id,
|
|
1643
|
+
name: response.name,
|
|
1644
|
+
enabled: response.enabled,
|
|
1645
|
+
status: response.status
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Disable an agent
|
|
1650
|
+
* @param poolId The pool ID
|
|
1651
|
+
* @param agentId The agent ID
|
|
1652
|
+
* @returns Updated agent
|
|
1653
|
+
*/
|
|
1654
|
+
async disableAgent(poolId, agentId) {
|
|
1655
|
+
if (!this.config.enableAgentPoolDisable) {
|
|
1656
|
+
throw new Error('Agent pool disable operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_DISABLE=true to enable.');
|
|
1657
|
+
}
|
|
1658
|
+
const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: false });
|
|
1659
|
+
return {
|
|
1660
|
+
id: response.id,
|
|
1661
|
+
name: response.name,
|
|
1662
|
+
enabled: response.enabled,
|
|
1663
|
+
status: response.status,
|
|
1664
|
+
message: 'Agent disabled - will complete current job then stop accepting new jobs'
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1668
|
+
// ENVIRONMENT OPERATIONS (DevOps Admin Tools)
|
|
1669
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1670
|
+
/**
|
|
1671
|
+
* List all environments in a project
|
|
1672
|
+
* @param project The project name
|
|
1673
|
+
* @returns List of environments
|
|
1674
|
+
*/
|
|
1675
|
+
async listEnvironments(project) {
|
|
1676
|
+
this.validateProject(project);
|
|
1677
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`);
|
|
1678
|
+
return {
|
|
1679
|
+
project,
|
|
1680
|
+
totalCount: response.value.length,
|
|
1681
|
+
environments: response.value.map((env) => ({
|
|
1682
|
+
id: env.id,
|
|
1683
|
+
name: env.name,
|
|
1684
|
+
description: env.description,
|
|
1685
|
+
createdBy: env.createdBy?.displayName,
|
|
1686
|
+
createdOn: env.createdOn,
|
|
1687
|
+
lastModifiedBy: env.lastModifiedBy?.displayName,
|
|
1688
|
+
lastModifiedOn: env.lastModifiedOn
|
|
1689
|
+
}))
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Get a specific environment
|
|
1694
|
+
* @param project The project name
|
|
1695
|
+
* @param environmentId The environment ID
|
|
1696
|
+
* @returns Environment details
|
|
1697
|
+
*/
|
|
1698
|
+
async getEnvironment(project, environmentId) {
|
|
1699
|
+
this.validateProject(project);
|
|
1700
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?expands=resourceReferences&api-version=${this.apiVersion}`);
|
|
1701
|
+
return {
|
|
1702
|
+
id: response.id,
|
|
1703
|
+
name: response.name,
|
|
1704
|
+
description: response.description,
|
|
1705
|
+
createdBy: response.createdBy?.displayName,
|
|
1706
|
+
createdOn: response.createdOn,
|
|
1707
|
+
lastModifiedBy: response.lastModifiedBy?.displayName,
|
|
1708
|
+
lastModifiedOn: response.lastModifiedOn,
|
|
1709
|
+
project,
|
|
1710
|
+
resources: response.resources || []
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Get deployment history for an environment
|
|
1715
|
+
* @param project The project name
|
|
1716
|
+
* @param environmentId The environment ID
|
|
1717
|
+
* @param top Maximum number of results
|
|
1718
|
+
* @returns Deployment records
|
|
1719
|
+
*/
|
|
1720
|
+
async getEnvironmentDeployments(project, environmentId, top = 10) {
|
|
1721
|
+
this.validateProject(project);
|
|
1722
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}/environmentdeploymentrecords?top=${top}&api-version=${this.apiVersion}`);
|
|
1723
|
+
return {
|
|
1724
|
+
project,
|
|
1725
|
+
environmentId,
|
|
1726
|
+
totalCount: response.value.length,
|
|
1727
|
+
deployments: response.value.map((dep) => ({
|
|
1728
|
+
id: dep.id,
|
|
1729
|
+
environmentId: dep.environmentId,
|
|
1730
|
+
definition: dep.definition ? {
|
|
1731
|
+
id: dep.definition.id,
|
|
1732
|
+
name: dep.definition.name
|
|
1733
|
+
} : null,
|
|
1734
|
+
owner: dep.owner?.displayName,
|
|
1735
|
+
planType: dep.planType,
|
|
1736
|
+
startTime: dep.startTime,
|
|
1737
|
+
finishTime: dep.finishTime,
|
|
1738
|
+
result: dep.result,
|
|
1739
|
+
queueTime: dep.queueTime
|
|
1740
|
+
}))
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Create a new environment
|
|
1745
|
+
* @param project The project name
|
|
1746
|
+
* @param name Environment name
|
|
1747
|
+
* @param description Optional description
|
|
1748
|
+
* @returns Created environment
|
|
1749
|
+
*/
|
|
1750
|
+
async createEnvironment(project, name, description) {
|
|
1751
|
+
this.validateProject(project);
|
|
1752
|
+
if (!this.config.enableEnvironmentUpsert) {
|
|
1753
|
+
throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
|
|
1754
|
+
}
|
|
1755
|
+
const environment = {
|
|
1756
|
+
name,
|
|
1757
|
+
description: description || ''
|
|
1758
|
+
};
|
|
1759
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`, 'POST', environment);
|
|
1760
|
+
return {
|
|
1761
|
+
id: response.id,
|
|
1762
|
+
name: response.name,
|
|
1763
|
+
description: response.description,
|
|
1764
|
+
project
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Update an environment
|
|
1769
|
+
* @param project The project name
|
|
1770
|
+
* @param environmentId The environment ID
|
|
1771
|
+
* @param updates Updated properties
|
|
1772
|
+
* @returns Updated environment
|
|
1773
|
+
*/
|
|
1774
|
+
async updateEnvironment(project, environmentId, updates) {
|
|
1775
|
+
this.validateProject(project);
|
|
1776
|
+
if (!this.config.enableEnvironmentUpsert) {
|
|
1777
|
+
throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
|
|
1778
|
+
}
|
|
1779
|
+
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'PATCH', updates);
|
|
1780
|
+
return {
|
|
1781
|
+
id: response.id,
|
|
1782
|
+
name: response.name,
|
|
1783
|
+
description: response.description,
|
|
1784
|
+
project
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Delete an environment
|
|
1789
|
+
* @param project The project name
|
|
1790
|
+
* @param environmentId The environment ID
|
|
1791
|
+
* @returns Deletion confirmation
|
|
1792
|
+
*/
|
|
1793
|
+
async deleteEnvironment(project, environmentId) {
|
|
1794
|
+
this.validateProject(project);
|
|
1795
|
+
if (!this.config.enableEnvironmentDelete) {
|
|
1796
|
+
throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
|
|
1797
|
+
}
|
|
1798
|
+
await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'DELETE');
|
|
1799
|
+
return {
|
|
1800
|
+
environmentId,
|
|
1801
|
+
project,
|
|
1802
|
+
deleted: true
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Get all checks configured for an environment
|
|
1807
|
+
* @param project The project name
|
|
1808
|
+
* @param environmentId The environment ID
|
|
1809
|
+
* @returns List of check configurations
|
|
1810
|
+
*/
|
|
1811
|
+
async getEnvironmentChecks(project, environmentId) {
|
|
1812
|
+
this.validateProject(project);
|
|
1813
|
+
const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?resourceType=environment&resourceId=${environmentId}&api-version=7.1-preview.1`);
|
|
1814
|
+
return {
|
|
1815
|
+
project,
|
|
1816
|
+
environmentId,
|
|
1817
|
+
totalCount: response.value.length,
|
|
1818
|
+
checks: response.value.map((check) => ({
|
|
1819
|
+
id: check.id,
|
|
1820
|
+
type: check.type?.name || check.type?.id,
|
|
1821
|
+
settings: check.settings,
|
|
1822
|
+
timeout: check.timeout,
|
|
1823
|
+
retryInterval: check.retryInterval,
|
|
1824
|
+
createdBy: check.createdBy?.displayName,
|
|
1825
|
+
createdOn: check.createdOn,
|
|
1826
|
+
modifiedBy: check.modifiedBy?.displayName,
|
|
1827
|
+
modifiedOn: check.modifiedOn,
|
|
1828
|
+
resource: check.resource
|
|
1829
|
+
}))
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Add a check to an environment
|
|
1834
|
+
* @param project The project name
|
|
1835
|
+
* @param environmentId The environment ID
|
|
1836
|
+
* @param checkType The type of check (e.g., "Approval", "ExclusiveLock")
|
|
1837
|
+
* @param configuration Check configuration
|
|
1838
|
+
* @returns Created check
|
|
1839
|
+
*/
|
|
1840
|
+
async addEnvironmentCheck(project, environmentId, checkType, configuration) {
|
|
1841
|
+
this.validateProject(project);
|
|
1842
|
+
if (!this.config.enableEnvironmentUpsert) {
|
|
1843
|
+
throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
|
|
1844
|
+
}
|
|
1845
|
+
const check = {
|
|
1846
|
+
type: { name: checkType },
|
|
1847
|
+
settings: configuration,
|
|
1848
|
+
resource: {
|
|
1849
|
+
type: 'environment',
|
|
1850
|
+
id: String(environmentId)
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?api-version=7.1-preview.1`, 'POST', check);
|
|
1854
|
+
return {
|
|
1855
|
+
id: response.id,
|
|
1856
|
+
type: response.type?.name,
|
|
1857
|
+
environmentId,
|
|
1858
|
+
project
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Update an existing environment check
|
|
1863
|
+
* @param project The project name
|
|
1864
|
+
* @param checkId The check configuration ID
|
|
1865
|
+
* @param updates Updated settings and/or timeout
|
|
1866
|
+
* @returns Updated check
|
|
1867
|
+
*/
|
|
1868
|
+
async updateEnvironmentCheck(project, checkId, updates) {
|
|
1869
|
+
this.validateProject(project);
|
|
1870
|
+
if (!this.config.enableEnvironmentUpsert) {
|
|
1871
|
+
throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
|
|
1872
|
+
}
|
|
1873
|
+
// Get current check configuration first
|
|
1874
|
+
const current = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`);
|
|
1875
|
+
// Merge updates
|
|
1876
|
+
const updated = {
|
|
1877
|
+
...current,
|
|
1878
|
+
settings: updates.settings !== undefined ? updates.settings : current.settings,
|
|
1879
|
+
timeout: updates.timeout !== undefined ? updates.timeout : current.timeout
|
|
1880
|
+
};
|
|
1881
|
+
const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'PATCH', updated);
|
|
1882
|
+
return {
|
|
1883
|
+
id: response.id,
|
|
1884
|
+
type: response.type?.name,
|
|
1885
|
+
settings: response.settings,
|
|
1886
|
+
timeout: response.timeout,
|
|
1887
|
+
project
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Remove a check from an environment
|
|
1892
|
+
* @param project The project name
|
|
1893
|
+
* @param checkId The check configuration ID
|
|
1894
|
+
* @returns Deletion confirmation
|
|
1895
|
+
*/
|
|
1896
|
+
async removeEnvironmentCheck(project, checkId) {
|
|
1897
|
+
this.validateProject(project);
|
|
1898
|
+
if (!this.config.enableEnvironmentDelete) {
|
|
1899
|
+
throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
|
|
1900
|
+
}
|
|
1901
|
+
await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'DELETE');
|
|
1902
|
+
return {
|
|
1903
|
+
checkId,
|
|
1904
|
+
project,
|
|
1905
|
+
deleted: true
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
716
1908
|
}
|
|
717
1909
|
//# sourceMappingURL=AzureDevOpsService.js.map
|