2014-11-17 19:39:00 +00:00
/ *
Copyright 2014 The Camlistore Authors
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
// Package gce provides tools to deploy Camlistore on Google Compute Engine.
2016-03-14 01:59:26 +00:00
package gce // import "camlistore.org/pkg/deploy/gce"
2014-11-17 19:39:00 +00:00
import (
2015-12-28 02:43:17 +00:00
"crypto/rand"
2014-11-17 19:39:00 +00:00
"encoding/json"
2015-01-23 22:40:23 +00:00
"errors"
2014-11-17 19:39:00 +00:00
"fmt"
"log"
"net/http"
2014-12-12 22:32:58 +00:00
"path"
2014-11-17 19:39:00 +00:00
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"camlistore.org/pkg/osutil"
2015-11-20 22:27:00 +00:00
2016-09-07 18:27:11 +00:00
"cloud.google.com/go/logging"
2015-12-23 16:47:08 +00:00
"go4.org/cloud/google/gceutil"
2015-11-20 22:27:00 +00:00
"go4.org/syncutil"
2016-09-07 18:27:11 +00:00
"golang.org/x/net/context"
2015-08-18 08:19:49 +00:00
"golang.org/x/oauth2"
2016-06-08 19:09:10 +00:00
"golang.org/x/oauth2/google"
2015-08-18 08:19:49 +00:00
compute "google.golang.org/api/compute/v1"
2015-11-24 23:10:27 +00:00
"google.golang.org/api/googleapi"
2015-08-18 08:19:49 +00:00
storage "google.golang.org/api/storage/v1"
2014-11-17 19:39:00 +00:00
)
const (
2015-12-28 23:15:11 +00:00
DefaultInstanceName = "camlistore-server"
DefaultMachineType = "g1-small"
DefaultRegion = "us-central1"
2014-11-17 19:39:00 +00:00
2015-12-28 23:15:11 +00:00
projectsAPIURL = "https://www.googleapis.com/compute/v1/projects/"
2015-12-28 02:43:17 +00:00
2015-12-28 23:15:11 +00:00
fallbackZone = "us-central1-a"
2015-12-28 02:43:17 +00:00
2015-01-28 14:44:17 +00:00
camliUsername = "camlistore" // directly set in compute metadata, so not user settable.
2014-11-17 19:39:00 +00:00
2014-12-12 22:32:58 +00:00
configDir = "config"
2017-01-13 15:04:23 +00:00
ConsoleURL = "https://console.developers.google.com"
helpDeleteInstance = ` To delete an existing Compute Engine instance: in your project console, navigate to "Compute", "Compute Engine", and "VM instances". Select your instance and click "Delete". `
2014-11-17 19:39:00 +00:00
)
2015-02-02 14:11:22 +00:00
var (
// Verbose enables more info to be printed.
Verbose bool
)
2014-11-17 19:39:00 +00:00
2015-12-07 17:09:11 +00:00
// certFilename returns the HTTPS certificate file name
func certFilename ( ) string {
return filepath . Base ( osutil . DefaultTLSCert ( ) )
}
// keyFilename returns the HTTPS key name
func keyFilename ( ) string {
return filepath . Base ( osutil . DefaultTLSKey ( ) )
}
2014-11-17 19:39:00 +00:00
// NewOAuthConfig returns an OAuth configuration template.
2015-01-09 19:37:07 +00:00
func NewOAuthConfig ( clientID , clientSecret string ) * oauth2 . Config {
return & oauth2 . Config {
Scopes : [ ] string {
2017-01-30 23:06:28 +00:00
logging . WriteScope ,
2015-11-24 23:10:27 +00:00
compute . DevstorageFullControlScope ,
2014-11-17 19:39:00 +00:00
compute . ComputeScope ,
"https://www.googleapis.com/auth/sqlservice" ,
"https://www.googleapis.com/auth/sqlservice.admin" ,
2015-01-09 19:37:07 +00:00
} ,
Endpoint : google . Endpoint ,
ClientID : clientID ,
2014-11-17 19:39:00 +00:00
ClientSecret : clientSecret ,
}
}
// InstanceConf is the configuration for the Google Compute Engine instance that will be deployed.
type InstanceConf struct {
Name string // Name given to the virtual machine instance.
Project string // Google project ID where the instance is created.
Machine string // Machine type.
2015-12-28 23:15:11 +00:00
Zone string // GCE zone; see https://cloud.google.com/compute/docs/zones
2014-11-17 19:39:00 +00:00
Hostname string // Fully qualified domain name.
2015-01-23 19:30:33 +00:00
configDir string // bucketBase() + "/config"
blobDir string // bucketBase() + "/blobs"
2015-01-19 15:23:46 +00:00
Ctime time . Time // Timestamp for this configuration.
2015-10-20 23:46:33 +00:00
WIP bool // Whether to use the camlistored-WORKINPROGRESS.tar.gz tarball instead of the "production" one
2014-11-17 19:39:00 +00:00
}
2015-01-23 19:30:33 +00:00
func ( conf * InstanceConf ) bucketBase ( ) string {
return conf . Project + "-camlistore"
}
2014-11-17 19:39:00 +00:00
// Deployer creates and starts an instance such as defined in Conf.
type Deployer struct {
2015-12-27 21:52:35 +00:00
// Client is an OAuth2 client, authenticated for working with
// the user's Google Cloud resources.
2015-02-02 14:11:22 +00:00
Client * http . Client
2015-12-27 21:52:35 +00:00
Conf * InstanceConf
2015-02-02 14:11:22 +00:00
// SHA-1 and SHA-256 fingerprints of the HTTPS certificate created during setupHTTPS, if any.
// Keyed by hash name: "SHA-1", and "SHA-256".
certFingerprints map [ string ] string
2015-11-13 18:39:29 +00:00
* log . Logger // Cannot be nil.
2014-11-17 19:39:00 +00:00
}
// Get returns the Instance corresponding to the Project, Zone, and Name defined in the
// Deployer's Conf.
2015-01-23 19:30:33 +00:00
func ( d * Deployer ) Get ( ) ( * compute . Instance , error ) {
2015-02-02 14:11:22 +00:00
computeService , err := compute . New ( d . Client )
2014-11-17 19:39:00 +00:00
if err != nil {
return nil , err
}
return computeService . Instances . Get ( d . Conf . Project , d . Conf . Zone , d . Conf . Name ) . Do ( )
}
2015-01-23 19:30:33 +00:00
type instanceExistsError struct {
project string
zone string
name string
}
func ( e instanceExistsError ) Error ( ) string {
if e . project == "" {
panic ( "instanceExistsErr has no project" )
}
2015-01-23 22:40:23 +00:00
msg := "some instance(s) already exist as (" + e . project
2015-01-23 19:30:33 +00:00
if e . zone != "" {
msg += ", " + e . zone
}
if e . name != "" {
msg += ", " + e . name
}
msg += "), you need to delete them first."
return msg
}
2015-01-23 22:40:23 +00:00
// projectHasInstance checks for all the possible zones if there's already an instance for the project.
// It returns the name of the zone at the first instance it finds, if any.
func ( d * Deployer ) projectHasInstance ( ) ( zone string , err error ) {
2015-02-02 14:11:22 +00:00
s , err := compute . New ( d . Client )
2015-01-23 22:40:23 +00:00
if err != nil {
return "" , err
}
2015-02-11 21:50:21 +00:00
// TODO(mpl): make use of the handler's cached zones.
2015-01-23 22:40:23 +00:00
zl , err := compute . NewZonesService ( s ) . List ( d . Conf . Project ) . Do ( )
if err != nil {
return "" , fmt . Errorf ( "could not get a list of zones: %v" , err )
}
2015-02-02 14:11:22 +00:00
computeService , _ := compute . New ( d . Client )
2015-01-23 22:40:23 +00:00
var zoneOnce sync . Once
var grp syncutil . Group
errc := make ( chan error , 1 )
zonec := make ( chan string , 1 )
timeout := time . NewTimer ( 30 * time . Second )
defer timeout . Stop ( )
for _ , z := range zl . Items {
z := z
grp . Go ( func ( ) error {
list , err := computeService . Instances . List ( d . Conf . Project , z . Name ) . Do ( )
if err != nil {
return fmt . Errorf ( "could not list existing instances: %v" , err )
}
if len ( list . Items ) > 0 {
zoneOnce . Do ( func ( ) {
zonec <- z . Name
} )
}
return nil
} )
}
go func ( ) {
errc <- grp . Err ( )
} ( )
// We block until either an instance was found in a zone, or all the instance
// listing is done. Or we timed-out.
select {
case err = <- errc :
return "" , err
case zone = <- zonec :
// We voluntarily ignore any listing error if we found at least one instance
// because that's what we primarily want to report about.
return zone , nil
case <- timeout . C :
return "" , errors . New ( "timed out" )
}
}
2015-01-27 17:06:07 +00:00
type projectIDError struct {
id string
cause error
}
func ( e projectIDError ) Error ( ) string {
if e . id == "" {
panic ( "projectIDError without an id" )
}
if e . cause != nil {
return fmt . Sprintf ( "project ID error for %v: %v" , e . id , e . cause )
}
return fmt . Sprintf ( "project ID error for %v" , e . id )
}
func ( d * Deployer ) checkProjectID ( ) error {
// TODO(mpl): cache the computeService in Deployer, instead of recreating a new one everytime?
2015-02-02 14:11:22 +00:00
s , err := compute . New ( d . Client )
2015-01-27 17:06:07 +00:00
if err != nil {
return projectIDError {
id : d . Conf . Project ,
cause : err ,
}
}
project , err := compute . NewProjectsService ( s ) . Get ( d . Conf . Project ) . Do ( )
if err != nil {
return projectIDError {
id : d . Conf . Project ,
cause : err ,
}
}
if project . Name != d . Conf . Project {
return projectIDError {
id : d . Conf . Project ,
cause : fmt . Errorf ( "project ID do not match: got %q, wanted %q" , project . Name , d . Conf . Project ) ,
}
}
return nil
}
2017-01-13 15:04:23 +00:00
var errAttrNotFound = errors . New ( "attribute not found" )
// getInstanceAttribute returns the value for attr in the custom metadata of the
// instance. It returns errAttrNotFound is such a metadata attributed does not
// exist.
func ( d * Deployer ) getInstanceAttribute ( attr string ) ( string , error ) {
s , err := compute . New ( d . Client )
if err != nil {
return "" , fmt . Errorf ( "error getting compute service: %v" , err )
}
inst , err := compute . NewInstancesService ( s ) . Get ( d . Conf . Project , d . Conf . Zone , d . Conf . Name ) . Do ( )
if err != nil {
return "" , fmt . Errorf ( "error getting instance: %v" , err )
}
for _ , v := range inst . Metadata . Items {
if v . Key == attr {
return * ( v . Value ) , nil
}
}
return "" , errAttrNotFound
}
2014-11-17 19:39:00 +00:00
// Create sets up and starts a Google Compute Engine instance as defined in d.Conf. It
// creates the necessary Google Storage buckets beforehand.
2015-12-12 21:47:31 +00:00
func ( d * Deployer ) Create ( ctx context . Context ) ( * compute . Instance , error ) {
2015-01-27 17:06:07 +00:00
if err := d . checkProjectID ( ) ; err != nil {
return nil , err
}
2015-02-02 14:11:22 +00:00
computeService , _ := compute . New ( d . Client )
storageService , _ := storage . New ( d . Client )
2014-11-17 19:39:00 +00:00
2015-01-29 13:11:59 +00:00
fwc := make ( chan error , 1 )
go func ( ) {
fwc <- d . setFirewall ( ctx , computeService )
} ( )
2014-11-17 19:39:00 +00:00
config := cloudConfig ( d . Conf )
const maxCloudConfig = 32 << 10 // per compute API docs
if len ( config ) > maxCloudConfig {
return nil , fmt . Errorf ( "cloud config length of %d bytes is over %d byte limit" , len ( config ) , maxCloudConfig )
}
2015-01-23 22:40:23 +00:00
if zone , err := d . projectHasInstance ( ) ; zone != "" {
2015-01-23 19:30:33 +00:00
return nil , instanceExistsError {
project : d . Conf . Project ,
2015-01-23 22:40:23 +00:00
zone : zone ,
2015-01-23 19:30:33 +00:00
}
2015-01-23 22:40:23 +00:00
} else if err != nil {
return nil , fmt . Errorf ( "could not scan project for existing instances: %v" , err )
2015-01-23 19:30:33 +00:00
}
2014-11-17 19:39:00 +00:00
if err := d . setBuckets ( storageService , ctx ) ; err != nil {
return nil , fmt . Errorf ( "could not create buckets: %v" , err )
}
2015-01-23 19:30:33 +00:00
if err := d . createInstance ( computeService , ctx ) ; err != nil {
2014-11-17 19:39:00 +00:00
return nil , fmt . Errorf ( "could not create compute instance: %v" , err )
}
inst , err := computeService . Instances . Get ( d . Conf . Project , d . Conf . Zone , d . Conf . Name ) . Do ( )
if err != nil {
return nil , fmt . Errorf ( "error getting instance after creation: %v" , err )
}
if Verbose {
ij , _ := json . MarshalIndent ( inst , "" , " " )
2015-11-13 18:39:29 +00:00
d . Printf ( "Instance: %s" , ij )
2014-11-17 19:39:00 +00:00
}
2015-01-29 13:11:59 +00:00
if err = <- fwc ; err != nil {
return nil , fmt . Errorf ( "could not create firewall rules: %v" , err )
}
2014-11-17 19:39:00 +00:00
return inst , nil
}
2015-12-28 02:43:17 +00:00
func randPassword ( ) string {
buf := make ( [ ] byte , 5 )
if n , err := rand . Read ( buf ) ; err != nil || n != len ( buf ) {
log . Fatalf ( "crypto/rand.Read = %v, %v" , n , err )
}
return fmt . Sprintf ( "%x" , buf )
}
2015-12-28 23:15:11 +00:00
// LooksLikeRegion reports whether s looks like a GCE region.
func LooksLikeRegion ( s string ) bool {
return strings . Count ( s , "-" ) == 1
}
2014-11-17 19:39:00 +00:00
// createInstance starts the creation of the Compute Engine instance and waits for the
// result of the creation operation. It should be called after setBuckets and setupHTTPS.
2015-12-12 21:47:31 +00:00
func ( d * Deployer ) createInstance ( computeService * compute . Service , ctx context . Context ) error {
2015-11-17 16:58:01 +00:00
coreosImgURL , err := gceutil . CoreOSImageURL ( d . Client )
if err != nil {
return fmt . Errorf ( "error looking up latest CoreOS stable image: %v" , err )
}
2014-11-17 19:39:00 +00:00
prefix := projectsAPIURL + d . Conf . Project
machType := prefix + "/zones/" + d . Conf . Zone + "/machineTypes/" + d . Conf . Machine
config := cloudConfig ( d . Conf )
instance := & compute . Instance {
Name : d . Conf . Name ,
Description : "Camlistore server" ,
MachineType : machType ,
Disks : [ ] * compute . AttachedDisk {
{
AutoDelete : true ,
Boot : true ,
Type : "PERSISTENT" ,
InitializeParams : & compute . AttachedDiskInitializeParams {
DiskName : d . Conf . Name + "-coreos-stateless-pd" ,
SourceImage : coreosImgURL ,
} ,
} ,
} ,
Tags : & compute . Tags {
Items : [ ] string { "http-server" , "https-server" } ,
} ,
Metadata : & compute . Metadata {
Items : [ ] * compute . MetadataItems {
{
Key : "camlistore-username" ,
2015-11-24 23:10:27 +00:00
Value : googleapi . String ( camliUsername ) ,
2014-11-17 19:39:00 +00:00
} ,
{
Key : "camlistore-password" ,
2016-02-10 18:39:45 +00:00
Value : googleapi . String ( randPassword ( ) ) ,
2014-11-17 19:39:00 +00:00
} ,
{
2014-12-12 22:32:58 +00:00
Key : "camlistore-blob-dir" ,
2015-11-24 23:10:27 +00:00
Value : googleapi . String ( "gs://" + d . Conf . blobDir ) ,
2014-11-17 19:39:00 +00:00
} ,
{
2014-12-12 22:32:58 +00:00
Key : "camlistore-config-dir" ,
2015-11-24 23:10:27 +00:00
Value : googleapi . String ( "gs://" + d . Conf . configDir ) ,
2014-11-17 19:39:00 +00:00
} ,
{
Key : "user-data" ,
2015-11-24 23:10:27 +00:00
Value : googleapi . String ( config ) ,
2014-11-17 19:39:00 +00:00
} ,
} ,
} ,
NetworkInterfaces : [ ] * compute . NetworkInterface {
& compute . NetworkInterface {
AccessConfigs : [ ] * compute . AccessConfig {
& compute . AccessConfig {
Type : "ONE_TO_ONE_NAT" ,
Name : "External NAT" ,
} ,
} ,
Network : prefix + "/global/networks/default" ,
} ,
} ,
ServiceAccounts : [ ] * compute . ServiceAccount {
{
Email : "default" ,
Scopes : [ ] string {
2017-01-30 23:06:28 +00:00
logging . WriteScope ,
2015-11-24 23:10:27 +00:00
compute . DevstorageFullControlScope ,
2014-11-17 19:39:00 +00:00
compute . ComputeScope ,
"https://www.googleapis.com/auth/sqlservice" ,
"https://www.googleapis.com/auth/sqlservice.admin" ,
} ,
} ,
} ,
}
2017-01-13 15:04:23 +00:00
if d . Conf . Hostname != "" {
2014-11-17 19:39:00 +00:00
instance . Metadata . Items = append ( instance . Metadata . Items , & compute . MetadataItems {
Key : "camlistore-hostname" ,
2015-11-24 23:10:27 +00:00
Value : googleapi . String ( d . Conf . Hostname ) ,
2014-11-17 19:39:00 +00:00
} )
}
const localMySQL = false // later
if localMySQL {
instance . Disks = append ( instance . Disks , & compute . AttachedDisk {
AutoDelete : false ,
Boot : false ,
Type : "PERSISTENT" ,
InitializeParams : & compute . AttachedDiskInitializeParams {
DiskName : "camlistore-mysql-index-pd" ,
DiskSizeGb : 4 ,
} ,
} )
}
if Verbose {
2015-11-13 18:39:29 +00:00
d . Print ( "Creating instance..." )
2014-11-17 19:39:00 +00:00
}
op , err := computeService . Instances . Insert ( d . Conf . Project , d . Conf . Zone , instance ) . Do ( )
if err != nil {
return fmt . Errorf ( "failed to create instance: %v" , err )
}
opName := op . Name
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Created. Waiting on operation %v" , opName )
2014-11-17 19:39:00 +00:00
}
OpLoop :
for {
2015-12-12 21:47:31 +00:00
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
2014-11-17 19:39:00 +00:00
}
time . Sleep ( 2 * time . Second )
op , err := computeService . ZoneOperations . Get ( d . Conf . Project , d . Conf . Zone , opName ) . Do ( )
if err != nil {
return fmt . Errorf ( "failed to get op %s: %v" , opName , err )
}
switch op . Status {
case "PENDING" , "RUNNING" :
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Waiting on operation %v" , opName )
2014-11-17 19:39:00 +00:00
}
continue
case "DONE" :
if op . Error != nil {
for _ , operr := range op . Error . Errors {
2015-11-13 18:39:29 +00:00
d . Printf ( "Error: %+v" , operr )
2014-11-17 19:39:00 +00:00
}
return fmt . Errorf ( "failed to start." )
}
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Success. %+v" , op )
2014-11-17 19:39:00 +00:00
}
break OpLoop
default :
return fmt . Errorf ( "unknown status %q: %+v" , op . Status , op )
}
}
return nil
}
func cloudConfig ( conf * InstanceConf ) string {
config := strings . Replace ( baseInstanceConfig , "INNODB_BUFFER_POOL_SIZE=NNN" , "INNODB_BUFFER_POOL_SIZE=" + strconv . Itoa ( innodbBufferPoolSize ( conf . Machine ) ) , - 1 )
2015-10-20 23:46:33 +00:00
camlistoredTarball := "https://storage.googleapis.com/camlistore-release/docker/"
if conf . WIP {
camlistoredTarball += "camlistored-WORKINPROGRESS.tar.gz"
} else {
camlistoredTarball += "camlistored.tar.gz"
}
config = strings . Replace ( config , "CAMLISTORED_TARBALL" , camlistoredTarball , 1 )
2014-11-17 19:39:00 +00:00
return config
}
// setBuckets defines the buckets needed by the instance and creates them.
2015-12-12 21:47:31 +00:00
func ( d * Deployer ) setBuckets ( storageService * storage . Service , ctx context . Context ) error {
2014-12-12 22:32:58 +00:00
projBucket := d . Conf . Project + "-camlistore"
2014-11-17 19:39:00 +00:00
needBucket := map [ string ] bool {
2014-12-12 22:32:58 +00:00
projBucket : true ,
2014-11-17 19:39:00 +00:00
}
buckets , err := storageService . Buckets . List ( d . Conf . Project ) . Do ( )
if err != nil {
return fmt . Errorf ( "error listing buckets: %v" , err )
}
for _ , it := range buckets . Items {
delete ( needBucket , it . Name )
}
if len ( needBucket ) > 0 {
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Need to create buckets: %v" , needBucket )
2014-11-17 19:39:00 +00:00
}
var waitBucket sync . WaitGroup
var bucketErr error
for name := range needBucket {
2015-12-12 21:47:31 +00:00
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
2014-11-17 19:39:00 +00:00
}
name := name
waitBucket . Add ( 1 )
go func ( ) {
defer waitBucket . Done ( )
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Creating bucket %s" , name )
2014-11-17 19:39:00 +00:00
}
b , err := storageService . Buckets . Insert ( d . Conf . Project , & storage . Bucket {
Id : name ,
Name : name ,
} ) . Do ( )
if err != nil && bucketErr == nil {
bucketErr = fmt . Errorf ( "error creating bucket %s: %v" , name , err )
return
}
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Created bucket %s: %+v" , name , b )
2014-11-17 19:39:00 +00:00
}
} ( )
}
waitBucket . Wait ( )
if bucketErr != nil {
return bucketErr
}
}
2015-01-23 19:30:33 +00:00
2014-12-12 22:32:58 +00:00
d . Conf . configDir = path . Join ( projBucket , configDir )
d . Conf . blobDir = path . Join ( projBucket , "blobs" )
2014-11-17 19:39:00 +00:00
return nil
}
2015-01-29 13:11:59 +00:00
// setFirewall adds the firewall rules needed for ports 80 & 433 to the default network.
2015-12-12 21:47:31 +00:00
func ( d * Deployer ) setFirewall ( ctx context . Context , computeService * compute . Service ) error {
2015-01-29 13:11:59 +00:00
defaultNet , err := computeService . Networks . Get ( d . Conf . Project , "default" ) . Do ( )
if err != nil {
return fmt . Errorf ( "error getting default network: %v" , err )
}
needRules := map [ string ] compute . Firewall {
"default-allow-http" : compute . Firewall {
Name : "default-allow-http" ,
SourceRanges : [ ] string { "0.0.0.0/0" } ,
SourceTags : [ ] string { "http-server" } ,
2015-11-24 23:10:27 +00:00
Allowed : [ ] * compute . FirewallAllowed { { IPProtocol : "tcp" , Ports : [ ] string { "80" } } } ,
2015-01-29 13:11:59 +00:00
Network : defaultNet . SelfLink ,
} ,
"default-allow-https" : compute . Firewall {
Name : "default-allow-https" ,
SourceRanges : [ ] string { "0.0.0.0/0" } ,
SourceTags : [ ] string { "https-server" } ,
2015-11-24 23:10:27 +00:00
Allowed : [ ] * compute . FirewallAllowed { { IPProtocol : "tcp" , Ports : [ ] string { "443" } } } ,
2015-01-29 13:11:59 +00:00
Network : defaultNet . SelfLink ,
} ,
}
rules , err := computeService . Firewalls . List ( d . Conf . Project ) . Do ( )
if err != nil {
return fmt . Errorf ( "error listing rules: %v" , err )
}
for _ , it := range rules . Items {
delete ( needRules , it . Name )
}
if len ( needRules ) == 0 {
return nil
}
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Need to create rules: %v" , needRules )
2015-01-29 13:11:59 +00:00
}
var wg syncutil . Group
for name , rule := range needRules {
2015-12-12 21:47:31 +00:00
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
2015-01-29 13:11:59 +00:00
}
name , rule := name , rule
wg . Go ( func ( ) error {
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Creating rule %s" , name )
2015-01-29 13:11:59 +00:00
}
r , err := computeService . Firewalls . Insert ( d . Conf . Project , & rule ) . Do ( )
if err != nil {
return fmt . Errorf ( "error creating rule %s: %v" , name , err )
}
if Verbose {
2015-11-13 18:39:29 +00:00
d . Printf ( "Created rule %s: %+v" , name , r )
2015-01-29 13:11:59 +00:00
}
return nil
} )
}
return wg . Err ( )
}
2014-11-17 19:39:00 +00:00
// returns the MySQL InnoDB buffer pool size (in bytes) as a function
// of the GCE machine type.
func innodbBufferPoolSize ( machine string ) int {
// Totally arbitrary. We don't need much here because
// camlistored slurps this all into its RAM on start-up
// anyway. So this is all prety overkill and more than the
// 8MB default.
switch machine {
case "f1-micro" :
return 32 << 20
case "g1-small" :
return 64 << 20
default :
return 128 << 20
}
}
const baseInstanceConfig = ` # cloud - config
write_files :
- path : / var / lib / camlistore / tmp / README
permissions : 0644
content : |
This is the Camlistore / tmp directory .
- path : / var / lib / camlistore / mysql / README
permissions : 0644
content : |
This is the Camlistore MySQL data directory .
coreos :
units :
- name : cam - journal - gatewayd . service
content : |
[ Unit ]
Description = Journal Gateway Service
Requires = cam - journal - gatewayd . socket
[ Service ]
ExecStart = / usr / lib / systemd / systemd - journal - gatewayd
User = systemd - journal - gateway
Group = systemd - journal - gateway
SupplementaryGroups = systemd - journal
PrivateTmp = yes
PrivateDevices = yes
PrivateNetwork = yes
ProtectSystem = full
ProtectHome = yes
[ Install ]
Also = cam - journal - gatewayd . socket
- name : cam - journal - gatewayd . socket
command : start
content : |
[ Unit ]
Description = Journal Gateway Service Socket
[ Socket ]
ListenStream = / run / camjournald . sock
[ Install ]
WantedBy = sockets . target
- name : mysql . service
command : start
content : |
[ Unit ]
Description = MySQL
After = docker . service
Requires = docker . service
[ Service ]
2015-10-19 20:05:59 +00:00
ExecStartPre = / bin / bash - c ' / usr / bin / curl https : //storage.googleapis.com/camlistore-release/docker/systemd-docker.tar.gz | /bin/gunzip -c | /usr/bin/docker load'
ExecStartPre = / usr / bin / docker run -- rm - v / opt / bin : / opt / bin camlistore / systemd - docker
2014-11-17 19:39:00 +00:00
ExecStart = / opt / bin / systemd - docker run -- rm -- name % n - v / var / lib / camlistore / mysql : / mysql - e INNODB_BUFFER_POOL_SIZE = NNN camlistore / mysql
RestartSec = 1 s
Restart = always
Type = notify
NotifyAccess = all
[ Install ]
WantedBy = multi - user . target
- name : camlistored . service
command : start
content : |
[ Unit ]
Description = Camlistore
2015-02-11 22:51:28 +00:00
After = docker . service mysql . service
2014-11-17 19:39:00 +00:00
Requires = docker . service mysql . service
[ Service ]
2015-10-19 20:05:59 +00:00
ExecStartPre = / usr / bin / docker run -- rm - v / opt / bin : / opt / bin camlistore / systemd - docker
2015-10-20 23:46:33 +00:00
ExecStartPre = / bin / bash - c ' / usr / bin / curl CAMLISTORED_TARBALL | / bin / gunzip - c | / usr / bin / docker load '
2015-04-01 14:06:37 +00:00
ExecStart = / opt / bin / systemd - docker run -- rm - p 80 : 80 - p 443 : 443 -- name % n - v / run / camjournald . sock : / run / camjournald . sock - v / var / lib / camlistore / tmp : / tmp -- link = mysql . service : mysqldb camlistore / server
2014-11-17 19:39:00 +00:00
RestartSec = 1 s
Restart = always
Type = notify
NotifyAccess = all
[ Install ]
WantedBy = multi - user . target
`