mirror of
https://github.com/FlipsideCrypto/convox.git
synced 2026-02-06 10:56:56 +00:00
digitalocean provider
This commit is contained in:
parent
e908a4b968
commit
037fc795b0
@ -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
|
||||
|
||||
@ -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
13
provider/do/app.go
Normal 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
75
provider/do/build.go
Normal 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
|
||||
}
|
||||
5
provider/do/deployment.go
Normal file
5
provider/do/deployment.go
Normal file
@ -0,0 +1,5 @@
|
||||
package do
|
||||
|
||||
func (p *Provider) DeploymentTimeout() int {
|
||||
return 1800
|
||||
}
|
||||
87
provider/do/do.go
Normal file
87
provider/do/do.go
Normal 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
10
provider/do/heartbeat.go
Normal 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
106
provider/do/helpers.go
Normal 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
13
provider/do/ingress.go
Normal 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
21
provider/do/log.go
Normal 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
9
provider/do/manifest.go
Normal 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
163
provider/do/object.go
Normal 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
11
provider/do/repository.go
Normal 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
7
provider/do/resolver.go
Normal 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
11
provider/do/service.go
Normal 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
9
provider/do/system.go
Normal file
@ -0,0 +1,9 @@
|
||||
package do
|
||||
|
||||
func (p *Provider) SystemHost() string {
|
||||
return p.Domain
|
||||
}
|
||||
|
||||
func (p *Provider) SystemStatus() (string, error) {
|
||||
return "running", nil
|
||||
}
|
||||
@ -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
48
terraform/api/do/main.tf
Normal 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
|
||||
}
|
||||
}
|
||||
3
terraform/api/do/outputs.tf
Normal file
3
terraform/api/do/outputs.tf
Normal file
@ -0,0 +1,3 @@
|
||||
output "endpoint" {
|
||||
value = module.k8s.endpoint
|
||||
}
|
||||
1
terraform/api/do/registry.tf
Normal file
1
terraform/api/do/registry.tf
Normal file
@ -0,0 +1 @@
|
||||
# data "google_container_registry_repository" "registry" {}
|
||||
11
terraform/api/do/storage.tf
Normal file
11
terraform/api/do/storage.tf
Normal 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"
|
||||
}
|
||||
39
terraform/api/do/variables.tf
Normal file
39
terraform/api/do/variables.tf
Normal 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"
|
||||
}
|
||||
@ -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"
|
||||
|
||||
18
terraform/cluster/do/kubeconfig.tpl
Normal file
18
terraform/cluster/do/kubeconfig.tpl
Normal 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}
|
||||
154
terraform/cluster/do/main.tf
Normal file
154
terraform/cluster/do/main.tf
Normal 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"
|
||||
}
|
||||
}
|
||||
8
terraform/cluster/do/outputs.tf
Normal file
8
terraform/cluster/do/outputs.tf
Normal 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
|
||||
}
|
||||
11
terraform/cluster/do/variables.tf
Normal file
11
terraform/cluster/do/variables.tf
Normal 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
60
terraform/rack/do/main.tf
Normal 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
|
||||
}
|
||||
7
terraform/rack/do/outputs.tf
Normal file
7
terraform/rack/do/outputs.tf
Normal file
@ -0,0 +1,7 @@
|
||||
output "api" {
|
||||
value = module.api.endpoint
|
||||
}
|
||||
|
||||
output "endpoint" {
|
||||
value = module.router.endpoint
|
||||
}
|
||||
174
terraform/rack/do/registry.tf
Normal file
174
terraform/rack/do/registry.tf
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
terraform/rack/do/variables.tf
Normal file
23
terraform/rack/do/variables.tf
Normal 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"
|
||||
}
|
||||
74
terraform/router/do/main.tf
Normal file
74
terraform/router/do/main.tf
Normal 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}"
|
||||
}
|
||||
4
terraform/router/do/outputs.tf
Normal file
4
terraform/router/do/outputs.tf
Normal file
@ -0,0 +1,4 @@
|
||||
output "endpoint" {
|
||||
value = data.http.alias.body
|
||||
}
|
||||
|
||||
7
terraform/router/do/redis.tf
Normal file
7
terraform/router/do/redis.tf
Normal 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
|
||||
}
|
||||
15
terraform/router/do/variables.tf
Normal file
15
terraform/router/do/variables.tf
Normal file
@ -0,0 +1,15 @@
|
||||
variable "name" {
|
||||
type = "string"
|
||||
}
|
||||
|
||||
variable "namespace" {
|
||||
type = "string"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
type = "string"
|
||||
}
|
||||
|
||||
variable "release" {
|
||||
type = "string"
|
||||
}
|
||||
50
terraform/system/do/main.tf
Normal file
50
terraform/system/do/main.tf
Normal 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
|
||||
}
|
||||
7
terraform/system/do/outputs.tf
Normal file
7
terraform/system/do/outputs.tf
Normal file
@ -0,0 +1,7 @@
|
||||
output "api" {
|
||||
value = module.rack.api
|
||||
}
|
||||
|
||||
output "endpoint" {
|
||||
value = module.rack.endpoint
|
||||
}
|
||||
23
terraform/system/do/variables.tf
Normal file
23
terraform/system/do/variables.tf
Normal 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"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user