diff --git a/.gitignore b/.gitignore
index 3b505dd..1b206d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ clientconfig.yml
# Configuration for the CSB server.
secrets.env
zscaler.crt
+# Sqlite database for local development.
+.csb.db
diff --git a/Dockerfile b/Dockerfile
index a48eb3c..3535f0a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,9 +18,9 @@ RUN set -e; if [ "$BUILD_ENV" = "production" ] ; then echo "production env"; els
cp /app/zscaler.crt $CERT_DIR ; update-ca-certificates ; \
fi
-RUN /app/csb pak build brokerpaks/cg-smtp
+RUN /app/csb pak build brokerpaks/aws-ses
FROM ${base_image}
# Copy brokerpaks to final image
-COPY --from=build /app/cg-smtp-0.1.0.brokerpak /app/
+COPY --from=build /app/aws-ses-0.1.0.brokerpak /app/
diff --git a/Makefile b/Makefile
index 2bc42fc..7eea4d2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,16 @@
build:
- PAK_BUILD_CACHE_PATH=/tmp/pak-build-cache cloud-service-broker pak build brokerpaks/cg-smtp
+ PAK_BUILD_CACHE_PATH=/tmp/pak-build-cache cloud-service-broker pak build brokerpaks/aws-ses
run: build
# Load environment variables from secrets.env, scoped to this command only.
@bash -c 'env $$(cat secrets.env | xargs) cloud-service-broker serve'
watch:
- find . | grep -E '\.env|\.tf' | entr -r make run
+ find . | grep -E '\.env|\.tf|\.yml' | entr -r make run
+
+clean:
+ rm -r /tmp/pak-build-cache
+ rm *.brokerpak
+
+run-docproxy:
+ find docproxy | entr -r go run ./docproxy/.
diff --git a/README.md b/README.md
index 25d9e25..31cab4c 100644
--- a/README.md
+++ b/README.md
@@ -6,3 +6,7 @@ This repo contains configuration, including brokerpaks, for the cloud.gov deploy
- https://github.com/GSA-TTS/datagov-brokerpak-smtp
- https://github.com/GSA/ttsnotify-brokerpak-sms
+
+## Credits
+
+- AWS Architecture icons are sourced from https://aws.amazon.com/architecture/icons/.
diff --git a/brokerpaks/cg-smtp/cg-smtp.yml b/brokerpaks/aws-ses/aws-ses.yml
similarity index 79%
rename from brokerpaks/cg-smtp/cg-smtp.yml
rename to brokerpaks/aws-ses/aws-ses.yml
index e1bfb85..0115cd8 100644
--- a/brokerpaks/cg-smtp/cg-smtp.yml
+++ b/brokerpaks/aws-ses/aws-ses.yml
@@ -1,43 +1,42 @@
version: 1
-name: cg-smtp
+name: aws-ses
id: 260f2ead-b9e9-48b5-9a01-6e3097208ad7
-description: SMTP service provided by Amazon Simple Email Service (SES)
-display_name: SMTP (using AWS SES)
-image_url: https://example.com/icon.jpg
+description: Send email from verified domains using Amazon Simple Email Service (SES). Supports SMTP and the SES HTTP API.
+display_name: AWS Simple Email Service
+image_url: https://services.cloud.gov/images/amazon-ses.svg
documentation_url: https://aws.amazon.com/ses/ # todo
-provider_display_name: ""
+provider_display_name: "Cloud.gov"
support_url: https://cloud.gov/contact/
-tags: [aws, ses, smtp]
+tags: [aws, csb]
plans:
- name: base
id: 35ffb84b-a898-442e-b5f9-0a6a5229827d
- description: Provision SMTP credentials for sending email from any user at a domain, like 'agency.gov'.
+ description: Provision SMTP credentials for sending email from any user at a domain, like `agency.gov`.
display_name: Send-only service
provision:
plan_inputs:
user_inputs:
- field_name: domain
type: string
- default: ""
- details: Domain from mail will be sent. For example, agency.gov. If left empty, a temporary cloud.gov subdomain will be generated.
+ required: false
+ prohibit_update: true
+ details: Domain from which mail will be sent. For example, `agency.gov`. If left empty, a temporary cloud.gov subdomain will be generated.
- field_name: dmarc_report_uri_aggregate
type: string
required: true
- default: ""
- details: The mailto URI to which DMARC aggregate reports should be sent. For example, 'mailto:dmarc@example.gov'. Reports are automatically sent to reports@dmarc.cyber.dhs.gov.
+ details: The mailto URI to which DMARC aggregate reports should be sent. For example, `mailto:dmarc@example.gov`. Reports are automatically sent to `reports@dmarc.cyber.dhs.gov`. If you specify a domain and later update this parameter, remember to update your DNS with the new records in the `required_records` output.
- field_name: dmarc_report_uri_failure
type: string
required: true
- default: ""
- details: The mailto URI to which DMARC individual message failure reports should be sent. For example, 'mailto:dmarc@example.gov'.
+ details: The mailto URI to which DMARC individual message failure reports should be sent. For example, `mailto:dmarc@example.gov`. If you specify a domain and later update this parameter, remember to update your DNS with the new records in the `required_records` output.
- field_name: enable_feedback_notifications
type: boolean
details: Flag to toggle creation of SNS topics for feedback notifications.
default: false
- field_name: mail_from_subdomain
type: string
- default: ""
- details: Subdomain to use as the sending email server.
+ required: false
+ details: The custom MAIL FROM domain that you want the verified identity to use. See the [SES v2 API reference](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_PutEmailIdentityMailFromAttributes.html) for requirements.
computed_inputs:
- name: aws_access_key_id_govcloud
type: string
@@ -60,7 +59,7 @@ provision:
- name: default_domain
overwrite: true
type: string
- default: ${config("aws.zone")}
+ default: ${config("cg_smtp.aws.zone")}
- name: labels
default: ${json.marshal(request.default_labels)}
overwrite: true
@@ -73,7 +72,7 @@ provision:
default: ${request.context}
- name: service_offering_name
type: string
- default: cg-smtp
+ default: aws-ses
- name: service_plan_name
type: string
default: base
@@ -89,10 +88,10 @@ provision:
details: If a domain was supplied, you must create these records in that zone in your DNS system.
- field_name: dmarc_report_uri_aggregate
type: string
- details: The mailto URI to which DMARC aggregate reports should be sent. For example, 'mailto:dmarc@example.gov'. Reports are automatically sent to reports@dmarc.cyber.dhs.gov.
+ details: The mailto URI to which DMARC aggregate reports should be sent. For example, `mailto:dmarc@example.gov`. Reports are automatically sent to `reports@dmarc.cyber.dhs.gov`.
- field_name: dmarc_report_uri_failure
type: string
- details: The mailto URI to which DMARC individual message failure reports should be sent. For example, 'mailto:dmarc@example.gov'.
+ details: The mailto URI to which DMARC individual message failure reports should be sent. For example, `mailto:dmarc@example.gov`.
- field_name: instructions
type: string
details: Any further steps that you must take before using the service.
@@ -101,7 +100,7 @@ provision:
details: ARN of the SES Configuration Set associated with the identity. Used to create bindings.
- field_name: domain_arn
type: string
- details: Instance SES domain identity (used when creating bindings)
+ details: Instance SES domain identity. Used to create bindings.
- field_name: bounce_topic_arn
type: string
details: ARN of the SNS topic receiving bounce feedback notifications.
@@ -125,11 +124,10 @@ bind:
- field_name: source_ips
type: array
default: []
- details: IP Ranges that requests to SES must come from.
+ details: A list of IP ranges in CIDR format. If specified, requests made with this binding must originate from the specified ranges. By default, all requests are allowed.
prohibit_update: false
- field_name: notification_webhook
type: string
- default: ""
details: HTTPS endpoint to subscribe to feedback notifications.
computed_inputs:
- name: aws_access_key_id_govcloud
@@ -177,7 +175,7 @@ bind:
default: ${request.context}
- name: service_offering_name
type: string
- default: cg-smtp
+ default: aws-ses
- name: service_plan_name
type: string
default: base
diff --git a/brokerpaks/cg-smtp/client/.cfignore b/brokerpaks/aws-ses/client/.cfignore
similarity index 100%
rename from brokerpaks/cg-smtp/client/.cfignore
rename to brokerpaks/aws-ses/client/.cfignore
diff --git a/brokerpaks/cg-smtp/client/.gitignore b/brokerpaks/aws-ses/client/.gitignore
similarity index 100%
rename from brokerpaks/cg-smtp/client/.gitignore
rename to brokerpaks/aws-ses/client/.gitignore
diff --git a/brokerpaks/cg-smtp/client/bin/down.sh b/brokerpaks/aws-ses/client/bin/down.sh
similarity index 100%
rename from brokerpaks/cg-smtp/client/bin/down.sh
rename to brokerpaks/aws-ses/client/bin/down.sh
diff --git a/brokerpaks/cg-smtp/client/bin/up.sh b/brokerpaks/aws-ses/client/bin/up.sh
similarity index 100%
rename from brokerpaks/cg-smtp/client/bin/up.sh
rename to brokerpaks/aws-ses/client/bin/up.sh
diff --git a/brokerpaks/cg-smtp/client/go.mod b/brokerpaks/aws-ses/client/go.mod
similarity index 100%
rename from brokerpaks/cg-smtp/client/go.mod
rename to brokerpaks/aws-ses/client/go.mod
diff --git a/brokerpaks/cg-smtp/client/main.go b/brokerpaks/aws-ses/client/main.go
similarity index 99%
rename from brokerpaks/cg-smtp/client/main.go
rename to brokerpaks/aws-ses/client/main.go
index a018144..65eb48b 100644
--- a/brokerpaks/cg-smtp/client/main.go
+++ b/brokerpaks/aws-ses/client/main.go
@@ -15,7 +15,7 @@ import (
type VCAPServices struct {
SMTPService []struct {
Credentials Credentials `json:"credentials"`
- } `json:"cg-smtp"`
+ } `json:"aws-ses"`
}
type Credentials struct {
diff --git a/brokerpaks/cg-smtp/client/manifest.yml b/brokerpaks/aws-ses/client/manifest.yml
similarity index 100%
rename from brokerpaks/cg-smtp/client/manifest.yml
rename to brokerpaks/aws-ses/client/manifest.yml
diff --git a/brokerpaks/cg-smtp/manifest.yml b/brokerpaks/aws-ses/manifest.yml
similarity index 94%
rename from brokerpaks/cg-smtp/manifest.yml
rename to brokerpaks/aws-ses/manifest.yml
index 32093b6..fec0bf9 100644
--- a/brokerpaks/cg-smtp/manifest.yml
+++ b/brokerpaks/aws-ses/manifest.yml
@@ -1,5 +1,5 @@
packversion: 1
-name: cg-smtp
+name: aws-ses
version: 0.1.0
metadata:
author: cloud.gov team
@@ -22,23 +22,23 @@ terraform_binaries:
version: 5.53.0
source: https://github.com/terraform-providers/terraform-provider-aws/archive/v5.53.0.zip
service_definitions:
- - cg-smtp.yml
+ - aws-ses.yml
parameters: []
required_env_variables:
- - AWS_ZONE
+ - AWS_ACCESS_KEY_ID_COMMERCIAL
- AWS_ACCESS_KEY_ID_GOVCLOUD
- - AWS_SECRET_ACCESS_KEY_GOVCLOUD
+ - AWS_REGION_COMMERCIAL
- AWS_REGION_GOVCLOUD
- - AWS_ACCESS_KEY_ID_COMMERCIAL
- AWS_SECRET_ACCESS_KEY_COMMERCIAL
- - AWS_REGION_COMMERCIAL
+ - AWS_SECRET_ACCESS_KEY_GOVCLOUD
+ - CG_SMTP_AWS_ZONE
- CLOUD_GOV_ENVIRONMENT
env_config_mapping:
+ AWS_ACCESS_KEY_ID_COMMERCIAL: aws.commercial.access_key_id
AWS_ACCESS_KEY_ID_GOVCLOUD: aws.govcloud.access_key_id
- AWS_SECRET_ACCESS_KEY_GOVCLOUD: aws.govcloud.secret_access_key
+ AWS_REGION_COMMERCIAL: aws.commercial.region
AWS_REGION_GOVCLOUD: aws.govcloud.region
- AWS_ACCESS_KEY_ID_COMMERCIAL: aws.commercial.access_key_id
AWS_SECRET_ACCESS_KEY_COMMERCIAL: aws.commercial.secret_access_key
- AWS_REGION_COMMERCIAL: aws.commercial.region
- AWS_ZONE: aws.zone
+ AWS_SECRET_ACCESS_KEY_GOVCLOUD: aws.govcloud.secret_access_key
+ CG_SMTP_AWS_ZONE: cg_smtp.aws.zone
CLOUD_GOV_ENVIRONMENT: cloud_gov.environment
diff --git a/brokerpaks/cg-smtp/terraform/bind/README.md b/brokerpaks/aws-ses/terraform/bind/README.md
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/README.md
rename to brokerpaks/aws-ses/terraform/bind/README.md
diff --git a/brokerpaks/cg-smtp/terraform/bind/main.tf b/brokerpaks/aws-ses/terraform/bind/main.tf
similarity index 87%
rename from brokerpaks/cg-smtp/terraform/bind/main.tf
rename to brokerpaks/aws-ses/terraform/bind/main.tf
index 402fe0f..bc2bdd0 100644
--- a/brokerpaks/cg-smtp/terraform/bind/main.tf
+++ b/brokerpaks/aws-ses/terraform/bind/main.tf
@@ -7,6 +7,10 @@ locals {
subscribed_webhook = ((local.subscribe_bounce_notification || local.subscribe_complaint_notification || local.subscribe_delivery_notification) ? var.notification_webhook : null)
}
+# Trivy: It is best practice to manage access via groups intead of by directly attaching
+# policies to users. However, each binding may specify separate source IP constraints
+# on sending, so we cannot use a group with a single policy for all users.
+#trivy:ignore:AVD-AWS-0143
resource "aws_iam_user" "user" {
name = local.user_name
path = "/cf/"
diff --git a/brokerpaks/cg-smtp/terraform/bind/outputs.tf b/brokerpaks/aws-ses/terraform/bind/outputs.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/outputs.tf
rename to brokerpaks/aws-ses/terraform/bind/outputs.tf
diff --git a/brokerpaks/cg-smtp/terraform/bind/provider.tf b/brokerpaks/aws-ses/terraform/bind/provider.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/provider.tf
rename to brokerpaks/aws-ses/terraform/bind/provider.tf
diff --git a/brokerpaks/cg-smtp/terraform/bind/terraform.tfvars-template b/brokerpaks/aws-ses/terraform/bind/terraform.tfvars-template
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/terraform.tfvars-template
rename to brokerpaks/aws-ses/terraform/bind/terraform.tfvars-template
diff --git a/brokerpaks/cg-smtp/terraform/bind/variables.tf b/brokerpaks/aws-ses/terraform/bind/variables.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/variables.tf
rename to brokerpaks/aws-ses/terraform/bind/variables.tf
diff --git a/brokerpaks/cg-smtp/terraform/bind/versions.tf b/brokerpaks/aws-ses/terraform/bind/versions.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/bind/versions.tf
rename to brokerpaks/aws-ses/terraform/bind/versions.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/README.md b/brokerpaks/aws-ses/terraform/provision/README.md
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/README.md
rename to brokerpaks/aws-ses/terraform/provision/README.md
diff --git a/brokerpaks/cg-smtp/terraform/provision/main.tf b/brokerpaks/aws-ses/terraform/provision/main.tf
similarity index 97%
rename from brokerpaks/cg-smtp/terraform/provision/main.tf
rename to brokerpaks/aws-ses/terraform/provision/main.tf
index 0cf94d7..d2db5c8 100644
--- a/brokerpaks/cg-smtp/terraform/provision/main.tf
+++ b/brokerpaks/aws-ses/terraform/provision/main.tf
@@ -92,7 +92,6 @@ locals {
resource "aws_sesv2_email_identity" "identity" {
configuration_set_name = aws_sesv2_configuration_set.config.configuration_set_name
email_identity = local.domain
- # Should match https://github.com/cloud-gov/go-broker-tags/blob/main/tags.go#L10
lifecycle {
prevent_destroy = true
diff --git a/brokerpaks/cg-smtp/terraform/provision/notification.tf b/brokerpaks/aws-ses/terraform/provision/notification.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/notification.tf
rename to brokerpaks/aws-ses/terraform/provision/notification.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/outputs.tf b/brokerpaks/aws-ses/terraform/provision/outputs.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/outputs.tf
rename to brokerpaks/aws-ses/terraform/provision/outputs.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/providers.tf b/brokerpaks/aws-ses/terraform/provision/providers.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/providers.tf
rename to brokerpaks/aws-ses/terraform/provision/providers.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/terraform.tfvars-template b/brokerpaks/aws-ses/terraform/provision/terraform.tfvars-template
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/terraform.tfvars-template
rename to brokerpaks/aws-ses/terraform/provision/terraform.tfvars-template
diff --git a/brokerpaks/cg-smtp/terraform/provision/variables.tf b/brokerpaks/aws-ses/terraform/provision/variables.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/variables.tf
rename to brokerpaks/aws-ses/terraform/provision/variables.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/verification.tf b/brokerpaks/aws-ses/terraform/provision/verification.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/verification.tf
rename to brokerpaks/aws-ses/terraform/provision/verification.tf
diff --git a/brokerpaks/cg-smtp/terraform/provision/versions.tf b/brokerpaks/aws-ses/terraform/provision/versions.tf
similarity index 100%
rename from brokerpaks/cg-smtp/terraform/provision/versions.tf
rename to brokerpaks/aws-ses/terraform/provision/versions.tf
diff --git a/docproxy/go.mod b/docproxy/go.mod
index 0e53dcc..137f816 100644
--- a/docproxy/go.mod
+++ b/docproxy/go.mod
@@ -1,4 +1,4 @@
-module github.com/cloud-gov/csb-docproxy
+module github.com/cloud-gov/csb/docproxy
go 1.23.1
diff --git a/docproxy/images/amazon-ses.svg b/docproxy/images/amazon-ses.svg
new file mode 100644
index 0000000..aeb14de
--- /dev/null
+++ b/docproxy/images/amazon-ses.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/docproxy/images/cloud-gov-logo.svg b/docproxy/images/cloud-gov-logo.svg
new file mode 100644
index 0000000..b2f40f2
--- /dev/null
+++ b/docproxy/images/cloud-gov-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docproxy/main.go b/docproxy/main.go
index 1b06838..8bd89b0 100644
--- a/docproxy/main.go
+++ b/docproxy/main.go
@@ -2,9 +2,13 @@ package main
import (
"embed"
+ "fmt"
"log/slog"
"net/http"
+ "net/url"
"os"
+ "slices"
+ "strconv"
"strings"
"golang.org/x/net/html"
@@ -85,6 +89,19 @@ func modifyDocument(n *html.Node) {
return false
},
+ func(n *html.Node) bool {
+ if n.Type == html.TextNode && n.Parent.Parent.Type == html.ElementNode && n.Parent.Data == "a" {
+ cls := html.Attribute{
+ Key: "class",
+ Val: "navbar-brand",
+ }
+ if i := slices.Index(n.Parent.Attr, cls); i >= 0 {
+ // Change page title.
+ n.Data = "Services Reference"
+ }
+ }
+ return false
+ },
}
walk(n, func(n *html.Node) bool {
for _, m := range modifications {
@@ -106,10 +123,13 @@ var fonts embed.FS
//go:embed images/favicon.ico
var favicon []byte
-// run registers routes and starts the server. It is separate from main so it
-// can return errors conventionally and main can handle them all in one place.
-func run() error {
- slog.SetLogLoggerLevel(slog.LevelInfo)
+//go:embed images/cloud-gov-logo.svg
+var logo []byte
+
+//go:embed images/amazon-ses.svg
+var amazonSES []byte
+
+func routes(c config) {
http.HandleFunc("/styles.css", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/css; charset=utf-8")
w.Write(stylesheet)
@@ -127,8 +147,16 @@ func run() error {
w.Header().Add("Content-Type", "image/vnd.microsoft.icon")
w.Write(favicon)
})
+ http.HandleFunc("/images/cloud-gov-logo.svg", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "image/svg+xml")
+ w.Write(logo)
+ })
+ http.HandleFunc("/images/amazon-ses.svg", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "image/svg+xml")
+ w.Write(amazonSES)
+ })
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- resp, err := http.Get("https://csb.dev.us-gov-west-1.aws-us-gov.cloud.gov")
+ resp, err := http.Get(c.BrokerURL.String())
if err != nil {
slog.Error("Getting CSB site", "error", err)
w.WriteHeader(http.StatusBadGateway)
@@ -148,8 +176,46 @@ func run() error {
w.WriteHeader(http.StatusInternalServerError)
}
})
+}
+
+type config struct {
+ Port uint16
+ BrokerURL *url.URL
+}
+
+func loadConfig() (config, error) {
+ c := config{}
+
+ port := os.Getenv("PORT")
+ p, err := strconv.ParseUint(port, 10, 16)
+ if err != nil {
+ return config{}, fmt.Errorf("Invalid PORT: %w", err)
+ }
+ c.Port = uint16(p)
+
+ brokerURL := os.Getenv("BROKER_URL")
+ u, err := url.Parse(brokerURL)
+ if err != nil {
+ return config{}, fmt.Errorf("Invalid BROKER_URL: %w", err)
+ }
+ c.BrokerURL = u
+
+ return c, nil
+}
+
+// run registers routes and starts the server. It is separate from main so it
+// can return errors conventionally and main can handle them all in one place.
+func run() error {
+ slog.SetLogLoggerLevel(slog.LevelInfo)
+ config, err := loadConfig()
+ if err != nil {
+ return err
+ }
+
+ routes(config)
+ addr := fmt.Sprintf("localhost:%v", config.Port)
slog.Info("Starting server...")
- return http.ListenAndServe("localhost:8080", nil)
+ return http.ListenAndServe(addr, nil)
}
func main() {
diff --git a/docproxy/styles.css b/docproxy/styles.css
index be0f7c1..7d3cb82 100644
--- a/docproxy/styles.css
+++ b/docproxy/styles.css
@@ -17,7 +17,7 @@ body {
nav {
background-color: #f8f9fa !important;
border-bottom: 4px solid #007bff !important;
- background-image: url(https://cloud.gov/assets/images/content/cloud-gov-logo.svg);
+ background-image: url(images/cloud-gov-logo.svg);
background-position: right;
background-repeat: no-repeat;
background-origin: content-box;
@@ -43,8 +43,17 @@ pre code {
border: revert;
}
-h1::first-letter {
- font-size: 0;
+h1 {
+ /* Align text to center of image */
+ padding-top: 8px;
+}
+
+h1 img {
+ height: 48px;
+ width: 48px;
+ margin-right: 0.5em;
+ /* Compensate for h1 padding */
+ margin-top: -8px;
}
@font-face {
diff --git a/docs/diagrams.md b/docs/diagrams.md
new file mode 100644
index 0000000..0aac6b0
--- /dev/null
+++ b/docs/diagrams.md
@@ -0,0 +1,139 @@
+# Diagrams
+
+## Service Brokers, Before
+
+The current state of service brokers on Cloud.gov. Open Service Broker API requests are sent to the Cloud Foundry API (CAPI). Service brokers are registered with CAPI to handle requests for particular service offerings. CAPI forwards the request to the registered broker, which communicates with AWS to fulfill the request. (For brevity, not all service brokers and AWS APIs currently in use are depicted.)
+
+```mermaid
+---
+title: "Figure 1: Service Brokers Before Change"
+---
+
+flowchart LR
+ u["Cloud.gov Customer"]
+
+ subgraph "AWS"
+
+ subgraph "Cloud.gov"
+ direction LR
+ capi["Cloud Foundry API"]
+
+ subgraph "Service Brokers"
+ awsbroker["AWS Broker"]
+ s3broker["S3 Broker"]
+ extbroker["External Domain Broker"]
+ end
+ end
+
+ rdsapi["AWS RDS API"]
+ s3api["AWS S3 API"]
+ route53api["AWS Route 53 API"]
+ end
+
+ letsencrypt["Let's Encrypt API"]
+
+ u -->|OSBAPI requests| capi
+
+ capi --> awsbroker
+ capi --> s3broker
+ capi --> extbroker
+
+ awsbroker --> rdsapi
+ s3broker --> s3api
+ extbroker --> route53api
+ extbroker --> letsencrypt
+```
+
+## Service Brokers, After
+
+This change adds a new broker, the Cloud Service Broker (CSB). The CSB uses OpenTofu, an open-source fork of Terraform, to deploy services. The first new service deployed using the CSB will be AWS Simple Email Service (SES).
+
+```mermaid
+---
+title: "Figure 2: Service Brokers After Change"
+---
+
+flowchart LR
+ classDef new fill:#ecffec,stroke:#73d893
+ u["Cloud.gov Customer"]
+
+ subgraph "AWS"
+
+ subgraph "Cloud.gov"
+ direction LR
+ capi["Cloud Foundry API"]
+
+ subgraph "Service Brokers"
+ awsbroker["AWS Broker"]
+ s3broker["S3 Broker"]
+ csb["Cloud Service Broker"]:::new
+ extbroker["External Domain Broker"]
+ end
+ end
+
+ rdsapi["AWS RDS API"]
+ s3api["AWS S3 API"]
+ sesapi["AWS SES API"]:::new
+ snsapi["AWS SNS API"]:::new
+ route53api["AWS Route 53 API"]
+ end
+
+ letsencrypt["Let's Encrypt API"]
+
+ u -->|OSBAPI requests| capi
+
+ capi --> awsbroker
+ capi --> s3broker
+ capi --> csb
+ capi --> extbroker
+
+ awsbroker --> rdsapi
+ s3broker --> s3api
+ csb --> sesapi
+ csb --> snsapi
+ csb --> route53api
+ extbroker --> route53api
+ extbroker --> letsencrypt
+```
+
+## New HTTP Services
+
+New HTTP services introduced by the Cloud Service Broker SCR are in green. (For brevity, not all existing Cloud.gov web services are depicted.)
+
+- The **CSB** fulfills provisioning and binding requests for certain service offerings.
+- The **Documentation Proxy** is a server that displays documentation for service offerings maintained by the CSB. The CSB exposes a documentation endpoint, `docs/`. When a user makes a request to this service, the service GETs the `docs/` page and returns it to the user with some visual changes.
+- The **Service Updater** regularly updates customer service instances so the instances stay up to date with the latest plans offered by the CSB. It may accept administrative HTTPS requests, but only on an internal domain.
+
+```mermaid
+---
+title: "Figure 3: New HTTP Services"
+---
+
+flowchart LR
+ classDef new fill:#ecffec,stroke:#73d893
+ u["Cloud.gov Customer"]
+
+ subgraph "AWS"
+
+ subgraph "Cloud.gov"
+ direction LR
+ logs["logs.fr.cloud.gov - Cloud.gov Logs"]
+ etc["(...other services...)"]
+ capi["api.fr.cloud.gov - Cloud Foundry API"]
+ docproxy["services.cloud.gov - Cloud Service Broker Documentation Proxy"]:::new
+ updater["Service updater (internal only)"]:::new
+
+ subgraph "Service Brokers"
+ csb["csb.app.cloud.gov - Cloud Service Broker"]:::new
+ end
+ end
+ end
+
+
+ u -->|Logs Dashboard| logs
+ u -->|Other requests| etc
+ u -->|OSBAPI requests| capi
+ u -->|Documentation page| docproxy
+ capi --> csb
+ docproxy --> csb
+```
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..c9b8652
--- /dev/null
+++ b/go.work
@@ -0,0 +1,6 @@
+go 1.23.2
+
+use (
+ ./docproxy
+ ./service-updater
+)
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..58afebd
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,119 @@
+cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
+cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
+cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
+cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
+cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
+github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
+go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
+go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E=
+go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
+go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
+go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
+google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
+google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/service-updater/go.mod b/service-updater/go.mod
new file mode 100644
index 0000000..ea7d97f
--- /dev/null
+++ b/service-updater/go.mod
@@ -0,0 +1,17 @@
+module github.com/cloud-gov/csb/service-updater
+
+go 1.23.2
+
+require github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9
+
+require (
+ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect
+ github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 // indirect
+ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/stretchr/testify v1.9.0 // indirect
+ golang.org/x/oauth2 v0.21.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/service-updater/go.sum b/service-updater/go.sum
new file mode 100644
index 0000000..f292371
--- /dev/null
+++ b/service-updater/go.sum
@@ -0,0 +1,29 @@
+github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9 h1:HK3+nJEPgwlhc5H74aw/V4mVowqWaTKGjHONdVQQ2Vw=
+github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9/go.mod h1:eUjFfpsU3lRv388wKlXMmkQfsJ9pveUHZEia7AoBCPY=
+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
+github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
+github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw=
+github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
+github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
+github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/service-updater/main.go b/service-updater/main.go
new file mode 100644
index 0000000..ad44ae7
--- /dev/null
+++ b/service-updater/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/cloudfoundry/go-cfclient/v3/client"
+ "github.com/cloudfoundry/go-cfclient/v3/config"
+)
+
+func run() error {
+ // Use CF client to get information about a service instance
+ // Get services created by the CSB - filter by
+ // Use client to upgrade a service
+ // Create timer to upgrade periodically
+
+ // Simple version upgrades all instances every day, just to be safe.
+ // Less simple version checks to see if they need an upgrade based on plan
+ // hash or similar.
+ cfg, err := config.NewFromCFHome()
+ if err != nil {
+ return err
+ }
+ cf, err := client.New(cfg)
+ if err != nil {
+ return err
+ }
+ // todo: Filter by tag = csb
+ instances, _, err := cf.ServiceInstances.List(context.Background(), nil)
+ if err != nil {
+ return err
+ }
+ // how to update each without doing it all at once? and how to keep track of updates and log if they go wrong?
+ fmt.Println(instances[0].Name)
+ return nil
+}
+
+func main() {
+ err := run()
+ if err != nil {
+ panic(err)
+ }
+}