Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feature: Preserve Existing .env File and Update Values if Present #5

Merged
merged 11 commits into from
Oct 16, 2024

Conversation

macalbert
Copy link
Owner

@macalbert macalbert commented Oct 16, 2024

Description

This PR introduces a new feature to Envilder, allowing it to append or update values in an existing .env file instead of overwriting the entire file. If an .env file already exists, Envilder will keep the previous environment variables intact and only update the ones specified in the new parameter map. If the variable exists, it will be updated with the latest value from AWS SSM; if it doesn’t exist, it will be appended to the .env file.

Approach

The change modifies the functionality of the run method to:

  • Read and parse an existing .env file if present.
  • Keep any existing environment variables that are not part of the new mapping.
  • Update the values of environment variables that are part of the mapping from AWS SSM.
  • Append any new environment variables to the .env file.

This approach ensures that existing configurations are preserved, while still allowing for updates to specific values when needed.

Open Questions and Pre-Merge TODOs

  • Confirm that all existing and new tests pass after this feature is added.
  • Ensure that edge cases (like malformed .env files or variables with no value) are handled properly.
  • Verify the behavior with larger .env files to ensure performance remains optimal.

Learning

The implementation required a better understanding of how to read and update environment files without erasing or overwriting existing variables. The decision to use a combination of reading, parsing, and re-writing only necessary variables ensures that the feature is robust and doesn't inadvertently remove existing configurations.

Summary by CodeRabbit

  • New Features

    • Enhanced environment variable management with new functions for loading parameters and updating environment files.
    • Improved error handling and logging for SSM parameter fetching.
    • Added whimsical error messages for better user experience during CLI errors.
  • Bug Fixes

    • Added test cases to handle scenarios such as appending and overwriting SSM parameters, and logging warnings for parameters with no values.
  • Documentation

    • Updated sample parameter map to reflect new key-value pairs.
  • Chores

    • Version updated from "0.1.5" to "0.2.0" in the package.json file.
    • Added new dependency "dotenv": "^16.4.5" to the project.

@macalbert macalbert added the enhancement New feature or request label Oct 16, 2024
@macalbert macalbert self-assigned this Oct 16, 2024
Copy link

coderabbitai bot commented Oct 16, 2024

Walkthrough

The changes introduced in this pull request enhance the functionality of the Envilder CLI by modifying the run function in src/index.ts. It now includes new helper functions for loading a parameter map and existing environment variables, as well as for writing updated variables back to the environment file. Additionally, the test suite in tests/index.test.ts has been expanded to cover new scenarios related to SSM parameter handling, including cases for appending, overwriting, and logging warnings. The tests/sample/param_map.json file has been updated to reflect changes in key-value pairs. The package.json file has been updated to a new version and includes a new dependency.

Changes

File Change Summary
src/index.ts - Added functions: fetchSSMParameter, loadParamMap, loadExistingEnvVariables, writeEnvFile.
- Updated run function to load parameter map and environment variables, fetch SSM values, and write back to the environment file.
tests/index.test.ts - Enhanced mocked SSM responses for additional cases.
- Added tests for appending, overwriting SSM parameters, and logging warnings for empty values.
- Modified test for non-existent SSM parameters.
tests/sample/param_map.json - Removed keys: "NEXT_PUBLIC_CREDENTIAL_EMAIL", "NEXT_PUBLIC_CREDENTIAL_PASSWORD", "NEXT_PUBLIC_JWT_SECRET".
- Added key: "TOKEN_SECRET" with value "/Test/Token".
package.json - Version updated from "0.1.5" to "0.2.0".
- Added dependency: "dotenv": "^16.4.5".
src/cli/cliRunner.ts - Changed error message to a whimsical message: "🚨 Uh-oh! Looks like Mario fell into the wrong pipe! 🍄💥".

Poem

🐇 In the garden where secrets bloom,
New functions sprout, dispelling gloom.
With parameters fetched from the sky,
Our environment grows, oh my, oh my!
Logs sing warnings, as tests do play,
In the world of code, we hop and sway! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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.

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 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

@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: 6

🧹 Outside diff range and nitpick comments (2)
src/index.ts (1)

Line range hint 38-59: Fetch SSM parameters concurrently to improve performance.

The fetchAndUpdateEnvVariables function currently fetches SSM parameters sequentially within a for...of loop. This can lead to increased execution time, especially when dealing with numerous parameters.

Consider modifying the function to fetch parameters concurrently using Promise.all:

async function fetchAndUpdateEnvVariables(
  paramMap: Record<string, string>,
  existingEnvVariables: Record<string, string>,
): Promise<Record<string, string>> {
  console.log('Fetching parameters...');

- for (const [envVar, ssmName] of Object.entries(paramMap)) {
-   try {
-     const value = await fetchSSMParameter(ssmName);
-     if (value) {
-       existingEnvVariables[envVar] = value;
-       console.log(`${envVar}=${value}`);
-     } else {
-       console.error(`Warning: No value found for ${ssmName}`);
-     }
-   } catch (error) {
-     console.error(`Error fetching parameter ${ssmName}: ${error}`);
-     throw new Error(`ParameterNotFound: ${ssmName}`);
-   }
- }

+ await Promise.all(
+   Object.entries(paramMap).map(async ([envVar, ssmName]) => {
+     try {
+       const value = await fetchSSMParameter(ssmName);
+       if (value !== undefined) {
+         existingEnvVariables[envVar] = value;
+         console.log(`${envVar}=${value}`);
+       } else {
+         console.error(`Warning: No value found for ${ssmName}`);
+       }
+     } catch (error) {
+       console.error(`Error fetching parameter ${ssmName}:`, error);
+       throw new Error(`ParameterNotFound: ${ssmName}`);
+     }
+   })
+ );

  return existingEnvVariables;
}

This change will initiate all fetch requests simultaneously, reducing the total execution time.

tests/index.test.ts (1)

129-144: Ensure Variables with Empty Values Are Not Added to .env File

The test checks that a warning is logged when an SSM parameter has no value. It would strengthen the test to also verify that the variable with the empty value is not written to the .env file, ensuring that the application correctly handles this scenario.

Consider adding an assertion to verify the .env file does not include the parameter with an empty value:

       // Assert
       expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Warning: No value found for'));

+      const envFileContent = fs.readFileSync(mockEnvFilePath, 'utf-8');
+      expect(envFileContent).not.toContain('NEXT_PUBLIC_CREDENTIAL_PASSWORD=');

       fs.unlinkSync(mockEnvFilePath);
       fs.unlinkSync(mockMapPath);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 40d519a and fcd559f.

📒 Files selected for processing (3)
  • src/index.ts (2 hunks)
  • tests/index.test.ts (2 hunks)
  • tests/sample/param_map.json (1 hunks)
🧰 Additional context used
🔇 Additional comments (3)
tests/sample/param_map.json (1)

1-3: Significant changes to authentication-related parameters

The modifications to this file represent a substantial shift in how authentication-related parameters are handled:

  1. Removed keys:

    • "NEXT_PUBLIC_CREDENTIAL_EMAIL"
    • "NEXT_PUBLIC_CREDENTIAL_PASSWORD"
    • "NEXT_PUBLIC_JWT_SECRET"
  2. Added key:

    • "TOKEN_SECRET": "/Test/Token"

These changes have several implications:

  • The authentication mechanism appears to have been simplified, moving from separate credential and JWT secret keys to a single token secret.
  • The removal of the "NEXT_PUBLIC_" prefix suggests this information is no longer intended to be public-facing.
  • The new value "/Test/Token" is less specific than the previous values, which could be a security concern if not properly managed.

Please ensure that:

  1. All parts of the codebase that previously used the removed keys have been updated accordingly.
  2. The new "TOKEN_SECRET" is being used securely and isn't exposing sensitive information.
  3. If "/Test/Token" is a placeholder, it should be replaced with the actual path before deployment.

To verify the usage of these keys in the codebase, you can run the following script:

This will help identify any places where the old keys are still being used or where the new key needs to be implemented.

src/index.ts (1)

68-69: Verify AWS SSM client configuration and error handling.

In the fetchSSMParameter function, if the SSM client is not properly configured with AWS credentials or region, the request will fail. Additionally, consider handling specific AWS errors more gracefully.

Run the following script to confirm that the AWS SSM client can access the parameters:

If the command fails, ensure that:

  • AWS credentials are correctly set in your environment.
  • The AWS region is specified and correct.
  • The IAM role or user has permissions to access the SSM parameters.
tests/index.test.ts (1)

21-24: Appropriate Mock for Empty SSM Parameter Value

The added mock implementation correctly simulates an SSM parameter that exists but has an empty value. This is essential for testing how the application handles parameters without values.

src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Show resolved Hide resolved
tests/index.test.ts Show resolved Hide resolved
tests/index.test.ts Outdated Show resolved Hide resolved
Copy link

@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

🧹 Outside diff range and nitpick comments (4)
src/index.ts (1)

39-66: LGTM: Improved error handling in fetchAndUpdateEnvVariables.

The changes to fetchAndUpdateEnvVariables significantly improve error handling and address the previous review comment about continuing to fetch remaining parameters after an error occurs. The function now collects errors and throws a consolidated error at the end if any parameters failed to fetch, which is a great improvement.

However, consider using Promise.all instead of a for...of loop for potential performance benefits, especially when dealing with a large number of parameters:

async function fetchAndUpdateEnvVariables(
  paramMap: Record<string, string>,
  existingEnvVariables: Record<string, string>,
): Promise<Record<string, string>> {
  console.log('Fetching parameters...');
  const errors: string[] = [];

  await Promise.all(
    Object.entries(paramMap).map(async ([envVar, ssmName]) => {
      try {
        const value = await fetchSSMParameter(ssmName);
        if (value) {
          existingEnvVariables[envVar] = value;
          console.log(`${envVar}=${value}`);
        } else {
          console.error(`Warning: No value found for: '${ssmName}'`);
        }
      } catch (error) {
        console.error(`Error fetching parameter: '${ssmName}'`);
        errors.push(`ParameterNotFound: ${ssmName}`);
      }
    })
  );

  if (errors.length > 0) {
    throw new Error(`Some parameters could not be fetched:\n${errors.join('\n')}`);
  }

  return existingEnvVariables;
}

This change would allow for concurrent fetching of parameters, potentially improving performance.

tests/index.test.ts (3)

77-100: LGTM: New test case for appending SSM parameters

This new test case is a valuable addition as it verifies that existing environment variables are preserved while new SSM parameters are appended. The test structure follows the Arrange-Act-Assert pattern, which is a good practice.

However, there's an unintended whitespace issue in the existingEnvContent. To avoid potential parsing issues, consider removing the leading whitespace:

-    const existingEnvContent = 'EXISTING_VAR=existingValue';
+    const existingEnvContent = 'EXISTING_VAR=existingValue';

102-123: LGTM: New test case for overwriting SSM parameters

This new test case is a great addition as it verifies that existing environment variables are correctly overwritten with new SSM parameter values. The test structure follows the Arrange-Act-Assert pattern, which is a good practice.

Similar to the previous test, there's an unintended whitespace issue in the existingEnvContent. To avoid potential parsing issues, consider removing the leading whitespace:

-    const existingEnvContent = '[email protected]';
+    const existingEnvContent = '[email protected]';

125-141: LGTM: New test case for logging warning on empty SSM parameter value

This new test case is an excellent addition as it verifies the behavior when an SSM parameter has no value. The use of a console spy to check for the warning message is a good approach.

Consider adding an assertion to verify that the environment variable is not set when the SSM parameter has no value. This would ensure that empty values are not inadvertently added to the .env file:

expect(updatedEnvFileContent).not.toContain('NEXT_PUBLIC_CREDENTIAL_PASSWORD=');
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f6bf230 and 4b18ffc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • package.json (2 hunks)
  • src/cli/cliRunner.ts (1 hunks)
  • src/index.ts (1 hunks)
  • tests/index.test.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/cli/cliRunner.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🔇 Additional comments (10)
src/index.ts (7)

3-3: LGTM: Good addition of dotenv package.

The import of dotenv is a great addition. It addresses the previous suggestion to use a robust parser for .env files, which will help handle various edge cases and improve the overall reliability of the environment variable handling.


7-15: LGTM: Excellent refactoring of the run function.

The changes to the run function are well-structured and align perfectly with the PR objectives. The use of helper functions (loadParamMap, loadExistingEnvVariables, fetchAndUpdateEnvVariables, and writeEnvFile) improves modularity and readability. This approach effectively preserves existing environment variables while updating or adding new ones from the SSM parameters.


17-25: LGTM: Robust implementation of loadParamMap.

The loadParamMap function is well-implemented with proper error handling for JSON parsing. This addresses the previous review comment about handling potential JSON parsing errors. The function now logs the error and throws a more informative error message, which will help with debugging and improve the overall robustness of the application.


27-37: LGTM: Excellent implementation of loadExistingEnvVariables.

The loadExistingEnvVariables function is well-implemented and addresses the previous review comment about using a robust parser for .env files. By utilizing dotenv.parse, the function now correctly handles various edge cases that may occur in .env files. The function also properly handles the scenario when the file doesn't exist, returning an empty object in that case.


68-76: LGTM: Well-implemented fetchSSMParameter function.

The fetchSSMParameter function is a great addition that encapsulates the logic for fetching a parameter from SSM. It correctly uses the AWS SDK and improves code modularity by separating this functionality. The use of WithDecryption: true ensures that encrypted parameters are properly handled.


1-83: Overall, excellent improvements to Envilder's .env file handling.

The changes in this PR significantly enhance Envilder's ability to handle .env files. The new implementation successfully preserves existing environment variables while updating or adding new ones from SSM parameters. Key improvements include:

  1. Use of dotenv for robust parsing of .env files.
  2. Modular design with separate functions for different responsibilities.
  3. Improved error handling, especially in loadParamMap and fetchAndUpdateEnvVariables.
  4. Better code organization and readability.

These changes align well with the PR objectives and address most of the previous review comments. The code is now more robust and maintainable.

To further improve the implementation, consider:

  1. Using Promise.all in fetchAndUpdateEnvVariables for potential performance benefits.
  2. Enhancing writeEnvFile to properly handle special characters in environment variable values.

Great work on this feature enhancement!


78-83: ⚠️ Potential issue

Improve writeEnvFile to handle special characters.

While the writeEnvFile function is a good addition, it doesn't handle escaping of special characters in environment variable values. This could lead to issues when parsing the .env file later, especially for values containing newlines, quotes, or equal signs.

Consider using dotenv.stringify or manually escaping special characters:

import * as dotenv from 'dotenv';

function writeEnvFile(envFilePath: string, envVariables: Record<string, string>): void {
  const envContent = Object.entries(envVariables)
    .map(([key, value]) => `${key}=${dotenv.stringify(value)}`)
    .join('\n');

  fs.writeFileSync(envFilePath, envContent);
}

Alternatively, if you prefer manual escaping:

function writeEnvFile(envFilePath: string, envVariables: Record<string, string>): void {
  const envContent = Object.entries(envVariables)
    .map(([key, value]) => {
      const escapedValue = value.replace(/(\n|\r|\n\r)/g, '\\n').replace(/"/g, '\\"');
      return `${key}="${escapedValue}"`;
    })
    .join('\n');

  fs.writeFileSync(envFilePath, envContent);
}

This will ensure that the generated .env file can be correctly parsed later, even with complex values.

tests/index.test.ts (3)

21-23: LGTM: Enhanced mock implementation for SSM

The addition of a new condition for the '/path/to/ssm/password_no_value' parameter is a good improvement. It allows testing the scenario where an SSM parameter exists but has no value, which is an important edge case to consider.


Line range hint 1-141: Overall assessment: Significant improvements to test coverage

The changes to this file have greatly enhanced the test suite for the Envilder CLI. New test cases cover important scenarios such as appending new SSM parameters, overwriting existing variables, and handling SSM parameters with no value. These additions will help ensure the robustness and reliability of the CLI.

A few minor issues were identified:

  1. Inconsistency in SSM parameter naming in one test case.
  2. Unintended whitespace in some test data.
  3. A suggestion for an additional assertion in the empty value test case.

Addressing these minor points will further improve the quality of the test suite. Great work on expanding the test coverage!


65-65: ⚠️ Potential issue

Maintain consistency in SSM parameter naming

The change from a specific SSM parameter path to a generic string 'non-existent parameter' is inconsistent with the best practices for SSM parameter naming and contradicts the realistic paths used in other tests.

As mentioned in a previous review, it's recommended to use realistic SSM parameter paths in tests. Please consider reverting this change and using a path like '/path/to/ssm/non_existent_parameter' instead.

Also applies to: 73-75

@macalbert macalbert merged commit 65fbc72 into main Oct 16, 2024
2 checks passed
@macalbert macalbert deleted the macalbert/keep-previous-envfile branch October 16, 2024 18:19
@coderabbitai coderabbitai bot mentioned this pull request Oct 30, 2024
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant