ec/ecresource/deploymentresource/deployment/v2/deployment_update_payload.go (193 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package v2
import (
"context"
"github.com/elastic/cloud-sdk-go/pkg/api"
"github.com/elastic/cloud-sdk-go/pkg/api/deploymentapi/deptemplateapi"
"github.com/elastic/cloud-sdk-go/pkg/client/deployments"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/util/ec"
apmv2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/apm/v2"
elasticsearchv2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/elasticsearch/v2"
enterprisesearchv2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/enterprisesearch/v2"
integrationsserverv2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/integrationsserver/v2"
kibanav2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/kibana/v2"
observabilityv2 "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource/observability/v2"
"github.com/elastic/terraform-provider-ec/ec/internal/converters"
"github.com/hashicorp/terraform-plugin-framework/diag"
)
func (plan DeploymentTF) getBaseUpdatePayloads(ctx context.Context, client *api.API, state DeploymentTF, migrateTemplateRequest *deployments.MigrateDeploymentTemplateOK) (*models.DeploymentUpdateResources, diag.Diagnostics) {
var diags diag.Diagnostics
newDtId := plan.DeploymentTemplateId.ValueString()
prevDtId := state.DeploymentTemplateId.ValueString()
template, err := deptemplateapi.Get(deptemplateapi.GetParams{
API: client,
TemplateID: newDtId,
Region: plan.Region.ValueString(),
HideInstanceConfigurations: true,
})
if err != nil {
diags.AddError("Failed to get template", err.Error())
return nil, diags
}
baseUpdatePayloads := &models.DeploymentUpdateResources{
Apm: template.DeploymentTemplate.Resources.Apm,
Appsearch: template.DeploymentTemplate.Resources.Appsearch,
Elasticsearch: template.DeploymentTemplate.Resources.Elasticsearch,
EnterpriseSearch: template.DeploymentTemplate.Resources.EnterpriseSearch,
IntegrationsServer: template.DeploymentTemplate.Resources.IntegrationsServer,
Kibana: template.DeploymentTemplate.Resources.Kibana,
}
planHasNodeTypes, d := elasticsearchv2.PlanHasNodeTypes(ctx, plan.Elasticsearch)
// Template migration isn't available for deployments using node types
if planHasNodeTypes {
return baseUpdatePayloads, diags
}
if d.HasError() {
return nil, d
}
templateChanged := newDtId != prevDtId && prevDtId != ""
// If the deployment template has changed, we should use the template migration API to build the base update payloads
if templateChanged {
// If the template has changed, we can't use the migrate request from private state. This is why we set
// migrateTemplateRequest to nil here, so that a new request has to be performed
d = plan.updateBasePayloadsForMigration(client, newDtId, nil, baseUpdatePayloads)
diags.Append(d...)
return baseUpdatePayloads, diags
}
// If migrate_to_latest_hardware isn't set, we won't update the base payloads
if !plan.MigrateToLatestHardware.ValueBool() {
return baseUpdatePayloads, diags
}
isMigrationAvailable, d := plan.checkAvailableMigration(ctx, state)
diags.Append(d...)
// If MigrateToLatestHardware is true and a migration is available, we should use the template migration API to
// build the base update payloads
if isMigrationAvailable {
// Update baseUpdatePayloads according to migrateTemplateRequest
d = plan.updateBasePayloadsForMigration(client, newDtId, migrateTemplateRequest, baseUpdatePayloads)
diags.Append(d...)
}
return baseUpdatePayloads, diags
}
func (plan DeploymentTF) updateBasePayloadsForMigration(client *api.API, newDtId string, migrateTemplateRequest *deployments.MigrateDeploymentTemplateOK, baseUpdatePayloads *models.DeploymentUpdateResources) diag.Diagnostics {
var err error
var diags diag.Diagnostics
// If migrate request is nil, we need fetch a new update request from the template migration API
if migrateTemplateRequest == nil {
migrateTemplateRequest, err = client.V1API.Deployments.MigrateDeploymentTemplate(
deployments.NewMigrateDeploymentTemplateParams().WithDeploymentID(plan.Id.ValueString()).WithTemplateID(newDtId),
client.AuthWriter,
)
if err != nil {
diags.AddError("Failed to get template migration request", err.Error())
return diags
}
}
if len(migrateTemplateRequest.Payload.Resources.Apm) > 0 {
baseUpdatePayloads.Apm = migrateTemplateRequest.Payload.Resources.Apm
}
if len(migrateTemplateRequest.Payload.Resources.Appsearch) > 0 {
baseUpdatePayloads.Appsearch = migrateTemplateRequest.Payload.Resources.Appsearch
}
if len(migrateTemplateRequest.Payload.Resources.Elasticsearch) > 0 {
baseUpdatePayloads.Elasticsearch = migrateTemplateRequest.Payload.Resources.Elasticsearch
}
if len(migrateTemplateRequest.Payload.Resources.EnterpriseSearch) > 0 {
baseUpdatePayloads.EnterpriseSearch = migrateTemplateRequest.Payload.Resources.EnterpriseSearch
}
if len(migrateTemplateRequest.Payload.Resources.IntegrationsServer) > 0 {
baseUpdatePayloads.IntegrationsServer = migrateTemplateRequest.Payload.Resources.IntegrationsServer
}
if len(migrateTemplateRequest.Payload.Resources.Kibana) > 0 {
baseUpdatePayloads.Kibana = migrateTemplateRequest.Payload.Resources.Kibana
}
return diags
}
func (plan DeploymentTF) UpdateRequest(ctx context.Context, client *api.API, state DeploymentTF, migrateTemplateRequest *deployments.MigrateDeploymentTemplateOK) (*models.DeploymentUpdateRequest, diag.Diagnostics) {
// The alias behaves like this:
// - Not set in the config (null/unknown) -> Alias is not managed by terraform and left untouched
// - Set to empty string -> Alias will be removed
// - Set to non-empty string -> Alias will be set to this value
var alias *string
if plan.Alias.IsNull() || plan.Alias.IsUnknown() {
alias = nil
} else {
alias = ec.String(plan.Alias.ValueString())
}
var result = models.DeploymentUpdateRequest{
Name: plan.Name.ValueString(),
Alias: alias,
PruneOrphans: ec.Bool(true),
Resources: &models.DeploymentUpdateResources{},
Settings: &models.DeploymentUpdateSettings{},
Metadata: &models.DeploymentUpdateMetadata{},
}
dtID := plan.DeploymentTemplateId.ValueString()
var diagnostics diag.Diagnostics
basePayloads, diags := plan.getBaseUpdatePayloads(ctx, client, state, migrateTemplateRequest)
if diags.HasError() {
return nil, diags
}
useNodeRoles, diags := elasticsearchv2.UseNodeRoles(ctx, state.Version, plan.Version, plan.Elasticsearch)
if diags.HasError() {
return nil, diags
}
elasticsearchPayload, diags := elasticsearchv2.ElasticsearchPayload(ctx, plan.Elasticsearch, &state.Elasticsearch, basePayloads, dtID, plan.Version.ValueString(), useNodeRoles)
if diags.HasError() {
diagnostics.Append(diags...)
}
if elasticsearchPayload != nil {
// if the restore snapshot operation has been specified, the snapshot restore
// can't be full once the cluster has been created, so the Strategy must be set
// to "partial".
ensurePartialSnapshotStrategy(elasticsearchPayload)
result.Resources.Elasticsearch = append(result.Resources.Elasticsearch, elasticsearchPayload)
}
kibanaPayload, diags := kibanav2.KibanaPayload(ctx, plan.Kibana, basePayloads)
if diags.HasError() {
diagnostics.Append(diags...)
}
if kibanaPayload != nil {
result.Resources.Kibana = append(result.Resources.Kibana, kibanaPayload)
}
apmPayload, diags := apmv2.ApmPayload(ctx, plan.Apm, basePayloads)
if diags.HasError() {
diagnostics.Append(diags...)
}
if apmPayload != nil {
result.Resources.Apm = append(result.Resources.Apm, apmPayload)
}
integrationsServerPayload, diags := integrationsserverv2.IntegrationsServerPayload(ctx, plan.IntegrationsServer, basePayloads)
if diags.HasError() {
diagnostics.Append(diags...)
}
if integrationsServerPayload != nil {
result.Resources.IntegrationsServer = append(result.Resources.IntegrationsServer, integrationsServerPayload)
}
enterpriseSearchPayload, diags := enterprisesearchv2.EnterpriseSearchesPayload(ctx, plan.EnterpriseSearch, basePayloads)
if diags.HasError() {
diagnostics.Append(diags...)
}
if enterpriseSearchPayload != nil {
result.Resources.EnterpriseSearch = append(result.Resources.EnterpriseSearch, enterpriseSearchPayload)
}
observabilityPayload, diags := observabilityv2.ObservabilityPayload(ctx, plan.Observability, client)
if diags.HasError() {
diagnostics.Append(diags...)
}
result.Settings.Observability = observabilityPayload
// In order to stop shipping logs and metrics, an empty Observability
// object must be passed, as opposed to a nil object when creating a
// deployment without observability settings.
if plan.Observability.IsNull() && !state.Observability.IsNull() {
result.Settings.Observability = &models.DeploymentObservabilitySettings{}
}
result.Metadata.Tags, diags = converters.TypesMapToModelsTags(ctx, plan.Tags)
if diags.HasError() {
diagnostics.Append(diags...)
}
return &result, diagnostics
}
func (plan DeploymentTF) checkAvailableMigration(ctx context.Context, state DeploymentTF) (bool, diag.Diagnostics) {
var diags diag.Diagnostics
esHasMigration, d := elasticsearchv2.CheckAvailableMigration(ctx, plan.Elasticsearch, state.Elasticsearch)
diags.Append(d...)
apmHasMigration, d := apmv2.CheckAvailableMigration(ctx, plan.Apm, state.Apm)
diags.Append(d...)
entSearchHasMigration, d := enterprisesearchv2.CheckAvailableMigration(ctx, plan.EnterpriseSearch, state.EnterpriseSearch)
diags.Append(d...)
intServerHasMigration, d := integrationsserverv2.CheckAvailableMigration(ctx, plan.IntegrationsServer, state.IntegrationsServer)
diags.Append(d...)
kibanaHasMigration, d := kibanav2.CheckAvailableMigration(ctx, plan.Kibana, state.Kibana)
diags.Append(d...)
isMigrationAvailable := esHasMigration || apmHasMigration || entSearchHasMigration || intServerHasMigration || kibanaHasMigration
return isMigrationAvailable, diags
}
func ensurePartialSnapshotStrategy(es *models.ElasticsearchPayload) {
transient := es.Plan.Transient
if transient == nil || transient.RestoreSnapshot == nil {
return
}
transient.RestoreSnapshot.Strategy = "partial"
}