Skip to content

Commit

Permalink
Merge pull request #1554 from erhancagirici/partition
Browse files Browse the repository at this point in the history
[revive] Use the configured AWS partition when constructing ARNs
  • Loading branch information
turkenf authored Jan 29, 2025
2 parents 6e0f4f2 + b1d2f30 commit 02c2243
Show file tree
Hide file tree
Showing 17 changed files with 672 additions and 70 deletions.
2 changes: 1 addition & 1 deletion apis/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ type URLConfig struct {
// and region by choosing Static type. Alternatively, you can provide
// configuration for dynamically resolving the URL with the config you provide
// once you set the type as Dynamic.
// +kubebuilder:validation:Enum=Static;Dynamic
// +kubebuilder:validation:Enum=Static;Dynamic;Auto
Type string `json:"type"`

// Static is the full URL you'd like the AWS SDK to use.
Expand Down
225 changes: 225 additions & 0 deletions cmd/partitiongen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package main

import (
"bytes"
_ "embed"
"encoding/json"
"flag"
"fmt"
"go/format"
"html/template"
"io"
"log"
"net/http"
"os"
"sort"

"github.com/crossplane/crossplane-runtime/pkg/errors"
)

//go:embed partitions_gen.go.tmpl
var templateBody string

const documentVersion = 3

type PartitionDatum struct {
ID string
Name string
DNSSuffix string
RegionRegex string
CredentialScopeRegion string
IAMRegions map[string]string
GlobalServiceSigningRegions map[string]string
Regions []RegionDatum
}

type RegionDatum struct {
ID string
Description string
}

type TemplateData struct {
Partitions []PartitionDatum
}

type EndpointsDocument struct {
Partitions []PartitionModel `json:"partitions"`
Version uint64 `json:"version"`
}

type PartitionModel struct {
Defaults DefaultsModel `json:"defaults"`
DnsSuffix string `json:"dnsSuffix"`
Partition string `json:"partition"`
PartitionName string `json:"partitionName"`
RegionRegex string `json:"regionRegex"`
Regions map[string]RegionModel `json:"regions"`
Services map[string]ServiceModel `json:"services"`
}

type DefaultsModel struct {
Hostname string `json:"hostname"`
Protocols []string `json:"protocols"`
SignatureVersions []string `json:"signatureVersions"`
Variants []VariantModel `json:"variants"`
}

type VariantModel struct {
DnsSuffix string `json:"dnsSuffix"`
Hostname string `json:"hostname"`
Tags []string `json:"tags"`
}

type RegionModel struct {
Description string `json:"description"`
}

type ServiceModel struct {
Endpoints map[string]EndpointModel `json:"endpoints"`
IsRegionalized bool `json:"isRegionalized,omitempty"`
PartitionEndpoint string `json:"partitionEndpoint,omitempty"`
Defaults *DefaultsModel `json:"defaults,omitempty"`
}

type EndpointModel struct {
CredentialScope *CredentialScopeModel `json:"credentialScope,omitempty"`
Hostname string `json:"hostname"`
Protocols []string `json:"protocols,omitempty"`
Variants []VariantModel `json:"variants,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
}

type CredentialScopeModel struct {
Region string `json:"region,omitempty"`
Service string `json:"service,omitempty"`
}

func usage() {
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, "\tmain.go <aws-sdk-go-v2-endpoints-json-url>\n\n")
}

func main() { //nolint:gocyclo
flag.Usage = usage
flag.Parse()

args := flag.Args()

if len(args) < 1 {
flag.Usage()
os.Exit(2)
}

inputURL := args[0]
targetFilename := `zz_partitions_gen.go`
var endpointDocu EndpointsDocument

log.Println("Generating AWS partition definitions file", targetFilename)

if err := readEndpointsDocumentFromURL(inputURL, &endpointDocu); err != nil {
log.Fatalf("error reading JSON from %s: %s", inputURL, err)
}

templateData := TemplateData{}

if endpointDocu.Version != documentVersion {
log.Fatalf("unsupported endpoints document version: %d, expected version: %d", endpointDocu.Version, documentVersion)
}

for _, partition := range endpointDocu.Partitions {
partitionDatum := PartitionDatum{
GlobalServiceSigningRegions: make(map[string]string),
}
partitionDatum.ID = partition.Partition
partitionDatum.Name = partition.PartitionName
partitionDatum.DNSSuffix = partition.DnsSuffix
partitionDatum.RegionRegex = partition.RegionRegex
for id, region := range partition.Regions {
regionDatum := RegionDatum{
ID: id,
Description: region.Description,
}
partitionDatum.Regions = append(partitionDatum.Regions, regionDatum)
}

for svcName, svc := range partition.Services {
if svc.PartitionEndpoint != "" {
defaultEndpoint, ok := svc.Endpoints[svc.PartitionEndpoint]
if !ok {
log.Fatalf("partition endpoint %q not found for service %q in partition %q", svc.PartitionEndpoint, svcName, partition.Partition)
}
if defaultEndpoint.CredentialScope == nil {
continue
}
partitionDatum.GlobalServiceSigningRegions[svcName] = defaultEndpoint.CredentialScope.Region
}
}
templateData.Partitions = append(templateData.Partitions, partitionDatum)

}

sort.SliceStable(templateData.Partitions, func(i, j int) bool {
return templateData.Partitions[i].ID < templateData.Partitions[j].ID
})

for i := 0; i < len(templateData.Partitions); i++ {
sort.SliceStable(templateData.Partitions[i].Regions, func(j, k int) bool {
return templateData.Partitions[i].Regions[j].ID < templateData.Partitions[i].Regions[k].ID
})
}

tmpl, err := template.New("partitions").Parse(templateBody)

if err != nil {
log.Fatalf("parsing function template: %v", err)
}

targetFile, err := os.OpenFile(targetFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("failed to open file %q for write: %v", targetFilename, err)
}
defer func() {
if err := targetFile.Close(); err != nil {
log.Fatalf("Failed to close the file %q: %s", targetFilename, err.Error())
}
}()

buf := &bytes.Buffer{}
if err := tmpl.Execute(buf, templateData); err != nil {
log.Fatalf("cannot execute template: %v", err) //nolint:gocritic
}
gofmtFormattedBytes, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("cannot gofmt generated partitions file: %v", err)
}
if _, err := targetFile.Write(gofmtFormattedBytes); err != nil {
log.Fatalf("cannot write generated file: %v", err)
}

log.Println("Successfully generated AWS partition definitions file", targetFilename)
}

func readEndpointsDocumentFromURL(url string, to *EndpointsDocument) error {
r, err := http.Get(url) //nolint only for endpoint generation, with a fixed AWS url
if err != nil {
return errors.Wrap(err, "cannot fetch remote endpoints document")
}
if r.StatusCode < 200 || r.StatusCode > 299 {
return errors.Errorf("fetching endpoints document returned non-2xx HTTP status code: %s", r.Status)
}

defer func() {
if err := r.Body.Close(); err != nil {
log.Printf("error closing response body: %v", err)
}
}()

epDocumentRaw, err := io.ReadAll(r.Body)
if err != nil {
return errors.Wrap(err, "cannot read HTTP body")
}
return errors.Wrap(json.Unmarshal(epDocumentRaw, to), "cannot unmarshal endpoints document")
}
24 changes: 24 additions & 0 deletions cmd/partitiongen/partitions_gen.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

// Code generated by cmd/endpoints/main.go; DO NOT EDIT.

package clients

var (
partitions = map[string]awsPartition{
{{- range .Partitions }}
"{{ .ID }}": {
id: "{{ .ID }}",
name: "{{ .Name }}",
dnsSuffix: "{{ .DNSSuffix }}",
serviceToDefaultRegions: map[string]string{
{{- range $svc, $region := .GlobalServiceSigningRegions }}
"{{ $svc }}": "{{ $region }}",
{{- end }}
},
},
{{- end }}
}
)
Loading

0 comments on commit 02c2243

Please sign in to comment.