digitalocean provider

This commit is contained in:
David Dollar 2019-11-01 21:38:23 -04:00
parent e908a4b968
commit 037fc795b0
No known key found for this signature in database
GPG Key ID: AFAF263FB45B2124
38 changed files with 1294 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package router
import (
"context"
"crypto/tls"
"fmt"
"github.com/go-redis/redis"
@ -12,10 +13,19 @@ type CacheRedis struct {
redis *redis.Client
}
func NewCacheRedis(addr string) (*CacheRedis, error) {
func NewCacheRedis(addr, password string, secure bool) (*CacheRedis, error) {
fmt.Printf("ns=cache.redis at=new addr=%s\n", addr)
rc := redis.NewClient(&redis.Options{Addr: addr})
opts := &redis.Options{
Addr: addr,
Password: password,
}
if secure {
opts.TLSConfig = &tls.Config{}
}
rc := redis.NewClient(opts)
if _, err := rc.Ping().Result(); err != nil {
return nil, err

View File

@ -75,7 +75,7 @@ func New() (*Router, error) {
r.cache = c
case "redis":
c, err := NewCacheRedis(os.Getenv("REDIS_ADDR"))
c, err := NewCacheRedis(os.Getenv("REDIS_ADDR"), os.Getenv("REDIS_AUTH"), os.Getenv("REDIS_SECURE") == "true")
if err != nil {
return nil, err
}

13
provider/do/app.go Normal file
View File

@ -0,0 +1,13 @@
package do
func (p *Provider) AppIdles(name string) (bool, error) {
return false, nil
}
func (p *Provider) AppParameters() map[string]string {
return map[string]string{}
}
func (p *Provider) AppStatus(name string) (string, error) {
return "running", nil
}

75
provider/do/build.go Normal file
View File

@ -0,0 +1,75 @@
package do
import (
"fmt"
"io"
"net/url"
"os/exec"
"strings"
"github.com/convox/convox/pkg/structs"
)
func (p *Provider) BuildExport(app, id string, w io.Writer) error {
if err := p.authAppRepository(app); err != nil {
return err
}
return p.Provider.BuildExport(app, id, w)
}
func (p *Provider) BuildImport(app string, r io.Reader) (*structs.Build, error) {
if err := p.authAppRepository(app); err != nil {
return nil, err
}
return p.Provider.BuildImport(app, r)
}
func (p *Provider) BuildLogs(app, id string, opts structs.LogsOptions) (io.ReadCloser, error) {
b, err := p.BuildGet(app, id)
if err != nil {
return nil, err
}
opts.Since = nil
switch b.Status {
case "running":
return p.ProcessLogs(app, b.Process, opts)
default:
u, err := url.Parse(b.Logs)
if err != nil {
return nil, err
}
switch u.Scheme {
case "object":
return p.ObjectFetch(u.Hostname(), u.Path)
default:
return nil, fmt.Errorf("unable to read logs for build: %s", id)
}
}
}
func (p *Provider) authAppRepository(app string) error {
repo, _, err := p.RepositoryHost(app)
if err != nil {
return err
}
user, pass, err := p.RepositoryAuth(app)
if err != nil {
return err
}
cmd := exec.Command("docker", "login", "-u", user, "--password-stdin", repo)
cmd.Stdin = strings.NewReader(pass)
if err := cmd.Run(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,5 @@
package do
func (p *Provider) DeploymentTimeout() int {
return 1800
}

87
provider/do/do.go Normal file
View File

@ -0,0 +1,87 @@
package do
import (
"context"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3iface"
"github.com/convox/convox/pkg/structs"
"github.com/convox/convox/provider/k8s"
"k8s.io/apimachinery/pkg/util/runtime"
)
type Provider struct {
*k8s.Provider
Bucket string
Region string
Registry string
Secret string
SpacesAccess string
SpacesEndpoint string
SpacesSecret string
S3 s3iface.S3API
}
func FromEnv() (*Provider, error) {
k, err := k8s.FromEnv()
if err != nil {
return nil, err
}
p := &Provider{
Provider: k,
Bucket: os.Getenv("BUCKET"),
Region: os.Getenv("REGION"),
Registry: os.Getenv("REGISTRY"),
Secret: os.Getenv("SECRET"),
SpacesAccess: os.Getenv("SPACES_ACCESS"),
SpacesEndpoint: os.Getenv("SPACES_ENDPOINT"),
SpacesSecret: os.Getenv("SPACES_SECRET"),
}
k.Engine = p
return p, nil
}
func (p *Provider) Initialize(opts structs.ProviderOptions) error {
if err := p.initializeDOServices(); err != nil {
return err
}
if err := p.Provider.Initialize(opts); err != nil {
return err
}
runtime.ErrorHandlers = []func(error){}
return nil
}
func (p *Provider) WithContext(ctx context.Context) structs.Provider {
pp := *p
pp.Provider = pp.Provider.WithContext(ctx).(*k8s.Provider)
return &pp
}
func (p *Provider) initializeDOServices() error {
s, err := session.NewSession(&aws.Config{
Region: aws.String(p.Region),
Credentials: credentials.NewStaticCredentials(p.SpacesAccess, p.SpacesSecret, ""),
})
if err != nil {
return err
}
p.S3 = s3.New(s, &aws.Config{
Endpoint: aws.String(p.SpacesEndpoint),
})
return nil
}

10
provider/do/heartbeat.go Normal file
View File

@ -0,0 +1,10 @@
package do
func (p *Provider) Heartbeat() (map[string]interface{}, error) {
hs := map[string]interface{}{
"instance_type": "unknown",
"region": p.Region,
}
return hs, nil
}

106
provider/do/helpers.go Normal file
View File

@ -0,0 +1,106 @@
package do
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"time"
// gv "github.com/GoogleCloudPlatform/gke-managed-certs/pkg/clientgen/clientset/versioned"
am "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (p *Provider) appRegistry(app string) (string, error) {
ns, err := p.Provider.Cluster.CoreV1().Namespaces().Get(p.AppNamespace(app), am.GetOptions{})
if err != nil {
return "", err
}
registry, ok := ns.ObjectMeta.Annotations["convox.registry"]
if !ok {
return "", fmt.Errorf("no registry for app: %s", app)
}
return registry, nil
}
// func (p *Provider) gkeManagedCertsClient() (gv.Interface, error) {
// return gv.NewForConfig(p.Config)
// }
func (p *Provider) watchForProcessTermination(ctx context.Context, app, pid string, cancel func()) {
defer cancel()
tick := time.NewTicker(2 * time.Second)
defer tick.Stop()
for {
select {
case <-ctx.Done():
return
case <-tick.C:
if _, err := p.ProcessGet(app, pid); err != nil {
time.Sleep(2 * time.Second)
cancel()
return
}
}
}
}
func kubectl(args ...string) error {
cmd := exec.Command("kubectl", args...)
cmd.Env = os.Environ()
out, err := cmd.CombinedOutput()
if err != nil {
return errors.New(strings.TrimSpace(string(out)))
}
return nil
}
var outputConverter = regexp.MustCompile("([a-z])([A-Z])") // lower case letter followed by upper case
func outputToEnvironment(name string) string {
return strings.ToUpper(outputConverter.ReplaceAllString(name, "${1}_${2}"))
}
func upperName(name string) string {
if name == "" {
return ""
}
// replace underscores with dashes
name = strings.Replace(name, "_", "-", -1)
// myapp -> Myapp; my-app -> MyApp
us := strings.ToUpper(name[0:1]) + name[1:]
for {
i := strings.Index(us, "-")
if i == -1 {
break
}
s := us[0:i]
if len(us) > i+1 {
s += strings.ToUpper(us[i+1 : i+2])
}
if len(us) > i+2 {
s += us[i+2:]
}
us = s
}
return us
}

13
provider/do/ingress.go Normal file
View File

@ -0,0 +1,13 @@
package do
func (p *Provider) IngressAnnotations(app string) (map[string]string, error) {
ans := map[string]string{
"kubernetes.io/ingress.class": "convox",
}
return ans, nil
}
func (p *Provider) IngressSecrets(app string) ([]string, error) {
return []string{}, nil
}

21
provider/do/log.go Normal file
View File

@ -0,0 +1,21 @@
package do
import (
"io"
"io/ioutil"
"strings"
"sync"
"time"
"github.com/convox/convox/pkg/structs"
)
var sequenceTokens sync.Map
func (p *Provider) Log(app, stream string, ts time.Time, message string) error {
return nil
}
func (p *Provider) AppLogs(name string, opts structs.LogsOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
}

9
provider/do/manifest.go Normal file
View File

@ -0,0 +1,9 @@
package do
import (
"github.com/convox/convox/pkg/manifest"
)
func (p *Provider) ManifestValidate(m *manifest.Manifest) error {
return nil
}

163
provider/do/object.go Normal file
View File

@ -0,0 +1,163 @@
package do
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/convox/convox/pkg/structs"
)
func (p *Provider) ObjectDelete(app, key string) error {
exists, err := p.ObjectExists(app, key)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("object not found: %s", key)
}
_, err = p.S3.DeleteObject(&s3.DeleteObjectInput{
Bucket: aws.String(p.Bucket),
Key: aws.String(p.objectKey(app, key)),
})
if err != nil {
return err
}
return nil
}
func (p *Provider) ObjectExists(app, key string) (bool, error) {
_, err := p.S3.HeadObject(&s3.HeadObjectInput{
Bucket: aws.String(p.Bucket),
Key: aws.String(p.objectKey(app, key)),
})
if err, ok := err.(awserr.Error); ok && err.Code() == "NotFound" {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
// ObjectFetch fetches an Object
func (p *Provider) ObjectFetch(app, key string) (io.ReadCloser, error) {
res, err := p.S3.GetObject(&s3.GetObjectInput{
Bucket: aws.String(p.Bucket),
Key: aws.String(p.objectKey(app, key)),
})
if ae, ok := err.(awserr.Error); ok && ae.Code() == "NoSuchKey" {
return nil, fmt.Errorf("object not found: %s", key)
}
if err != nil {
return nil, err
}
return res.Body, nil
}
func (p *Provider) ObjectList(app, prefix string) ([]string, error) {
res, err := p.S3.ListObjectsV2(&s3.ListObjectsV2Input{
Bucket: aws.String(p.Bucket),
Delimiter: aws.String("/"),
Prefix: aws.String(p.objectKey(app, prefix)),
})
if err != nil {
return nil, err
}
objects := []string{}
for _, item := range res.Contents {
objects = append(objects, *item.Key)
}
return objects, nil
}
// ObjectStore stores an Object
func (p *Provider) ObjectStore(app, key string, r io.Reader, opts structs.ObjectStoreOptions) (*structs.Object, error) {
if key == "" {
k, err := generateTempKey()
if err != nil {
return nil, err
}
key = k
}
up := s3manager.NewUploaderWithClient(p.S3)
req := &s3manager.UploadInput{
Bucket: aws.String(p.Bucket),
Key: aws.String(p.objectKey(app, key)),
Body: r,
}
if opts.Public != nil && *opts.Public {
req.ACL = aws.String("public-read")
}
res, err := up.Upload(req)
if err != nil {
return nil, err
}
url := fmt.Sprintf("object://%s/%s", app, key)
if opts.Public != nil && *opts.Public {
url = res.Location
}
o := &structs.Object{Url: url}
return o, nil
}
func (p *Provider) objectKey(app, key string) string {
return fmt.Sprintf("%s/%s", app, key)
}
// func (p *Provider) objectPresignedURL(o *structs.Object, duration time.Duration) (string, error) {
// ou, err := url.Parse(o.Url)
// if err != nil {
// return "", err
// }
// if ou.Scheme != "object" {
// return "", fmt.Errorf("url is not an object: %s", o.Url)
// }
// req, _ := p.S3.GetObjectRequest(&s3.GetObjectInput{
// Bucket: aws.String(p.Bucket),
// Key: aws.String(p.objectKey(ou.Hostname(), ou.Path)),
// })
// su, err := req.Presign(duration)
// if err != nil {
// return "", err
// }
// return su, nil
// }
func generateTempKey() (string, error) {
data := make([]byte, 1024)
if _, err := rand.Read(data); err != nil {
return "", err
}
hash := sha256.Sum256(data)
return fmt.Sprintf("tmp/%s", hex.EncodeToString(hash[:])[0:30]), nil
}

11
provider/do/repository.go Normal file
View File

@ -0,0 +1,11 @@
package do
import "fmt"
func (p *Provider) RepositoryAuth(app string) (string, string, error) {
return "docker", p.Secret, nil
}
func (p *Provider) RepositoryHost(app string) (string, bool, error) {
return fmt.Sprintf("%s/%s", p.Registry, app), true, nil
}

7
provider/do/resolver.go Normal file
View File

@ -0,0 +1,7 @@
package do
import "fmt"
func (p *Provider) Resolver() (string, error) {
return "", fmt.Errorf("no resolver")
}

11
provider/do/service.go Normal file
View File

@ -0,0 +1,11 @@
package do
import (
"fmt"
"github.com/convox/convox/pkg/manifest"
)
func (p *Provider) ServiceHost(app string, s manifest.Service) string {
return fmt.Sprintf("%s.%s.%s", s.Name, app, p.Domain)
}

9
provider/do/system.go Normal file
View File

@ -0,0 +1,9 @@
package do
func (p *Provider) SystemHost() string {
return p.Domain
}
func (p *Provider) SystemStatus() (string, error) {
return "running", nil
}

View File

@ -6,6 +6,7 @@ import (
"github.com/convox/convox/pkg/structs"
"github.com/convox/convox/provider/aws"
"github.com/convox/convox/provider/do"
"github.com/convox/convox/provider/gcp"
"github.com/convox/convox/provider/k8s"
)
@ -18,6 +19,8 @@ func FromEnv() (structs.Provider, error) {
switch name {
case "aws":
return aws.FromEnv()
case "do":
return do.FromEnv()
case "gcp":
return gcp.FromEnv()
case "k8s":

48
terraform/api/do/main.tf Normal file
View File

@ -0,0 +1,48 @@
terraform {
required_version = ">= 0.12.0"
}
provider "digitalocean" {
version = "~> 1.9"
}
provider "kubernetes" {
version = "~> 1.8"
config_path = var.kubeconfig
}
locals {
tags = {
System = "convox"
Rack = var.name
}
}
module "k8s" {
source = "../k8s"
providers = {
kubernetes = kubernetes
}
domain = var.domain
kubeconfig = var.kubeconfig
name = var.name
namespace = var.namespace
release = var.release
annotations = {}
env = {
BUCKET = digitalocean_spaces_bucket.storage.name
PROVIDER = "do"
REGION = var.region
REGISTRY = "registry.${var.domain}"
ROUTER = var.router
SECRET = var.secret
SPACES_ACCESS = var.access_id
SPACES_ENDPOINT = "https://${var.region}.digitaloceanspaces.com"
SPACES_SECRET = var.secret_key
}
}

View File

@ -0,0 +1,3 @@
output "endpoint" {
value = module.k8s.endpoint
}

View File

@ -0,0 +1 @@
# data "google_container_registry_repository" "registry" {}

View File

@ -0,0 +1,11 @@
resource "random_string" "suffix" {
length = 12
special = false
upper = false
}
resource "digitalocean_spaces_bucket" "storage" {
name = "${var.name}-storage-${random_string.suffix.result}"
region = var.region
acl = "private"
}

View File

@ -0,0 +1,39 @@
variable "access_id" {
type = "string"
}
variable "domain" {
type = "string"
}
variable "kubeconfig" {
type = "string"
}
variable "name" {
type = "string"
}
variable "namespace" {
type = "string"
}
variable "region" {
type = "string"
}
variable "release" {
type = "string"
}
variable "router" {
type = "string"
}
variable "secret" {
type = "string"
}
variable "secret_key" {
type = "string"
}

View File

@ -246,7 +246,7 @@ resource "kubernetes_service" "api" {
resource "kubernetes_ingress" "api" {
metadata {
namespace = var.namespace
name = var.name
name = "api"
annotations = {
"convox.idles" : "true"

View File

@ -0,0 +1,18 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ${ca}
server: ${endpoint}
name: do
contexts:
- context:
cluster: do
user: do
name: do
current-context: do
kind: Config
preferences: {}
users:
- name: do
user:
token: ${token}

View File

@ -0,0 +1,154 @@
terraform {
required_version = ">= 0.12.0"
}
provider "digitalocean" {
version = "~> 1.9"
}
provider "local" {
version = "~> 1.3"
}
provider "random" {
version = "~> 2.2"
}
resource "digitalocean_kubernetes_cluster" "rack" {
name = var.name
region = var.region
version = "1.14.8-do.0"
node_pool {
name = "rack"
size = var.node_type
node_count = 3
}
}
# data "google_client_config" "current" {}
# data "google_container_engine_versions" "available" {
# location = data.google_client_config.current.region
# version_prefix = "1.14."
# }
# data "google_project" "current" {}
# resource "random_string" "password" {
# length = 64
# special = true
# }
# resource "google_container_cluster" "rack" {
# provider = "google-beta"
# name = var.name
# location = data.google_client_config.current.region
# remove_default_node_pool = true
# initial_node_count = 1
# logging_service = "logging.googleapis.com"
# min_master_version = data.google_container_engine_versions.available.latest_master_version
# ip_allocation_policy {
# use_ip_aliases = true
# }
# workload_identity_config {
# identity_namespace = "${data.google_project.current.project_id}.svc.id.goog"
# }
# master_auth {
# username = "gcloud"
# password = random_string.password.result
# client_certificate_config {
# issue_client_certificate = true
# }
# }
# }
# resource "google_container_node_pool" "rack" {
# provider = "google-beta"
# name = "${google_container_cluster.rack.name}-nodes-${var.node_type}"
# location = google_container_cluster.rack.location
# cluster = google_container_cluster.rack.name
# node_count = 1
# node_config {
# preemptible = true
# machine_type = var.node_type
# metadata = {
# disable-legacy-endpoints = "true"
# }
# workload_metadata_config {
# node_metadata = "GKE_METADATA_SERVER"
# }
# service_account = google_service_account.nodes.email
# oauth_scopes = [
# "https://www.googleapis.com/auth/cloud-platform",
# "https://www.googleapis.com/auth/devstorage.read_write",
# "https://www.googleapis.com/auth/logging.write",
# "https://www.googleapis.com/auth/monitoring",
# ]
# }
# lifecycle {
# create_before_destroy = true
# }
# }
resource "local_file" "kubeconfig" {
depends_on = [
kubernetes_cluster_role_binding.client,
digitalocean_kubernetes_cluster.rack,
]
filename = pathexpand("~/.kube/config.do.${var.name}")
content = templatefile("${path.module}/kubeconfig.tpl", {
ca = digitalocean_kubernetes_cluster.rack.kube_config[0].cluster_ca_certificate
endpoint = digitalocean_kubernetes_cluster.rack.endpoint
token = digitalocean_kubernetes_cluster.rack.kube_config[0].token
})
lifecycle {
ignore_changes = [content]
}
}
provider "kubernetes" {
version = "~> 1.8"
alias = "direct"
load_config_file = false
cluster_ca_certificate = "${base64decode(digitalocean_kubernetes_cluster.rack.kube_config[0].cluster_ca_certificate)}"
host = digitalocean_kubernetes_cluster.rack.endpoint
token = digitalocean_kubernetes_cluster.rack.kube_config[0].token
}
resource "kubernetes_cluster_role_binding" "client" {
provider = "kubernetes.direct"
metadata {
name = "client-binding"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = "cluster-admin"
}
subject {
kind = "User"
name = "client"
}
}

View File

@ -0,0 +1,8 @@
output "kubeconfig" {
depends_on = [
local_file.kubeconfig,
kubernetes_cluster_role_binding.client,
digitalocean_kubernetes_cluster.rack,
]
value = local_file.kubeconfig.filename
}

View File

@ -0,0 +1,11 @@
variable "name" {
type = string
}
variable "node_type" {
type = string
}
variable "region" {
type = string
}

60
terraform/rack/do/main.tf Normal file
View File

@ -0,0 +1,60 @@
terraform {
required_version = ">= 0.12.0"
}
provider "digitalocean" {
version = "~> 1.9"
}
provider "kubernetes" {
version = "~> 1.9"
config_path = var.kubeconfig
}
module "k8s" {
source = "../k8s"
providers = {
kubernetes = kubernetes
}
domain = module.router.endpoint
kubeconfig = var.kubeconfig
name = var.name
release = var.release
}
module "api" {
source = "../../api/do"
providers = {
digitalocean = digitalocean
kubernetes = kubernetes
}
access_id = var.access_id
domain = module.router.endpoint
kubeconfig = var.kubeconfig
name = var.name
namespace = module.k8s.namespace
region = var.region
release = var.release
router = module.router.endpoint
secret = random_string.secret.result
secret_key = var.secret_key
}
module "router" {
source = "../../router/do"
providers = {
digitalocean = digitalocean
kubernetes = kubernetes
}
name = var.name
namespace = module.k8s.namespace
region = var.region
release = var.release
}

View File

@ -0,0 +1,7 @@
output "api" {
value = module.api.endpoint
}
output "endpoint" {
value = module.router.endpoint
}

View File

@ -0,0 +1,174 @@
resource "random_string" "suffix" {
length = 12
special = false
upper = false
}
resource "digitalocean_spaces_bucket" "registry" {
name = "${var.name}-registry-${random_string.suffix.result}"
region = var.region
acl = "private"
}
resource "random_string" "secret" {
length = 30
}
resource "kubernetes_deployment" "registry" {
metadata {
namespace = module.k8s.namespace
name = "registry"
labels = {
serivce = "registry"
}
}
spec {
min_ready_seconds = 1
revision_history_limit = 0
selector {
match_labels = {
system = "convox"
service = "registry"
}
}
strategy {
type = "RollingUpdate"
rolling_update {
max_surge = 1
max_unavailable = 0
}
}
template {
metadata {
labels = {
system = "convox"
service = "registry"
}
}
spec {
container {
name = "main"
image = "registry:2"
image_pull_policy = "IfNotPresent"
env {
name = "REGISTRY_HTTP_SECRET"
value = random_string.secret.result
}
env {
name = "REGISTRY_STORAGE"
value = "s3"
}
env {
name = "REGISTRY_STORAGE_S3_ACCESSKEY"
value = var.access_id
}
env {
name = "REGISTRY_STORAGE_S3_BUCKET"
value = digitalocean_spaces_bucket.registry.name
}
env {
name = "REGISTRY_STORAGE_S3_REGION"
value = var.region
}
env {
name = "REGISTRY_STORAGE_S3_REGIONENDPOINT"
value = "https://${var.region}.digitaloceanspaces.com"
}
env {
name = "REGISTRY_STORAGE_S3_SECRETKEY"
value = var.secret_key
}
port {
container_port = 5000
protocol = "TCP"
}
volume_mount {
name = "registry"
mount_path = "/var/lib/registry"
}
}
volume {
name = "registry"
host_path {
path = "/var/lib/registry"
}
}
}
}
}
}
resource "kubernetes_service" "registry" {
metadata {
namespace = module.k8s.namespace
name = "registry"
}
spec {
type = "ClusterIP"
selector = {
system = "convox"
service = "registry"
}
port {
name = "http"
port = 80
target_port = 5000
protocol = "TCP"
}
}
}
resource "kubernetes_ingress" "registry" {
metadata {
namespace = module.k8s.namespace
name = "registry"
annotations = {
"convox.idles" : "true"
}
labels = {
system = "convox"
service = "registry"
}
}
spec {
tls {
hosts = ["registry.${module.router.endpoint}"]
}
rule {
host = "registry.${module.router.endpoint}"
http {
path {
backend {
service_name = kubernetes_service.registry.metadata.0.name
service_port = 80
}
}
}
}
}
}

View File

@ -0,0 +1,23 @@
variable "access_id" {
type = "string"
}
variable "kubeconfig" {
type = "string"
}
variable "name" {
type = "string"
}
variable "region" {
type = "string"
}
variable "release" {
type = "string"
}
variable "secret_key" {
type = "string"
}

View File

@ -0,0 +1,74 @@
terraform {
required_version = ">= 0.12.0"
}
provider "digitalocean" {
version = "~> 1.9"
}
provider "kubernetes" {
version = "~> 1.9"
}
locals {
tags = {
System = "convox"
Rack = var.name
}
}
module "k8s" {
source = "../k8s"
providers = {
kubernetes = kubernetes
}
namespace = var.namespace
release = var.release
env = {
CACHE = "redis"
REDIS_ADDR = "${digitalocean_database_cluster.cache.private_host}:${digitalocean_database_cluster.cache.port}"
REDIS_AUTH = digitalocean_database_cluster.cache.password
REDIS_SECURE = "true"
}
}
resource "kubernetes_service" "router" {
metadata {
namespace = var.namespace
name = "router"
}
spec {
type = "LoadBalancer"
port {
name = "http"
port = 80
protocol = "TCP"
target_port = 80
}
port {
name = "https"
port = 443
protocol = "TCP"
target_port = 443
}
selector = {
system = "convox"
service = "router"
}
}
lifecycle {
ignore_changes = [metadata[0].annotations]
}
}
data "http" "alias" {
url = "https://alias.convox.com/alias/${kubernetes_service.router.load_balancer_ingress.0.ip}"
}

View File

@ -0,0 +1,4 @@
output "endpoint" {
value = data.http.alias.body
}

View File

@ -0,0 +1,7 @@
resource "digitalocean_database_cluster" "cache" {
name = "${var.name}-router"
engine = "redis"
size = "db-s-1vcpu-1gb"
region = var.region
node_count = 1
}

View File

@ -0,0 +1,15 @@
variable "name" {
type = "string"
}
variable "namespace" {
type = "string"
}
variable "region" {
type = "string"
}
variable "release" {
type = "string"
}

View File

@ -0,0 +1,50 @@
terraform {
required_version = ">= 0.12.0"
}
provider "digitalocean" {
version = "~> 1.9"
}
provider "kubernetes" {
version = "~> 1.9"
config_path = module.cluster.kubeconfig
}
data "http" "releases" {
url = "https://api.github.com/repos/convox/convox/releases"
}
locals {
current = jsondecode(data.http.releases.body).0.tag_name
release = coalesce(var.release, local.current)
}
module "cluster" {
source = "../../cluster/do"
providers = {
digitalocean = digitalocean
}
name = var.name
node_type = var.node_type
region = var.region
}
module "rack" {
source = "../../rack/do"
providers = {
digitalocean = digitalocean
kubernetes = kubernetes
}
access_id = var.access_id
kubeconfig = module.cluster.kubeconfig
name = var.name
region = var.region
release = local.release
secret_key = var.secret_key
}

View File

@ -0,0 +1,7 @@
output "api" {
value = module.rack.api
}
output "endpoint" {
value = module.rack.endpoint
}

View File

@ -0,0 +1,23 @@
variable "access_id" {
type = "string"
}
variable "name" {
type = "string"
}
variable "node_type" {
type = "string"
}
variable "region" {
type = "string"
}
variable "release" {
default = ""
}
variable "secret_key" {
type = "string"
}