Skip to content

Commit 3e72553

Browse files
authored
Merge pull request #6 from link-foundation/issue-5-898734966645
fix: Improve Docker/server support and fix glab --jq compatibility
2 parents a572a7d + 7041ee5 commit 3e72553

File tree

4 files changed

+209
-14
lines changed

4 files changed

+209
-14
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'glab-setup-git-identity': patch
3+
---
4+
5+
Fix Docker/server support and glab --jq compatibility
6+
7+
- Fix README.md manual commands to use pipe to jq instead of --jq flag
8+
- Add "Authentication in Docker/Server Environments" section to README
9+
- Enhance CLI with helpful headless auth instructions
10+
- Fix src/index.js to parse JSON in JavaScript for better glab version compatibility
11+
- Optimize getGitLabUserInfo to use a single API call

README.md

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,24 @@ A tool to setup git identity based on current GitLab user.
1313
Instead of manually running:
1414

1515
```bash
16-
glab auth login --hostname gitlab.com --git-protocol https
16+
# Authenticate with GitLab (interactive mode - no extra flags)
17+
glab auth login
18+
19+
# Or for non-interactive mode with a token:
20+
# glab auth login --hostname gitlab.com --git-protocol https --token YOUR_TOKEN
21+
1722
glab auth git-credential # For HTTPS authentication helper
1823

19-
USERNAME=$(glab api user --jq '.username')
20-
EMAIL=$(glab api user --jq '.email')
24+
# Get user info (requires jq to be installed)
25+
USERNAME=$(glab api user | jq -r '.username')
26+
EMAIL=$(glab api user | jq -r '.email')
2127

2228
git config --global user.name "$USERNAME"
2329
git config --global user.email "$EMAIL"
2430
```
2531

32+
> **Note for manual commands**: The commands above require `jq` to be installed (`apt install jq` or `brew install jq`). The `glab api` command does not have a built-in `--jq` flag - you must pipe output to the external `jq` tool.
33+
2634
You can simply run:
2735

2836
```bash
@@ -163,7 +171,61 @@ The tool runs `glab auth login` automatically, followed by configuring git to us
163171
If automatic authentication fails, you can run the commands manually:
164172

165173
```bash
166-
glab auth login --hostname gitlab.com --git-protocol https
174+
glab auth login
175+
```
176+
177+
### Authentication in Docker/Server Environments (Headless)
178+
179+
When running in Docker containers or on remote servers without a browser, `glab auth login` will display a URL to open but fail to launch a browser:
180+
181+
```
182+
Failed opening a browser at https://gitlab.com/oauth/authorize?...
183+
Encountered error: exec: "xdg-open": executable file not found in $PATH
184+
Try entering the URL in your browser manually.
185+
```
186+
187+
**To complete authentication in headless environments:**
188+
189+
1. **Copy the authorization URL** displayed by glab and open it in your local browser
190+
2. Complete the GitLab OAuth flow in your browser
191+
3. You'll be redirected to a URL like: `http://localhost:7171/auth/redirect?code=...&state=...`
192+
4. **Use `curl` to send the redirect URL back to glab**:
193+
194+
```bash
195+
# Method 1: Using screen (recommended for Docker)
196+
# Terminal 1: Start glab auth in screen
197+
screen -S glab-auth
198+
glab auth login
199+
# Press Ctrl+A, D to detach from screen
200+
201+
# Terminal 2: After completing OAuth in browser, send the redirect URL
202+
curl -L "http://localhost:7171/auth/redirect?code=YOUR_CODE&state=YOUR_STATE"
203+
204+
# Return to screen to see auth completion
205+
screen -r glab-auth
206+
```
207+
208+
```bash
209+
# Method 2: Using SSH port forwarding (for remote servers)
210+
# On your local machine, forward the callback port:
211+
ssh -L 7171:localhost:7171 user@remote-server
212+
213+
# Then on the server, run glab auth login
214+
# The OAuth redirect will go through the SSH tunnel to your local machine
215+
```
216+
217+
**Alternatively, use token-based authentication** (recommended for CI/CD and automation):
218+
219+
```bash
220+
# Generate a Personal Access Token at:
221+
# https://gitlab.com/-/profile/personal_access_tokens
222+
# Required scopes: api, write_repository
223+
224+
# Then authenticate non-interactively:
225+
glab auth login --hostname gitlab.com --token YOUR_TOKEN
226+
227+
# Or with this tool:
228+
glab-setup-git-identity --token YOUR_TOKEN
167229
```
168230

169231
### Successful Run

src/cli.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,40 @@ async function handleAlreadyAuthenticated() {
260260
return true;
261261
}
262262

263+
/**
264+
* Print headless authentication instructions
265+
*/
266+
function printHeadlessAuthInstructions() {
267+
console.log('');
268+
console.log('=== Authentication in Docker/Server Environments ===');
269+
console.log('');
270+
console.log(
271+
'If you see a browser URL but cannot open it, follow these steps:'
272+
);
273+
console.log('');
274+
console.log('1. Copy the authorization URL displayed above');
275+
console.log('2. Open it in your local browser and complete the OAuth flow');
276+
console.log(
277+
'3. You will be redirected to: http://localhost:7171/auth/redirect?code=...&state=...'
278+
);
279+
console.log('4. Use curl to send that redirect URL back to glab:');
280+
console.log('');
281+
console.log(
282+
' curl -L "http://localhost:7171/auth/redirect?code=YOUR_CODE&state=YOUR_STATE"'
283+
);
284+
console.log('');
285+
console.log('Alternatively, use token-based authentication:');
286+
console.log('');
287+
console.log('1. Generate a Personal Access Token at:');
288+
console.log(` https://${config.hostname}/-/profile/personal_access_tokens`);
289+
console.log(' Required scopes: api, write_repository');
290+
console.log('');
291+
console.log('2. Re-run with your token:');
292+
console.log(` glab-setup-git-identity --token YOUR_TOKEN`);
293+
console.log('');
294+
console.log('================================================');
295+
}
296+
263297
/**
264298
* Handle case when not authenticated
265299
* @returns {Promise<boolean>} True if login succeeded
@@ -268,14 +302,27 @@ async function handleNotAuthenticated() {
268302
console.log('GitLab CLI is not authenticated. Starting authentication...');
269303
console.log('');
270304

305+
// Print headless instructions before attempting auth
306+
// This helps users in Docker/server environments know what to do
307+
printHeadlessAuthInstructions();
308+
console.log('');
309+
271310
const loginSuccess = await runGlabAuthLogin(getAuthOptions());
272311

273312
if (!loginSuccess) {
274313
console.log('');
275-
console.log('Authentication failed. Please try running manually:');
314+
console.log('Authentication failed. Please try one of the following:');
315+
console.log('');
316+
console.log('Option 1: Interactive login');
317+
console.log(' glab auth login');
318+
console.log('');
319+
console.log('Option 2: Token-based login (recommended for headless)');
276320
console.log(
277-
` glab auth login --hostname ${config.hostname} --git-protocol ${config.gitProtocol}`
321+
` glab auth login --hostname ${config.hostname} --token YOUR_TOKEN`
278322
);
323+
console.log('');
324+
console.log('Option 3: Use this tool with a token');
325+
console.log(` glab-setup-git-identity --token YOUR_TOKEN`);
279326
return false;
280327
}
281328

src/index.js

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ export async function isGlabAuthenticated(options = {}) {
330330
/**
331331
* Get GitLab username from authenticated user
332332
*
333+
* Note: This function parses the JSON response in JavaScript rather than using
334+
* glab's --jq flag, as the --jq flag is not available in all glab versions.
335+
*
333336
* @param {Object} options - Options
334337
* @param {string} options.hostname - GitLab hostname (optional)
335338
* @param {boolean} options.verbose - Enable verbose logging
@@ -342,7 +345,7 @@ export async function getGitLabUsername(options = {}) {
342345

343346
log.debug('Getting GitLab username...');
344347

345-
const args = ['api', 'user', '--jq', '.username'];
348+
const args = ['api', 'user'];
346349
if (hostname) {
347350
args.push('--hostname', hostname);
348351
}
@@ -353,7 +356,23 @@ export async function getGitLabUsername(options = {}) {
353356
throw new Error(`Failed to get GitLab username: ${result.stderr}`);
354357
}
355358

356-
const username = result.stdout.trim();
359+
// Parse JSON response in JavaScript (glab's --jq flag is not available in all versions)
360+
let userData;
361+
try {
362+
userData = JSON.parse(result.stdout.trim());
363+
} catch (parseError) {
364+
throw new Error(
365+
`Failed to parse GitLab user data: ${parseError.message}. Raw output: ${result.stdout}`
366+
);
367+
}
368+
369+
const username = userData.username;
370+
if (!username) {
371+
throw new Error(
372+
'No username found in GitLab user data. Please ensure your GitLab account has a username.'
373+
);
374+
}
375+
357376
log.debug(`GitLab username: ${username}`);
358377

359378
return username;
@@ -362,6 +381,9 @@ export async function getGitLabUsername(options = {}) {
362381
/**
363382
* Get primary email from GitLab user
364383
*
384+
* Note: This function parses the JSON response in JavaScript rather than using
385+
* glab's --jq flag, as the --jq flag is not available in all glab versions.
386+
*
365387
* @param {Object} options - Options
366388
* @param {string} options.hostname - GitLab hostname (optional)
367389
* @param {boolean} options.verbose - Enable verbose logging
@@ -374,7 +396,7 @@ export async function getGitLabEmail(options = {}) {
374396

375397
log.debug('Getting GitLab primary email...');
376398

377-
const args = ['api', 'user', '--jq', '.email'];
399+
const args = ['api', 'user'];
378400
if (hostname) {
379401
args.push('--hostname', hostname);
380402
}
@@ -385,7 +407,17 @@ export async function getGitLabEmail(options = {}) {
385407
throw new Error(`Failed to get GitLab email: ${result.stderr}`);
386408
}
387409

388-
const email = result.stdout.trim();
410+
// Parse JSON response in JavaScript (glab's --jq flag is not available in all versions)
411+
let userData;
412+
try {
413+
userData = JSON.parse(result.stdout.trim());
414+
} catch (parseError) {
415+
throw new Error(
416+
`Failed to parse GitLab user data: ${parseError.message}. Raw output: ${result.stdout}`
417+
);
418+
}
419+
420+
const email = userData.email;
389421

390422
if (!email) {
391423
throw new Error(
@@ -401,17 +433,60 @@ export async function getGitLabEmail(options = {}) {
401433
/**
402434
* Get GitLab user information (username and primary email)
403435
*
436+
* Note: This function makes a single API call and parses both username and email
437+
* from the response, which is more efficient than calling getGitLabUsername and
438+
* getGitLabEmail separately.
439+
*
404440
* @param {Object} options - Options
405441
* @param {string} options.hostname - GitLab hostname (optional)
406442
* @param {boolean} options.verbose - Enable verbose logging
407443
* @param {Object} options.logger - Custom logger
408444
* @returns {Promise<{username: string, email: string}>} User information
409445
*/
410446
export async function getGitLabUserInfo(options = {}) {
411-
const [username, email] = await Promise.all([
412-
getGitLabUsername(options),
413-
getGitLabEmail(options),
414-
]);
447+
const { hostname, verbose = false, logger = console } = options;
448+
const log = createDefaultLogger({ verbose, logger });
449+
450+
log.debug('Getting GitLab user information...');
451+
452+
const args = ['api', 'user'];
453+
if (hostname) {
454+
args.push('--hostname', hostname);
455+
}
456+
457+
const result = await $`glab ${args}`.run({ capture: true });
458+
459+
if (result.code !== 0) {
460+
throw new Error(`Failed to get GitLab user info: ${result.stderr}`);
461+
}
462+
463+
// Parse JSON response in JavaScript (glab's --jq flag is not available in all versions)
464+
let userData;
465+
try {
466+
userData = JSON.parse(result.stdout.trim());
467+
} catch (parseError) {
468+
throw new Error(
469+
`Failed to parse GitLab user data: ${parseError.message}. Raw output: ${result.stdout}`
470+
);
471+
}
472+
473+
const username = userData.username;
474+
const email = userData.email;
475+
476+
if (!username) {
477+
throw new Error(
478+
'No username found in GitLab user data. Please ensure your GitLab account has a username.'
479+
);
480+
}
481+
482+
if (!email) {
483+
throw new Error(
484+
'No email found on GitLab account. Please set a primary email in your GitLab settings.'
485+
);
486+
}
487+
488+
log.debug(`GitLab username: ${username}`);
489+
log.debug(`GitLab primary email: ${email}`);
415490

416491
return { username, email };
417492
}

0 commit comments

Comments
 (0)