Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[STORY-443] Add ability to create/update domain without Letsencrypt Certificate Generation #379

Merged
merged 2 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 44 additions & 24 deletions domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (

type DomainsService interface {
DomainsList(ctx context.Context, app string) ([]Domain, error)
DomainsAdd(ctx context.Context, app string, d Domain) (Domain, error)
DomainsAdd(ctx context.Context, app string, d DomainsAddParams) (Domain, error)
DomainsUpdate(ctx context.Context, app string, id string, d DomainsUpdateParams) (Domain, error)
DomainsRemove(ctx context.Context, app string, id string) error
// DomainsUpdate helpers
DomainSetCanonical(ctx context.Context, app, id string) (Domain, error)
DomainUnsetCanonical(ctx context.Context, app string) (Domain, error)
DomainSetCertificate(ctx context.Context, app, id, tlsCert, tlsKey string) (Domain, error)
Expand Down Expand Up @@ -44,15 +46,20 @@ type ACMEErrorVariables struct {
}

type Domain struct {
ID string `json:"id"`
AppID string `json:"app_id"`
Name string `json:"name"`
TLSCert string `json:"tlscert,omitempty"`
TLSKey string `json:"tlskey,omitempty"`
SSL bool `json:"ssl"`
Validity time.Time `json:"validity"`
Canonical bool `json:"canonical"`
LetsEncrypt bool `json:"letsencrypt"`
ID string `json:"id"`
AppID string `json:"app_id"`
Name string `json:"name"`
TLSCert string `json:"tlscert,omitempty"`
TLSKey string `json:"tlskey,omitempty"`
// LetsEncrypteEnabled is true (default) if automatic certificate generation
curzolapierre marked this conversation as resolved.
Show resolved Hide resolved
// is enabled
LetsEncryptEnabled bool `json:"letsencrypt_enabled"`
SSL bool `json:"ssl"`
Validity time.Time `json:"validity"`
Canonical bool `json:"canonical"`
// LetsEncrypt is true if the certificate is generated by LetsEncrypt
LetsEncrypt bool `json:"letsencrypt"`
// LetsEncryptStatus is the status of the LetsEncrypt certificate generation
LetsEncryptStatus LetsEncryptStatus `json:"letsencrypt_status"`
SslStatus SslStatus `json:"ssl_status"`
AcmeDNSFqdn string `json:"acme_dns_fqdn"`
Expand Down Expand Up @@ -84,9 +91,17 @@ func (c *Client) DomainsList(ctx context.Context, app string) ([]Domain, error)
return domainRes.Domains, nil
}

func (c *Client) DomainsAdd(ctx context.Context, app string, d Domain) (Domain, error) {
type DomainsAddParams struct {
Name string `json:"name"`
Canonical *bool `json:"canonical,omitempty"`
TLSCert *string `json:"tlscert,omitempty"`
TLSKey *string `json:"tlskey,omitempty"`
LetsEncryptEnabled *bool `json:"letsencrypt_enabled,omitempty"`
}

func (c *Client) DomainsAdd(ctx context.Context, app string, params DomainsAddParams) (Domain, error) {
var domainRes DomainRes
err := c.ScalingoAPI().SubresourceAdd(ctx, "apps", app, "domains", DomainRes{d}, &domainRes)
err := c.ScalingoAPI().SubresourceAdd(ctx, "apps", app, "domains", map[string]DomainsAddParams{"domain": params}, &domainRes)
if err != nil {
return Domain{}, errgo.Notef(err, "fail to add a domain")
}
Expand All @@ -108,38 +123,42 @@ func (c *Client) DomainsShow(ctx context.Context, app, id string) (Domain, error
return domainRes.Domain, nil
}

func (c *Client) domainsUpdate(ctx context.Context, app, id string, domain Domain) (Domain, error) {
type DomainsUpdateParams struct {
Canonical *bool `json:"canonical,omitempty"`
TLSCert *string `json:"tlscert,omitempty"`
TLSKey *string `json:"tlskey,omitempty"`
LetsEncryptEnabled *bool `json:"letsencrypt_enabled,omitempty"`
}

func (c *Client) DomainsUpdate(ctx context.Context, app, id string, params DomainsUpdateParams) (Domain, error) {
var domainRes DomainRes
err := c.ScalingoAPI().SubresourceUpdate(ctx, "apps", app, "domains", id, DomainRes{Domain: domain}, &domainRes)
err := c.ScalingoAPI().SubresourceUpdate(ctx, "apps", app, "domains", id, map[string]DomainsUpdateParams{"domain": params}, &domainRes)
if err != nil {
return Domain{}, errgo.Notef(err, "fail to update the domain")
}
return domainRes.Domain, nil
}

func (c *Client) DomainSetCertificate(ctx context.Context, app, id, tlsCert, tlsKey string) (Domain, error) {
domain, err := c.domainsUpdate(ctx, app, id, Domain{TLSCert: tlsCert, TLSKey: tlsKey})
domain, err := c.DomainsUpdate(ctx, app, id, DomainsUpdateParams{TLSCert: &tlsCert, TLSKey: &tlsKey})
if err != nil {
return Domain{}, errgo.Notef(err, "fail to set the domain certificate")
}
return domain, nil
}

func (c *Client) DomainUnsetCertificate(ctx context.Context, app, id string) (Domain, error) {
var domainRes DomainRes
err := c.ScalingoAPI().SubresourceUpdate(
ctx, "apps", app, "domains", id, map[string]domainUnsetCertificateParams{
"domain": {TLSCert: "", TLSKey: ""},
}, &domainRes,
)
empty := ""
domain, err := c.DomainsUpdate(ctx, app, id, DomainsUpdateParams{TLSCert: &empty, TLSKey: &empty})
if err != nil {
return Domain{}, errgo.Notef(err, "fail to unset the domain certificate")
}
return domainRes.Domain, nil
return domain, nil
}

func (c *Client) DomainSetCanonical(ctx context.Context, app, id string) (Domain, error) {
domain, err := c.domainsUpdate(ctx, app, id, Domain{Canonical: true})
value := true
domain, err := c.DomainsUpdate(ctx, app, id, DomainsUpdateParams{Canonical: &value})
if err != nil {
return Domain{}, errgo.Notef(err, "fail to set the domain as canonical")
}
Expand All @@ -154,7 +173,8 @@ func (c *Client) DomainUnsetCanonical(ctx context.Context, app string) (Domain,

for _, domain := range domains {
if domain.Canonical {
domain, err := c.domainsUpdate(ctx, app, domain.ID, Domain{Canonical: false})
value := false
domain, err := c.DomainsUpdate(ctx, app, domain.ID, DomainsUpdateParams{Canonical: &value})
if err != nil {
return Domain{}, errgo.Notef(err, "fail to unset the domain as canonical")
}
Expand Down
54 changes: 45 additions & 9 deletions domains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)

func TestDomainsClient_DomainCanonical(t *testing.T) {
func TestDomainsClient_Domain_Updates(t *testing.T) {
ctx := context.Background()
appName := "my-app"
domainID := "domain-id"
Expand All @@ -27,6 +27,7 @@ func TestDomainsClient_DomainCanonical(t *testing.T) {
mockDomainsList func(t *testing.T, w http.ResponseWriter, r *http.Request)
expectedError string
}{
// Canonical status update
"it should set the domain as canonical": {
testedClientCall: func(c DomainsService) error {
_, err := c.DomainSetCanonical(ctx, appName, domainID)
Expand All @@ -38,7 +39,7 @@ func TestDomainsClient_DomainCanonical(t *testing.T) {
responseStatus: 200,
},
"it should unset the domain as canonical": {
mockDomainsList: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
mockDomainsList: func(t *testing.T, w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(200)
err := json.NewEncoder(w).Encode(DomainsRes{Domains: []Domain{
{
Expand All @@ -57,6 +58,29 @@ func TestDomainsClient_DomainCanonical(t *testing.T) {
expectedParams: `"canonical":false`,
responseStatus: 200,
},
"it should return an error if there is no canonical domain": {
mockDomainsList: func(t *testing.T, w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(200)
err := json.NewEncoder(w).Encode(DomainsRes{Domains: []Domain{}})
assert.NoError(t, err)
},
testedClientCall: func(c DomainsService) error {
_, err := c.DomainUnsetCanonical(ctx, appName)
return err
},
expectedError: "no canonical domain configured",
},
// Domain certificate update
"it should set the domain certificate": {
testedClientCall: func(c DomainsService) error {
_, err := c.DomainSetCertificate(ctx, appName, domainID, "cert", "key")
return err
},
expectedEndpoint: "/v1/apps/my-app/domains/domain-id",
expectedMethod: "PATCH",
expectedParams: `"tlscert":"cert","tlskey":"key"`,
responseStatus: 200,
},
"it should unset the domain certificate": {
testedClientCall: func(c DomainsService) error {
_, err := c.DomainUnsetCertificate(ctx, appName, domainID)
Expand All @@ -67,17 +91,29 @@ func TestDomainsClient_DomainCanonical(t *testing.T) {
expectedParams: `"tlscert":"","tlskey":""`,
responseStatus: 200,
},
"it should return an error if there is no canonical domain": {
mockDomainsList: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
err := json.NewEncoder(w).Encode(DomainsRes{Domains: []Domain{}})
assert.NoError(t, err)
// Letsencrypt certificate generation
"it should update letsencrypt_enabled to false": {
testedClientCall: func(c DomainsService) error {
f := false
_, err := c.DomainsUpdate(ctx, appName, domainID, DomainsUpdateParams{
LetsEncryptEnabled: &f,
})
return err
},
expectedEndpoint: "/v1/apps/my-app/domains/domain-id",
expectedMethod: "PATCH",
expectedParams: `"letsencrypt_enabled":false`,
responseStatus: 200,
},
"it should not update anything if no params": {
testedClientCall: func(c DomainsService) error {
_, err := c.DomainUnsetCanonical(ctx, appName)
_, err := c.DomainsUpdate(ctx, appName, domainID, DomainsUpdateParams{})
return err
},
expectedError: "no canonical domain configured",
expectedEndpoint: "/v1/apps/my-app/domains/domain-id",
expectedMethod: "PATCH",
expectedParams: `{"domain":{}}`,
responseStatus: 200,
},
}

Expand Down