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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- Manually pass auth token for ado server deployments. [#543](https://github.com/sourcebot-dev/sourcebot/pull/543)

## [4.7.2] - 2025-09-22

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions docs/docs/connections/ado-cloud.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"repos": [
"organizationName/projectName/repoName",
"organizationName/projectName/repoName2
Expand All @@ -26,6 +27,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"orgs": [
"organizationName",
"organizationName2
Expand All @@ -37,6 +39,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"projects": [
"organizationName/projectName",
"organizationName/projectName2"
Expand All @@ -48,6 +51,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
// Include all repos in my-org...
"orgs": [
"my-org"
Expand Down Expand Up @@ -91,6 +95,7 @@ Next, provide the access token via the `token` property, either as an environmen
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"token": {
// note: this env var can be named anything. It
// doesn't need to be `ADO_TOKEN`.
Expand Down Expand Up @@ -121,6 +126,7 @@ Next, provide the access token via the `token` property, either as an environmen
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"token": {
"secret": "mysecret"
}
Expand Down
9 changes: 8 additions & 1 deletion docs/docs/connections/ado-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"useTfsPath": true
"deploymentType": "server",
"useTfsPath": true,
"repos": [
"organizationName/projectName/repoName",
"organizationName/projectName/repoName2
Expand All @@ -28,6 +29,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "server",
"repos": [
"organizationName/projectName/repoName",
"organizationName/projectName/repoName2
Expand All @@ -39,6 +41,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "server",
"orgs": [
"collectionName",
"collectionName2"
Expand All @@ -50,6 +53,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "server",
"projects": [
"collectionName/projectName",
"collectionName/projectName2"
Expand All @@ -61,6 +65,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
```json
{
"type": "azuredevops",
"deploymentType": "server",
// Include all repos in my-org...
"orgs": [
"my-org"
Expand Down Expand Up @@ -104,6 +109,7 @@ Next, provide the access token via the `token` property, either as an environmen
```json
{
"type": "azuredevops",
"deploymentType": "server",
"token": {
// note: this env var can be named anything. It
// doesn't need to be `ADO_TOKEN`.
Expand Down Expand Up @@ -134,6 +140,7 @@ Next, provide the access token via the `token` property, either as an environmen
```json
{
"type": "azuredevops",
"deploymentType": "server",
"token": {
"secret": "mysecret"
}
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/schemas/v3/azuredevops.schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -199,7 +198,8 @@
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
}
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/schemas/v3/connection.schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,6 @@
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -1068,7 +1067,8 @@
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
},
Expand Down
4 changes: 2 additions & 2 deletions docs/snippets/schemas/v3/index.schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,6 @@
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -1351,7 +1350,8 @@
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
},
Expand Down
21 changes: 14 additions & 7 deletions packages/backend/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ type onProgressFn = (event: SimpleGitProgressEvent) => void;
export const cloneRepository = async (
{
cloneUrl,
authHeader,
path,
onProgress,
}: {
cloneUrl: string,
authHeader?: string,
path: string,
onProgress?: onProgressFn
}
Expand All @@ -24,13 +26,12 @@ export const cloneRepository = async (
path,
})

await git.clone(
cloneUrl,
path,
[
"--bare",
]
);
const cloneArgs = [
"--bare",
...(authHeader ? ["-c", `http.extraHeader=${authHeader}`] : [])
];

await git.clone(cloneUrl, path, cloneArgs);

await unsetGitConfig(path, ["remote.origin.url"]);
} catch (error: unknown) {
Expand All @@ -50,10 +51,12 @@ export const cloneRepository = async (
export const fetchRepository = async (
{
cloneUrl,
authHeader,
path,
onProgress,
}: {
cloneUrl: string,
authHeader?: string,
path: string,
onProgress?: onProgressFn
}
Expand All @@ -65,6 +68,10 @@ export const fetchRepository = async (
path: path,
})

if (authHeader) {
await git.addConfig("http.extraHeader", authHeader);
}

await git.fetch([
cloneUrl,
"+refs/heads/*:refs/heads/*",
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/repoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export class RepoManager {

const credentials = await getAuthCredentialsForRepo(repo, this.db);
const cloneUrlMaybeWithToken = credentials?.cloneUrlWithToken ?? repo.cloneUrl;
const authHeader = credentials?.authToken ?? undefined;

if (existsSync(repoPath) && !isReadOnly) {
// @NOTE: in #483, we changed the cloning method s.t., we _no longer_
Expand All @@ -188,6 +189,7 @@ export class RepoManager {
logger.info(`Fetching ${repo.displayName}...`);
const { durationMs } = await measure(() => fetchRepository({
cloneUrl: cloneUrlMaybeWithToken,
authHeader,
path: repoPath,
onProgress: ({ method, stage, progress }) => {
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
Expand All @@ -203,6 +205,7 @@ export class RepoManager {

const { durationMs } = await measure(() => cloneRepository({
cloneUrl: cloneUrlMaybeWithToken,
authHeader,
path: repoPath,
onProgress: ({ method, stage, progress }) => {
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ export type RepoWithConnections = Repo & { connections: (RepoToConnection & { co
export type RepoAuthCredentials = {
hostUrl?: string;
token: string;
cloneUrlWithToken: string;
cloneUrlWithToken?: string;
authToken?: string;
}
40 changes: 26 additions & 14 deletions packages/backend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,31 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
const config = connection.config as unknown as AzureDevOpsConnectionConfig;
if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db, logger);
return {
hostUrl: config.url,
token,
cloneUrlWithToken: createGitCloneUrlWithToken(
repo.cloneUrl,
{
// @note: If we don't provide a username, the password will be set as the username. This seems to work
// for ADO cloud but not for ADO server. To fix this, we set a placeholder username to ensure the password
// is set correctly
username: 'user',
password: token
}
),

// For ADO server, multiple auth schemes may be supported. If the ADO deployment supports NTLM, the git clone will default
// to this over basic auth. As a result, we cannot embed the token in the clone URL and must force basic auth by passing in the token
// appropriately in the header. To do this, we set the authToken field here
if (config.deploymentType === 'server') {
return {
hostUrl: config.url,
token,
authToken: "Authorization: Basic " + Buffer.from(`:${token}`).toString('base64')
}
} else {
return {
hostUrl: config.url,
token,
cloneUrlWithToken: createGitCloneUrlWithToken(
repo.cloneUrl,
{
// @note: If we don't provide a username, the password will be set as the username. This seems to work
// for ADO cloud but not for ADO server. To fix this, we set a placeholder username to ensure the password
// is set correctly
username: 'user',
password: token
}
),
}
}
}
}
Expand All @@ -228,4 +240,4 @@ const createGitCloneUrlWithToken = (cloneUrl: string, credentials: { username?:
url.password = credentials.password;
}
return url.toString();
}
}
4 changes: 2 additions & 2 deletions packages/schemas/src/v3/azuredevops.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ const schema = {
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -198,7 +197,8 @@ const schema = {
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
} as const;
Expand Down
2 changes: 1 addition & 1 deletion packages/schemas/src/v3/azuredevops.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface AzureDevOpsConnectionConfig {
/**
* The type of Azure DevOps deployment
*/
deploymentType?: "cloud" | "server";
deploymentType: "cloud" | "server";
/**
* Use legacy TFS path format (/tfs) in API URLs. Required for older TFS installations (TFS 2018 and earlier). When true, API URLs will include /tfs in the path (e.g., https://server/tfs/collection/_apis/...).
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/schemas/src/v3/connection.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,6 @@ const schema = {
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -1067,7 +1066,8 @@ const schema = {
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
},
Expand Down
2 changes: 1 addition & 1 deletion packages/schemas/src/v3/connection.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export interface AzureDevOpsConnectionConfig {
/**
* The type of Azure DevOps deployment
*/
deploymentType?: "cloud" | "server";
deploymentType: "cloud" | "server";
/**
* Use legacy TFS path format (/tfs) in API URLs. Required for older TFS installations (TFS 2018 and earlier). When true, API URLs will include /tfs in the path (e.g., https://server/tfs/collection/_apis/...).
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/schemas/src/v3/index.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1213,7 +1213,6 @@ const schema = {
"cloud",
"server"
],
"default": "cloud",
"description": "The type of Azure DevOps deployment"
},
"useTfsPath": {
Expand Down Expand Up @@ -1350,7 +1349,8 @@ const schema = {
},
"required": [
"type",
"token"
"token",
"deploymentType"
],
"additionalProperties": false
},
Expand Down
2 changes: 1 addition & 1 deletion packages/schemas/src/v3/index.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ export interface AzureDevOpsConnectionConfig {
/**
* The type of Azure DevOps deployment
*/
deploymentType?: "cloud" | "server";
deploymentType: "cloud" | "server";
/**
* Use legacy TFS path format (/tfs) in API URLs. Required for older TFS installations (TFS 2018 and earlier). When true, API URLs will include /tfs in the path (e.g., https://server/tfs/collection/_apis/...).
*/
Expand Down
Loading