Skip to content

Commit 5bf740c

Browse files
Copilottobio
andcommitted
Implement core alias resource structure and API functions
Co-authored-by: tobio <444668+tobio@users.noreply.github.com>
1 parent 91ab7d6 commit 5bf740c

File tree

10 files changed

+763
-0
lines changed

10 files changed

+763
-0
lines changed

internal/clients/elasticsearch/index.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,152 @@ func DeleteDataStreamLifecycle(ctx context.Context, apiClient *clients.ApiClient
581581
return nil
582582
}
583583

584+
func GetAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string) (map[string]models.Index, fwdiags.Diagnostics) {
585+
esClient, err := apiClient.GetESClient()
586+
if err != nil {
587+
return nil, fwdiags.Diagnostics{
588+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
589+
}
590+
}
591+
592+
res, err := esClient.Indices.GetAlias(
593+
esClient.Indices.GetAlias.WithName(aliasName),
594+
esClient.Indices.GetAlias.WithContext(ctx),
595+
)
596+
if err != nil {
597+
return nil, fwdiags.Diagnostics{
598+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
599+
}
600+
}
601+
defer res.Body.Close()
602+
603+
if res.StatusCode == http.StatusNotFound {
604+
return nil, nil
605+
}
606+
607+
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to get alias '%s'", aliasName))
608+
if diagutil.FrameworkDiagsFromSDK(diags).HasError() {
609+
return nil, diagutil.FrameworkDiagsFromSDK(diags)
610+
}
611+
612+
indices := make(map[string]models.Index)
613+
if err := json.NewDecoder(res.Body).Decode(&indices); err != nil {
614+
return nil, fwdiags.Diagnostics{
615+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
616+
}
617+
}
618+
619+
return indices, nil
620+
}
621+
622+
func PutAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string, alias *models.IndexAlias) fwdiags.Diagnostics {
623+
esClient, err := apiClient.GetESClient()
624+
if err != nil {
625+
return fwdiags.Diagnostics{
626+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
627+
}
628+
}
629+
630+
// Build the request body for index aliases API
631+
aliasActions := map[string]interface{}{
632+
"actions": []map[string]interface{}{
633+
{
634+
"add": map[string]interface{}{
635+
"indices": indices,
636+
"alias": aliasName,
637+
"filter": alias.Filter,
638+
},
639+
},
640+
},
641+
}
642+
643+
// Only include non-empty optional fields
644+
addAction := aliasActions["actions"].([]map[string]interface{})[0]["add"].(map[string]interface{})
645+
if alias.IndexRouting != "" {
646+
addAction["index_routing"] = alias.IndexRouting
647+
}
648+
if alias.SearchRouting != "" {
649+
addAction["search_routing"] = alias.SearchRouting
650+
}
651+
if alias.Routing != "" {
652+
addAction["routing"] = alias.Routing
653+
}
654+
if alias.IsHidden {
655+
addAction["is_hidden"] = alias.IsHidden
656+
}
657+
if alias.IsWriteIndex {
658+
addAction["is_write_index"] = alias.IsWriteIndex
659+
}
660+
661+
// Remove filter if it's nil or empty
662+
if alias.Filter == nil {
663+
delete(addAction, "filter")
664+
}
665+
666+
aliasBytes, err := json.Marshal(aliasActions)
667+
if err != nil {
668+
return fwdiags.Diagnostics{
669+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
670+
}
671+
}
672+
673+
res, err := esClient.Indices.UpdateAliases(
674+
bytes.NewReader(aliasBytes),
675+
esClient.Indices.UpdateAliases.WithContext(ctx),
676+
)
677+
if err != nil {
678+
return fwdiags.Diagnostics{
679+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
680+
}
681+
}
682+
defer res.Body.Close()
683+
684+
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to create/update alias '%s'", aliasName))
685+
return diagutil.FrameworkDiagsFromSDK(diags)
686+
}
687+
688+
func DeleteAlias(ctx context.Context, apiClient *clients.ApiClient, aliasName string, indices []string) fwdiags.Diagnostics {
689+
esClient, err := apiClient.GetESClient()
690+
if err != nil {
691+
return fwdiags.Diagnostics{
692+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
693+
}
694+
}
695+
696+
// Use UpdateAliases API for deletion to handle multiple indices
697+
aliasActions := map[string]interface{}{
698+
"actions": []map[string]interface{}{
699+
{
700+
"remove": map[string]interface{}{
701+
"indices": indices,
702+
"alias": aliasName,
703+
},
704+
},
705+
},
706+
}
707+
708+
aliasBytes, err := json.Marshal(aliasActions)
709+
if err != nil {
710+
return fwdiags.Diagnostics{
711+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
712+
}
713+
}
714+
715+
res, err := esClient.Indices.UpdateAliases(
716+
bytes.NewReader(aliasBytes),
717+
esClient.Indices.UpdateAliases.WithContext(ctx),
718+
)
719+
if err != nil {
720+
return fwdiags.Diagnostics{
721+
fwdiags.NewErrorDiagnostic(err.Error(), err.Error()),
722+
}
723+
}
724+
defer res.Body.Close()
725+
726+
diags := diagutil.CheckError(res, fmt.Sprintf("Unable to delete alias '%s'", aliasName))
727+
return diagutil.FrameworkDiagsFromSDK(diags)
728+
}
729+
584730
func PutIngestPipeline(ctx context.Context, apiClient *clients.ApiClient, pipeline *models.IngestPipeline) diag.Diagnostics {
585731
var diags diag.Diagnostics
586732
pipelineBytes, err := json.Marshal(pipeline)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package alias_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
9+
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-testing/terraform"
12+
)
13+
14+
func TestAccResourceAlias(t *testing.T) {
15+
// generate random names
16+
aliasName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlpha)
17+
indexName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlpha)
18+
indexName2 := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlpha)
19+
20+
resource.Test(t, resource.TestCase{
21+
PreCheck: func() { acctest.PreCheck(t) },
22+
CheckDestroy: checkResourceAliasDestroy,
23+
ProtoV6ProviderFactories: acctest.Providers,
24+
Steps: []resource.TestStep{
25+
{
26+
Config: testAccResourceAliasCreate(aliasName, indexName),
27+
Check: resource.ComposeTestCheckFunc(
28+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "name", aliasName),
29+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "indices.#", "1"),
30+
resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_alias.test_alias", "indices.*", indexName),
31+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "is_hidden", "false"),
32+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "is_write_index", "false"),
33+
),
34+
},
35+
{
36+
Config: testAccResourceAliasUpdate(aliasName, indexName, indexName2),
37+
Check: resource.ComposeTestCheckFunc(
38+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "name", aliasName),
39+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "indices.#", "2"),
40+
resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_alias.test_alias", "indices.*", indexName),
41+
resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_alias.test_alias", "indices.*", indexName2),
42+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "is_write_index", "true"),
43+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "routing", "test-routing"),
44+
),
45+
},
46+
{
47+
Config: testAccResourceAliasWithFilter(aliasName, indexName),
48+
Check: resource.ComposeTestCheckFunc(
49+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "name", aliasName),
50+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "indices.#", "1"),
51+
resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_alias.test_alias", "indices.*", indexName),
52+
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_alias.test_alias", "filter"),
53+
),
54+
},
55+
},
56+
})
57+
}
58+
59+
func TestAccResourceAliasDataStream(t *testing.T) {
60+
// generate random names
61+
aliasName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlpha)
62+
dsName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlpha)
63+
64+
resource.Test(t, resource.TestCase{
65+
PreCheck: func() { acctest.PreCheck(t) },
66+
CheckDestroy: checkResourceAliasDestroy,
67+
ProtoV6ProviderFactories: acctest.Providers,
68+
Steps: []resource.TestStep{
69+
{
70+
Config: testAccResourceAliasDataStreamCreate(aliasName, dsName),
71+
Check: resource.ComposeTestCheckFunc(
72+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "name", aliasName),
73+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_alias.test_alias", "indices.#", "1"),
74+
resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_alias.test_alias", "indices.*", dsName),
75+
),
76+
},
77+
},
78+
})
79+
}
80+
81+
func testAccResourceAliasCreate(aliasName, indexName string) string {
82+
return fmt.Sprintf(`
83+
resource "elasticstack_elasticsearch_index" "test_index" {
84+
name = "%s"
85+
}
86+
87+
resource "elasticstack_elasticsearch_alias" "test_alias" {
88+
name = "%s"
89+
indices = [elasticstack_elasticsearch_index.test_index.name]
90+
}
91+
`, indexName, aliasName)
92+
}
93+
94+
func testAccResourceAliasUpdate(aliasName, indexName, indexName2 string) string {
95+
return fmt.Sprintf(`
96+
resource "elasticstack_elasticsearch_index" "test_index" {
97+
name = "%s"
98+
}
99+
100+
resource "elasticstack_elasticsearch_index" "test_index2" {
101+
name = "%s"
102+
}
103+
104+
resource "elasticstack_elasticsearch_alias" "test_alias" {
105+
name = "%s"
106+
indices = [elasticstack_elasticsearch_index.test_index.name, elasticstack_elasticsearch_index.test_index2.name]
107+
is_write_index = true
108+
routing = "test-routing"
109+
}
110+
`, indexName, indexName2, aliasName)
111+
}
112+
113+
func testAccResourceAliasWithFilter(aliasName, indexName string) string {
114+
return fmt.Sprintf(`
115+
resource "elasticstack_elasticsearch_index" "test_index" {
116+
name = "%s"
117+
}
118+
119+
resource "elasticstack_elasticsearch_alias" "test_alias" {
120+
name = "%s"
121+
indices = [elasticstack_elasticsearch_index.test_index.name]
122+
filter = jsonencode({
123+
term = {
124+
status = "published"
125+
}
126+
})
127+
}
128+
`, indexName, aliasName)
129+
}
130+
131+
func testAccResourceAliasDataStreamCreate(aliasName, dsName string) string {
132+
return fmt.Sprintf(`
133+
resource "elasticstack_elasticsearch_index_template" "test_ds_template" {
134+
name = "%s"
135+
index_patterns = ["%s"]
136+
data_stream {}
137+
}
138+
139+
resource "elasticstack_elasticsearch_data_stream" "test_ds" {
140+
name = "%s"
141+
depends_on = [
142+
elasticstack_elasticsearch_index_template.test_ds_template
143+
]
144+
}
145+
146+
resource "elasticstack_elasticsearch_alias" "test_alias" {
147+
name = "%s"
148+
indices = [elasticstack_elasticsearch_data_stream.test_ds.name]
149+
}
150+
`, dsName, dsName, dsName, aliasName)
151+
}
152+
153+
func checkResourceAliasDestroy(s *terraform.State) error {
154+
client, err := clients.NewAcceptanceTestingClient()
155+
if err != nil {
156+
return err
157+
}
158+
159+
for _, rs := range s.RootModule().Resources {
160+
if rs.Type != "elasticstack_elasticsearch_alias" {
161+
continue
162+
}
163+
compId, _ := clients.CompositeIdFromStr(rs.Primary.ID)
164+
165+
esClient, err := client.GetESClient()
166+
if err != nil {
167+
return err
168+
}
169+
170+
res, err := esClient.Indices.GetAlias(
171+
esClient.Indices.GetAlias.WithName(compId.ResourceId),
172+
)
173+
if err != nil {
174+
return err
175+
}
176+
177+
if res.StatusCode != 404 {
178+
return fmt.Errorf("Alias (%s) still exists", compId.ResourceId)
179+
}
180+
}
181+
return nil
182+
}

0 commit comments

Comments
 (0)