Skip to content

Commit c76a508

Browse files
committed
Implement script resource
1 parent b357bbf commit c76a508

File tree

5 files changed

+309
-0
lines changed

5 files changed

+309
-0
lines changed

internal/clients/cluster.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,59 @@ func (a *ApiClient) GetElasticsearchSettings(ctx context.Context) (map[string]in
180180
}
181181
return clusterSettings, diags
182182
}
183+
184+
func (a *ApiClient) GetElasticsearchScript(ctx context.Context, id string) (*models.Script, diag.Diagnostics) {
185+
res, err := a.es.GetScript(id, a.es.GetScript.WithContext(ctx))
186+
if err != nil {
187+
return nil, diag.FromErr(err)
188+
}
189+
defer res.Body.Close()
190+
if res.StatusCode == http.StatusNotFound {
191+
return nil, nil
192+
}
193+
if diags := utils.CheckError(res, fmt.Sprintf("Unable to get the script: %s", id)); diags.HasError() {
194+
return nil, diags
195+
}
196+
type getScriptResponse = struct {
197+
Script *models.Script `json:"script"`
198+
}
199+
var scriptResponse getScriptResponse
200+
if err := json.NewDecoder(res.Body).Decode(&scriptResponse); err != nil {
201+
return nil, diag.FromErr(err)
202+
}
203+
204+
return scriptResponse.Script, nil
205+
}
206+
207+
func (a *ApiClient) PutElasticsearchScript(ctx context.Context, script *models.Script) diag.Diagnostics {
208+
req := struct {
209+
Script *models.Script `json:"script"`
210+
}{
211+
Script: script,
212+
}
213+
scriptBytes, err := json.Marshal(req)
214+
if err != nil {
215+
return diag.FromErr(err)
216+
}
217+
res, err := a.es.PutScript(script.ID, bytes.NewReader(scriptBytes), a.es.PutScript.WithContext(ctx), a.es.PutScript.WithScriptContext(script.Context))
218+
if err != nil {
219+
return diag.FromErr(err)
220+
}
221+
defer res.Body.Close()
222+
if diags := utils.CheckError(res, "Unable to put the script"); diags.HasError() {
223+
return diags
224+
}
225+
return nil
226+
}
227+
228+
func (a *ApiClient) DeleteElasticsearchScript(ctx context.Context, id string) diag.Diagnostics {
229+
res, err := a.es.DeleteScript(id, a.es.DeleteScript.WithContext(ctx))
230+
if err != nil {
231+
return diag.FromErr(err)
232+
}
233+
defer res.Body.Close()
234+
if diags := utils.CheckError(res, fmt.Sprintf("Unable to delete script: %s", id)); diags.HasError() {
235+
return diags
236+
}
237+
return nil
238+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package cluster
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/models"
10+
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
11+
"github.com/hashicorp/terraform-plugin-log/tflog"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
15+
)
16+
17+
func ResourceScript() *schema.Resource {
18+
scriptSchema := map[string]*schema.Schema{
19+
"script_id": {
20+
Description: "Identifier for the stored script. Must be unique within the cluster.",
21+
Type: schema.TypeString,
22+
Required: true,
23+
ForceNew: true,
24+
},
25+
"lang": {
26+
Description: "Script language. For search templates, use `mustache`. Defaults to `painless`.",
27+
Type: schema.TypeString,
28+
Optional: true,
29+
Default: "painless",
30+
ValidateFunc: validation.StringInSlice([]string{"painless", "expression", "mustache", "java"}, false),
31+
},
32+
"source": {
33+
Description: "For scripts, a string containing the script. For search templates, an object containing the search template.",
34+
Type: schema.TypeString,
35+
Required: true,
36+
},
37+
"params": {
38+
Description: "Parameters for the script or search template.",
39+
Type: schema.TypeString,
40+
Optional: true,
41+
DiffSuppressFunc: utils.DiffJsonSuppress,
42+
ValidateFunc: validation.StringIsJSON,
43+
},
44+
"context": {
45+
Description: "Context in which the script or search template should run.",
46+
Type: schema.TypeString,
47+
Optional: true,
48+
},
49+
}
50+
utils.AddConnectionSchema(scriptSchema)
51+
52+
return &schema.Resource{
53+
Description: "Creates or updates a stored script or search template. See https://www.elastic.co/guide/en/elasticsearch/reference/current/create-stored-script-api.html",
54+
55+
CreateContext: resourceScriptPut,
56+
UpdateContext: resourceScriptPut,
57+
ReadContext: resourceScriptRead,
58+
DeleteContext: resourceScriptDelete,
59+
60+
Importer: &schema.ResourceImporter{
61+
StateContext: schema.ImportStatePassthroughContext,
62+
},
63+
64+
Schema: scriptSchema,
65+
}
66+
}
67+
68+
func resourceScriptRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
69+
var diags diag.Diagnostics
70+
client, err := clients.NewApiClient(d, meta)
71+
if err != nil {
72+
return diag.FromErr(err)
73+
}
74+
75+
id := d.Id()
76+
compId, diags := clients.CompositeIdFromStr(id)
77+
if diags.HasError() {
78+
return diags
79+
}
80+
81+
script, diags := client.GetElasticsearchScript(ctx, compId.ResourceId)
82+
if !d.IsNewResource() && script == nil && diags == nil {
83+
tflog.Warn(ctx, fmt.Sprintf(`Script "%s" not found, removing from state`, compId.ResourceId))
84+
d.SetId("")
85+
}
86+
if diags.HasError() {
87+
return diags
88+
}
89+
90+
if err := d.Set("script_id", compId.ResourceId); err != nil {
91+
return diag.FromErr(err)
92+
}
93+
if err := d.Set("lang", script.Language); err != nil {
94+
return diag.FromErr(err)
95+
}
96+
if err := d.Set("source", script.Source); err != nil {
97+
return diag.FromErr(err)
98+
}
99+
100+
return diags
101+
}
102+
103+
func resourceScriptPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
104+
client, err := clients.NewApiClient(d, meta)
105+
if err != nil {
106+
return diag.FromErr(err)
107+
}
108+
109+
scriptID := d.Get("script_id").(string)
110+
id, diags := client.ID(ctx, scriptID)
111+
if diags.HasError() {
112+
return diags
113+
}
114+
115+
script := models.Script{
116+
ID: scriptID,
117+
Language: d.Get("lang").(string),
118+
Source: d.Get("source").(string),
119+
}
120+
if paramsJSON, ok := d.GetOk("params"); ok {
121+
var params map[string]interface{}
122+
bytes := []byte(paramsJSON.(string))
123+
err = json.Unmarshal(bytes, &params)
124+
if err != nil {
125+
return diag.FromErr(err)
126+
}
127+
script.Params = params
128+
}
129+
if scriptContext, ok := d.GetOk("context"); ok {
130+
script.Context = scriptContext.(string)
131+
}
132+
if diags := client.PutElasticsearchScript(ctx, &script); diags.HasError() {
133+
return diags
134+
}
135+
136+
d.SetId(id.String())
137+
return resourceScriptRead(ctx, d, meta)
138+
}
139+
140+
func resourceScriptDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
141+
client, err := clients.NewApiClient(d, meta)
142+
if err != nil {
143+
return diag.FromErr(err)
144+
}
145+
146+
compId, diags := clients.CompositeIdFromStr(d.Id())
147+
if diags.HasError() {
148+
return diags
149+
}
150+
return client.DeleteElasticsearchScript(ctx, compId.ResourceId)
151+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package cluster_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-sdk/v2/helper/acctest"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
12+
)
13+
14+
func TestAccResourceScript(t *testing.T) {
15+
scriptID := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
16+
17+
resource.UnitTest(t, resource.TestCase{
18+
PreCheck: func() { acctest.PreCheck(t) },
19+
CheckDestroy: checkScriptDestroy,
20+
ProviderFactories: acctest.Providers,
21+
Steps: []resource.TestStep{
22+
{
23+
Config: testAccScriptCreate(scriptID),
24+
Check: resource.ComposeTestCheckFunc(
25+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "script_id", scriptID),
26+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "lang", "painless"),
27+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "source", "Math.log(_score * 2) + params['my_modifier']"),
28+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "context", "score"),
29+
),
30+
},
31+
{
32+
Config: testAccScriptUpdate(scriptID),
33+
Check: resource.ComposeTestCheckFunc(
34+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "script_id", scriptID),
35+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "lang", "painless"),
36+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "source", "Math.log(_score * 4) + params['changed_modifier']"),
37+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_script.test", "params", `{"changed_modifier":2}`),
38+
),
39+
},
40+
},
41+
})
42+
}
43+
44+
func testAccScriptCreate(id string) string {
45+
return fmt.Sprintf(`
46+
provider "elasticstack" {
47+
elasticsearch {}
48+
}
49+
50+
resource "elasticstack_elasticsearch_script" "test" {
51+
script_id = "%s"
52+
source = "Math.log(_score * 2) + params['my_modifier']"
53+
context = "score"
54+
}
55+
`, id)
56+
}
57+
58+
func testAccScriptUpdate(id string) string {
59+
return fmt.Sprintf(`
60+
provider "elasticstack" {
61+
elasticsearch {}
62+
}
63+
64+
resource "elasticstack_elasticsearch_script" "test" {
65+
script_id = "%s"
66+
source = "Math.log(_score * 4) + params['changed_modifier']"
67+
params = jsonencode({
68+
changed_modifier = 2
69+
})
70+
}
71+
`, id)
72+
}
73+
74+
func checkScriptDestroy(s *terraform.State) error {
75+
client := acctest.Provider.Meta().(*clients.ApiClient)
76+
77+
for _, rs := range s.RootModule().Resources {
78+
if rs.Type != "elasticstack_elasticsearch_script" {
79+
continue
80+
}
81+
82+
compId, _ := clients.CompositeIdFromStr(rs.Primary.ID)
83+
res, err := client.GetESClient().GetScript(compId.ResourceId)
84+
if err != nil {
85+
return err
86+
}
87+
88+
if res.StatusCode != 404 {
89+
return fmt.Errorf("script (%s) still exists", compId.ResourceId)
90+
}
91+
}
92+
return nil
93+
}

internal/models/models.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,11 @@ type LogstashPipeline struct {
197197
PipelineSettings map[string]interface{} `json:"pipeline_settings"`
198198
Username string `json:"username"`
199199
}
200+
201+
type Script struct {
202+
ID string `json:"-"`
203+
Language string `json:"lang"`
204+
Source string `json:"source"`
205+
Params map[string]interface{} `json:"params"`
206+
Context string `json:"-"`
207+
}

provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func New(version string) func() *schema.Provider {
135135
"elasticstack_elasticsearch_security_user": security.ResourceUser(),
136136
"elasticstack_elasticsearch_snapshot_lifecycle": cluster.ResourceSlm(),
137137
"elasticstack_elasticsearch_snapshot_repository": cluster.ResourceSnapshotRepository(),
138+
"elasticstack_elasticsearch_script": cluster.ResourceScript(),
138139
},
139140
}
140141

0 commit comments

Comments
 (0)