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

package manager for vendor pull #768

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open

package manager for vendor pull #768

wants to merge 49 commits into from

Conversation

haitham911
Copy link
Collaborator

@haitham911 haitham911 commented Nov 9, 2024

what

  • New Feature interactive package manager for vendor pull
    interactive shell for atmos vendor pull --everything
    Support --everything flag
    for test non TTY mod run atmos vendor pull --everything </dev/null |& cat > atmos_vendor_pull.log
    2024-11-09 15_02_32-vendor_model go - atmos  WSL_ Ubuntu  - Visual Studio Code
    2024-11-09 15_02_51-vendor_model go - atmos  WSL_ Ubuntu  - Visual Studio Code

why

build an interface for a package manager using bubbletea

references

https://linear.app/cloudposse/issue/DEV-270/atmos-vendor-pull-ux
https://linear.app/cloudposse/issue/DEV-2716/support-atmos-vendor-pull-everything

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced user interface for vendor commands utilizing the Bubble Tea framework.
    • New --everything flag for the vendorPullCmd command, allowing users to vendor all components in one go.
    • Improved functionality for managing vendor package installations, including enhanced error handling and progress feedback.
    • Updated configuration files and documentation to utilize the new --everything option in vendor commands.
  • Bug Fixes

    • Refined validation logic for command-line flags to prevent conflicts.
  • Refactor

    • Modularized code structure for better readability and maintainability.
    • Improved error handling and logging for vendor operations.
  • Documentation

    • Updated internal documentation to reflect changes in methods and functionalities.
    • Revised CLI Commands Cheat Sheet and other documentation to include the new --everything flag for vendor commands.

@osterman
Copy link
Member

osterman commented Nov 9, 2024

running download error: could not open a new TTY: open /dev/tty: no such device or address

We need a fallback for headless terminals. This is probably handled by charmbracelet as well. We might just not be using it.

Copy link
Contributor

coderabbitai bot commented Nov 9, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces updates focused on dependency management in the go.mod file and enhancements to vendor component handling in the codebase. Key changes include updates to dependency versions, the addition of new functions for managing vendor components, and a restructuring of existing functions to improve modularity and error handling. These modifications aim to refine the user experience for vendor operations within the CLI application.

Changes

File Path Change Summary
go.mod - Updated github.com/charmbracelet/bubbletea to v1.2.1.
- Re-added github.com/mattn/go-isatty v0.0.20.
- Added github.com/charmbracelet/harmonica v0.2.0.
- Confirmed github.com/charmbracelet/x/ansi at v0.4.5.
internal/exec/vendor_component_utils.go - Added ExecuteStackVendorInternal function.
- Refactored ExecuteComponentVendorInternal for improved structure and error handling.
- Introduced CheckTTYSupport function.
internal/exec/vendor_model.go - Added pkgType and updated pkgAtmosVendor and modelVendor types.
- Added methods for managing vendor installations and progress.
internal/exec/vendor_model_component.go - Added pkgComponentVendor type.
- Introduced methods for initializing and managing component installations.
internal/exec/vendor_utils.go - Refactored ExecuteVendorPullCommand and ExecuteAtmosVendorInternal functions for better modularity.
- Added functions for source validation and logging.
cmd/vendor_pull.go - Added --everything flag to vendorPullCmd for executing the command to vendor all components.

Assessment against linked issues

Objective Addressed Explanation
Support atmos vendor pull --everything (DEV-2716)
Improve vendor pull UX (DEV-270)

Possibly related PRs

Suggested reviewers

  • osterman
  • aknysh

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 or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

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

🧹 Outside diff range and nitpick comments (9)
internal/exec/vendor_model.go (1)

148-148: Add missing space in completion message

There's a missing space after the period in the string "Vendored %d components.Failed to vendor %d components.\n", which causes the message to read incorrectly. Adding a space will improve readability.

Apply this diff to fix the formatting:

return doneStyle.Render(fmt.Sprintf("Vendored %d components.Failed to vendor %d components.\n", n-m.failedPkg, m.failedPkg))
+return doneStyle.Render(fmt.Sprintf("Vendored %d components. Failed to vendor %d components.\n", n-m.failedPkg, m.failedPkg))
internal/exec/vendor_model_component.go (5)

23-34: Export pkgComponentVendor struct

If there's a possibility of using pkgComponentVendor outside this package in the future, consider exporting it by capitalizing the struct name. Adding comments to structs and fields will also enhance code readability.


135-137: Add missing space in status message

There's a missing space after the period in the status message. This will improve the readability of the output.

-return doneStyle.Render(fmt.Sprintf("Vendored %d components.Failed to vendor %d components.\n", n-m.failedPkg, m.failedPkg))
+return doneStyle.Render(fmt.Sprintf("Vendored %d components. Failed to vendor %d components.\n", n-m.failedPkg, m.failedPkg))

166-199: Refactor to eliminate duplicate code in downloadComponentAndInstall

The function downloadComponentAndInstall contains similar code blocks for handling components and mixins. Refactoring can reduce duplication and simplify maintenance.

Here's a possible refactor:

 func downloadComponentAndInstall(p pkgComponentVendor, dryRun bool, cliConfig schema.CliConfiguration) tea.Cmd {
     return func() tea.Msg {
         if dryRun {
             time.Sleep(100 * time.Millisecond)
             return installedPkgMsg{
                 err:  nil,
                 name: p.name,
             }
         }

-        if p.IsComponent {
-            err := installComponent(p, cliConfig)
-            if err != nil {
-                return installedPkgMsg{
-                    err:  err,
-                    name: p.name,
-                }
-            }
-            return installedPkgMsg{
-                err:  nil,
-                name: p.name,
-            }
-        }
-        if p.IsMixins {
-            err := installMixin(p, cliConfig)
-            if err != nil {
-                return installedPkgMsg{
-                    err:  err,
-                    name: p.name,
-                }
-            }
-            return installedPkgMsg{
-                err:  nil,
-                name: p.name,
-            }
-        }
+        var err error
+        if p.IsComponent {
+            err = installComponent(p, cliConfig)
+        } else if p.IsMixins {
+            err = installMixin(p, cliConfig)
+        } else {
+            err = fmt.Errorf("unknown install operation")
+        }
         return installedPkgMsg{
             err:  err,
             name: p.name,
         }
     }
 }

258-259: Include package type in error message

Adding the package type to the error message will help in debugging unknown package types.

-return fmt.Errorf("unknown package type")
+return fmt.Errorf("unknown package type: %v", p.pkgType)

297-298: Include package type in error message

Similarly, in installMixin, include the package type in the error message for better clarity.

-return fmt.Errorf("unknown package type")
+return fmt.Errorf("unknown package type: %v", p.pkgType)
internal/exec/vendor_component_utils.go (3)

96-105: Implement ExecuteStackVendorInternal function

The ExecuteStackVendorInternal function currently returns a "not supported yet" error. Consider implementing the necessary logic or providing a more detailed message.

Would you like assistance in drafting the implementation or creating a GitHub issue to track this task?


343-343: Wrap error with %w for proper error handling

To enable error unwrapping, replace %v with %w when formatting errors.

Apply this diff:

- return fmt.Errorf("error initializing model: %v", err)
+ return fmt.Errorf("error initializing model: %w", err)

341-345: Handle errors from the TUI run

Ensure that errors from tea.NewProgram(model).Run() are properly handled to maintain robustness.

Apply this diff:

if _, err := tea.NewProgram(model).Run(); err != nil {
-   return fmt.Errorf("running download error: %w", err)
+   return fmt.Errorf("error running TUI program: %w", err)
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3342e8e and 5248650.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • go.mod (1 hunks)
  • internal/exec/vendor_component_utils.go (4 hunks)
  • internal/exec/vendor_model.go (1 hunks)
  • internal/exec/vendor_model_component.go (1 hunks)
  • internal/exec/vendor_utils.go (5 hunks)
🔇 Additional comments (10)
go.mod (1)

96-96: LGTM! Clean dependency addition for TUI animations.

The addition of github.com/charmbracelet/harmonica aligns perfectly with implementing an improved vendor pull interface using Bubble Tea. This indirect dependency will provide smooth animation capabilities for the TUI.

Let's verify the harmonica package usage in the codebase:

internal/exec/vendor_utils.go (8)

10-10: Importing Bubble Tea library for TUI functionality

The addition of the tea import integrates the Bubble Tea library, enabling the new TUI enhancements.


178-179: Calling logInitialMessage to enhance user feedback

Incorporating logInitialMessage improves the user experience by logging initial processing information.


Line range hint 250-309: Refactoring source processing into modular functions

The introduction of shouldSkipSource and validateSourceFields enhances code readability and maintainability by encapsulating specific logic.


271-280: Clarifying source type determination logic

The determineSourceType function cleanly separates the logic for source type determination, improving code clarity.


311-321: Implementing TUI for package processing

Introducing the TUI enhances the user interface during package processing with Bubble Tea.


372-379: Defining logInitialMessage function

Centralizing initial logging supports better code organization and reduces redundancy.


380-392: Duplicate issue: Modifying a value receiver in validateSourceFields

As mentioned earlier, modifying s.File won't affect the original struct since s is passed by value.


435-491: Generating skip function for file copying

The generateSkipFunction effectively handles inclusion and exclusion patterns, enhancing modularity and reusability.

internal/exec/vendor_component_utils.go (1)

15-15: Bubble Tea import added for TUI integration

The addition of the Bubble Tea package is appropriate for implementing TUI features, aligning with the PR objectives.

internal/exec/vendor_model.go Outdated Show resolved Hide resolved
internal/exec/vendor_model.go Outdated Show resolved Hide resolved
internal/exec/vendor_model.go Outdated Show resolved Hide resolved
internal/exec/vendor_utils.go Outdated Show resolved Hide resolved
Comment on lines +110 to +111
if strings.HasSuffix(src, ".git") {
return true, nil
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 9, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct .git directory exclusion logic

Using strings.HasSuffix(src, ".git") may incorrectly exclude files or directories ending with .git. To specifically exclude the .git directory, use filepath.Base(src) == ".git".

Apply this diff:

- if strings.HasSuffix(src, ".git") {
+ if filepath.Base(src) == ".git" {
    return true, nil
}
📝 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
if strings.HasSuffix(src, ".git") {
return true, nil
if filepath.Base(src) == ".git" {
return true, nil

Copy link
Member

Choose a reason for hiding this comment

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

@aknysh what do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

internal/exec/vendor_component_utils.go Outdated Show resolved Hide resolved
@osterman osterman changed the title Dev 270 n @auto-title Nov 9, 2024
@osterman osterman added the minor New features that do not break anything label Nov 9, 2024
@osterman
Copy link
Member

osterman commented Nov 9, 2024

Running atmos vendor pull --logs-level Debug provides no useful debug information.

image

@osterman osterman changed the title @auto-title @coderabbitai Nov 9, 2024
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: 6

🧹 Outside diff range and nitpick comments (4)
cmd/vendor_pull.go (1)

35-35: The new --everything flag looks solid, warrior! However, let's ensure proper user guidance.

While the flag implementation is clean, consider enhancing the help text to:

  1. Warn about potential longer execution time
  2. Mention any prerequisites (like vendor config file)
-vendorPullCmd.PersistentFlags().Bool("everything", false, "Vendor all components: atmos vendor pull --everything")
+vendorPullCmd.PersistentFlags().Bool("everything", false, "Vendor all components (may take longer): atmos vendor pull --everything. Requires valid vendor config file.")
internal/exec/vendor_model_component.go (2)

22-34: Consider adding field documentation.

The pkgComponentVendor struct would benefit from GoDoc comments explaining each field's purpose and constraints. This helps with maintainability and makes the code more self-documenting.

Example:

// pkgComponentVendor represents a vendor component package with its metadata
type pkgComponentVendor struct {
    // uri is the source location of the package
    uri string
    // name is the unique identifier of the package
    name string
    // sourceIsLocalFile indicates if the package is from a local filesystem
    sourceIsLocalFile bool
    // ... (document other fields)
}

35-66: Enhance error handling in initialization.

While the initialization is solid, consider adding validation for the input parameters and more descriptive error messages.

 func newModelComponentVendorInternal(pkgs []pkgComponentVendor, dryRun bool, cliConfig schema.CliConfiguration) (modelVendor, error) {
+    if cliConfig == nil {
+        return modelVendor{}, fmt.Errorf("cli configuration cannot be nil")
+    }
+
     p := progress.New(
         progress.WithDefaultGradient(),
         progress.WithWidth(30),
         progress.WithoutPercentage(),
     )
internal/exec/vendor_model.go (1)

80-86: Consider renaming the variable for clarity

In the loop, the variable p is used to represent a pkgVendor. To improve readability, consider renaming p to vendorPkg or similar, avoiding confusion with the loop variable pkg.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 68d6e61 and 0ace698.

📒 Files selected for processing (4)
  • cmd/vendor_pull.go (1 hunks)
  • internal/exec/vendor_model.go (1 hunks)
  • internal/exec/vendor_model_component.go (1 hunks)
  • internal/exec/vendor_utils.go (8 hunks)
🔇 Additional comments (9)
cmd/vendor_pull.go (2)

Line range hint 1-37: Your command structure stands strong, but progress tracking seems missing in action!

The PR objectives mention using Bubble Tea for progress tracking, but it's not visible in this file. This might be implemented in internal/exec/vendor_utils.go, but let's verify:

#!/bin/bash
# Check if Bubble Tea is properly imported and used
rg -l "github.com/charmbracelet/bubbletea" 

# Look for progress tracking in vendor utils
ast-grep --pattern 'type $_ struct {
  $$$
  progress $$$
  $$$
}'

Line range hint 16-24: Battle-harden the command for headless environments, brave one!

Based on the PR comments about TTY issues, we should ensure proper fallback for headless environments.

Let's check if the ExecuteVendorPullCmd handles this:

Consider implementing a fallback mechanism:

  1. Detect TTY availability before initializing Bubble Tea
  2. Fallback to simple progress logging in headless environments
internal/exec/vendor_model_component.go (3)

1-21: LGTM! Clean and well-organized imports.

All imports are necessary and properly organized.


130-142: Add context timeout for HTTP operations.

This is a duplicate of a previous review comment. The HTTP client still lacks timeout configuration, which could lead to hanging operations.


118-124: Enhance temporary directory security.

This is a duplicate of a previous review comment. The temporary directory creation needs more secure permissions and better cleanup handling.

internal/exec/vendor_utils.go (3)

10-10: LGTM! Clean import addition.

The Bubble Tea framework import is correctly added for implementing the terminal user interface.


73-83: LGTM! Robust flag validation.

The implementation properly handles the new --everything flag and includes comprehensive validation of flag combinations with clear error messages.


95-97: LGTM! Proper validation for --everything flag.

The code correctly validates the existence of the vendor config file when the --everything flag is set.

internal/exec/vendor_model.go (1)

183-212: Effective rendering of the installation progress

The View function provides a clear and user-friendly display of the installation progress, handling different terminal widths and states gracefully.

internal/exec/vendor_model_component.go Outdated Show resolved Hide resolved
Comment on lines +67 to +111
func downloadComponentAndInstall(p *pkgComponentVendor, dryRun bool, cliConfig schema.CliConfiguration) tea.Cmd {
return func() tea.Msg {
if dryRun {
// Simulate the action
time.Sleep(100 * time.Millisecond)
return installedPkgMsg{
err: nil,
name: p.name,
}
}
if p.IsComponent {
err := installComponent(p, cliConfig)
if err != nil {
return installedPkgMsg{
err: err,
name: p.name,
}

}
return installedPkgMsg{
err: nil,
name: p.name,
}
}
if p.IsMixins {
err := installMixin(p, cliConfig)
if err != nil {
return installedPkgMsg{
err: err,
name: p.name,
}

}
return installedPkgMsg{
err: nil,
name: p.name,
}
}
u.LogTrace(cliConfig, fmt.Sprintf("Unknown install operation for %s", p.name))
return installedPkgMsg{
err: fmt.Errorf("unknown install operation"),
name: p.name,
}
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 11, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add concurrent operation safety measures.

The download and installation operations could benefit from mutex protection when accessing shared resources.

 type pkgComponentVendor struct {
+    mu sync.Mutex // protect concurrent operations
     // ... existing fields
 }

 func downloadComponentAndInstall(p *pkgComponentVendor, dryRun bool, cliConfig schema.CliConfiguration) tea.Cmd {
     return func() tea.Msg {
+        p.mu.Lock()
+        defer p.mu.Unlock()
         if dryRun {
             // ... rest of the code

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Member

Choose a reason for hiding this comment

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

@aknysh do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

internal/exec/vendor_utils.go Outdated Show resolved Hide resolved
internal/exec/vendor_utils.go Outdated Show resolved Hide resolved
internal/exec/vendor_model.go Show resolved Hide resolved
internal/exec/vendor_model.go Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (2)
examples/demo-component-versions/atmos.yaml (1)

28-28: Fix indentation for consistency

The command step should maintain consistent indentation with other YAML elements.

Apply this diff to fix the indentation:

- atmos vendor pull --everything
+    atmos vendor pull --everything
🧰 Tools
🪛 yamllint

[warning] 28-28: wrong indentation: expected 4 but found 2

(indentation)

examples/demo-vendoring/atmos.yaml (1)

30-30: Consider adding documentation for the --everything flag

The addition of --everything flag aligns with the PR objectives to enhance the vendor pull functionality. However, since this is a demo file, it would be helpful to add comments explaining:

  • The purpose of the --everything flag
  • How it differs from the default behavior
  • Any considerations for non-TTY environments

Consider adding comments above the command:

commands:
- name: "test"
  description: "Run all tests"
  steps:
+ # Uses the interactive package manager to pull all vendor components
+ # For non-TTY environments, redirect output: atmos vendor pull --everything > pull.log
  - atmos vendor pull --everything
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0ace698 and 2e75a5e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • examples/demo-component-versions/atmos.yaml (1 hunks)
  • examples/demo-vendoring/atmos.yaml (1 hunks)
  • go.mod (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go.mod
🧰 Additional context used
🪛 yamllint
examples/demo-component-versions/atmos.yaml

[warning] 28-28: wrong indentation: expected 4 but found 2

(indentation)

🔇 Additional comments (2)
examples/demo-component-versions/atmos.yaml (1)

28-28: Verify CI/CD compatibility with the new --everything flag

The addition of the --everything flag aligns with the new interactive package manager interface. However, since this is in the test command, we should ensure it works correctly in non-interactive CI/CD environments.

Consider adding a comment above the command to document the expected behavior in non-TTY environments, as this could affect automated testing scenarios.

🧰 Tools
🪛 yamllint

[warning] 28-28: wrong indentation: expected 4 but found 2

(indentation)

examples/demo-vendoring/atmos.yaml (1)

30-30: Verify the command's behavior in non-TTY environments

Given the concerns about TTY handling mentioned in the PR comments, we should verify that this example works in headless environments.

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

🧹 Outside diff range and nitpick comments (8)
internal/exec/vendor_model_component.go (3)

22-33: Add documentation and fix naming inconsistency.

The struct is well-organized, but could benefit from:

  1. Adding documentation comments for the struct and its fields
  2. Renaming IsMixins to IsMixin for consistency with IsComponent
+// pkgComponentVendor represents a vendor component package with its metadata and configuration
 type pkgComponentVendor struct {
+    // uri is the source location of the package
     uri                 string
     // ... add comments for other fields
-    IsMixins            bool
+    IsMixin             bool
     mixinFilename       string
 }

67-111: Extract common installation logic.

The download and installation logic contains duplicate patterns between component and mixin handling. Consider extracting common functionality into a shared helper function.

+func handleInstallation(p *pkgComponentVendor, installer func(*pkgComponentVendor, schema.CliConfiguration) error, cliConfig schema.CliConfiguration) installedPkgMsg {
+    err := installer(p, cliConfig)
+    return installedPkgMsg{
+        err:  err,
+        name: p.name,
+    }
+}

 func downloadComponentAndInstall(p *pkgComponentVendor, dryRun bool, cliConfig schema.CliConfiguration) tea.Cmd {
     return func() tea.Msg {
         if dryRun {
             // ... dry run code ...
         }
         if p.IsComponent {
-            err := installComponent(p, cliConfig)
-            if err != nil {
-                return installedPkgMsg{
-                    err:  err,
-                    name: p.name,
-                }
-            }
-            return installedPkgMsg{
-                err:  nil,
-                name: p.name,
-            }
+            return handleInstallation(p, installComponent, cliConfig)
         }
         if p.IsMixins {
-            err := installMixin(p, cliConfig)
-            // ... similar code ...
+            return handleInstallation(p, installMixin, cliConfig)
         }
         // ... rest of the code ...
     }
 }

185-248: Enhance error messages in installMixin.

The error messages could be more descriptive to help with debugging.

-            return fmt.Errorf("local mixin installation not implemented")
+            return fmt.Errorf("local mixin installation not implemented for URI: %s", p.uri)
internal/exec/vendor_model.go (2)

23-58: Add documentation for types and structs.

Consider adding documentation comments for the types and structs to improve code maintainability. This will help other developers understand the purpose and usage of each field.

+// pkgType represents the type of package being vendored
 type pkgType int

+// Package type constants
 const (
     pkgTypeRemote pkgType = iota
     pkgTypeOci
     pkgTypeLocal
 )

+// pkgVendor represents a vendor package with its metadata
 type pkgVendor struct {
     name             string
     version          string
     atmosPackage     *pkgAtmosVendor
     componentPackage *pkgComponentVendor
 }

194-196: Enhance error reporting clarity.

The current message could be clearer about successful vs. failed components. Consider adding more context to help users understand the outcome.

-return doneStyle.Render(fmt.Sprintf("Vendored %d components. Failed to vendor %d components.\n", n-m.failedPkg, m.failedPkg))
+return doneStyle.Render(fmt.Sprintf("Summary: %d components successfully vendored, %d components failed.\nCheck the logs above for details on failed components.\n", n-m.failedPkg, m.failedPkg))
internal/exec/vendor_component_utils.go (1)

98-105: TODO comment needs implementation timeline

The ExecuteStackVendorInternal function is currently a placeholder. Consider adding a timeline for implementation or tracking this in an issue.

Would you like me to create a GitHub issue to track the implementation of the stack vendor functionality?

internal/exec/vendor_utils.go (2)

325-341: Enhance error messages in TUI initialization.

The error messages could be more descriptive to help with troubleshooting:

-			return fmt.Errorf("error initializing model: %v", err)
+			return fmt.Errorf("failed to initialize TUI model: %v (check terminal capabilities)", err)

-			return fmt.Errorf("running atmos vendor internal download error: %w", err)
+			return fmt.Errorf("TUI download process failed: %w", err)

402-414: Add version format validation in validateSourceFields.

Consider adding version format validation to ensure semantic versioning compliance when a version is specified.

 func validateSourceFields(s *schema.AtmosVendorSource, vendorConfigFileName string) error {
+	if s.Version != "" {
+		if !isValidSemver(s.Version) {
+			return fmt.Errorf("invalid version format '%s' in vendor config file '%s'", s.Version, s.File)
+		}
+	}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2e75a5e and 82a1507.

📒 Files selected for processing (4)
  • internal/exec/vendor_component_utils.go (4 hunks)
  • internal/exec/vendor_model.go (1 hunks)
  • internal/exec/vendor_model_component.go (1 hunks)
  • internal/exec/vendor_utils.go (8 hunks)
🧰 Additional context used
📓 Learnings (1)
internal/exec/vendor_component_utils.go (1)
Learnt from: osterman
PR: cloudposse/atmos#768
File: internal/exec/vendor_component_utils.go:354-360
Timestamp: 2024-11-12T05:52:05.088Z
Learning: In the vendoring process, a TTY can exist without being interactive. If the process does not prompt the user, we should not require interactive mode to display the TUI. The `CheckTTYSupport` function should check TTY support on stdout rather than stdin.
🔇 Additional comments (6)
internal/exec/vendor_model_component.go (2)

3-20: LGTM! Well-organized imports.

The imports are clean and appropriate for the task. The Bubble Tea framework imports align well with the PR objective of creating an interactive terminal UI.


35-65: LGTM! Solid initialization logic.

The model initialization is well-implemented with appropriate progress bar configuration and empty package handling.

internal/exec/vendor_model.go (1)

312-328: LGTM! Error handling is well implemented.

The function properly handles different package types and includes appropriate error handling for invalid packages.

internal/exec/vendor_component_utils.go (2)

247-265: LGTM: Clean package type implementation

The package type determination and component package creation is well-structured and follows good practices.


333-346: LGTM: Well-implemented TUI handling

The TUI implementation with fallback for non-TTY environments is clean and user-friendly. Good job on providing a warning message when falling back to basic output.

internal/exec/vendor_utils.go (1)

73-83: LGTM! Flag handling is well-implemented.

The validation logic for the --everything flag is thorough and prevents invalid combinations with other flags.

internal/exec/vendor_model.go Outdated Show resolved Hide resolved
internal/exec/vendor_model.go Show resolved Hide resolved
internal/exec/vendor_component_utils.go Outdated Show resolved Hide resolved
Comment on lines +457 to +513
func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
if strings.HasSuffix(src, ".git") {
return true, nil
}

trimmedSrc := u.TrimBasePathFromPath(tempDir+"/", src)

// Exclude the files that match the 'excluded_paths' patterns
// It supports POSIX-style Globs for file names/paths (double-star `**` is supported)
// https://en.wikipedia.org/wiki/Glob_(programming)
// https://github.com/bmatcuk/doublestar#patterns
for _, excludePath := range s.ExcludedPaths {
excludeMatch, err := u.PathMatch(excludePath, src)
if err != nil {
return true, err
} else if excludeMatch {
// If the file matches ANY of the 'excluded_paths' patterns, exclude the file
u.LogTrace(cliConfig, fmt.Sprintf("Excluding the file '%s' since it matches the '%s' pattern from 'excluded_paths'\n",
trimmedSrc,
excludePath,
))
return true, nil
}
}

// Only include the files that match the 'included_paths' patterns (if any pattern is specified)
if len(s.IncludedPaths) > 0 {
anyMatches := false
for _, includePath := range s.IncludedPaths {
includeMatch, err := u.PathMatch(includePath, src)
if err != nil {
return true, err
} else if includeMatch {
// If the file matches ANY of the 'included_paths' patterns, include the file
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s' since it matches the '%s' pattern from 'included_paths'\n",
trimmedSrc,
includePath,
))
anyMatches = true
break
}
}

if anyMatches {
return false, nil
} else {
u.LogTrace(cliConfig, fmt.Sprintf("Excluding '%s' since it does not match any pattern from 'included_paths'\n", trimmedSrc))
return true, nil
}
}

// If 'included_paths' is not provided, include all files that were not excluded
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s'\n", u.TrimBasePathFromPath(tempDir+"/", src)))
return false, nil
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 12, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add path validation for security.

The skip function should validate paths to prevent path traversal attacks:

 func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
 	return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
+		// Validate path is within project boundary
+		if !strings.HasPrefix(filepath.Clean(src), filepath.Clean(tempDir)) {
+			return true, fmt.Errorf("path %s is outside of project boundary", src)
+		}
+
 		if strings.HasSuffix(src, ".git") {
 			return true, nil
 		}
📝 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
func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
if strings.HasSuffix(src, ".git") {
return true, nil
}
trimmedSrc := u.TrimBasePathFromPath(tempDir+"/", src)
// Exclude the files that match the 'excluded_paths' patterns
// It supports POSIX-style Globs for file names/paths (double-star `**` is supported)
// https://en.wikipedia.org/wiki/Glob_(programming)
// https://github.com/bmatcuk/doublestar#patterns
for _, excludePath := range s.ExcludedPaths {
excludeMatch, err := u.PathMatch(excludePath, src)
if err != nil {
return true, err
} else if excludeMatch {
// If the file matches ANY of the 'excluded_paths' patterns, exclude the file
u.LogTrace(cliConfig, fmt.Sprintf("Excluding the file '%s' since it matches the '%s' pattern from 'excluded_paths'\n",
trimmedSrc,
excludePath,
))
return true, nil
}
}
// Only include the files that match the 'included_paths' patterns (if any pattern is specified)
if len(s.IncludedPaths) > 0 {
anyMatches := false
for _, includePath := range s.IncludedPaths {
includeMatch, err := u.PathMatch(includePath, src)
if err != nil {
return true, err
} else if includeMatch {
// If the file matches ANY of the 'included_paths' patterns, include the file
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s' since it matches the '%s' pattern from 'included_paths'\n",
trimmedSrc,
includePath,
))
anyMatches = true
break
}
}
if anyMatches {
return false, nil
} else {
u.LogTrace(cliConfig, fmt.Sprintf("Excluding '%s' since it does not match any pattern from 'included_paths'\n", trimmedSrc))
return true, nil
}
}
// If 'included_paths' is not provided, include all files that were not excluded
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s'\n", u.TrimBasePathFromPath(tempDir+"/", src)))
return false, nil
}
}
func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
// Validate path is within project boundary
if !strings.HasPrefix(filepath.Clean(src), filepath.Clean(tempDir)) {
return true, fmt.Errorf("path %s is outside of project boundary", src)
}
if strings.HasSuffix(src, ".git") {
return true, nil
}
trimmedSrc := u.TrimBasePathFromPath(tempDir+"/", src)
// Exclude the files that match the 'excluded_paths' patterns
// It supports POSIX-style Globs for file names/paths (double-star `**` is supported)
// https://en.wikipedia.org/wiki/Glob_(programming)
// https://github.com/bmatcuk/doublestar#patterns
for _, excludePath := range s.ExcludedPaths {
excludeMatch, err := u.PathMatch(excludePath, src)
if err != nil {
return true, err
} else if excludeMatch {
// If the file matches ANY of the 'excluded_paths' patterns, exclude the file
u.LogTrace(cliConfig, fmt.Sprintf("Excluding the file '%s' since it matches the '%s' pattern from 'excluded_paths'\n",
trimmedSrc,
excludePath,
))
return true, nil
}
}
// Only include the files that match the 'included_paths' patterns (if any pattern is specified)
if len(s.IncludedPaths) > 0 {
anyMatches := false
for _, includePath := range s.IncludedPaths {
includeMatch, err := u.PathMatch(includePath, src)
if err != nil {
return true, err
} else if includeMatch {
// If the file matches ANY of the 'included_paths' patterns, include the file
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s' since it matches the '%s' pattern from 'included_paths'\n",
trimmedSrc,
includePath,
))
anyMatches = true
break
}
}
if anyMatches {
return false, nil
} else {
u.LogTrace(cliConfig, fmt.Sprintf("Excluding '%s' since it does not match any pattern from 'included_paths'\n", trimmedSrc))
return true, nil
}
}
// If 'included_paths' is not provided, include all files that were not excluded
u.LogTrace(cliConfig, fmt.Sprintf("Including '%s'\n", u.TrimBasePathFromPath(tempDir+"/", src)))
return false, nil
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@aknysh I am not sure if we should Validate path is within project boundary . what do you think about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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

🧹 Outside diff range and nitpick comments (6)
website/docs/cheatsheets/vendoring.mdx (1)

Line range hint 70-76: Consider enhancing the command examples with descriptions, warrior! 🛡️

While the command examples are comprehensive, they would benefit from brief descriptions explaining the purpose of each variant, especially the new --everything flag. This would help users understand when to use each option.

Consider adding descriptions like this:

-    atmos vendor pull --everything
-    atmos vendor pull --component vpc-mixin-1
-    atmos vendor pull -c vpc-mixin-2
-    atmos vendor pull -c vpc-mixin-3
-    atmos vendor pull -c vpc-mixin-4
-    atmos vendor pull --tags test
-    atmos vendor pull --tags networking,storage
+    # Pull all vendor components interactively
+    atmos vendor pull --everything
+    
+    # Pull specific components
+    atmos vendor pull --component vpc-mixin-1
+    atmos vendor pull -c vpc-mixin-2  # Short form
+    
+    # Pull components by tags
+    atmos vendor pull --tags test
+    atmos vendor pull --tags networking,storage
website/docs/cheatsheets/commands.mdx (1)

193-196: Enhance the command description for clarity.

The current description doesn't fully capture the new interactive capabilities introduced by this PR.

Consider updating the description to:

-    <p>Pull sources and mixins from remote repositories for Terraform and Helmfile components and other artifacts</p>
+    <p>Interactive package manager to pull sources and mixins from remote repositories for Terraform and Helmfile components and other artifacts. Supports both TTY and non-TTY environments.</p>
website/docs/cli/commands/vendor/vendor-pull.mdx (3)

Line range hint 117-122: Examples should demonstrate non-TTY usage.

Based on the PR comments about TTY-related issues, it would be helpful to include examples of running the command in non-TTY mode.

Add examples for non-TTY usage:

atmos vendor pull --everything
atmos vendor pull --component vpc
atmos vendor pull -c vpc-flow-logs-bucket
atmos vendor pull -c echo-server --type helmfile
atmos vendor pull --tags dev,test
atmos vendor pull --tags networking --dry-run
+# For CI/CD pipelines or logging
+atmos vendor pull --everything > vendor-pull.log 2>&1

Line range hint 127-135: Add debug logging information.

Given the PR comment about debug logging not being helpful, we should document the debug capabilities.

Consider adding:

  If `vendor.yaml` is not found, Atmos will look for the `component.yaml` manifest in the component's folder. If `component.yaml` is not found,
  an error will be thrown. The flag `--component` is required in this case

+ For troubleshooting, you can enable debug logging:
+ - Use `--logs-level Debug` to see detailed operation logs
+ - In non-TTY environments, logs are automatically written in a more parseable format
+ - The debug output includes progress information for each vendored component
🧰 Tools
🪛 LanguageTool

[grammar] ~130-~130: The word ‘vendor’ is a noun or an adjective. A verb or adverb is missing or misspelled here, or maybe a comma is missing.
Context: ...componentis not specified, Atmos will vendor all the artifacts defined in thevendo...

(PRP_MD_NN)


Line range hint 143-148: Update Flags table with logging options.

The flags table should include logging-related flags mentioned in the PR comments.

Add the following rows to the flags table:

| Flag          | Description                                                                                                  | Alias | Required |
|:--------------|:-------------------------------------------------------------------------------------------------------------|:------|:---------|
| `--component` | Atmos component to pull                                                                                      | `-c`  | no       |
| `--tags`      | Only vendor the components that have the specified tags.<br/>`tags` is a comma-separated values (CSV) string |       | no       |
| `--type`      | Component type: `terraform` or `helmfile` (`terraform` is default)                                           | `-t`  | no       |
| `--dry-run`   | Dry run                                                                                                      |       | no       |
+| `--logs-level`| Set logging level (Debug, Info, Warn, Error)                                                                |       | no       |
website/docs/core-concepts/vendor/vendor-manifest.mdx (1)

414-414: Consider adding non-TTY mode example output.

While the terminal output example is helpful, considering the PR objectives and the comment from user osterman about TTY issues, it would be valuable to include an example of running the command in non-TTY mode.

Add a section showing the command with output redirection:

> atmos vendor pull --everything

Processing vendor config file 'vendor.yaml'
...

+ # Example for non-TTY mode
+ > atmos vendor pull --everything > vendor-pull.log 2>&1
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 82a1507 and e60ecda.

📒 Files selected for processing (4)
  • website/docs/cheatsheets/commands.mdx (1 hunks)
  • website/docs/cheatsheets/vendoring.mdx (1 hunks)
  • website/docs/cli/commands/vendor/vendor-pull.mdx (3 hunks)
  • website/docs/core-concepts/vendor/vendor-manifest.mdx (3 hunks)
🔇 Additional comments (2)
website/docs/core-concepts/vendor/vendor-manifest.mdx (2)

30-30: LGTM! Command update aligns with PR objectives.

The addition of the --everything flag in the documentation accurately reflects the changes introduced in this PR for the interactive package manager interface.


157-157: LGTM! Comprehensive command examples.

The examples effectively demonstrate all supported command variations including the new --everything flag, component-specific pulls, and tag-based filtering.

website/docs/cheatsheets/vendoring.mdx Show resolved Hide resolved
website/docs/cheatsheets/commands.mdx Show resolved Hide resolved
@aknysh
Copy link
Member

aknysh commented Nov 12, 2024

@haitham911 please resolve the conflicts

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

🧹 Outside diff range and nitpick comments (5)
internal/exec/vendor_component_utils.go (3)

98-105: Add more descriptive TODO comment

The TODO comment should provide more context about what needs to be implemented and any specific requirements or considerations.

-// TODO: implement this
+// TODO: Implement stack vendoring support
+// Requirements:
+// - Process all components within the stack
+// - Handle dependencies between components
+// - Support parallel processing where possible

333-346: Consider adding fallback progress indicator

While the code correctly handles non-TTY environments by disabling the TUI renderer, it could benefit from a simple text-based progress indicator for better user experience in headless environments.

 if !CheckTTYSupport() {
     opts = []tea.ProgramOption{tea.WithoutRenderer(), tea.WithInput(nil)}
     u.LogWarning(cliConfig, "TTY is not supported. Running in non-interactive mode")
+    // Add text-based progress handler for non-TTY environments
+    model.EnableTextProgress()
 }

352-356: Update TTY support check function

The function name and comment don't accurately reflect that it checks both stdin and stdout TTY support. Also, the variable names are redundant.

-// CheckTTYSupport checks stdin support TTY.
+// CheckTTYSupport checks if both stdin and stdout support TTY.
 func CheckTTYSupport() bool {
-    stdinTTYStdin := isatty.IsTerminal(os.Stdin.Fd())
-    stdoutTTYStdout := isatty.IsTerminal(os.Stdout.Fd())
+    stdinTTY := isatty.IsTerminal(os.Stdin.Fd())
+    stdoutTTY := isatty.IsTerminal(os.Stdout.Fd())
-    return stdinTTYStdin && stdoutTTYStdout
+    return stdinTTY && stdoutTTY
 }
internal/exec/vendor_utils.go (2)

Line range hint 264-326: Consider adding package validation before processing.

While the package creation logic is sound, consider validating package fields before appending to the packages slice:

  1. Validate target paths for potential conflicts
  2. Check for duplicate package names within the same source

328-344: Enhance error handling for TUI initialization.

The TUI initialization could be improved:

  1. The error message on line 339 could be more descriptive
  2. Consider adding debug logging for non-TTY mode
-			return fmt.Errorf("error initializing model: %v", err)
+			return fmt.Errorf("failed to initialize TUI model: %v (check terminal capabilities)", err)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e60ecda and 6430189.

📒 Files selected for processing (2)
  • internal/exec/vendor_component_utils.go (4 hunks)
  • internal/exec/vendor_utils.go (8 hunks)
🔇 Additional comments (4)
internal/exec/vendor_component_utils.go (2)

111-112: Fix .git directory exclusion logic

The current implementation may incorrectly exclude files or directories ending with .git. This was previously flagged in past reviews but hasn't been addressed.

-if strings.HasSuffix(src, ".git") {
+if filepath.Base(src) == ".git" {
   return true, nil
}

247-265: Enhance package type determination

The package type determination logic is clear and follows a good pattern. The code effectively handles OCI, local, and remote package types.

internal/exec/vendor_utils.go (2)

73-83: LGTM! Flag handling is well-implemented.

The validation logic for the --everything flag is thorough and prevents invalid flag combinations. The error messages are clear and helpful.


460-516: ⚠️ Potential issue

Add path validation for security.

The skip function should validate paths to prevent path traversal attacks:

 func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
 	return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
+		// Validate path is within project boundary
+		if !strings.HasPrefix(filepath.Clean(src), filepath.Clean(tempDir)) {
+			return true, fmt.Errorf("path %s is outside of project boundary", src)
+		}
+
 		if strings.HasSuffix(src, ".git") {
 			return true, nil
 		}

Likely invalid or redundant comment.

internal/exec/vendor_component_utils.go Show resolved Hide resolved
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

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6430189 and 7ac6601.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • go.mod (2 hunks)
  • internal/exec/vendor_model.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • go.mod
🔇 Additional comments (4)
internal/exec/vendor_model.go (4)

246-247: LGTM: Proper timeout handling for downloads.

The 30-minute timeout with proper context handling is a good practice to prevent hanging downloads.


320-328: LGTM: Proper error handling for invalid packages.

The error handling for cases where no valid package is provided is implemented correctly with appropriate error logging.


240-244: ⚠️ Potential issue

Fix error handling and improve temp directory creation.

The error return type is inconsistent with the expected installedPkgMsg, and the temporary directory name could be more descriptive and secure.

-tempDir, err := os.MkdirTemp("", strconv.FormatInt(time.Now().Unix(), 10))
+tempDir, err := os.MkdirTemp("", fmt.Sprintf("atmos-vendor-%d-*", time.Now().Unix()))
if err != nil {
    u.LogTrace(cliConfig, fmt.Sprintf("Failed to create temp directory %s", err))
-    return err
+    return installedPkgMsg{
+        err:  fmt.Errorf("failed to create temp directory: %w", err),
+        name: p.name,
+    }
}

Likely invalid or redundant comment.


277-281: ⚠️ Potential issue

Enhance symlink security in local file copy.

The current implementation follows symlinks without validation, which could potentially lead to security vulnerabilities if symlinks point to sensitive locations outside the intended directory structure.

✅ Verification successful

Symlink handling in local file copy is secure.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potentially dangerous symlinks in the codebase
find . -type l -exec ls -l {} \; | grep -v "^l.*-> \."

Length of output: 59


Script:

#!/bin/bash
# Find symlinks that point outside the repository root
repo_root=$(pwd)
find . -type l | while read -r symlink; do
    target=$(readlink -f "$symlink")
    if [[ $target != $repo_root* ]]; then
        echo "$symlink -> $target"
    fi
done

Length of output: 220

internal/exec/vendor_model.go Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (4)
internal/exec/vendor_utils.go (4)

74-83: Consider making the default behavior more explicit.

The default behavior setting for the everything flag could be more explicit. Consider extracting this logic into a named function for better clarity.

-// If neither `everything`, `component`, `stack`, nor `tags` flags are set, default to `everything = true`
-if !everything && !flags.Changed("everything") && component == "" && stack == "" && len(tags) == 0 {
-    everything = true
-}
+func shouldDefaultToEverything(flags *pflag.FlagSet, everything bool, component, stack string, tags []string) bool {
+    return !everything && !flags.Changed("everything") && component == "" && stack == "" && len(tags) == 0
+}
+
+everything = shouldDefaultToEverything(flags, everything, component, stack, tags)

100-102: Enhance error message with actionable guidance.

The error message could be more helpful by suggesting what the user should do next.

-return fmt.Errorf("the '--everything' flag is set, but the vendor config file '%s' does not exist", cfg.AtmosVendorConfigFileName)
+return fmt.Errorf("the '--everything' flag is set, but the vendor config file '%s' does not exist. Create this file first or specify a component using '--component'", cfg.AtmosVendorConfigFileName)

343-344: Enhance error message for model initialization failure.

The error message could be more descriptive to help with troubleshooting.

-return fmt.Errorf("error initializing model: %v", err)
+return fmt.Errorf("failed to initialize TUI model: %v (check terminal capabilities and permissions)", err)

337-341: Enhance non-TTY mode logging.

The non-TTY mode could benefit from more detailed logging to help users understand what's happening.

if !CheckTTYSupport() {
    opts = []tea.ProgramOption{tea.WithoutRenderer(), tea.WithInput(nil)}
-    u.LogWarning(cliConfig, "TTY is not supported. Running in non-interactive mode")
+    u.LogWarning(cliConfig, "No TTY detected. Falling back to basic output. This can happen when no terminal is attached or when commands are pipelined.")
+    u.LogInfo(cliConfig, "Progress will be logged as regular output messages")
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac6601 and 7757b44.

📒 Files selected for processing (1)
  • internal/exec/vendor_utils.go (8 hunks)
🔇 Additional comments (1)
internal/exec/vendor_utils.go (1)

465-521: ⚠️ Potential issue

Add path validation for security.

The skip function should validate paths to prevent path traversal attacks.

func generateSkipFunction(cliConfig schema.CliConfiguration, tempDir string, s *schema.AtmosVendorSource) func(os.FileInfo, string, string) (bool, error) {
    return func(srcInfo os.FileInfo, src, dest string) (bool, error) {
+        // Validate path is within project boundary
+        cleanSrc := filepath.Clean(src)
+        cleanTemp := filepath.Clean(tempDir)
+        if !strings.HasPrefix(cleanSrc, cleanTemp) {
+            return true, fmt.Errorf("path %s is outside of project boundary", src)
+        }
+
        if strings.HasSuffix(src, ".git") {

Likely invalid or redundant comment.

Comment on lines +522 to +528
func validateURI(uri string) error {
if uri == "" {
return fmt.Errorf("URI cannot be empty")
}
// Add more validation as needed
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance URI validation.

The URI validation could be more comprehensive to catch potential issues early.

func validateURI(uri string) error {
    if uri == "" {
        return fmt.Errorf("URI cannot be empty")
    }
-    // Add more validation as needed
+    // Validate URI format
+    if strings.Contains(uri, "..") {
+        return fmt.Errorf("URI cannot contain path traversal sequences")
+    }
+    if strings.Contains(uri, " ") {
+        return fmt.Errorf("URI cannot contain spaces")
+    }
+    // Validate scheme
+    if strings.HasPrefix(uri, "oci://") {
+        if !strings.Contains(uri[6:], "/") {
+            return fmt.Errorf("invalid OCI URI format")
+        }
+    }
    return nil
}
📝 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
func validateURI(uri string) error {
if uri == "" {
return fmt.Errorf("URI cannot be empty")
}
// Add more validation as needed
return nil
}
func validateURI(uri string) error {
if uri == "" {
return fmt.Errorf("URI cannot be empty")
}
// Validate URI format
if strings.Contains(uri, "..") {
return fmt.Errorf("URI cannot contain path traversal sequences")
}
if strings.Contains(uri, " ") {
return fmt.Errorf("URI cannot contain spaces")
}
// Validate scheme
if strings.HasPrefix(uri, "oci://") {
if !strings.Contains(uri[6:], "/") {
return fmt.Errorf("invalid OCI URI format")
}
}
return nil
}

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

🧹 Outside diff range and nitpick comments (4)
internal/exec/vendor_model_component.go (4)

22-33: Consider adding field validation

The pkgComponentVendor struct looks well-organized, but consider adding validation for critical fields like uri and componentPath. This could prevent issues with invalid paths or URIs later in the installation process.

+func (p *pkgComponentVendor) validate() error {
+    if p.uri == "" {
+        return fmt.Errorf("uri cannot be empty")
+    }
+    if p.componentPath == "" {
+        return fmt.Errorf("componentPath cannot be empty")
+    }
+    return nil
+}

35-65: Consider dynamic progress bar width

The progress bar width is currently fixed at 30. Consider making it dynamic based on the terminal width for better user experience.

+    width, _, err := term.GetSize(int(os.Stdout.Fd()))
+    if err != nil {
+        width = 30 // fallback width
+    }
     p := progress.New(
         progress.WithDefaultGradient(),
-        progress.WithWidth(30),
+        progress.WithWidth(width/3), // Use a third of terminal width
         progress.WithoutPercentage(),
     )

112-184: Enhance error context with wrapping

Consider wrapping errors with additional context to make debugging easier.

     if err = client.Get(); err != nil {
         u.LogTrace(cliConfig, fmt.Sprintf("Failed to download package %s error %s", p.name, err))
-        return err
+        return fmt.Errorf("failed to download package %s: %w", p.name, err)
     }

185-248: Consider reducing code duplication with component installation

The mixin installation shares similar code patterns with component installation, particularly around temporary directory creation and cleanup. Consider extracting these common patterns into helper functions.

+func createTempDir(cliConfig schema.CliConfiguration) (string, func(), error) {
+    tempDir, err := os.MkdirTemp("", strconv.FormatInt(time.Now().Unix(), 10))
+    if err != nil {
+        return "", nil, fmt.Errorf("failed to create temp directory: %w", err)
+    }
+    cleanup := func() {
+        removeTempDir(cliConfig, tempDir)
+    }
+    return tempDir, cleanup, nil
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7757b44 and e1f1fff.

📒 Files selected for processing (2)
  • internal/exec/vendor_model.go (1 hunks)
  • internal/exec/vendor_model_component.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/vendor_model.go
🔇 Additional comments (1)
internal/exec/vendor_model_component.go (1)

67-111: LGTM! Download and installation logic looks solid

The function handles both components and mixins appropriately, with good error handling and dry run support.

Comment on lines +3 to +20
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"time"

"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
"github.com/hashicorp/go-getter"
cp "github.com/otiai10/copy"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

SECURITY: Replace go-getter with a more secure alternative

The codebase is using the latest version of go-getter (v1.7.6), but this version still contains critical security vulnerabilities. Since this package is used for downloading and installing components, these vulnerabilities pose significant security risks:

  • Remote code execution via Git config manipulation
  • Argument injection in Git operations
  • Resource exhaustion attacks
  • Data amplification issues

Consider replacing go-getter with more secure alternatives:

  • go-git for Git operations
  • Native Go HTTP client with proper security controls
  • archive/zip and archive/tar for archive handling
🔗 Analysis chain

Verify dependency version constraints in go.mod

The imports look appropriate for the functionality. However, we should ensure version constraints are properly set in go.mod, especially for critical dependencies like go-getter and charmbracelet libraries.


Critical security vulnerabilities found in go-getter dependency

The verification revealed multiple high-severity vulnerabilities in the github.com/hashicorp/go-getter dependency:

  • CRITICAL: Argument injection vulnerability in Git branch fetching
  • HIGH: Code execution vulnerability via Git config manipulation
  • HIGH: Resource exhaustion issues in unsafe downloads
  • MODERATE: Data amplification vulnerability

Let's verify if there's a newer version available that addresses these issues:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check dependency versions and known vulnerabilities
if [ -f "go.mod" ]; then
    echo "Checking go.mod for dependency versions..."
    grep -A 5 "require (" go.mod || grep "require " go.mod
    
    # Check for known vulnerabilities using GitHub API
    for pkg in "github.com/hashicorp/go-getter" "github.com/charmbracelet/bubbles" "github.com/otiai10/copy"; do
        echo "Checking vulnerabilities for $pkg..."
        gh api graphql -f query='{
            securityVulnerabilities(first:5, ecosystem:GO, package:"'$pkg'") {
                nodes { advisory { severity summary } }
            }
        }'
    done
fi

Length of output: 2745


Script:

#!/bin/bash
# Check current go-getter version and latest available
echo "Current version in go.mod:"
grep "github.com/hashicorp/go-getter" go.mod

echo -e "\nLatest release info:"
gh api repos/hashicorp/go-getter/releases/latest --jq '.tag_name + " (" + .published_at + ")\n" + .body'

Length of output: 662

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor New features that do not break anything
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants