Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Sep 30, 2025

Elasticsearch Alias Resource - All PR Feedback Addressed ✅

This PR completely implements the requested alias resource with all feedback addressed, comprehensive refactoring completed, and all tests passing.

🏗️ Schema Design with Nested Attributes

Proper Plugin Framework Implementation: Uses nested attributes instead of blocks, following Terraform plugin framework best practices:

resource "elasticstack_elasticsearch_alias" "my-index" {
  name = "my-index"
  
  write_index = {
    name = "my-index-write"
    filter = jsonencode({term = {status = "active"}})
    index_routing = "user-1"
  }
  
  read_indices = [
    {
      name = "my-index-archive-2024"
      filter = jsonencode({range = {date = {gte = "2024-01-01"}}})
    },
    {
      name = "my-index-archive-2023"
    }
  ]
}

Atomic Operations & Safety

  • No Data Loss: Uses UpdateAliasesAtomic API for safe write index transitions
  • Precise Actions: Only removes/adds indices that actually changed
  • Write Index Management: Properly handles single write index constraint
  • Validation: Prevents write index from appearing in read indices

🔧 Code Quality Improvements (All PR Feedback Addressed)

API Functions (Comments on index.go):

  • Error Handling: Updated to use CheckErrorFromFW directly instead of SDK conversions
  • Cleanup: Removed unused PutAlias and DeleteAlias functions
  • Consistency: Improved error handling patterns across all functions

Models Refactoring (Comments on models.go):

  • Model Consolidation: Merged writeIndexModel and readIndexModel into single indexModel
  • Code Deduplication: Extracted indexFromAlias helper for API→model conversion
  • Helper Functions: Created indexToConfig for model→config conversion
  • Schema Integration: Attribute types now derived from schema using getIndexAttrTypes()

Resource Validation (Comments on resource.go):

  • Early Returns: Flattened ValidateConfig function with early returns
  • Cleaner Logic: Simplified nested validation structure
  • Better Error Handling: Improved diagnostic collection and reporting

General Improvements:

  • Documentation: Updated description with proper HTML link formatting
  • Redundancy Removal: Eliminated unnecessary checks and code paths
  • Schema Compliance: All attribute types properly derived from schema definition

🧪 Comprehensive Test Coverage - ALL PASSING

All Scenarios Covered:

  • ✅ Write index transitions (single → multiple → removal)
  • ✅ Filter and routing configurations
  • ✅ Data stream compatibility
  • ✅ Hidden index support
  • ✅ Direct index creation to avoid Terraform resource conflicts
  • ✅ Proper variable declarations for ConfigVariables
  • ✅ Proper cleanup and destroy verification

Test Results:

=== RUN   TestAccResourceAlias
--- PASS: TestAccResourceAlias (2.06s)
=== RUN   TestAccResourceAliasWriteIndex
--- PASS: TestAccResourceAliasWriteIndex (2.07s)
=== RUN   TestAccResourceAliasDataStream
--- PASS: TestAccResourceAliasDataStream (0.83s)
PASS
ok  	github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/alias	4.976s

🛡️ Quality Assurance - ALL PASSING

  • ✅ All unit tests passing
  • ✅ All acceptance tests passing against Stack resources
  • Linting clean (0 issues)
  • All PR feedback addressed
  • Proper schema design (nested attributes, not blocks)
  • Code refactored and deduplicated
  • ✅ Documentation auto-generated
  • ✅ Code follows repository patterns
  • ✅ Plugin framework implementation

🎯 Addresses All Original Requirements

  • ✅ Located in internal/elasticsearch/index/alias
  • ✅ Full API coverage (Create/Update, Get, Delete)
  • ✅ Read-after-write for state consistency
  • ✅ Comprehensive acceptance test coverage with Stack environment
  • ✅ Plugin framework implementation with proper nested attributes
  • ✅ Atomic alias operations solving the original 2-step problem
  • All code review feedback implemented

🔧 Technical Implementation

  • Clean API: Proper error handling with framework diagnostics
  • Consolidated Models: Single reusable indexModel for both read/write indices
  • Helper Functions: Extracted conversion logic into reusable functions
  • Schema Integration: Attribute types derived from schema definitions
  • Atomic Operations: Safe write index transitions without data loss
  • Validation: Early return patterns for cleaner code flow

This implementation provides a complete solution for managing Elasticsearch aliases with atomic operations, preventing the data loss scenarios described in the original issue, and incorporates all the improvements requested in the code review.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Feature] Introduce alias resource</issue_title>
<issue_description>Add a new resource to manage Elasticsearch aliases.

The resource must:

  • Be created in internal/elasticseach/index/alias
  • Cover the full API (Create/Update, Get, Delete)
  • Read the alias back after create/update operations. This should re-use the read code to ensure the resource state is consistently updated.
  • Have full acceptance test coverage, using both indices and data streams across both create, and update workflows.
  • All unit and acceptance tests must pass, the agent environment has running Stack instances which can be accessed via the default environment variables defined in Copilot instructions.

Original issue


Is your feature request related to a problem? Please describe.
Currently to update alias from one index to another, we need to take 2 steps in separate operations.

  1. add to the new index
  2. remove it from the old index

Also sometimes we don't want to allow multiple indices having the same alias but it can happen with the current way of managing alias separately in the index resource.

Describe the resource you would like to have implemented.
We would like to have alias resource to manage all the alias in the single resource to make it easier to apply multiple alias updates all at once.

Describe alternatives you've considered
Using index resource.

Additional context
Available APIs.

Comments on the Issue (you are @copilot in this section)

Fixes #285

✨ 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 Copilot AI changed the title [WIP] [Feature] Introduce alias resource [Feature] Introduce elasticsearch alias resource Sep 30, 2025
@Copilot Copilot AI requested a review from tobio September 30, 2025 05:21
Copilot finished work on behalf of tobio September 30, 2025 05:21
tobio

This comment was marked as outdated.

Copy link
Contributor Author

Copilot AI commented Oct 1, 2025

@copilot address the PR comments.

All PR comments have been addressed in commit efbb995 with a major schema redesign, atomic API operations, comprehensive test coverage, and proper resource lifecycle management. The implementation now follows all the patterns and requirements specified in the code review.

@Copilot Copilot AI changed the title [Feature] Introduce elasticsearch alias resource [Feature] Introduce elasticsearch alias resource with atomic write index management Oct 1, 2025
@Copilot Copilot AI requested a review from tobio October 1, 2025 10:47
Copilot finished work on behalf of tobio October 1, 2025 10:47
provider "elasticstack" {
elasticsearch {}
}

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit dbf477e. Added variable declarations to all Terraform configurations and updated the test approach to use direct index creation via Elasticsearch API instead of Terraform-managed indices. This avoids resource conflicts while still providing comprehensive test coverage. All acceptance tests now pass against the Stack resources:

=== RUN   TestAccResourceAlias
--- PASS: TestAccResourceAlias (1.60s)
=== RUN   TestAccResourceAliasWriteIndex  
--- PASS: TestAccResourceAliasWriteIndex (2.08s)
=== RUN   TestAccResourceAliasDataStream
--- PASS: TestAccResourceAliasDataStream (0.82s)
PASS

The tests now properly declare all required input variables and use the createTestIndex helper function to create indices directly via the Elasticsearch API, ensuring proper cleanup while avoiding conflicts with the index resource's alias management.

@Copilot Copilot AI requested a review from tobio October 2, 2025 05:04
Copilot finished work on behalf of tobio October 2, 2025 05:04
@tobio
Copy link
Member

tobio commented Oct 2, 2025

@copilot linting is failing. Please follow the Copilot instructions, to make sure that the project builds, passes lint, and that the relevant acceptance tests pass.

Copy link
Contributor Author

Copilot AI commented Oct 2, 2025

@copilot linting is failing. Please follow the Copilot instructions, to make sure that the project builds, passes lint, and that the relevant acceptance tests pass.

Fixed in commit dd27d0a. All linting issues have been resolved:

Issues Fixed:

  • ✅ Removed 7 unused constants (staticcheck warnings)
  • ✅ Fixed unnecessary fmt.Sprintf usage
  • ✅ Code properly formatted with go fmt

Quality Verification:

  • ✅ Project builds successfully
  • ✅ All acceptance tests pass against Stack resources:
    === RUN   TestAccResourceAlias
    --- PASS: TestAccResourceAlias (1.63s)
    === RUN   TestAccResourceAliasWriteIndex
    --- PASS: TestAccResourceAliasWriteIndex (2.06s)
    === RUN   TestAccResourceAliasDataStream
    --- PASS: TestAccResourceAliasDataStream (0.88s)
    PASS
    
  • ✅ Linting now shows "0 issues"
  • ✅ Documentation auto-generated

The alias resource implementation is now complete with clean code, comprehensive test coverage, and all quality checks passing.

Copilot finished work on behalf of tobio October 2, 2025 05:23
},
},
},
Blocks: map[string]schema.Block{
Copy link
Member

Choose a reason for hiding this comment

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

@copilot These Blocks should be moved to nested attributes. write_index should be a SingleNestedAttributes and read_indices should be a SetNestedAttribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Converted schema from blocks to proper nested attributes in commit 0bec889. Changes made:

  • write_index now uses schema.SingleNestedAttribute
  • read_indices now uses schema.SetNestedAttribute
  • Updated all test configurations to use attribute syntax (write_index = { ... } and read_indices = [{ ... }])
  • All acceptance tests passing with the new schema design
  • Documentation auto-updated to reflect proper nested attribute structure

@Copilot Copilot AI changed the title [Feature] Introduce elasticsearch alias resource with atomic write index management [Feature] Introduce elasticsearch alias resource with atomic write index management and nested attributes Oct 2, 2025
@Copilot Copilot AI requested a review from tobio October 2, 2025 08:30
Copilot finished work on behalf of tobio October 2, 2025 08:30
Copy link
Member

@tobio tobio left a comment

Choose a reason for hiding this comment

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

@copilot address the PR feedback

Comment on lines 607 to 610
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to get alias '%s'", aliasName))
if diagutil.FrameworkDiagsFromSDK(diags).HasError() {
return nil, diagutil.FrameworkDiagsFromSDK(diags)
}
Copy link
Member

Choose a reason for hiding this comment

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

CheckErrorFromFW will return the right type directly.

Suggested change
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to get alias '%s'", aliasName))
if diagutil.FrameworkDiagsFromSDK(diags).HasError() {
return nil, diagutil.FrameworkDiagsFromSDK(diags)
}
diags := diagutil.CheckErrorFromFW(res, fmt.Sprintf("Unable to get alias '%s'", aliasName))
if diags.HasError() {
return nil, diags
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 3c8a8b2. Updated to use CheckErrorFromFW directly as suggested.

return indices, nil
}

func PutAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string, alias *models.IndexAlias) fwdiags.Diagnostics {
Copy link
Member

Choose a reason for hiding this comment

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

This function is now unused and can be deleted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed unused PutAlias function in commit 3c8a8b2.

return diagutil.FrameworkDiagsFromSDK(diags)
}

func DeleteAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string) fwdiags.Diagnostics {
Copy link
Member

Choose a reason for hiding this comment

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

This function is now unused and can be deleted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed unused DeleteAlias function in commit 3c8a8b2.

Comment on lines 820 to 821
diags := diagutil.CheckError(res, "Unable to update aliases atomically")
return diagutil.FrameworkDiagsFromSDK(diags)
Copy link
Member

Choose a reason for hiding this comment

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

CheckErrorFromFW will return the correct type.

Suggested change
diags := diagutil.CheckError(res, "Unable to update aliases atomically")
return diagutil.FrameworkDiagsFromSDK(diags)
return diagutil.CheckErrorFromFW(res, "Unable to update aliases atomically")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 3c8a8b2. Now using CheckErrorFromFW directly as suggested.

ReadIndices types.Set `tfsdk:"read_indices"`
}

type writeIndexModel struct {
Copy link
Member

Choose a reason for hiding this comment

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

writeIndexModel and readIndexModel are identical. Rename one to indexModel and re-use it for both read and write values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored in commit 3c8a8b2. Consolidated writeIndexModel and readIndexModel into a single indexModel type that's reused for both write and read indices.


// Handle write index
if !model.WriteIndex.IsNull() {
var writeIndex writeIndexModel
Copy link
Member

Choose a reason for hiding this comment

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

Extract the body into a re-usable function that can be called for both read and write indices.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted in commit 3c8a8b2. Created indexToConfig helper function that handles conversion for both read and write indices, eliminating code duplication.

}

// Helper functions for attribute types
func (writeIndexModel) attrTypes() map[string]attr.Type {
Copy link
Member

Choose a reason for hiding this comment

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

These attribute types should be obtained from the schema instead of duplicated here. See getSslAttrTypes in internal/fleet/output/schema.go for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented in commit 3c8a8b2. Added getIndexAttrTypes() function that extracts attribute types from the schema, following the same pattern as getSslAttrTypes.

Comment on lines 51 to 75
if !config.WriteIndex.IsNull() && !config.ReadIndices.IsNull() {
// Get the write index name
var writeIndex writeIndexModel
if diags := config.WriteIndex.As(ctx, &writeIndex, basetypes.ObjectAsOptions{}); !diags.HasError() {
writeIndexName := writeIndex.Name.ValueString()

// Only validate if write index name is not empty
if writeIndexName != "" {
// Get all read indices
var readIndices []readIndexModel
if diags := config.ReadIndices.ElementsAs(ctx, &readIndices, false); !diags.HasError() {
for _, readIndex := range readIndices {
readIndexName := readIndex.Name.ValueString()
if readIndexName != "" && readIndexName == writeIndexName {
resp.Diagnostics.AddError(
"Invalid Configuration",
fmt.Sprintf("Index '%s' cannot be both a write index and a read index", writeIndexName),
)
return
}
}
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Flatten this function out by returning early.

Suggested change
if !config.WriteIndex.IsNull() && !config.ReadIndices.IsNull() {
// Get the write index name
var writeIndex writeIndexModel
if diags := config.WriteIndex.As(ctx, &writeIndex, basetypes.ObjectAsOptions{}); !diags.HasError() {
writeIndexName := writeIndex.Name.ValueString()
// Only validate if write index name is not empty
if writeIndexName != "" {
// Get all read indices
var readIndices []readIndexModel
if diags := config.ReadIndices.ElementsAs(ctx, &readIndices, false); !diags.HasError() {
for _, readIndex := range readIndices {
readIndexName := readIndex.Name.ValueString()
if readIndexName != "" && readIndexName == writeIndexName {
resp.Diagnostics.AddError(
"Invalid Configuration",
fmt.Sprintf("Index '%s' cannot be both a write index and a read index", writeIndexName),
)
return
}
}
}
}
}
}
if config.WriteIndex.IsNull() {
return
}
if config.ReadIndices.IsNull() {
return
}
// Get the write index name
var writeIndex writeIndexModel
diags := config.WriteIndex.As(ctx, &writeIndex, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError {
return
}
writeIndexName := writeIndex.Name.ValueString()
// Only validate if write index name is not empty
if writeIndexName == "" {
return
}
// Get all read indices
var readIndices []readIndexModel
if diags := config.ReadIndices.ElementsAs(ctx, &readIndices, false); !diags.HasError() {
for _, readIndex := range readIndices {
readIndexName := readIndex.Name.ValueString()
if readIndexName != "" && readIndexName == writeIndexName {
resp.Diagnostics.AddError(
"Invalid Configuration",
fmt.Sprintf("Index '%s' cannot be both a write index and a read index", writeIndexName),
)
return
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Flattened in commit 3c8a8b2. Updated ValidateConfig to use early returns and simplified the nested structure as suggested.

Comment on lines 40 to 42
if resp.Diagnostics.HasError() {
return
}
Copy link
Member

Choose a reason for hiding this comment

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

This is redundant

Suggested change
if resp.Diagnostics.HasError() {
return
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed redundant check in commit 3c8a8b2.

func (r *aliasResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manages an Elasticsearch alias. " +
"See, https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"See, https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html",
"See the [alias documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html) for more details.",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated description in commit 3c8a8b2 to include proper HTML link to alias documentation.

Copy link
Contributor Author

Copilot AI commented Oct 2, 2025

@copilot address the PR feedback

All PR feedback has been addressed in commit 3c8a8b2:

  • ✅ Fixed API error handling with CheckErrorFromFW
  • ✅ Removed unused PutAlias and DeleteAlias functions
  • ✅ Consolidated duplicate models into single indexModel
  • ✅ Extracted helper functions to eliminate code duplication
  • ✅ Updated attribute types to use schema-derived helpers
  • ✅ Flattened ValidateConfig with early returns
  • ✅ Updated documentation link formatting
  • ✅ All tests passing, linting clean

@Copilot Copilot AI requested a review from tobio October 2, 2025 11:50
Copilot finished work on behalf of tobio October 2, 2025 11:50
@tobio tobio force-pushed the copilot/fix-7855e918-4f89-4206-9e30-02dc9febbc48 branch from 252fca6 to 92ee168 Compare October 3, 2025 12:32
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.

[Feature] Introduce alias resource
2 participants