diff --git a/ovh/provider_new.go b/ovh/provider_new.go index eea83843a..d54412be5 100644 --- a/ovh/provider_new.go +++ b/ovh/provider_new.go @@ -215,6 +215,7 @@ func (p *OvhProvider) Resources(_ context.Context) []func() resource.Resource { NewDomainZoneImportResource, NewIpFirewallResource, NewIpFirewallRuleResource, + NewIploadbalancingSslResource, NewIploadbalancingUdpFrontendResource, NewIpMitigationResource, NewVpsResource, diff --git a/ovh/resource_iploadbalancing_ssl.go b/ovh/resource_iploadbalancing_ssl.go new file mode 100644 index 000000000..5f9cb57b9 --- /dev/null +++ b/ovh/resource_iploadbalancing_ssl.go @@ -0,0 +1,178 @@ +package ovh + +import ( + "context" + "fmt" + "net/url" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ resource.ResourceWithConfigure = (*iploadbalancingSslResource)(nil) + _ resource.ResourceWithImportState = (*iploadbalancingSslResource)(nil) +) + +func NewIploadbalancingSslResource() resource.Resource { + return &iploadbalancingSslResource{} +} + +type iploadbalancingSslResource struct { + config *Config +} + +func (r *iploadbalancingSslResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_iploadbalancing_ssl" +} + +func (d *iploadbalancingSslResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + config, ok := req.ProviderData.(*Config) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.config = config +} + +func (d *iploadbalancingSslResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = IploadbalancingSslResourceSchema(ctx) +} + +func (r *iploadbalancingSslResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data, responseData IploadbalancingSslModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + endpoint := "/ipLoadbalancing/" + url.PathEscape(data.ServiceName.ValueString()) + "/ssl" + if err := r.config.OVHClient.Post(endpoint, data.ToCreate(), &responseData); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Post %s", endpoint), + err.Error(), + ) + return + } + + responseData.MergeWith(&data) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &responseData)...) +} + +func (r *iploadbalancingSslResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data, responseData IploadbalancingSslModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + endpoint := "/ipLoadbalancing/" + url.PathEscape(data.ServiceName.ValueString()) + "/ssl/" + strconv.FormatInt(data.Id.ValueInt64(), 10) + + if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Get %s", endpoint), + err.Error(), + ) + return + } + + data.MergeWith(&responseData) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *iploadbalancingSslResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data, planData, responseData IploadbalancingSslModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planData)...) + if resp.Diagnostics.HasError() { + return + } + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Update resource + endpoint := "/ipLoadbalancing/" + url.PathEscape(data.ServiceName.ValueString()) + "/ssl/" + strconv.FormatInt(data.Id.ValueInt64(), 10) + if err := r.config.OVHClient.Put(endpoint, planData.ToUpdate(), nil); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Put %s", endpoint), + err.Error(), + ) + return + } + + // Read updated resource + endpoint = "/ipLoadbalancing/" + url.PathEscape(data.ServiceName.ValueString()) + "/ssl/" + strconv.FormatInt(data.Id.ValueInt64(), 10) + if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Get %s", endpoint), + err.Error(), + ) + return + } + + responseData.MergeWith(&planData) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &responseData)...) +} + +func (r *iploadbalancingSslResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data IploadbalancingSslModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete API call logic + endpoint := "/ipLoadbalancing/" + url.PathEscape(data.ServiceName.ValueString()) + "/ssl/" + strconv.FormatInt(data.Id.ValueInt64(), 10) + if err := r.config.OVHClient.Delete(endpoint, nil); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Delete %s", endpoint), + err.Error(), + ) + } +} + +func (r *iploadbalancingSslResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + splits := strings.Split(req.ID, "/") + if len(splits) != 2 { + resp.Diagnostics.AddError("Given ID is malformed", "ID must be formatted like the following: /") + return + } + + serviceName := splits[0] + sslId, err := strconv.Atoi(splits[1]) + if err != nil { + resp.Diagnostics.AddError("Given ID is malformed", "ID must be formatted like the following: / where sslId is a number") + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("service_name"), serviceName)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), sslId)...) +} diff --git a/ovh/resource_iploadbalancing_ssl_gen.go b/ovh/resource_iploadbalancing_ssl_gen.go new file mode 100644 index 000000000..d0163058d --- /dev/null +++ b/ovh/resource_iploadbalancing_ssl_gen.go @@ -0,0 +1,200 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package ovh + +import ( + "context" + ovhtypes "github.com/ovh/terraform-provider-ovh/ovh/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func IploadbalancingSslResourceSchema(ctx context.Context) schema.Schema { + attrs := map[string]schema.Attribute{ + "certificate": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Certificate", + MarkdownDescription: "Certificate", + }, + "chain": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "Certificate chain", + MarkdownDescription: "Certificate chain", + }, + "display_name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "Human readable name for your ssl certificate, this field is for you", + MarkdownDescription: "Human readable name for your ssl certificate, this field is for you", + }, + "expire_date": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Expire date of your SSL certificate", + MarkdownDescription: "Expire date of your SSL certificate", + }, + "fingerprint": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Fingerprint of your SSL certificate", + MarkdownDescription: "Fingerprint of your SSL certificate", + }, + "id": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Computed: true, + Description: "Id of your SSL certificate", + MarkdownDescription: "Id of your SSL certificate", + }, + "key": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Certificate key", + MarkdownDescription: "Certificate key", + Sensitive: true, + }, + "san": schema.ListAttribute{ + CustomType: ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + Computed: true, + Description: "Subject Alternative Name of your SSL certificate", + MarkdownDescription: "Subject Alternative Name of your SSL certificate", + }, + "serial": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Serial of your SSL certificate (Deprecated, use fingerprint instead!)", + MarkdownDescription: "Serial of your SSL certificate (Deprecated, use fingerprint instead!)", + }, + "service_name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "The internal name of your IP load balancing", + MarkdownDescription: "The internal name of your IP load balancing", + }, + "subject": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Subject of your SSL certificate", + MarkdownDescription: "Subject of your SSL certificate", + }, + "type": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Type of your SSL certificate.\n'built' for SSL certificates managed by the IP Load Balancing. 'custom' for user manager certificates.", + MarkdownDescription: "Type of your SSL certificate.\n'built' for SSL certificates managed by the IP Load Balancing. 'custom' for user manager certificates.", + }, + } + + return schema.Schema{ + Attributes: attrs, + } +} + +type IploadbalancingSslModel struct { + Certificate ovhtypes.TfStringValue `tfsdk:"certificate" json:"certificate"` + Chain ovhtypes.TfStringValue `tfsdk:"chain" json:"chain"` + DisplayName ovhtypes.TfStringValue `tfsdk:"display_name" json:"displayName"` + ExpireDate ovhtypes.TfStringValue `tfsdk:"expire_date" json:"expireDate"` + Fingerprint ovhtypes.TfStringValue `tfsdk:"fingerprint" json:"fingerprint"` + Id ovhtypes.TfInt64Value `tfsdk:"id" json:"id"` + Key ovhtypes.TfStringValue `tfsdk:"key" json:"key"` + San ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"san" json:"san"` + Serial ovhtypes.TfStringValue `tfsdk:"serial" json:"serial"` + ServiceName ovhtypes.TfStringValue `tfsdk:"service_name" json:"serviceName"` + Subject ovhtypes.TfStringValue `tfsdk:"subject" json:"subject"` + Type ovhtypes.TfStringValue `tfsdk:"type" json:"type"` +} + +func (v *IploadbalancingSslModel) MergeWith(other *IploadbalancingSslModel) { + + if (v.Certificate.IsUnknown() || v.Certificate.IsNull()) && !other.Certificate.IsUnknown() { + v.Certificate = other.Certificate + } + + if (v.Chain.IsUnknown() || v.Chain.IsNull()) && !other.Chain.IsUnknown() { + v.Chain = other.Chain + } + + if (v.DisplayName.IsUnknown() || v.DisplayName.IsNull()) && !other.DisplayName.IsUnknown() { + v.DisplayName = other.DisplayName + } + + if (v.ExpireDate.IsUnknown() || v.ExpireDate.IsNull()) && !other.ExpireDate.IsUnknown() { + v.ExpireDate = other.ExpireDate + } + + if (v.Fingerprint.IsUnknown() || v.Fingerprint.IsNull()) && !other.Fingerprint.IsUnknown() { + v.Fingerprint = other.Fingerprint + } + + if (v.Id.IsUnknown() || v.Id.IsNull()) && !other.Id.IsUnknown() { + v.Id = other.Id + } + + if (v.Key.IsUnknown() || v.Key.IsNull()) && !other.Key.IsUnknown() { + v.Key = other.Key + } + + if (v.San.IsUnknown() || v.San.IsNull()) && !other.San.IsUnknown() { + v.San = other.San + } + + if (v.Serial.IsUnknown() || v.Serial.IsNull()) && !other.Serial.IsUnknown() { + v.Serial = other.Serial + } + + if (v.ServiceName.IsUnknown() || v.ServiceName.IsNull()) && !other.ServiceName.IsUnknown() { + v.ServiceName = other.ServiceName + } + + if (v.Subject.IsUnknown() || v.Subject.IsNull()) && !other.Subject.IsUnknown() { + v.Subject = other.Subject + } + + if (v.Type.IsUnknown() || v.Type.IsNull()) && !other.Type.IsUnknown() { + v.Type = other.Type + } + +} + +type IploadbalancingSslWritableModel struct { + Certificate *ovhtypes.TfStringValue `tfsdk:"certificate" json:"certificate,omitempty"` + Chain *ovhtypes.TfStringValue `tfsdk:"chain" json:"chain,omitempty"` + DisplayName *ovhtypes.TfStringValue `tfsdk:"display_name" json:"displayName,omitempty"` + Key *ovhtypes.TfStringValue `tfsdk:"key" json:"key,omitempty"` +} + +func (v IploadbalancingSslModel) ToCreate() *IploadbalancingSslWritableModel { + res := &IploadbalancingSslWritableModel{} + + if !v.Certificate.IsUnknown() { + res.Certificate = &v.Certificate + } + + if !v.Chain.IsUnknown() { + res.Chain = &v.Chain + } + + if !v.DisplayName.IsUnknown() { + res.DisplayName = &v.DisplayName + } + + if !v.Key.IsUnknown() { + res.Key = &v.Key + } + + return res +} + +func (v IploadbalancingSslModel) ToUpdate() *IploadbalancingSslWritableModel { + res := &IploadbalancingSslWritableModel{} + + if !v.DisplayName.IsUnknown() { + res.DisplayName = &v.DisplayName + } + + return res +} diff --git a/ovh/resource_iploadbalancing_ssl_test.go b/ovh/resource_iploadbalancing_ssl_test.go new file mode 100644 index 000000000..d3a5e6f97 --- /dev/null +++ b/ovh/resource_iploadbalancing_ssl_test.go @@ -0,0 +1,154 @@ +package ovh + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const certificate = ` +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIUchdtmNBNsdO0rJFBZEr14/5zAe4wDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUu +Y29tMB4XDTI0MDkwNTEzNTkxNVoXDTI1MDkwNTEzNTkxNVowXzELMAkGA1UEBhMC +QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp +dHMgUHR5IEx0ZDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2MNhJknJe62vUehYuzcRFUC3Xh3L4t5H2ZqD +xWloB6SUpzIhAmXtjmAvdwTfdB8Ne5+paOjki1/9HfV4OU3s5RAU21yGENEaZ7x/ +DJJnTyqwrFJY+iV+szvxRww5qt3SKVkRsDDwtk2yG7cEw4d2mlYoJAEAfL3EQeTt +/m/y+39Kq1ScnjcCslm9BdQUXiq8gqx+z6GyaZakVO8Kks0Sb45mkcN0KA71m9is +giLWVIrMy2Sb0pUprVZm8kd87pUBVM3bhbdv0iQTZRKirLJKLKnnq5wGFnb0yRer +8L7f91sMnZmohkWGJZ+oirzbjmX/H3h3wRkn1ns0I5PTkpQcUQIDAQABo1MwUTAd +BgNVHQ4EFgQUKrQqUfXiw0OaOD4d5GtNmKEe+CYwHwYDVR0jBBgwFoAUKrQqUfXi +w0OaOD4d5GtNmKEe+CYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEAVH1KHduXXKVeFXKLUnSpFMyjw/yw7WKiDH2myen8dA6UDUMj4W01PyoWx3Ic +U1D37zNgIXFr8+ViEi5Bpfz1FAGsRCNfP42Wi52xygkm7tezMT+VQN7WLRqPs4n9 +ogwNAm9HrYFuEBAeQZlCL1VIJhvk02JHELyZh+kUz77JjO/RcRS/qNIsHbf3TxT0 +6mNVQsoYWeBgKR894kGbCjsHFEKJT2Hf5IpsRy8fD70qSx/dE3paXHFzAXDybVxu +e5fge2/fk/rSbUm5CUsOwoxjNx100eRbH0BQTpdtgV1SzF3G127XTldZVkcdai6G +TOP9quQjYN/Q8Q+sMud9sDFeKA== +-----END CERTIFICATE----- +` + +const key = ` +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYw2EmScl7ra9R +6Fi7NxEVQLdeHcvi3kfZmoPFaWgHpJSnMiECZe2OYC93BN90Hw17n6lo6OSLX/0d +9Xg5TezlEBTbXIYQ0RpnvH8MkmdPKrCsUlj6JX6zO/FHDDmq3dIpWRGwMPC2TbIb +twTDh3aaVigkAQB8vcRB5O3+b/L7f0qrVJyeNwKyWb0F1BReKryCrH7PobJplqRU +7wqSzRJvjmaRw3QoDvWb2KyCItZUiszLZJvSlSmtVmbyR3zulQFUzduFt2/SJBNl +EqKsskosqeernAYWdvTJF6vwvt/3WwydmaiGRYYln6iKvNuOZf8feHfBGSfWezQj +k9OSlBxRAgMBAAECggEAWlUl1c53NF7/ypsQ60g6CsjTAdpZ/twSRklhs7HHJDQ+ +rOSzo+u1UZmc/jUeKCbOuB+j+m/f2oNwmP0UkpD6ccU/Y+FNj5GMtwFzUtpqSjAo +u09//BMHF4uZ87lRCPdzHz8ao3npvpdna6xcRF3eG9he1w5B1TpCIRHV6qxdrter +IS99GIJvA96OUgDe1jBSjJl9IsnaE4dMNyBQkMajo7NFgwPRIJk/XBbUUe7t+iaM +L/9dGKIZ+WIFsXqXc++wdTCo8ECEMT5/Yy/sFpUOvraeBLYm/+F1PQEoTQZ0c+p6 +ZI+owxIDCGv/wJivt2qX9+HHJWHKaKfFuFunEStoDwKBgQD6e9+hjrVd9L8kilfx +Jso1aC51SrCE6tuLx5Q4CnklEyYidFBhEcxMUUgQiK2EQP2+GA/e+ma/IZbO+b2E +2nYkoxKHn8afa6JbXx4HO5q6fZDgcwfI4pX64R1IBpFikUySJM0hUBWVIY+2UF0f +NOLGCBiUU/i4yx5HNxufKPILxwKBgQDdiWcFawqgExaNNdpvU8lCL81hW+V3jNzC +CqhFw0n20MObd5qZwSk3voCAhav30KYHjr8Smecp6rbmLpei0rd8CGa30tL9FdtR +P152YlxHYoYHU2KdWD46/rgClvMMGYAc+1dJv58OiHvehQmzGOKbci0u21MxPxe/ +KDsAkM8nJwKBgQCUE5ztribL538EBADfH/ZUQkWMs13NBeZKKO8XfiGF6F8X6TkH +WXUz/K0kkRg64gzfTuw6/j61aQ71RrBiFJ/ZIso2gR7zabbuWzmuPu9Gpip6daY5 +fLH7QQ+FX9Sct5bToovd0LEhm1iRB8s1Qpd5SJn3PfkAjZtVsF9U5OjKSwKBgQCm +zyYeY0od3CGX9Fvkhb8+MgZAb9Spnww+o42u8exIh0syTe3AJjzl93CE1aH2OEo7 +2JUw6WexHUXYrm6JMIbuQtktQvaRkJqSY9e55jg7nAj1jSjs9xvsig1+DbE2hCD+ +MZa5NisK42P52kzCaVN/3on9BTJwG2TDEATVWTRR8wKBgBJ5Wv/pjAQK3JconXw9 +AaDcfFXW9/LkdRdHBNDcj8hFPuSlQiMyLNztYuzUH3DPK9HpACtKEfoOGA53QeqZ +tLT0VQ+kvGVHp3ff1oFXslRw4USnjD9wTfhCSDtPZUtZQKD33575FISRV8kVToba +s9niPsoEYo3+0dm/OhJymKKD +-----END PRIVATE KEY----- +` + +const testAccCheckOvhIpLoadbalancingSslConfig = ` +resource "ovh_iploadbalancing_ssl" "testssl" { + service_name = "%s" + display_name = "%s" + certificate = <