Skip to content

[#1] Implemented branch deployment feature.#8

Merged
richardgaunt merged 13 commits intomainfrom
feature/implement-deploy-branch
May 7, 2025
Merged

[#1] Implemented branch deployment feature.#8
richardgaunt merged 13 commits intomainfrom
feature/implement-deploy-branch

Conversation

@richardgaunt
Copy link
Owner

@richardgaunt richardgaunt commented May 7, 2025

Checklist before requesting a review

  • I have formatted the subject to include ticket number as [#1] Verb in past tense with dot at the end.
  • I have added a link to the issue tracker: Add the deploy branch capability #1
  • I have provided information in Changed section about WHY something was done if this was not a normal implementation
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have run new and existing relevant tests locally with my changes, and they passed
  • I have provided screenshots, where applicable

Changed

  1. Added functions to list branches from Git repository
  2. Implemented branch deployment via Lagoon CLI
  3. Added interactive menu option for branch deployment
  4. Added autocomplete branch selection with fallback to regular list
  5. Updated documentation to explain the new branch deployment feature

This implementation allows users to deploy any branch from a project's Git repository to Lagoon. It uses a secure approach to clone and list branches, with careful validation to prevent command injection.

Summary by CodeRabbit

  • New Features

    • Added interactive branch deployment capability with branch autocomplete, prioritization of main branches, and SSH key configuration.
    • Provided real-time deployment feedback and status updates, with asynchronous deployment and monitoring via the UI.
  • Documentation

    • Updated README with comprehensive branch deployment instructions and feature descriptions.
  • Chores

    • Added dependency to support autocomplete prompts in the CLI.
    • Enhanced internal documentation for logging and API functions.

- Added functions to list branches from Git repository
- Implemented branch deployment via Lagoon CLI
- Added interactive menu option for branch deployment
- Added autocomplete branch selection
- Updated documentation
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 7, 2025

Warning

Rate limit exceeded

@richardgaunt has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between ae456ee and 5692c6a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • CLAUDE.md (1 hunks)
  • package.json (2 hunks)
  • src/interactive.mjs (7 hunks)
  • src/lagoon-api.mjs (7 hunks)
  • src/lagoon-ssh-key-configurator.mjs (1 hunks)
  • src/logger.mjs (3 hunks)

"""

Walkthrough

A new interactive CLI feature for deploying Git branches to Lagoon environments was added. Supporting functions for fetching branches and deploying them were implemented, with robust input validation and error handling. Documentation was updated to describe the new feature, and dependencies and imports were adjusted to support branch autocomplete in prompts.

Changes

File(s) Change Summary
README.md Updated documentation to describe the new branch deployment feature, including usage, behavior, and security notes.
package.json Added inquirer-autocomplete-prompt dependency for enhanced CLI branch selection.
index.mjs Updated import statement for startInteractiveMode to explicitly use .mjs extension.
src/interactive.mjs Added interactive "Deploy Branch" flow, integrating autocomplete branch selection, confirmation, deployment initiation, and feedback. Handles errors and fallback for prompts.
src/lagoon-api.mjs Added getGitBranches and deployBranch functions for Git branch retrieval and Lagoon deployment, with input validation and error handling. Improved JSDoc for existing functions.
src/lagoon-ssh-key-configurator.mjs Updated import of execLagoonCommand to explicitly use .mjs extension.
src/logger.mjs Improved JSDoc comments for logAction and logError for clarity. No logic changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant LagoonAPI
    participant GitRepo

    User->>CLI: Select "Deploy Branch" in interactive mode
    CLI->>LagoonAPI: Fetch project details (including Git URL)
    CLI->>GitRepo: Fetch list of branches
    GitRepo-->>CLI: Return branch list
    CLI->>User: Prompt to select branch (autocomplete)
    User->>CLI: Select branch
    CLI->>User: Confirm deployment
    User->>CLI: Confirm
    CLI->>LagoonAPI: Initiate deployment for selected branch
    LagoonAPI-->>CLI: Return deployment initiation status
    CLI->>User: Show deployment status and instructions
Loading

Poem

🐇
A bunny hops, a branch deploys,
With prompts and lists, it now enjoys.
Autocomplete to pick your way,
Secure and swift, the code will play.
Lagoon awaits, the build begins,
In docs and code, the rabbit grins!
🌱🚀
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/lagoon-api.js (2)

194-199: Use fs.mkdtemp() for safer, race-free temp-dir creation
path.join(os.tmpdir(), Date.now()) can still collide under heavy concurrency and inherits OS permissions from the parent directory. Node already ships a purpose-built helper (fs.mkdtemp) that guarantees a unique, securely-permissioned directory.

-async function createTempDir() {
-  const tempDir = path.join(os.tmpdir(), `lagoon-cli-wrapper-${Date.now()}`);
-  await fs.mkdir(tempDir, { recursive: true });
-  return tempDir;
-}
+async function createTempDir() {
+  const prefix = path.join(os.tmpdir(), 'lagoon-cli-wrapper-');
+  return await fs.mkdtemp(prefix);
+}

232-265: Guarantee cleanup with finally instead of nested try/catch
The nested structure works but is verbose and easy to miss additional early-returns in the future. A finally block expresses intent and ensures the temp dir is deleted exactly once.

-    const tempDir = await createTempDir();
-    
-    try {
-      await cloneRepository(gitUrl, tempDir);
-      const branches = await listBranches(tempDir);
-      await fs.rm(tempDir, { recursive: true, force: true });
-      return branches;
-    } catch (error) {
-      try {
-        await fs.rm(tempDir, { recursive: true, force: true });
-      } catch (cleanupError) {
-        console.error(`Failed to clean up temporary directory: ${cleanupError.message}`);
-      }
-      throw error;
-    }
+    const tempDir = await createTempDir();
+    try {
+      await cloneRepository(gitUrl, tempDir);
+      return await listBranches(tempDir);
+    } finally {
+      await fs.rm(tempDir, { recursive: true, force: true });
+    }

This reduces complexity and guarantees removal even if future return statements are added.

src/interactive.js (1)

493-507: Branch sorting logic could be simplified.

While the sorting logic works as intended (prioritizing main/master/develop branches), it could be simplified using a more concise approach.

-    const sortedBranches = branches.sort((a, b) => {
-      const priority = ['main', 'master', 'develop'];
-      const aIndex = priority.indexOf(a);
-      const bIndex = priority.indexOf(b);
-      
-      if (aIndex !== -1 && bIndex !== -1) {
-        return aIndex - bIndex;
-      } else if (aIndex !== -1) {
-        return -1;
-      } else if (bIndex !== -1) {
-        return 1;
-      } else {
-        return a.localeCompare(b);
-      }
-    });
+    const priority = ['main', 'master', 'develop'];
+    const sortedBranches = branches.sort((a, b) => {
+      const aIndex = priority.indexOf(a);
+      const bIndex = priority.indexOf(b);
+      
+      // If both are priority branches, sort by priority order
+      // If only one is priority, it comes first
+      // Otherwise, sort alphabetically
+      return (aIndex !== -1 && bIndex !== -1) ? aIndex - bIndex :
+             aIndex !== -1 ? -1 :
+             bIndex !== -1 ? 1 :
+             a.localeCompare(b);
+    });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c20bab6 and 5fbcb05.

📒 Files selected for processing (4)
  • README.md (3 hunks)
  • package.json (1 hunks)
  • src/interactive.js (4 hunks)
  • src/lagoon-api.js (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/lagoon-api.js (2)
src/logger.js (1)
  • logError (45-52)
src/interactive.js (1)
  • branches (477-477)
🔇 Additional comments (9)
package.json (1)

27-29: Dependency added – run npm audit & pin if vulnerable
inquirer-autocomplete-prompt looks fine, but please run npm audit (or pnpm audit) to ensure no transitive vulnerabilities are introduced, and consider pinning to an exact version if reproducibility is critical.

README.md (1)

98-108: Great documentation update 🎉
The new “Branch Deployment” section is clear, covers validation and asynchronous behaviour, and aligns well with the implemented API.

src/interactive.js (7)

79-81: Properly integrated with the existing workflow.

The new case for deployBranch is appropriately integrated with the existing switch statement and passes the correct parameters to the deployBranchFlow function.


183-183: Menu option correctly added.

The Deploy Branch menu option is properly added to the main menu choices.


458-470: Good validation of Git URL requirement.

The function correctly validates that the project has a Git URL configured before proceeding, providing clear feedback to the user when missing.


509-541: Good implementation of branch selection with autocomplete fallback.

The implementation provides a smooth user experience with autocomplete for branch selection, with an appropriate fallback to a standard list when needed. The filtering for the autocomplete works well for partial matches.


543-557: User confirmation step enhances safety.

Adding a confirmation step before deployment is a good security practice, especially with the default set to false to prevent accidental deployments.


558-570: Clear deployment feedback to users.

The code provides appropriate feedback during and after deployment, including useful information about the asynchronous nature of the deployment process.


571-581: Good error handling and user continuation flow.

The function properly handles errors during branch fetching and ensures the user can continue with the interactive flow regardless of the outcome.

Comment on lines +267 to +292
// Deploy a branch to Lagoon
export async function deployBranch(instance, project, branch) {
try {
// Validate branch name to prevent command injection
if (!/^[a-zA-Z0-9_.-]+$/.test(branch)) {
throw new Error('Invalid branch name. Branch names must contain only alphanumeric characters, underscores, hyphens, and periods.');
}

const command = `lagoon -l ${instance} -p ${project} deploy branch --branch ${branch} --output-json`;
const { stdout } = await execLagoonCommand(command, `Deploy Branch ${branch} to ${project}`);

// Parse the JSON response
const response = JSON.parse(stdout);

if (response.result === 'success') {
return {
success: true,
message: `Branch ${branch} is being deployed to ${project}`
};
} else {
throw new Error(`Failed to deploy branch ${branch}: ${JSON.stringify(response)}`);
}
} catch (error) {
throw new Error(`Failed to deploy branch ${branch}: ${error.message}`);
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Branch-name regex too restrictive – blocks common names with /
Valid git branches routinely include slashes (feature/xyz). Current regex ^[a-zA-Z0-9_.-]+$ will reject them, limiting usability without adding meaningful protection (the value is still injected into a shell string).

Recommend:

  1. Allow forward slashes: ^[A-Za-z0-9_.\\/\\-]+$
  2. Escape all CLI arguments instead of whitelisting – use spawn/execFile so each arg is passed verbatim, eliminating injection vectors from instance, project, and branch.
-    if (!/^[a-zA-Z0-9_.-]+$/.test(branch)) {
+    if (!/^[A-Za-z0-9_.\\/\\-]+$/.test(branch)) {

Long-term: switch the entire Lagoon call to spawn('lagoon', ['-l', instance, '-p', project, 'deploy', 'branch', '--branch', branch, '--output-json']).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Deploy a branch to Lagoon
export async function deployBranch(instance, project, branch) {
try {
// Validate branch name to prevent command injection
if (!/^[a-zA-Z0-9_.-]+$/.test(branch)) {
throw new Error('Invalid branch name. Branch names must contain only alphanumeric characters, underscores, hyphens, and periods.');
}
const command = `lagoon -l ${instance} -p ${project} deploy branch --branch ${branch} --output-json`;
const { stdout } = await execLagoonCommand(command, `Deploy Branch ${branch} to ${project}`);
// Parse the JSON response
const response = JSON.parse(stdout);
if (response.result === 'success') {
return {
success: true,
message: `Branch ${branch} is being deployed to ${project}`
};
} else {
throw new Error(`Failed to deploy branch ${branch}: ${JSON.stringify(response)}`);
}
} catch (error) {
throw new Error(`Failed to deploy branch ${branch}: ${error.message}`);
}
}
// Deploy a branch to Lagoon
export async function deployBranch(instance, project, branch) {
try {
// Validate branch name to prevent command injection
- if (!/^[a-zA-Z0-9_.-]+$/.test(branch)) {
+ if (!/^[A-Za-z0-9_.\/\-]+$/.test(branch)) {
throw new Error('Invalid branch name. Branch names must contain only alphanumeric characters, underscores, hyphens, and periods.');
}
const command = `lagoon -l ${instance} -p ${project} deploy branch --branch ${branch} --output-json`;
const { stdout } = await execLagoonCommand(command, `Deploy Branch ${branch} to ${project}`);
// Parse the JSON response
const response = JSON.parse(stdout);
if (response.result === 'success') {
return {
success: true,
message: `Branch ${branch} is being deployed to ${project}`
};
} else {
throw new Error(`Failed to deploy branch ${branch}: ${JSON.stringify(response)}`);
}
} catch (error) {
throw new Error(`Failed to deploy branch ${branch}: ${error.message}`);
}
}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create an issue for adding the long term fix - this will be need to implemented everywhere we call the lagoon command

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@richardgaunt I've created a GitHub issue for implementing the long-term security fix using spawn/execFile for all Lagoon CLI commands. The issue includes:

  • Description of the current security vulnerability
  • Proposed solution with code examples
  • Implementation plan
  • Benefits of the new approach
  • References to this PR discussion

I've assigned the issue to you as requested. This fix will indeed need to be implemented everywhere the Lagoon command is called to fully eliminate the command injection vulnerability.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the long-term fix

ISSUE_TITLE="Security Enhancement: Switch to spawn/execFile for Lagoon CLI command execution"
ISSUE_BODY="## Background

As identified in [PR #8](https://github.com/richardgaunt/lagoon-cli-wrapper/pull/8#discussion_r2077246796), the current approach of using string interpolation when executing Lagoon CLI commands creates potential command injection vulnerabilities.

## Problem

Currently, command arguments (like instance names, project names, branch names, etc.) are interpolated directly into shell command strings:

\`\`\`javascript
const command = \`lagoon -l \${instance} -p \${project} deploy branch --branch \${branch} --output-json\`;
const { stdout } = await execLagoonCommand(command, \`Deploy Branch \${branch} to \${project}\`);
\`\`\`

While we've implemented regex validation in some places, this approach is still vulnerable to command injection if an argument contains shell metacharacters.

## Proposed Solution

Replace string interpolation with Node.js \`spawn\` or \`execFile\` functions to pass arguments securely:

\`\`\`javascript
const { stdout } = await util.promisify(child_process.execFile)('lagoon', [
  '-l', instance,
  '-p', project,
  'deploy', 'branch',
  '--branch', branch,
  '--output-json'
]);
\`\`\`

Or using spawn with proper output handling:

\`\`\`javascript
const lagoonProcess = child_process.spawn('lagoon', [
  '-l', instance,
  '-p', project,
  'deploy', 'branch',
  '--branch', branch,
  '--output-json'
]);

// Handle output streams appropriately
\`\`\`

## Implementation Plan

1. Create a new secure wrapper function for Lagoon CLI calls
2. Refactor all instances of \`execLagoonCommand\` to use the new wrapper
3. Update the logging mechanism to work with the new approach
4. Add tests to verify the security of the implementation

## Benefits

- Eliminates command injection vulnerabilities
- More maintainable and secure code
- No need for complex regex validation of input parameters
- Explicit separation of command and arguments improves readability

## Related

- PR #8: Implemented branch deployment feature
- Discussion: https://github.com/richardgaunt/lagoon-cli-wrapper/pull/8#discussion_r2077246796

cc @richardgaunt"

# Create the issue using GitHub CLI
gh issue create --repo richardgaunt/lagoon-cli-wrapper --title "$ISSUE_TITLE" --body "$ISSUE_BODY" --assignee richardgaunt

Length of output: 4613


@richardgaunt I've successfully created issue #11 titled "Security Enhancement: Switch to spawn/execFile for Lagoon CLI command execution" in your repository.

The issue includes detailed information about:

  • The current security vulnerability
  • Code examples showing the problem
  • The proposed solution using spawn/execFile
  • A clear implementation plan
  • Benefits of making the security enhancement

You can view the issue here: #11


✏️ Learnings added
Learnt from: richardgaunt
PR: richardgaunt/lagoon-cli-wrapper#8
File: src/lagoon-api.js:0-0
Timestamp: 2025-05-07T10:29:17.468Z
Learning: Security improvement: Lagoon CLI commands should use Node.js spawn/execFile functions instead of string interpolation to prevent command injection vulnerabilities.

Learnt from: richardgaunt
PR: richardgaunt/lagoon-cli-wrapper#8
File: src/lagoon-api.js:0-0
Timestamp: 2025-05-07T10:29:17.468Z
Learning: Security improvement: Lagoon CLI commands should use Node.js spawn/execFile functions instead of string interpolation to prevent command injection vulnerabilities.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/interactive.mjs (1)

20-26: autocomplete fallback is fine, but you forgot to record availability

When the dynamic import fails you log a warning, yet later code still assumes the prompt type exists (see next comment).
Consider setting a boolean flag, e.g. const hasAutocompletePrompt = true/false, to drive the prompt selection logic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fbcb05 and 483d32f.

📒 Files selected for processing (5)
  • index.mjs (1 hunks)
  • src/interactive.mjs (5 hunks)
  • src/lagoon-api.mjs (4 hunks)
  • src/lagoon-ssh-key-configurator.mjs (1 hunks)
  • src/logger.mjs (2 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/lagoon-ssh-key-configurator.mjs
  • index.mjs
  • src/logger.mjs
🧰 Additional context used
🪛 ESLint
src/interactive.mjs

[error] 16-16: require file extension '.mjs'.

(n/file-extension-in-import)


[error] 17-17: require file extension '.mjs'.

(n/file-extension-in-import)


[error] 18-18: require file extension '.mjs'.

(n/file-extension-in-import)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/lagoon-api.mjs (3)

210-258: ⚠️ Potential issue

Command injection vulnerability in getGitBranches.

The function directly interpolates user input into a shell command without proper escaping, creating a security risk.

Replace the current implementation with a safer approach that uses execFile to avoid shell parsing:

-const command = `git ls-remote --heads ${gitUrl}`;
-console.log(chalk.blue(`Executing: ${chalk.bold(command)}`));
-
-try {
-  const { stdout, stderr } = await execAsync(command);
+console.log(chalk.blue(`Executing: ${chalk.bold(`git ls-remote --heads ${gitUrl}`)}`));
+
+try {
+  // Use execFile (promisified) to avoid shell parsing and lift the buffer cap
+  const execFileAsync = promisify(require('child_process').execFile);
+  const { stdout, stderr } = await execFileAsync('git', ['ls-remote', '--heads', gitUrl], {
+    maxBuffer: 1024 * 1024 * 10 // 10 MB, adjust as needed
+  });

This approach prevents command injection by separating the command from its arguments and increases the buffer size to handle repositories with many branches.


262-267: ⚠️ Potential issue

Over-restrictive branch name validation.

The current regex for branch validation is too restrictive and will reject valid Git branch names (like 'feature/xyz' or branches with '@' characters).

Modify the branch name validation to allow common branch naming patterns while still preventing command injection:

-if (!/^[a-zA-Z0-9_.-]+$/.test(branch)) {
+// Allow slashes, at-signs, and other characters common in Git branch names
+if (!/^[\w.@/-]+$/.test(branch)) {
  throw new Error('Invalid branch name. Branch names must contain only alphanumeric characters, underscores, hyphens, and periods.');
}

268-268: 🛠️ Refactor suggestion

Missing quotes around branch name in command.

Branch names with spaces or special characters might cause shell interpretation issues.

Quote the branch parameter to handle branches with spaces or special characters:

-const command = `lagoon -l ${instance} -p ${project} deploy branch --branch ${branch} --output-json`;
+const command = `lagoon -l ${instance} -p ${project} deploy branch --branch "${branch}" --output-json`;
🧹 Nitpick comments (1)
src/lagoon-api.mjs (1)

207-258: Consider adding error handling for network timeouts.

The current implementation handles authentication errors and "not found" errors, but network timeouts or connection issues might need specific handling too.

Add a timeout option and handle network-related errors:

try {
  // Use execFile (promisified) to avoid shell parsing
  const execFileAsync = promisify(require('child_process').execFile);
  const { stdout, stderr } = await execFileAsync('git', ['ls-remote', '--heads', gitUrl], {
    maxBuffer: 1024 * 1024 * 10, // 10 MB, adjust as needed
+   timeout: 30000 // 30 seconds timeout
  });
  
  // Rest of the function...
} catch (error) {
  // If the Git URL is using SSH, offer a hint about authentication
  if (gitUrl.startsWith('git@') && error.message.includes('Permission denied')) {
    throw new Error(`Authentication failed for ${gitUrl}. Please check your SSH key configuration.`);
  } else if (error.message.includes('not found')) {
    throw new Error(`Repository not found: ${gitUrl}. Please check if the URL is correct.`);
+  } else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNREFUSED') {
+    throw new Error(`Network error when accessing ${gitUrl}. Please check your internet connection and that the repository is accessible.`);
  } else {
    throw error;
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 483d32f and db7a385.

📒 Files selected for processing (2)
  • src/interactive.mjs (5 hunks)
  • src/lagoon-api.mjs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/interactive.mjs
🔇 Additional comments (5)
src/lagoon-api.mjs (5)

4-4: Clean import statement update.

The explicit import path './logger.mjs' improves code clarity and maintainability.


117-128: Great addition of JSDoc documentation.

The comprehensive JSDoc comments for the deleteEnvironment function significantly improve code understanding by clearly documenting parameters, return values, and potential error cases.


172-177: Well-structured JSDoc for gitUrlToGithubUrl.

The documentation is clear and complete, with proper parameter and return type annotations.


270-284: Good error handling and response parsing.

The function properly parses the JSON response and provides clear error messages when deployment fails.


229-239: Good branch name extraction logic.

The code correctly parses the output of git ls-remote and filters out invalid entries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/lagoon-api.mjs (2)

209-258: ⚠️ Potential issue

Command injection & buffer overflow risk in getGitBranches()

The current implementation has security vulnerabilities that were previously identified but not fully addressed:

  1. Command injection risk: gitUrl is inserted directly into a shell command without proper sanitization
  2. Memory limitation: Using exec buffers all output in memory which could cause issues with large repositories

Apply this fix to address both security concerns:

- const command = `git ls-remote --heads ${gitUrl}`;
- console.log(chalk.blue(`Executing: ${chalk.bold(command)}`));
- 
- try {
-   const { stdout, stderr } = await execAsync(command);
+ console.log(chalk.blue(`Executing: ${chalk.bold('git ls-remote --heads')} for ${gitUrl}`));
+ 
+ try {
+   // Use child_process.execFile instead of exec to prevent command injection
+   const execFileAsync = promisify(require('child_process').execFile);
+   const { stdout, stderr } = await execFileAsync('git', ['ls-remote', '--heads', gitUrl], {
+     maxBuffer: 1024 * 1024 * 10 // 10 MB buffer to handle repositories with many branches
+   });

Additionally, consider using execLagoonCommand with a modified version that accepts array arguments for better consistency with the rest of the codebase.


264-265: ⚠️ Potential issue

Fix regex pattern for branch name validation

The current regex pattern is too restrictive for valid Git branch names and unnecessarily escapes the hyphen character.

- if (!/^[a-zA-Z0-9_./\-]+$/.test(branch)) {
+ // Allow common Git branch name characters including slashes, at-signs, etc. while still blocking potential injection characters
+ if (!/^[\w./\-@]+$/.test(branch)) {

The updated regex uses \w (which includes alphanumeric characters plus underscore) and adds @ to support additional common branch name patterns while still preventing command injection.

🧰 Tools
🪛 ESLint

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)

🧹 Nitpick comments (2)
src/lagoon-api.mjs (2)

207-208: Consider clarifying or removing this comment.

This comment doesn't provide much context or value. Either expand on why no helper functions are needed for the git ls-remote approach or remove it to improve code clarity.


260-288: Consider adding JSDoc for the new functions

Both getGitBranches and deployBranch would benefit from JSDoc documentation similar to what you've added for the other functions, detailing parameters, return values, and potential errors.

🧰 Tools
🪛 ESLint

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db7a385 and 20b37c9.

📒 Files selected for processing (1)
  • src/lagoon-api.mjs (4 hunks)
🧰 Additional context used
🪛 ESLint
src/lagoon-api.mjs

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)

🔇 Additional comments (3)
src/lagoon-api.mjs (3)

117-128: Excellent addition of comprehensive JSDoc docs.

The detailed documentation for the deleteEnvironment function is clear and well-structured, properly documenting parameters, return values, and error conditions.


172-177: Thorough JSDoc documentation added.

The JSDoc for gitUrlToGithubUrl is well-written and clearly explains the function's purpose, parameters, and return values.


273-284: Good job handling the deploy response and providing clear feedback.

The function properly parses the JSON response, checks for success, and returns a meaningful message to the user.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/lagoon-api.mjs (2)

264-266: Consider allowing more Git branch name characters

Git branches can contain more characters than currently allowed, such as @ signs which are common in CI-generated branches.

-    if (!/^[a-zA-Z0-9_./\-]+$/.test(branch)) {
+    // Allow slashes, at-signs, and common Git branch characters while forbidding whitespace & control chars
+    if (!/^[\w.@/-]+$/.test(branch)) {
       throw new Error('Invalid branch name. Branch names must contain only alphanumeric characters, slashes, underscores, hyphens, and periods.');
     }

This keeps the shell safe while allowing more common branch naming conventions.

🧰 Tools
🪛 ESLint

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)


209-258: ⚠️ Potential issue

Command injection vulnerability in getGitBranches()

The function still uses exec with string interpolation which creates a command injection risk. A malicious URL could execute arbitrary commands.

Replace the current implementation with execFile to avoid shell parsing:

-    const command = `git ls-remote --heads ${gitUrl}`;
-    console.log(chalk.blue(`Executing: ${chalk.bold(command)}`));
-    
-    try {
-      const { stdout, stderr } = await execAsync(command);
+    console.log(chalk.blue(`Executing: ${chalk.bold(`git ls-remote --heads ${gitUrl}`)}`));
+    
+    try {
+      // Use execFile (via promisify) to avoid shell parsing
+      const execFileAsync = promisify(require('child_process').execFile);
+      const { stdout, stderr } = await execFileAsync('git', ['ls-remote', '--heads', gitUrl], {
+        maxBuffer: 1024 * 1024 * 10 // 10 MB, adjust as needed
+      });

Additionally, this addresses the potential memory issue, as repositories with many branches could exceed the default 200KB buffer.

🧹 Nitpick comments (3)
src/lagoon-api.mjs (3)

264-264: Fix unnecessary escape character in regex

The hyphen doesn't need to be escaped in this character class position.

-    if (!/^[a-zA-Z0-9_./\-]+$/.test(branch)) {
+    if (!/^[a-zA-Z0-9_.\/-]+$/.test(branch)) {
🧰 Tools
🪛 ESLint

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)


273-278: Consider error handling for non-JSON responses

If the Lagoon CLI fails in a way that doesn't produce valid JSON output, the JSON.parse will throw an error that might be confusing.

-    const { stdout } = await execLagoonCommand(command, `Deploy Branch ${branch} to ${project}`);
-
-    // Parse the JSON response
-    const response = JSON.parse(stdout);
+    const { stdout } = await execLagoonCommand(command, `Deploy Branch ${branch} to ${project}`);
+
+    // Parse the JSON response
+    let response;
+    try {
+      response = JSON.parse(stdout);
+    } catch (parseError) {
+      throw new Error(`Failed to parse Lagoon response: ${stdout.substring(0, 100)}... (Response was not valid JSON)`);
+    }

210-244: Add handling for empty branch lists

The function could return an empty array if no branches are found. Consider adding a check and providing a more informative message to the user.

       // Log the number of branches found
       console.log(chalk.green(`Found ${branches.length} branches in repository`));
       
+      // If no branches were found, this might indicate an issue
+      if (branches.length === 0) {
+        console.log(chalk.yellow(`Warning: No branches found in repository. This might indicate an issue with the repository URL or permissions.`));
+      }
+      
       return branches;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20b37c9 and ae456ee.

📒 Files selected for processing (1)
  • src/lagoon-api.mjs (4 hunks)
🧰 Additional context used
🪛 ESLint
src/lagoon-api.mjs

[error] 264-264: Unnecessary escape character: -.

(no-useless-escape)

🔇 Additional comments (3)
src/lagoon-api.mjs (3)

117-128: Good addition of comprehensive JSDoc comments!

The JSDoc comments for the deleteEnvironment function are thorough and clearly document the function's purpose, parameters, return values, and possible error conditions.


172-177: Well-documented function with clear JSDoc

The JSDoc for gitUrlToGithubUrl provides clear documentation about the function's purpose and behavior.


268-272: Good implementation of shell escaping

The comprehensive escaping for shell safety is a good implementation that addresses previous feedback.

richardgaunt and others added 3 commits May 7, 2025 20:26
Docstrings generation was requested by @richardgaunt.

* #8 (comment)

The following files were modified:

* `src/interactive.mjs`
* `src/lagoon-api.mjs`
* `src/logger.mjs`
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 7, 2025

Note

Generated docstrings for this pull request at #10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant