Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 7, 2026

Enables automatic detection of Bicep or Terraform as the infrastructure provider when infra.provider is not explicitly set in azure.yaml. The detection scans the infra directory for .bicep/.bicepparam or .tf/.tfvars files.

Implementation

Added detectProviderFromFiles function

  • Scans infra directory for IaC file extensions
  • Returns provisioning.Bicep if only Bicep files present
  • Returns provisioning.Terraform if only Terraform files present
  • Returns error if both types exist (requires explicit configuration)
  • Returns provisioning.NotSpecified for empty/non-existent directories
  • Ignores subdirectories

Updated ProjectInfrastructure function

  • Calls auto-detection after resolving infraRoot path when infraOptions.Provider == provisioning.NotSpecified
  • Logs detected provider: auto-detected infrastructure provider: <bicep|terraform>
  • Runs before existing layer/module checks, preserving all existing behavior

Example behavior

// Project with infra/main.bicep
// No infra.provider in azure.yaml
infra, err := im.ProjectInfrastructure(ctx, projectConfig)
// Logs: "auto-detected infrastructure provider: bicep"
// infra.Options.Provider == provisioning.Bicep

// Project with infra/main.tf
// Logs: "auto-detected infrastructure provider: terraform"
// infra.Options.Provider == provisioning.Terraform

// Project with both infra/main.bicep and infra/main.tf
// Returns error: "both Bicep and Terraform files detected in /path/to/infra. 
//                Please specify 'infra.provider' in azure.yaml..."

Backward Compatibility

Explicit infra.provider in azure.yaml always takes precedence. Auto-detection only runs when provider is NotSpecified. All existing projects continue to work unchanged.

Original prompt

Problem

Issue #564 requests auto-detection of the infrastructure provider (Bicep vs Terraform) based on files present in the infra folder, instead of requiring users to explicitly set infra.provider in azure.yaml.

Implementation Requirements

Add detectProviderFromFiles function in cli/azd/pkg/project/importer.go

Create a new function that:

  • Scans the infra directory for IaC files
  • Detects .bicep and .bicepparam files for Bicep
  • Detects .tf and .tfvars files for Terraform
  • Returns provisioning.Bicep if only Bicep files found
  • Returns provisioning.Terraform if only Terraform files found
  • Returns error with clear message if BOTH Bicep and Terraform files are found (user must specify in azure.yaml)
  • Returns provisioning.NotSpecified if no IaC files found or if directory doesn't exist
  • Should ignore directories, only check files
  • Should handle os.IsNotExist errors gracefully
// detectProviderFromFiles scans the infra directory and detects the IaC provider
// based on file extensions present. Returns an error if both bicep and terraform files exist.
func detectProviderFromFiles(infraPath string) (provisioning.ProviderKind, error) {
    files, err := os.ReadDir(infraPath)
    if err != nil {
        if os.IsNotExist(err) {
            return provisioning.NotSpecified, nil
        }
        return provisioning.NotSpecified, fmt.Errorf("reading infra directory: %w", err)
    }

    hasBicep := false
    hasTerraform := false

    for _, file := range files {
        if file.IsDir() {
            continue
        }
        
        ext := filepath.Ext(file.Name())
        switch ext {
        case ".bicep", ".bicepparam":
            hasBicep = true
        case ".tf", ".tfvars":
            hasTerraform = true
        }
        
        // Early exit if both found
        if hasBicep && hasTerraform {
            break
        }
    }

    // Decision logic
    switch {
    case hasBicep && hasTerraform:
        return provisioning.NotSpecified, fmt.Errorf(
            "both Bicep and Terraform files detected in %s. "+
            "Please specify 'infra.provider' in azure.yaml as either 'bicep' or 'terraform'",
            infraPath)
    case hasBicep:
        return provisioning.Bicep, nil
    case hasTerraform:
        return provisioning.Terraform, nil
    default:
        return provisioning.NotSpecified, nil
    }
}

Update ProjectInfrastructure function in cli/azd/pkg/project/importer.go

Modify the existing function to call detectProviderFromFiles when provider is not explicitly set:

  1. After getting infraOptions with defaults
  2. After computing the absolute infraRoot path
  3. Before checking for layers or modules
  4. Add this logic:
// Auto-detect provider if not explicitly set
if infraOptions.Provider == provisioning.NotSpecified {
    detectedProvider, err := detectProviderFromFiles(infraRoot)
    if err != nil {
        return nil, err
    }
    if detectedProvider != provisioning.NotSpecified {
        log.Printf("auto-detected infrastructure provider: %s", detectedProvider)
        infraOptions.Provider = detectedProvider
    }
}

This should be inserted after the existing code that resolves infraRoot to an absolute path and before the "short-circuit: If layers are defined" comment.

Add Unit Tests in cli/azd/pkg/project/importer_test.go

Create comprehensive tests for the new detectProviderFromFiles function:

  1. Test case: Only Bicep files present → returns provisioning.Bicep
  2. Test case: Only Terraform files present → returns provisioning.Terraform
  3. Test case: Both Bicep and Terraform files → returns error
  4. Test case: No IaC files present → returns provisioning.NotSpecified
  5. Test case: Directory doesn't exist → returns provisioning.NotSpecified (no error)
  6. Test case: Mixed file types with subdirectories (should ignore dirs)

Each test should:

  • Create a temporary directory structure
  • Populate with appropriate test files
  • Call detectProviderFromFiles
  • Assert the expected return value
  • Clean up temp directories

Acceptance Criteria

  • ✅ Function auto-detects Bicep when only .bicep or .bicepparam files exist
  • ✅ Function auto-detects Terraform when only .tf or .tfvars files exist
  • ✅ Function returns clear error when both types are present
  • ��� Backward compatible: explicit infra.provider in azure.yaml still honored
  • ✅ Non-breaking: existing behavior preserved when provider is specified
  • ✅ Follows DRY principles with minimal code changes
  • ✅ Unit tests cover all scenarios
  • ✅ Log message indicates when auto-detection is used

Related Issue

Fixes #564

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Add function to detect infrastructure provider from files Auto-detect infrastructure provider from infra directory files Jan 7, 2026
Copilot AI requested a review from spboyer January 7, 2026 13:28
@spboyer spboyer marked this pull request as ready for review January 9, 2026 18:57
Copilot AI review requested due to automatic review settings January 9, 2026 18:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements automatic detection of infrastructure providers (Bicep or Terraform) when infra.provider is not explicitly set in azure.yaml. The detection scans the infra directory for characteristic file extensions to determine the appropriate provider.

Key changes:

  • Added detectProviderFromFiles function that scans infra directory for .bicep/.bicepparam or .tf/.tfvars files
  • Updated ProjectInfrastructure to automatically detect and set the provider when not specified
  • Added comprehensive unit tests covering all detection scenarios including error cases

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
cli/azd/pkg/project/importer.go Adds detectProviderFromFiles function and integrates auto-detection into ProjectInfrastructure workflow
cli/azd/pkg/project/importer_test.go Adds comprehensive table-driven unit tests for provider detection, including edge cases for mixed files, empty directories, and directory exclusion
.gitignore Adds cli/azd/azd binary to gitignore (duplicate entry)

Comment on lines +775 to +896
func TestDetectProviderFromFiles(t *testing.T) {
tests := []struct {
name string
files []string
expectedResult provisioning.ProviderKind
expectError bool
errorContains string
}{
{
name: "only bicep files",
files: []string{"main.bicep", "modules.bicep"},
expectedResult: provisioning.Bicep,
expectError: false,
},
{
name: "only bicepparam files",
files: []string{"main.bicepparam"},
expectedResult: provisioning.Bicep,
expectError: false,
},
{
name: "only terraform files",
files: []string{"main.tf", "variables.tf"},
expectedResult: provisioning.Terraform,
expectError: false,
},
{
name: "only tfvars files",
files: []string{"terraform.tfvars"},
expectedResult: provisioning.Terraform,
expectError: false,
},
{
name: "both bicep and terraform files",
files: []string{"main.bicep", "main.tf"},
expectedResult: provisioning.NotSpecified,
expectError: true,
errorContains: "both Bicep and Terraform files detected",
},
{
name: "no IaC files",
files: []string{"readme.md", "config.json"},
expectedResult: provisioning.NotSpecified,
expectError: false,
},
{
name: "empty directory",
files: []string{},
expectedResult: provisioning.NotSpecified,
expectError: false,
},
{
name: "mixed with bicep and non-IaC files",
files: []string{"main.bicep", "readme.md", "config.json"},
expectedResult: provisioning.Bicep,
expectError: false,
},
{
name: "mixed with terraform and non-IaC files",
files: []string{"main.tf", "readme.md", "LICENSE"},
expectedResult: provisioning.Terraform,
expectError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create temporary directory
tmpDir, err := os.MkdirTemp("", "test-detect-provider-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

// Create test files
for _, fileName := range tt.files {
filePath := filepath.Join(tmpDir, fileName)
err := os.WriteFile(filePath, []byte("test content"), 0600)
require.NoError(t, err)
}

// Test detectProviderFromFiles
result, err := detectProviderFromFiles(tmpDir)

if tt.expectError {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errorContains)
} else {
require.NoError(t, err)
}

require.Equal(t, tt.expectedResult, result)
})
}
}

func TestDetectProviderFromFilesNonExistentDirectory(t *testing.T) {
// Test with non-existent directory
result, err := detectProviderFromFiles("/nonexistent/path/that/does/not/exist")
require.NoError(t, err, "should not error when directory doesn't exist")
require.Equal(t, provisioning.NotSpecified, result)
}

func TestDetectProviderFromFilesIgnoresDirectories(t *testing.T) {
// Create temporary directory structure
tmpDir, err := os.MkdirTemp("", "test-detect-provider-dirs-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

// Create subdirectories with IaC-like names
err = os.Mkdir(filepath.Join(tmpDir, "main.bicep"), 0755)
require.NoError(t, err)
err = os.Mkdir(filepath.Join(tmpDir, "main.tf"), 0755)
require.NoError(t, err)

// Create a real Bicep file
err = os.WriteFile(filepath.Join(tmpDir, "resources.bicep"), []byte("test"), 0600)
require.NoError(t, err)

// Should detect Bicep and ignore directories
result, err := detectProviderFromFiles(tmpDir)
require.NoError(t, err)
require.Equal(t, provisioning.Bicep, result)
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

While detectProviderFromFiles is well-tested in isolation, there's no integration test that verifies the complete auto-detection flow through ProjectInfrastructure. Consider adding a test similar to TestImportManagerProjectInfrastructure that creates a temporary project directory with Bicep or Terraform files and verifies that ProjectInfrastructure correctly auto-detects and sets the provider when infra.provider is not specified in the project configuration.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto detect IaC provider based on files in infra folder

2 participants