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

normalize windows path #884

Closed
wants to merge 12 commits into from
91 changes: 88 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,66 @@ jobs:

timeout-minutes: 20
steps:
- name: Download build artifacts
- name: Create atmos directory (Windows)
if: matrix.flavor.os == 'windows-latest'
shell: pwsh
run: |
mkdir -Force "D:\atmos"
Write-Host "Created D:\atmos directory"
Write-Host "Current directory contents:"
Get-ChildItem "D:\"

- name: Download build artifacts (Windows)
if: matrix.flavor.os == 'windows-latest'
uses: actions/download-artifact@v4
with:
name: build-artifacts-${{ matrix.flavor.target }}
path: D:\atmos

- name: List Windows artifacts
if: matrix.flavor.os == 'windows-latest'
shell: pwsh
run: |
Write-Host "D:\atmos contents:"
Get-ChildItem -Path "D:\atmos" -Recurse
Write-Host "Current PATH: $env:PATH"

- name: Setup atmos (Windows)
if: matrix.flavor.os == 'windows-latest'
shell: pwsh
run: |
$atmosFile = Get-ChildItem -Path "D:\atmos" -Filter "atmos*" -Recurse | Select-Object -First 1
if ($atmosFile) {
Write-Host "Found atmos at: $($atmosFile.FullName)"
Write-Host "File details:"
$atmosFile | Format-List *
Move-Item -Path $atmosFile.FullName -Destination "D:\atmos\atmos.exe" -Force
$env:PATH = "D:\atmos;" + $env:PATH
[Environment]::SetEnvironmentVariable("Path", $env:PATH, [System.EnvironmentVariableTarget]::User)
Write-Host "Moved atmos to D:\atmos\atmos.exe"
Write-Host "Updated PATH: $env:PATH"
if (Test-Path "D:\atmos\atmos.exe") {
Write-Host "Successfully created atmos.exe"
} else {
Write-Error "Failed to create atmos.exe"
exit 1
}
} else {
Write-Host "Contents of D:\atmos:"
Get-ChildItem -Path "D:\atmos" -Recurse
Write-Error "Could not find atmos executable"
exit 1
}

- name: Download build artifacts (Unix)
if: matrix.flavor.os != 'windows-latest'
uses: actions/download-artifact@v4
with:
name: build-artifacts-${{ matrix.flavor.target }}
path: /usr/local/bin

- name: Set execute permissions on atmos
- name: Setup atmos (Unix)
if: matrix.flavor.os != 'windows-latest'
run: chmod +x /usr/local/bin/atmos

- name: Check out code into the Go module directory
Expand All @@ -296,11 +349,43 @@ jobs:
terraform_version: ${{ env.TERRAFORM_VERSION }}
terraform_wrapper: false

- name: Run tests for ${{ matrix.demo-folder }}
- name: Run tests (Windows)
if: matrix.flavor.os == 'windows-latest'
shell: pwsh
env:
PATH: D:\atmos;${{ env.PATH }}
run: |
Write-Host "Verifying atmos.exe exists:"
if (Test-Path "D:\atmos\atmos.exe") {
Write-Host "atmos.exe found at D:\atmos\atmos.exe"
Write-Host "File details:"
Get-Item "D:\atmos\atmos.exe" | Format-List *
} else {
Write-Error "atmos.exe not found!"
exit 1
}
Write-Host "Current PATH: $env:PATH"
Write-Host "Testing atmos command..."
& "D:\atmos\atmos.exe" version
cd examples/${{ matrix.demo-folder }}
& "D:\atmos\atmos.exe" test

- name: Run tests (Unix)
if: matrix.flavor.os != 'windows-latest'
run: |
cd examples/${{ matrix.demo-folder }}
atmos test

- name: Upload test logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs-${{ matrix.flavor.target }}-${{ matrix.demo-folder }}
path: |
examples/${{ matrix.demo-folder }}/**/*.log
examples/${{ matrix.demo-folder }}/**/terraform.tfstate
examples/${{ matrix.demo-folder }}/**/terraform.tfstate.backup

# run other demo tests
lint:
name: "[lint] ${{ matrix.demo-folder }}"
Expand Down
2 changes: 2 additions & 0 deletions internal/exec/vendor_component_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func ReadAndProcessComponentVendorConfigFile(
return componentConfig, "", fmt.Errorf("type '%s' is not supported. Valid types are 'terraform' and 'helmfile'", componentType)
}

// Normalize component path for cross-platform compatibility
component = filepath.FromSlash(component)
componentPath := filepath.Join(atmosConfig.BasePath, componentBasePath, component)

dirExists, err := u.IsDirectory(componentPath)
Expand Down
62 changes: 53 additions & 9 deletions internal/exec/vendor_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"

Expand Down Expand Up @@ -234,6 +236,7 @@ func max(a, b int) int {
}
return b
}

func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.AtmosConfiguration) tea.Cmd {
return func() tea.Msg {
if dryRun {
Expand All @@ -245,14 +248,15 @@ func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.Atmos
}
}

// Create temp directory
tempDir, err := os.MkdirTemp("", fmt.Sprintf("atmos-vendor-%d-*", time.Now().Unix()))
// Create temp directory with a safe name for Windows
tempDir, err := os.MkdirTemp("", fmt.Sprintf("atmos-vendor-%d", time.Now().Unix()))
if err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to create temp directory: %w", err),
name: p.name,
}
}

// Ensure directory permissions are restricted
if err := os.Chmod(tempDir, 0700); err != nil {
return installedPkgMsg{
Expand All @@ -265,25 +269,45 @@ func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.Atmos
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

// Handle Windows path separators
uri := p.uri
if runtime.GOOS == "windows" {
uri = strings.ReplaceAll(uri, "\\", "/")
// Extract ref from URL if present
if strings.Contains(uri, "?ref=") {
parts := strings.Split(uri, "?ref=")
uri = parts[0]
tempDir = filepath.Join(tempDir, filepath.Base(uri))
// Set up git clone with specific ref
if err := runGitClone(ctx, parts[0], parts[1], tempDir); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to clone repository: %w", err),
name: p.name,
}
}
goto CopyToTarget
}
}

switch p.pkgType {
case pkgTypeRemote:
// Use go-getter to download remote packages
client := &getter.Client{
Ctx: ctx,
Dst: tempDir,
Src: p.uri,
Src: uri,
Mode: getter.ClientModeAny,
}
if err := client.Get(); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to download package: %w", err),
err: fmt.Errorf("failed to download package %s: %w", p.name, err),
name: p.name,
}
}

case pkgTypeOci:
// Process OCI images
if err := processOciImage(atmosConfig, p.uri, tempDir); err != nil {
if err := processOciImage(atmosConfig, uri, tempDir); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to process OCI image: %w", err),
name: p.name,
Expand All @@ -298,9 +322,9 @@ func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.Atmos
OnSymlink: func(src string) cp.SymlinkAction { return cp.Deep },
}
if p.sourceIsLocalFile {
tempDir = filepath.Join(tempDir, filepath.Base(p.uri))
tempDir = filepath.Join(tempDir, filepath.Base(uri))
}
if err := cp.Copy(p.uri, tempDir, copyOptions); err != nil {
if err := cp.Copy(uri, tempDir, copyOptions); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to copy package: %w", err),
name: p.name,
Expand All @@ -311,9 +335,10 @@ func downloadAndInstall(p *pkgAtmosVendor, dryRun bool, atmosConfig schema.Atmos
err: fmt.Errorf("unknown package type %s for package %s", p.pkgType.String(), p.name),
name: p.name,
}

}
if err := copyToTarget(atmosConfig, tempDir, p.targetPath, &p.atmosVendorSource, p.sourceIsLocalFile, p.uri); err != nil {

CopyToTarget:
if err := copyToTarget(atmosConfig, tempDir, p.targetPath, &p.atmosVendorSource, p.sourceIsLocalFile, uri); err != nil {
return installedPkgMsg{
err: fmt.Errorf("failed to copy package: %w", err),
name: p.name,
Expand Down Expand Up @@ -344,3 +369,22 @@ func ExecuteInstall(installer pkgVendor, dryRun bool, atmosConfig schema.AtmosCo
}
}
}

func runGitClone(ctx context.Context, repoURL, ref, destPath string) error {
// Initialize git command
cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--branch", ref, repoURL, destPath)
cmd.Env = os.Environ()

// Capture both stdout and stderr
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("git clone failed: %w\nOutput: %s", err, string(output))
}

// Verify the clone was successful
if _, err := os.Stat(filepath.Join(destPath, ".git")); err != nil {
return fmt.Errorf("git clone appeared to succeed but .git directory not found: %w", err)
}

return nil
}
10 changes: 7 additions & 3 deletions internal/exec/vendor_model_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,16 @@ func installComponent(p *pkgComponentVendor, atmosConfig schema.AtmosConfigurati

switch p.pkgType {
case pkgTypeRemote:
tempDir = filepath.Join(tempDir, filepath.Base(p.uri))
// Convert Windows backslashes to forward slashes for go-getter
uri := filepath.ToSlash(p.uri)
tempDir = filepath.Join(tempDir, filepath.Base(uri))

client := &getter.Client{
Ctx: context.Background(),
// Define the destination where the files will be stored. This will create the directory if it doesn't exist
Dst: tempDir,
// Source
Src: p.uri,
Src: uri,
Mode: getter.ClientModeAny,
}

Expand Down Expand Up @@ -187,10 +189,12 @@ func installMixin(p *pkgComponentVendor, atmosConfig schema.AtmosConfiguration)
defer cancel()
switch p.pkgType {
case pkgTypeRemote:
// Convert Windows backslashes to forward slashes for go-getter
uri := filepath.ToSlash(p.uri)
client := &getter.Client{
Ctx: ctx,
Dst: filepath.Join(tempDir, p.mixinFilename),
Src: p.uri,
Src: uri,
Mode: getter.ClientModeFile,
}

Expand Down
Loading
Loading