Skip to content

Commit

Permalink
Merge pull request #2 from kristofferahl/feature/acm-certificate-regions
Browse files Browse the repository at this point in the history
Feature: ACM certificate regions
  • Loading branch information
kristofferahl authored Sep 4, 2022
2 parents cb9c222 + 18e2ad4 commit 3954d4b
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 39 deletions.
4 changes: 4 additions & 0 deletions apis/acm.aws/v1alpha1/certificate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type CertificateSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Region where to create the AWS resource. If not specified, the value of AWS_REGION is used.
// +kubebuilder:validation:Optional
Region string `json:"region"`

// DomainName is the fully qualified domain name (fqdn) used to create the AWS ACM Certificate.
// +kubebuilder:validation:Required
DomainName string `json:"domainName"`
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/acm.aws.aeto.net_certificates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ spec:
description: DomainName is the fully qualified domain name (fqdn)
used to create the AWS ACM Certificate.
type: string
region:
description: Region where to create the AWS resource. If not specified,
the value of AWS_REGION is used.
type: string
tags:
additionalProperties:
type: string
Expand Down
42 changes: 21 additions & 21 deletions controllers/acm.aws/certificate_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request)
func (r *CertificateReconciler) reconcileCertificate(ctx reconcile.Context, certificate acmawsv1alpha1.Certificate) (*acmtypes.CertificateDetail, reconcile.Result) {
ca := ""

certs, err := r.AWS.FindAcmCertificatesByDomainName(ctx.Context, certificate.Spec.DomainName)
certs, err := r.AWS.FindAcmCertificatesByDomainName(ctx.Context, certificate.Spec.Region, certificate.Spec.DomainName)
if err != nil {
ctx.Log.Error(err, "failed to find AWS ACM Certificate summaries matching domain name", "domain-name", certificate.Spec.DomainName)
ctx.Log.Error(err, "failed to find AWS ACM Certificate summaries matching domain name", "region", r.AWS.Region(certificate.Spec.Region), "domain-name", certificate.Spec.DomainName)
return nil, ctx.Error(err)
}

Expand All @@ -124,14 +124,14 @@ func (r *CertificateReconciler) reconcileCertificate(ctx reconcile.Context, cert

matchedArns := make([]string, 0)
for _, cs := range certs {
tags, err := r.AWS.ListAcmCertificateTagsByArn(ctx.Context, *cs.CertificateArn)
tags, err := r.AWS.ListAcmCertificateTagsByArn(ctx.Context, certificate.Spec.Region, *cs.CertificateArn)
if err != nil {
var rnfe *acmtypes.ResourceNotFoundException
if errors.As(err, &rnfe) {
ctx.Log.Info("AWS ACM Certificate tags not found", "arn", *cs.CertificateArn)
ctx.Log.Info("AWS ACM Certificate tags not found", "region", r.AWS.Region(certificate.Spec.Region), "arn", *cs.CertificateArn)
return nil, ctx.RequeueIn(5, fmt.Sprintf("AWS ACM Certificate tags not found for arn %s", *cs.CertificateArn))
} else {
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate tags", "arn", *cs.CertificateArn)
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate tags", "region", r.AWS.Region(certificate.Spec.Region), "arn", *cs.CertificateArn)
return nil, ctx.Error(err)
}
}
Expand Down Expand Up @@ -169,33 +169,33 @@ func (r *CertificateReconciler) reconcileCertificate(ctx reconcile.Context, cert

// No arn set, try and create it
if ca == "" {
ctx.Log.Info("creating a new AWS ACM Certificate", "domain-name", certificate.Spec.DomainName)
ctx.Log.Info("creating a new AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "domain-name", certificate.Spec.DomainName)
arn, err := r.newAcmCertificate(ctx, certificate, certificateTags)
if err != nil {
ctx.Log.Error(err, "failed to create AWS ACM Certificate", "domain-name", certificate.Spec.DomainName)
ctx.Log.Error(err, "failed to create AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "domain-name", certificate.Spec.DomainName)
return nil, ctx.Error(err)
}
ca = arn
}

// Arn set, get certificate and set tags
if ca != "" {
cd, err := r.AWS.GetAcmCertificateDetailsByArn(ctx.Context, ca)
cd, err := r.AWS.GetAcmCertificateDetailsByArn(ctx.Context, certificate.Spec.Region, ca)
if err != nil {
var rnfe *acmtypes.ResourceNotFoundException
if errors.As(err, &rnfe) {
ctx.Log.Info("AWS ACM Certificate details not found", "arn", ca)
return nil, ctx.RequeueIn(5, fmt.Sprintf("AWS ACM Certificate details not found for arn %s", ca))
} else {
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate details", "arn", ca)
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate details", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca)
return nil, ctx.Error(err)
}
}

ctx.Log.V(1).Info("reconciling tags for AWS ACM Certificate", "domain-name", cd.DomainName, "arn", cd.CertificateArn, "tags", certificate.Spec.Tags)
err = r.AWS.SetAcmCertificateTagsByArn(ctx.Context, ca, certificateTags)
ctx.Log.V(1).Info("reconciling tags for AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "domain-name", cd.DomainName, "arn", cd.CertificateArn, "tags", certificate.Spec.Tags)
err = r.AWS.SetAcmCertificateTagsByArn(ctx.Context, certificate.Spec.Region, ca, certificateTags)
if err != nil {
ctx.Log.Error(err, "failed to reconcile tags for AWS ACM Certificate", "domain-name", certificate.Spec.DomainName, "arn", *cd.CertificateArn, "tags", certificate.Spec.Tags)
ctx.Log.Error(err, "failed to reconcile tags for AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "domain-name", certificate.Spec.DomainName, "arn", *cd.CertificateArn, "tags", certificate.Spec.Tags)
return nil, ctx.Error(err)
}

Expand All @@ -216,7 +216,7 @@ func (r *CertificateReconciler) reconcileCertificateValidation(ctx reconcile.Con
return r.reconcileCertificateDnsValidationRecord(ctx, certificate.Spec.Validation.Dns.HostedZonedId, *details)
}

ctx.Log.Info("unhandled status for AWS ACM Certificate", "status", details.Status, "arn", *details.CertificateArn)
ctx.Log.Info("unhandled status for AWS ACM Certificate", "status", details.Status, "region", r.AWS.Region(certificate.Spec.Region), "arn", *details.CertificateArn)
}

return ctx.Done()
Expand Down Expand Up @@ -271,14 +271,14 @@ func (r *CertificateReconciler) reconcileDelete(ctx reconcile.Context, certifica
return ctx.Done()
}

cd, err := r.AWS.GetAcmCertificateDetailsByArn(ctx.Context, ca)
cd, err := r.AWS.GetAcmCertificateDetailsByArn(ctx.Context, certificate.Spec.Region, ca)
if err != nil {
var rnfe *acmtypes.ResourceNotFoundException
if errors.As(err, &rnfe) {
ctx.Log.Info("AWS ACM Certificate details not found", "arn", ca)
ctx.Log.Info("AWS ACM Certificate details not found", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca)
return ctx.Done()
} else {
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate", "arn", ca)
ctx.Log.Error(err, "failed to fetch AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca)
return ctx.Error(err)
}
}
Expand All @@ -299,7 +299,7 @@ func (r *CertificateReconciler) reconcileDelete(ctx reconcile.Context, certifica
TTL: aws.Int64(300),
}

ctx.Log.Info("deleting Domain Validation record for AWS ACM Certificate", "arn", ca, "hosted-zone-id", certificate.Spec.Validation.Dns.HostedZonedId, "recordset", recordSet)
ctx.Log.Info("deleting Domain Validation record for AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca, "hosted-zone-id", certificate.Spec.Validation.Dns.HostedZonedId, "recordset", recordSet)
err := r.AWS.DeleteRoute53ResourceRecordSet(ctx.Context, certificate.Spec.Validation.Dns.HostedZonedId, recordSet, "deleting DNS validation record for AWS ACM Certificate")
if err != nil {
var oe *smithy.OperationError
Expand All @@ -325,16 +325,16 @@ func (r *CertificateReconciler) reconcileDelete(ctx reconcile.Context, certifica
}

// Certificate
ctx.Log.Info("deleting AWS ACM Certificate", "arn", ca)
_, err = r.AWS.Acm.DeleteCertificate(ctx.Context, &acm.DeleteCertificateInput{
ctx.Log.Info("deleting AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca)
_, err = r.AWS.Acm(certificate.Spec.Region).DeleteCertificate(ctx.Context, &acm.DeleteCertificateInput{
CertificateArn: aws.String(ca),
})
if err != nil {
var riue *acmtypes.ResourceInUseException
if errors.As(err, &riue) {
return ctx.RequeueIn(15, fmt.Sprintf("failed to delete AWS ACM Certificate %s as it is currently in use", ca))
}
ctx.Log.Error(err, "failed to delete AWS ACM Certificate", "arn", ca)
ctx.Log.Error(err, "failed to delete AWS ACM Certificate", "region", r.AWS.Region(certificate.Spec.Region), "arn", ca)
return ctx.Error(err)
}

Expand Down Expand Up @@ -403,7 +403,7 @@ func (r *CertificateReconciler) newAcmCertificate(ctx reconcile.Context, certifi
req.Tags = tags
}

res, err := r.AWS.Acm.RequestCertificate(ctx.Context, req)
res, err := r.AWS.Acm(certificate.Spec.Region).RequestCertificate(ctx.Context, req)
if err != nil {
return "", err
}
Expand Down
47 changes: 31 additions & 16 deletions internal/pkg/aws/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/acm"
acmtypes "github.com/aws/aws-sdk-go-v2/service/acm/types"
"github.com/aws/aws-sdk-go-v2/service/route53"
Expand All @@ -17,23 +18,36 @@ import (
// Clients wrapper for AWS
type Clients struct {
Log logr.Logger
Acm *acm.Client
Config aws.Config
Route53 *route53.Client
}

func (c Clients) Region(region string) string {
if region != "" {
return region
}
return c.Config.Region
}

func (c Clients) Acm(region string) *acm.Client {
region = c.Region(region)
cfg, _ := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
return acm.NewFromConfig(cfg)
}

var unescaper = strings.NewReplacer(`\057`, "/", `\052`, "*")

type UniqueConstraintException struct {
message string
}

func (e *UniqueConstraintException) Error() string {
return "boom"
return e.message
}

// FindOneAcmCertificateByDomainName returns the first matching ACM Certificate by domain name
func (c Clients) FindOneAcmCertificateByDomainName(ctx context.Context, domainName string) (*acmtypes.CertificateSummary, error) {
items, err := c.ListAcmCertificates(ctx)
func (c Clients) FindOneAcmCertificateByDomainName(ctx context.Context, region string, domainName string) (*acmtypes.CertificateSummary, error) {
items, err := c.ListAcmCertificates(ctx, region)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -61,8 +75,8 @@ func (c Clients) FindOneAcmCertificateByDomainName(ctx context.Context, domainNa
}

// FindAcmCertificatesByDomainName returns the first matching ACM Certificate by domain name
func (c Clients) FindAcmCertificatesByDomainName(ctx context.Context, domainName string) ([]acmtypes.CertificateSummary, error) {
items, err := c.ListAcmCertificates(ctx)
func (c Clients) FindAcmCertificatesByDomainName(ctx context.Context, region string, domainName string) ([]acmtypes.CertificateSummary, error) {
items, err := c.ListAcmCertificates(ctx, region)
if err != nil {
return nil, err
}
Expand All @@ -80,8 +94,8 @@ func (c Clients) FindAcmCertificatesByDomainName(ctx context.Context, domainName
}

// GetAcmCertificateDetailsByArn returns ACM Certificate details by ARN
func (c Clients) GetAcmCertificateDetailsByArn(ctx context.Context, arn string) (acmtypes.CertificateDetail, error) {
res, err := c.Acm.DescribeCertificate(ctx, &acm.DescribeCertificateInput{
func (c Clients) GetAcmCertificateDetailsByArn(ctx context.Context, region string, arn string) (acmtypes.CertificateDetail, error) {
res, err := c.Acm(region).DescribeCertificate(ctx, &acm.DescribeCertificateInput{
CertificateArn: aws.String(arn),
})
if err != nil {
Expand All @@ -92,10 +106,10 @@ func (c Clients) GetAcmCertificateDetailsByArn(ctx context.Context, arn string)
}

// ListAcmCertificates returns all ACM Certificates
func (c Clients) ListAcmCertificates(ctx context.Context) ([]acmtypes.CertificateSummary, error) {
func (c Clients) ListAcmCertificates(ctx context.Context, region string) ([]acmtypes.CertificateSummary, error) {
items := make([]acmtypes.CertificateSummary, 0)

paginator := acm.NewListCertificatesPaginator(c.Acm, &acm.ListCertificatesInput{
paginator := acm.NewListCertificatesPaginator(c.Acm(region), &acm.ListCertificatesInput{
MaxItems: aws.Int32(5),
})
for paginator.HasMorePages() {
Expand All @@ -110,8 +124,9 @@ func (c Clients) ListAcmCertificates(ctx context.Context) ([]acmtypes.Certificat
}

// SetAcmCertificateTagsByArn adds, removes and updates tags for the ACM Certificate by ARN
func (c Clients) SetAcmCertificateTagsByArn(ctx context.Context, arn string, tags map[string]string) error {
tagsRes, err := c.Acm.ListTagsForCertificate(ctx, &acm.ListTagsForCertificateInput{
func (c Clients) SetAcmCertificateTagsByArn(ctx context.Context, region string, arn string, tags map[string]string) error {
rc := c.Acm(region)
tagsRes, err := rc.ListTagsForCertificate(ctx, &acm.ListTagsForCertificateInput{
CertificateArn: aws.String(arn),
})
if err != nil {
Expand Down Expand Up @@ -139,7 +154,7 @@ func (c Clients) SetAcmCertificateTagsByArn(ctx context.Context, arn string, tag
}

if len(remove) > 0 {
_, err := c.Acm.RemoveTagsFromCertificate(ctx, &acm.RemoveTagsFromCertificateInput{
_, err := rc.RemoveTagsFromCertificate(ctx, &acm.RemoveTagsFromCertificateInput{
CertificateArn: aws.String(arn),
Tags: remove,
})
Expand All @@ -149,7 +164,7 @@ func (c Clients) SetAcmCertificateTagsByArn(ctx context.Context, arn string, tag
}

if len(add) > 0 {
_, err := c.Acm.AddTagsToCertificate(ctx, &acm.AddTagsToCertificateInput{
_, err := rc.AddTagsToCertificate(ctx, &acm.AddTagsToCertificateInput{
CertificateArn: aws.String(arn),
Tags: add,
})
Expand All @@ -162,9 +177,9 @@ func (c Clients) SetAcmCertificateTagsByArn(ctx context.Context, arn string, tag
}

// ListAcmCertificateTagsByArn lists tags for the ACM Certificate by ARN
func (c Clients) ListAcmCertificateTagsByArn(ctx context.Context, arn string) (tags map[string]string, err error) {
func (c Clients) ListAcmCertificateTagsByArn(ctx context.Context, region string, arn string) (tags map[string]string, err error) {
tags = make(map[string]string)
tagsRes, err := c.Acm.ListTagsForCertificate(ctx, &acm.ListTagsForCertificateInput{
tagsRes, err := c.Acm(region).ListTagsForCertificate(ctx, &acm.ListTagsForCertificateInput{
CertificateArn: aws.String(arn),
})
if err != nil {
Expand Down
3 changes: 1 addition & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"time"

awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/acm"
"github.com/aws/aws-sdk-go-v2/service/route53"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
Expand Down Expand Up @@ -155,7 +154,7 @@ func main() {

awsClients := aws.Clients{
Log: ctrl.Log.WithName("aws-client"),
Acm: acm.NewFromConfig(awsConfig),
Config: awsConfig,
Route53: route53.NewFromConfig(awsConfig),
}

Expand Down

0 comments on commit 3954d4b

Please sign in to comment.