From 249dda2575fc494be6412ab2f3f96a5e2a8cf46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilson=20J=C3=BAnior?= Date: Fri, 22 Nov 2024 18:48:50 -0300 Subject: [PATCH] Craft tsuru_certificate_issuer resource --- go.mod | 2 +- go.sum | 8 + internal/provider/provider.go | 2 + .../resource_tsuru_certificate_issuer.go | 157 ++++++++++++++++++ .../resource_tsuru_certificate_issuer_test.go | 111 +++++++++++++ 5 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 internal/provider/resource_tsuru_certificate_issuer.go create mode 100644 internal/provider/resource_tsuru_certificate_issuer_test.go diff --git a/go.mod b/go.mod index f7d10fb..66fc752 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/labstack/echo/v4 v4.9.1 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 - github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4 + github.com/tsuru/go-tsuruclient v0.0.0-20241122210020-b97d66b89165 github.com/tsuru/tsuru-client v0.0.0-20240325204824-8c0dc602a5be k8s.io/apimachinery v0.26.2 ) diff --git a/go.sum b/go.sum index b116de8..d56d703 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,14 @@ github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/A github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I= github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4 h1:MGmG6AxKP8XRe7nQqIQR+Tsb5tCzHnYpYk0tiuXVgxY= github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= +github.com/tsuru/go-tsuruclient v0.0.0-20241114131333-e45b7f4741d8 h1:qYTfCDEqp1Zc4dT3J4F16eTvrkuXvHkeBTtJZrdvwRw= +github.com/tsuru/go-tsuruclient v0.0.0-20241114131333-e45b7f4741d8/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= +github.com/tsuru/go-tsuruclient v0.0.0-20241122182258-89f15daca17c h1:JFirEZjWW/PbHqQ4DE7kPfplDxqxmNgZwtU1gi9P0Aw= +github.com/tsuru/go-tsuruclient v0.0.0-20241122182258-89f15daca17c/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= +github.com/tsuru/go-tsuruclient v0.0.0-20241122182551-a810edb7c845 h1:tQA0PAHmYww0xZ8xlpY5GJ/qDK5IjlHuC0zrBNF0/pY= +github.com/tsuru/go-tsuruclient v0.0.0-20241122182551-a810edb7c845/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= +github.com/tsuru/go-tsuruclient v0.0.0-20241122210020-b97d66b89165 h1:sDC3Z+/pfdviUdLIH2Tcxa2tVR6FSiTRIR0kWkK8iiQ= +github.com/tsuru/go-tsuruclient v0.0.0-20241122210020-b97d66b89165/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18= github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 h1:1XDdWFAjIbCSG1OjN9v9KdWhuM8UtYlFcfHe/Ldkchk= github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6/go.mod h1:ztYpOhW+u1k21FEqp7nZNgpWbr0dUKok5lgGCZi+1AQ= github.com/tsuru/tsuru v0.0.0-20240325190920-410c71393b77 h1:cuWFjNLaemdQZhojqJbb/rOXO97tlcPeLAHg/x+EQGk= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index adf5c08..58502f1 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -70,6 +70,8 @@ func Provider() *schema.Provider { "tsuru_app_deploy": resourceTsuruApplicationDeploy(), "tsuru_app": resourceTsuruApplication(), + "tsuru_certificate_issuer": resourceTsuruCertificateIssuer(), + "tsuru_job": resourceTsuruJob(), "tsuru_job_env": resourceTsuruJobEnvironment(), "tsuru_job_deploy": resourceTsuruJobDeploy(), diff --git a/internal/provider/resource_tsuru_certificate_issuer.go b/internal/provider/resource_tsuru_certificate_issuer.go new file mode 100644 index 0000000..3080d09 --- /dev/null +++ b/internal/provider/resource_tsuru_certificate_issuer.go @@ -0,0 +1,157 @@ +// Copyright 2024 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package provider + +import ( + "context" + "sort" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/tsuru/go-tsuruclient/pkg/tsuru" +) + +func resourceTsuruCertificateIssuer() *schema.Resource { + return &schema.Resource{ + Description: "Set a issuer to generate certificates to a tsuru application", + CreateContext: resourceTsuruCertificateIssuerSet, + ReadContext: resourceTsuruCertificateIssuerRead, + DeleteContext: resourceTsuruCertificateIssuerUnset, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Update: schema.DefaultTimeout(60 * time.Minute), + Delete: schema.DefaultTimeout(60 * time.Minute), + }, + Importer: &schema.ResourceImporter{ + StateContext: resourceTsuruApplicationImport, + }, + Schema: map[string]*schema.Schema{ + "app": { + Type: schema.TypeString, + Description: "Application name", + Required: true, + ForceNew: true, + }, + + "cname": { + Type: schema.TypeString, + Description: "Application CNAME", + Required: true, + ForceNew: true, + }, + + "issuer": { + Type: schema.TypeString, + Description: "Certificate Issuer", + Required: true, + ForceNew: true, + }, + + "router": { + Type: schema.TypeList, + Description: "Routers that are using the certificate", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "certificate": { + Type: schema.TypeList, + Description: "Certificate Generated by Issuer, filled after the certificate is ready", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "ready": { + Type: schema.TypeBool, + Description: "If the certificate is ready", + Computed: true, + }, + }, + } +} + +func resourceTsuruCertificateIssuerSet(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + provider := meta.(*tsuruProvider) + + app := d.Get("app").(string) + cname := d.Get("cname").(string) + issuer := d.Get("issuer").(string) + + _, err := provider.TsuruClient.AppApi.AppSetCertIssuer(context.Background(), app, tsuru.CertIssuerSetData{ + Cname: cname, + Issuer: issuer, + }) + + if err != nil { + return diag.Errorf("unable to set certificate issuer: %v", err) + } + + d.SetId(app + "::" + cname + "::" + issuer) + + return resourceTsuruCertificateIssuerRead(ctx, d, meta) +} + +func resourceTsuruCertificateIssuerUnset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + provider := meta.(*tsuruProvider) + parts, err := IDtoParts(d.Id(), 3) + if err != nil { + return diag.FromErr(err) + } + app := parts[0] + cname := parts[1] + + _, err = provider.TsuruClient.AppApi.AppUnsetCertIssuer(context.Background(), app, cname) + + if err != nil { + return diag.Errorf("unable to unset certificate issuer: %v", err) + } + + return resourceTsuruCertificateIssuerRead(ctx, d, meta) +} + +func resourceTsuruCertificateIssuerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + provider := meta.(*tsuruProvider) + parts, err := IDtoParts(d.Id(), 3) + if err != nil { + return diag.FromErr(err) + } + app := parts[0] + cname := parts[1] + issuer := parts[2] + + certificates, _, err := provider.TsuruClient.AppApi.AppGetCertificates(context.Background(), app) + if err != nil { + return diag.FromErr(err) + } + + usedRouters := []string{} + usedCertificates := []string{} + + for routerName, router := range certificates.Routers { + if cnameInRouter, ok := router.Cnames[cname]; ok { + if cnameInRouter.Issuer == issuer { + usedRouters = append(usedRouters, routerName) + + if cnameInRouter.Certificate != "" { + usedCertificates = append(usedCertificates, cnameInRouter.Certificate) + } + } + } + } + + sort.Strings(usedRouters) + sort.Strings(usedCertificates) + + d.Set("app", app) + d.Set("cname", cname) + d.Set("issuer", issuer) + + d.Set("router", usedRouters) + d.Set("certificate", usedCertificates) + d.Set("ready", len(usedCertificates) > 0) + + return nil +} diff --git a/internal/provider/resource_tsuru_certificate_issuer_test.go b/internal/provider/resource_tsuru_certificate_issuer_test.go new file mode 100644 index 0000000..086ff7b --- /dev/null +++ b/internal/provider/resource_tsuru_certificate_issuer_test.go @@ -0,0 +1,111 @@ +// Copyright 2021 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package provider + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + echo "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tsuru/go-tsuruclient/pkg/tsuru" +) + +func TestAccTsuruCertificateIssuer(t *testing.T) { + fakeServer := echo.New() + fakeServer.PUT("/1.24/apps/:app/certissuer", func(c echo.Context) error { + p := &tsuru.CertIssuerSetData{} + err := c.Bind(p) + require.NoError(t, err) + assert.Equal(t, "my-app", c.Param("app")) + assert.Equal(t, "my-cname.org", p.Cname) + assert.Equal(t, "lets-encrypt", p.Issuer) + + return nil + }) + + fakeServer.DELETE("/1.24/apps/:app/certissuer", func(c echo.Context) error { + assert.Equal(t, "my-app", c.Param("app")) + assert.Equal(t, "my-cname.org", c.QueryParam("cname")) + + return nil + }) + fakeServer.GET("/1.24/apps/:app/certificate", func(c echo.Context) error { + assert.Equal(t, "my-app", c.Param("app")) + + return c.JSON(http.StatusOK, tsuru.AppCertificates{ + Routers: map[string]tsuru.AppCertificatesRouters{ + "https-router": { + Cnames: map[string]tsuru.AppCertificatesCnames{ + "my-cname.org": { + Issuer: "lets-encrypt", + Certificate: "123", + }, + + "other-cname.org": { + Issuer: "self-signed", + Certificate: "321", + }, + }, + }, + "other-https-router": { + Cnames: map[string]tsuru.AppCertificatesCnames{ + "my-cname.org": { + Issuer: "rapid-ssl", + Certificate: "123", + }, + + "other-cname.org": { + Issuer: "self-signed", + Certificate: "4321", + }, + }, + }, + }, + }) + }) + + fakeServer.HTTPErrorHandler = func(err error, c echo.Context) { + t.Errorf("method=%s, path=%s, err=%s", c.Request().Method, c.Path(), err.Error()) + } + + server := httptest.NewServer(fakeServer) + os.Setenv("TSURU_TARGET", server.URL) + + resourceName := "tsuru_certificate_issuer.cert" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "tsuru_certificate_issuer.cert", + ProviderFactories: testAccProviderFactories, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccTsuruCertificateIssuer("my-app", "my-cname.org", "lets-encrypt"), + ExpectNonEmptyPlan: false, + Check: resource.ComposeAggregateTestCheckFunc( + testAccResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "app", "my-app"), + resource.TestCheckResourceAttr(resourceName, "cname", "my-cname.org"), + resource.TestCheckResourceAttr(resourceName, "issuer", "lets-encrypt"), + ), + }, + }, + }) +} + +func testAccTsuruCertificateIssuer(app, cname, issuer string) string { + return fmt.Sprintf(` +resource "tsuru_certificate_issuer" "cert" { + app = %q + cname = %q + issuer = %q +} +`, app, cname, issuer) +}