├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── amz └── amz.go ├── aws ├── Makefile ├── autoscaling │ ├── describe_auto_scaling_groups.go │ ├── describe_launch_configurations.go │ ├── describe_scaling_activities.go │ ├── execute_policy.go │ └── main.go ├── client.go ├── client_test.go ├── cloudformation │ ├── block_device_mapping.go │ ├── cloudformation.go │ ├── create_stack.go │ ├── dbg.go │ ├── delete_stack.go │ ├── describe_stack_events.go │ ├── describe_stack_resources.go │ ├── describe_stacks.go │ ├── ec2_instance.go │ ├── errors.go │ ├── estimate_template_cost.go │ ├── get_template.go │ ├── list_stack_resources.go │ ├── list_stacks.go │ ├── main.go │ ├── network_interface.go │ ├── rds_db_parameter_group.go │ ├── route.go │ ├── route53.go │ ├── route_table.go │ ├── subnet_route_table_association.go │ ├── update_stack.go │ ├── utils.go │ ├── vpc.go │ └── vpc_gateway_attachment.go ├── cloudwatch │ ├── fixtures │ │ ├── get_metric_statistics_response.xml │ │ └── list_metrics_response.xml │ ├── get_metric_statistics.go │ ├── list_metrics.go │ ├── metric.go │ ├── put_metric_data.go │ └── put_metric_data_test.go ├── dbg.go ├── dynamodb │ ├── README.md │ ├── debugger.go │ ├── describe_table.go │ ├── get_item.go │ ├── list_tables.go │ ├── main.go │ ├── put_item.go │ ├── scan.go │ └── update_item.go ├── ec2 │ ├── Makefile │ ├── addresses.go │ ├── client.go │ ├── client_test.go │ ├── deregister_image.go │ ├── describe_images.go │ ├── describe_instances.go │ ├── describe_subnets.go │ ├── describe_volumes.go │ ├── ec2.go │ ├── ec2_test.go │ ├── fixtures │ │ ├── create_image_response.xml │ │ ├── describe_addresses.xml │ │ ├── describe_images.xml │ │ ├── describe_instances.xml │ │ ├── describe_key_pairs.xml │ │ ├── describe_security_groups.xml │ │ ├── describe_spot_price_history.xml │ │ ├── describe_tags.xml │ │ ├── ri-light-linux.json │ │ ├── run_instances_response.xml │ │ └── terminate_instances_response.xml │ ├── images.go │ ├── network_interface.go │ ├── run_instances.go │ ├── security_groups.go │ ├── spot_instances.go │ ├── subnets.go │ ├── tags.go │ └── utils.go ├── elasticache │ ├── dbg.go │ └── describe_cache_clusters.go ├── elb │ ├── elb.go │ ├── elb_test.go │ └── fixtures │ │ ├── describe_instances_health.xml │ │ ├── describe_load_balancers.xml │ │ └── register_instances_with_load_balancer.xml ├── generic.go ├── generic_test.go ├── iam │ ├── client.go │ ├── fixtures │ │ ├── get_account_summary.xml │ │ ├── get_user.xml │ │ ├── list_account_aliases.xml │ │ └── list_users.xml │ └── iam_test.go ├── iam_credentials.go ├── pricing │ ├── .gitignore │ ├── Makefile │ ├── assets.go │ ├── assets │ │ ├── fetch.sh │ │ ├── instance_types.json │ │ ├── linux-od.json │ │ ├── linux-ri-heavy.json │ │ ├── linux-ri-light.json │ │ └── linux-ri-medium.json │ ├── configs.go │ ├── fixtures │ │ └── linux-od.json │ ├── main.go │ ├── main_test.go │ ├── parse.rb │ └── value_columns.go ├── rds │ ├── db_security_group.go │ ├── dbg.go │ ├── delete_db_instance.go │ ├── describe_db_engine_versions.go │ ├── describe_db_instances.go │ ├── describe_db_snapshots.go │ ├── main.go │ ├── modify_db_instance.go │ ├── restore_db_snapshot.go │ ├── utils.go │ └── utils_test.go ├── request_v4.go ├── request_v4_test.go ├── route53 │ ├── fixtures │ │ ├── get_hosted_zone_response.xml │ │ ├── list_hosted_zones.xml │ │ └── list_resource_record_sets.xml │ ├── route53.go │ └── route53_test.go ├── s3 │ ├── Makefile │ ├── acl.go │ ├── client.go │ ├── client_test.go │ ├── fixtures │ │ ├── list_bucket.xml │ │ └── service.xml │ ├── list_bucket.go │ ├── list_versions.go │ ├── multipart.go │ ├── policy.go │ ├── put_bucket.go │ └── util.go ├── sts │ ├── fixtures │ │ └── get_session_token_response.xml │ ├── get_session_token.go │ └── get_session_token_test.go └── util.go ├── cli ├── aws │ ├── cloudformation │ │ ├── main.go │ │ └── parameters_describe.go │ ├── cloudwatch │ │ └── cloudwatch.go │ ├── ec2 │ │ ├── ec2-prices │ │ │ └── main.go │ │ ├── ec2.go │ │ └── prices.go │ ├── elb │ │ └── elb.go │ ├── iam │ │ └── iam.go │ └── route53 │ │ └── route53.go ├── digitalocean │ └── digitalocean.go ├── hetzner │ └── hetzner.go ├── jiffybox │ └── jiffybox.go └── profitbricks │ └── profitbricks.go ├── cmd └── digo2 │ ├── Makefile │ ├── droplet_create.go │ ├── droplet_delete.go │ ├── droplet_reboot.go │ ├── droplet_show.go │ ├── droplets_list.go │ ├── images_list.go │ ├── keys_list.go │ ├── main.go │ └── regions_list.go ├── digitalocean ├── Makefile ├── README.md ├── account.go ├── account_test.go ├── config.go ├── constants.go ├── digital_ocean.go ├── droplet.go ├── env.go ├── image.go ├── pricing.go ├── region.go ├── resource.go ├── size.go ├── ssh_key.go └── v2 │ └── digitalocean │ ├── client.go │ ├── dbg.go │ ├── domains.go │ ├── droplets.go │ ├── images.go │ ├── keys.go │ ├── reboot_droplet.go │ ├── regions.go │ └── sizes.go ├── dyn53 └── main.go ├── gocloud ├── Makefile ├── compare.go └── main.go ├── hetzner ├── Makefile ├── main.go └── plans.go ├── jiffybox ├── Makefile ├── backups.go ├── box.go ├── client.go ├── distributions.go ├── distributions_test.go ├── fixtures │ ├── distributions.json │ ├── error_creating_response.json │ ├── jiffyBoxes.json │ └── no_module_response.json ├── jiffybox_test.go └── plans.go ├── plans.go └── profitbricks ├── Makefile ├── actions.go ├── actions ├── create_storage.go ├── dcs.go ├── server.go ├── snapshot.go └── storage.go ├── client.go ├── client_test.go ├── connected_storage.go ├── data_center.go ├── firewall.go ├── fixtures ├── error_response.xml ├── get_all_data_center_request.xml ├── get_all_data_center_response.xml ├── get_all_images_response.xml ├── get_all_snapshots.xml ├── get_all_storages.xml ├── get_data_center_request.xml ├── get_data_center_response.xml ├── get_server_request.xml ├── get_server_response.xml ├── get_storage_request.xml ├── get_storage_response.xml └── rollback_snapshot_response.xml ├── image.go ├── main.go ├── nic.go ├── pricing.go ├── server.go ├── snapshot.go ├── soap.go ├── storage.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | install: 4 | - go get -u github.com/dynport/dgtk/goassets 5 | - go get -v -t ./... 6 | - go test -v ./... 7 | 8 | go: 9 | - 1.2.1 10 | 11 | notifications: 12 | email: 13 | - travis@dynport.de 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test build 2 | 3 | build: 4 | go get ./... 5 | 6 | test: 7 | go test ./... 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gocloud 2 | 3 | [![Build Status](https://travis-ci.org/dynport/gocloud.png)](https://travis-ci.org/dynport/gocloud) 4 | 5 | ## Disclaimer 6 | 7 | * __This is still a heavy work in progress. THINGS WILL CHANGE!__ 8 | * See the cli tool available in gocloud (e.g. gocloud/ec2.go) for examples. 9 | 10 | ## Goal 11 | 12 | This should become a collection of golang libraries for all our beloved cloud service APIs (similar to fog). 13 | 14 | I know that rackspace started gophercloud but somehow I could not find anything I could already use. So why not start my own? 15 | 16 | ## CLI tool 17 | 18 | ### Installation 19 | 20 | make 21 | 22 | 23 | ### Usage 24 | 25 | Running `gocloud` without any arguments will give you a list of currently supported actions 26 | 27 | make 28 | gocloud 29 | 30 | You can e.g. list your currently running ec2 instances with 31 | 32 | gocloud aws ec2 instances describe 33 | 34 | You will get an error message if there are any environment variables not set. (all credentials are provided through ENV 35 | variables). 36 | 37 | ## Contribute 38 | 39 | All pull requests which make the api/code more consistent, dry, feature complete, etc. are always welcome. 40 | -------------------------------------------------------------------------------- /aws/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go get ./... 3 | -------------------------------------------------------------------------------- /aws/autoscaling/execute_policy.go: -------------------------------------------------------------------------------- 1 | package autoscaling 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | type ExecutePolicy struct { 11 | AutoScalingGroupName string 12 | HonorCooldown bool 13 | PolicyName string 14 | } 15 | 16 | func (action *ExecutePolicy) Execute(client *Client) (string, error) { 17 | ep, e := client.Endpoint() 18 | if e != nil { 19 | return "", e 20 | } 21 | req, e := http.NewRequest("GET", ep+action.query(), nil) 22 | 23 | if e != nil { 24 | return "", e 25 | } 26 | client.SignAwsRequestV2(req, time.Now()) 27 | rsp, e := http.DefaultClient.Do(req) 28 | if e != nil { 29 | return "", e 30 | } 31 | defer rsp.Body.Close() 32 | b, e := ioutil.ReadAll(rsp.Body) 33 | if e != nil { 34 | return "", e 35 | } 36 | if rsp.Status[0] != '2' { 37 | return "", fmt.Errorf("expected status 2xx, got %s. %s", rsp.Status, string(b)) 38 | } 39 | return string(b), nil 40 | } 41 | 42 | func (action *ExecutePolicy) query() string { 43 | values := Values{ 44 | "PolicyName": action.PolicyName, 45 | "AutoScalingGroupName": action.AutoScalingGroupName, 46 | "Action": "ExecutePolicy", 47 | "Version": "2011-01-01", 48 | } 49 | if action.HonorCooldown { 50 | values["HonorCooldown"] = "true" 51 | } 52 | return values.query() 53 | 54 | } 55 | -------------------------------------------------------------------------------- /aws/autoscaling/main.go: -------------------------------------------------------------------------------- 1 | package autoscaling 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func (client *Client) Endpoint() (string, error) { 12 | if client.Region == "" { 13 | return "", fmt.Errorf("Region must be set") 14 | } 15 | return "https://" + client.Region + ".autoscaling.amazonaws.com", nil 16 | } 17 | 18 | func (client *Client) Load(method string, query string, i interface{}) error { 19 | 20 | ep, e := client.Endpoint() 21 | if e != nil { 22 | return e 23 | } 24 | req, e := http.NewRequest(method, ep+query, nil) 25 | 26 | if e != nil { 27 | return e 28 | } 29 | client.SignAwsRequestV2(req, time.Now()) 30 | rsp, e := http.DefaultClient.Do(req) 31 | if e != nil { 32 | return e 33 | } 34 | defer rsp.Body.Close() 35 | b, e := ioutil.ReadAll(rsp.Body) 36 | if e != nil { 37 | return e 38 | } 39 | if rsp.Status[0] != '2' { 40 | return fmt.Errorf("expected status 2xx, got %s. %s", rsp.Status, string(b)) 41 | } 42 | if i == nil { 43 | return nil 44 | } 45 | return xml.Unmarshal(b, i) 46 | } 47 | -------------------------------------------------------------------------------- /aws/client_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | "time" 7 | 8 | . "github.com/smartystreets/goconvey/convey" 9 | ) 10 | 11 | func TestSignAwsRequestV2(t *testing.T) { 12 | Convey("SignAwsRequestV2", t, func() { 13 | client := Client{ 14 | Key: "AKIAIOSFODNN7EXAMPLE", 15 | Secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 16 | } 17 | time.Now() 18 | refTime := time.Date(2011, time.October, 3, 15, 19, 30, 0, time.UTC) 19 | req, e := http.NewRequest("GET", "https://elasticmapreduce.amazonaws.com/?Action=DescribeJobFlows&Version=2009-03-31", nil) 20 | So(e, ShouldBeNil) 21 | payload, _ := client.v2PayloadAndQuery(req, refTime) 22 | So(payload, ShouldContainSubstring, "AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31") 23 | client.SignAwsRequestV2(req, refTime) 24 | raw := req.URL.RawQuery 25 | 26 | So(raw, ShouldContainSubstring, "SignatureMethod=HmacSHA256") 27 | So(raw, ShouldContainSubstring, "AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE") 28 | So(raw, ShouldContainSubstring, "SignatureVersion=2") 29 | So(raw, ShouldContainSubstring, "Timestamp=2011-10-03T15%3A19%3A30") 30 | So(raw, ShouldContainSubstring, "Version=2009-03-31") 31 | So(raw, ShouldContainSubstring, "Signature=i91nKc4PWAt0JJIdXwz9HxZCJDdiy6cf%2FMj6vPxyYIs%3D") 32 | }) 33 | } 34 | 35 | func TestSignS3Request(t *testing.T) { 36 | Convey("SignS3Request", t, func() { 37 | client := Client{ 38 | Key: "44CF9590006BF252F707", 39 | Secret: "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV", 40 | } 41 | req, e := http.NewRequest("PUT", "/quotes/nelson", nil) 42 | So(e, ShouldBeNil) 43 | req.Header.Add("Content-Md5", "c8fdb181845a4ca6b8fec737b3581d76") 44 | req.Header.Add("Content-Type", "text/html") 45 | req.Header.Add("Date", "Thu, 17 Nov 2005 18:49:58 GMT") 46 | req.Header.Add("X-Amz-Meta-Author", "foo@bar.com") 47 | req.Header.Add("X-Amz-Magic", "abracadabra") 48 | 49 | payload := s3Payload(req) 50 | So(payload, ShouldStartWith, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson") 51 | 52 | client.SignS3Request(req) 53 | So(req.Header.Get("Authorization"), ShouldEqual, "AWS 44CF9590006BF252F707:jZNOcbfWmD/A/f3hSvVzXZjM2HU=") 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /aws/cloudformation/block_device_mapping.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | var DefaultBlockDeviceMapping = &BlockDeviceMapping{ 4 | DeviceName: "/dev/sda1", 5 | Ebs: &Ebs{ 6 | VolumeSize: "8", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /aws/cloudformation/dbg.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 18 | -------------------------------------------------------------------------------- /aws/cloudformation/delete_stack.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | func (c *Client) DeleteStack(name string) error { 4 | return c.loadCloudFormationResource("DeleteStack", Values{"StackName": name}, nil) 5 | } 6 | -------------------------------------------------------------------------------- /aws/cloudformation/describe_stack_events.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "time" 6 | ) 7 | 8 | type DescribeStackEventsResponse struct { 9 | XMLName xml.Name `xml:"DescribeStackEventsResponse"` 10 | DescribeStackEventsResult *DescribeStackEventsResult 11 | } 12 | 13 | type DescribeStackEventsResult struct { 14 | NextToken string `xml:"NextToken"` 15 | StackEvents []*StackEvent `xml:"StackEvents>member"` 16 | } 17 | 18 | type StackEvent struct { 19 | EventId string `xml:"EventId"` // Event-1-Id 20 | StackId string `xml:"StackId"` // arn:aws:cloudformation:us-east-1:123456789:stack/MyStack/aaf549a0-a413-11df-adb3-5081b3858e83 21 | StackName string `xml:"StackName"` // MyStack 22 | LogicalResourceId string `xml:"LogicalResourceId"` // MyStack 23 | PhysicalResourceId string `xml:"PhysicalResourceId"` // MyStack_One 24 | ResourceType string `xml:"ResourceType"` // AWS::CloudFormation::Stack 25 | Timestamp time.Time `xml:"Timestamp"` // 2010-07-27T22:26:28Z 26 | ResourceStatus string `xml:"ResourceStatus"` // CREATE_IN_PROGRESS 27 | ResourceStatusReason string `xml:"ResourceStatusReason"` // User initiated 28 | } 29 | 30 | type DescribeStackEventsParameters struct { 31 | NextToken string 32 | StackName string 33 | } 34 | 35 | func (c *Client) DescribeStackEvents(params *DescribeStackEventsParameters) (*DescribeStackEventsResponse, error) { 36 | if params == nil { 37 | params = &DescribeStackEventsParameters{} 38 | } 39 | v := Values{ 40 | "NextToken": params.NextToken, 41 | "StackName": params.StackName, 42 | } 43 | r := &DescribeStackEventsResponse{} 44 | e := c.loadCloudFormationResource("DescribeStackEvents", v, r) 45 | return r, e 46 | } 47 | -------------------------------------------------------------------------------- /aws/cloudformation/describe_stack_resources.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "time" 6 | ) 7 | 8 | type DescribeStackResourcesResponse struct { 9 | XMLName xml.Name `xml:"DescribeStackResourcesResponse"` 10 | DescribeStackResourcesResult *DescribeStackResourcesResult `xml:"DescribeStackResourcesResult"` 11 | } 12 | 13 | type DescribeStackResourcesResult struct { 14 | StackResources []*StackResource `xml:"StackResources>member"` 15 | } 16 | 17 | type StackResource struct { 18 | StackId string //arn:aws:cloudformation:us-east-1:123456789:stack/MyStack/aaf549a0-a413-11df-adb3-5081b3858e83 19 | StackName string //MyStack 20 | LogicalResourceId string //MyDBInstance 21 | PhysicalResourceId string //MyStack_DB1 22 | ResourceType string //AWS::DBInstance 23 | Timestamp time.Time //2010-07-27T22:27:28Z 24 | ResourceStatus string //CREATE_COMPLETE 25 | } 26 | 27 | type DescribeStackResourcesParameters struct { 28 | LogicalResourceId string 29 | PhysicalResourceId string 30 | StackName string 31 | } 32 | 33 | func (client *Client) DescribeStackResources(params DescribeStackResourcesParameters) (*DescribeStackResourcesResponse, error) { 34 | r := &DescribeStackResourcesResponse{} 35 | values := Values{ 36 | "StackName": params.StackName, 37 | "PhysicalResourceId": params.PhysicalResourceId, 38 | "LogicalResourceId": params.LogicalResourceId, 39 | } 40 | e := client.loadCloudFormationResource("DescribeStackResources", values, r) 41 | return r, e 42 | } 43 | -------------------------------------------------------------------------------- /aws/cloudformation/describe_stacks.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "time" 6 | ) 7 | 8 | type DescribeStacksResponse struct { 9 | XMLName xml.Name `xml:"DescribeStacksResponse"` 10 | DescribeStacksResult *DescribeStacksResult `xml:"DescribeStacksResult"` 11 | } 12 | 13 | type DescribeStacksResult struct { 14 | Stacks []*Stack `xml:"Stacks>member"` 15 | } 16 | 17 | type DescribeStacksParameters struct { 18 | NextToken string 19 | StackName string 20 | } 21 | 22 | type DescribeStacks struct { 23 | NextToken string 24 | StackName string 25 | } 26 | 27 | func (a *DescribeStacks) Execute(client *Client) (*DescribeStacksResponse, error) { 28 | r := &DescribeStacksResponse{} 29 | v := Values{ 30 | "NextToken": a.NextToken, 31 | "StackName": a.StackName, 32 | } 33 | e := client.loadCloudFormationResource("DescribeStacks", v, r) 34 | return r, e 35 | } 36 | 37 | func (client *Client) DescribeStacks(params *DescribeStacksParameters) (rsp *DescribeStacksResponse, e error) { 38 | action := &DescribeStacks{} 39 | if params != nil { 40 | action.NextToken = params.NextToken 41 | action.StackName = params.StackName 42 | } 43 | return action.Execute(client) 44 | } 45 | 46 | type Stack struct { 47 | StackName string `xml:"StackName"` // MyStack 48 | StackId string `xml:"StackId"` // arn:aws:cloudformation:us-east-1:123456789:stack/MyStack/aaf549a0-a413-11df-adb3-5081b3858e83 49 | CreationTime time.Time `xml:"CreationTime"` // 2010-07-27T22:28:28Z 50 | StackStatus string `xml:"StackStatus"` // CREATE_COMPLETE 51 | DisableRollback bool `xml:"DisableRollback"` // false 52 | TemplateDescription string `xml:"TemplateDescription"` 53 | Outputs []*Output `xml:"Outputs>member"` 54 | Parameters []*StackParameter `xml:"Parameters>member"` 55 | } 56 | 57 | type Output struct { 58 | OutputKey string `xml:"OutputKey"` 59 | OutputValue string `xml:"OutputValue"` 60 | } 61 | -------------------------------------------------------------------------------- /aws/cloudformation/ec2_instance.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | // http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html 4 | type Ec2Instance struct { 5 | ImageId interface{} `json:"ImageId,omitempty"` 6 | DisableApiTermination interface{} `json:"DisableApiTermination,omitempty"` 7 | KeyName interface{} `json:"KeyName,omitempty"` 8 | InstanceType interface{} `json:"InstanceType,omitempty"` 9 | SubnetId interface{} `json:"SubnetId,omitempty"` 10 | PrivateIpAddress interface{} `json:"PrivateIpAddress,omitempty"` 11 | NetworkInterfaces []*NetworkInterface `json:"NetworkInterfaces,omitempty"` 12 | UserData string `json:"UserData,omitempty"` 13 | BlockDeviceMappings []*BlockDeviceMapping `json:"BlockDeviceMappings,omitempty"` 14 | Tags []*Tag `json:"Tags,omitempty"` 15 | } 16 | 17 | type Ec2EIP struct { 18 | InstanceId interface{} `json:"InstanceId,omitempty"` 19 | Domain interface{} `json:"Domain,omitempty"` 20 | } 21 | -------------------------------------------------------------------------------- /aws/cloudformation/errors.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import "encoding/xml" 4 | 5 | type ErrorResponse struct { 6 | XMLName xml.Name `xml:"ErrorResponse"` 7 | Error *Error `xml:"Error"` 8 | RequestId string `xml:"RequestId"` 9 | } 10 | 11 | type Error struct { 12 | Type string `xml:"Type,omitempty"` 13 | Code string `xml:"Code,omitempty"` 14 | Message string `xml:"Message,omitempty"` 15 | } 16 | -------------------------------------------------------------------------------- /aws/cloudformation/estimate_template_cost.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | type EstimateTemplateCostResponse struct { 9 | XMLName xml.Name `xml:"EstimateTemplateCostResponse"` 10 | EstimateTemplateCostResult *EstimateTemplateCostResult `xml:"EstimateTemplateCostResult"` 11 | } 12 | 13 | type EstimateTemplateCostResult struct { 14 | Url string `xml:"Url"` 15 | } 16 | 17 | type EstimateTemplateCostParameters struct { 18 | TemplateBody string 19 | TemplateURL string 20 | Parameters []*StackParameter 21 | } 22 | 23 | func (c *Client) EstimateTemplateCost(params EstimateTemplateCostParameters) (*EstimateTemplateCostResponse, error) { 24 | r := &EstimateTemplateCostResponse{} 25 | v := Values{} 26 | if params.TemplateBody != "" { 27 | v["TemplateBody"] = params.TemplateBody 28 | } 29 | if params.TemplateURL != "" { 30 | v["TemplateURL"] = params.TemplateURL 31 | } 32 | for i, p := range params.Parameters { 33 | v["Parameters.member."+strconv.Itoa(i+1)+".ParameterKey"] = p.ParameterKey 34 | v["Parameters.member."+strconv.Itoa(i+1)+".ParameterValue"] = p.ParameterValue 35 | } 36 | e := c.loadCloudFormationResource("EstimateTemplateCost", v, r) 37 | return r, e 38 | } 39 | -------------------------------------------------------------------------------- /aws/cloudformation/get_template.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/dynport/gocloud/aws" 11 | ) 12 | 13 | type GetTemplate struct { 14 | StackName string 15 | } 16 | 17 | type GetTemplateResponse struct { 18 | XMLName xml.Name `xml:"GetTemplateResponse"` 19 | GetTemplateResult *GetTemplateResult `xml:"GetTemplateResult,omitempty"` 20 | } 21 | 22 | type GetTemplateResult struct { 23 | TemplateBody string `xml:"TemplateBody,omitempty"` 24 | } 25 | 26 | func endpoint(client *aws.Client) (string, error) { 27 | if client.Region == "" { 28 | return "", fmt.Errorf("Region must be set") 29 | } 30 | return "https://cloudformation." + client.Region + ".amazonaws.com", nil 31 | } 32 | 33 | func (t *GetTemplate) Execute(client *aws.Client) (*GetTemplateResponse, error) { 34 | v := Values{ 35 | "Action": "GetTemplate", 36 | "StackName": t.StackName, 37 | "Version": "2010-05-15", 38 | } 39 | 40 | ep, e := endpoint(client) 41 | if e != nil { 42 | return nil, e 43 | } 44 | req, e := http.NewRequest("GET", ep+"?"+v.Encode(), nil) 45 | if e != nil { 46 | return nil, e 47 | } 48 | client.SignAwsRequestV2(req, time.Now()) 49 | rsp, e := httpClient.Do(req) 50 | if e != nil { 51 | return nil, e 52 | } 53 | defer rsp.Body.Close() 54 | 55 | b, e := ioutil.ReadAll(rsp.Body) 56 | if e != nil { 57 | return nil, e 58 | } 59 | xmlRsp := &GetTemplateResponse{} 60 | e = xml.Unmarshal(b, xmlRsp) 61 | if e != nil { 62 | e = fmt.Errorf(e.Error() + ": " + string(b) + " status=" + rsp.Status) 63 | } 64 | return xmlRsp, e 65 | } 66 | -------------------------------------------------------------------------------- /aws/cloudformation/list_stack_resources.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import "encoding/xml" 4 | 5 | type ListStackResourcesResponse struct { 6 | XMLName xml.Name `xml:"ListStackResourcesResponse"` 7 | InternalListStackResourcesResult *InternalListStackResourcesResult `xml:"ListStackResourcesResult"` 8 | } 9 | 10 | type InternalListStackResourcesResult struct { 11 | NextToken *string `xml:"NextToken"` 12 | StackResources []*StackResource `xml:"StackResourceSummaries>member"` 13 | } 14 | 15 | type ListStackResourcesResult struct { 16 | StackResources []*StackResource 17 | } 18 | 19 | type ListStackResourcesParameters struct { 20 | LogicalResourceId string 21 | PhysicalResourceId string 22 | StackName string 23 | } 24 | 25 | func (client *Client) ListStackResources(params ListStackResourcesParameters) (*ListStackResourcesResult, error) { 26 | var NextToken string 27 | result := &ListStackResourcesResult{ 28 | StackResources: []*StackResource{}, 29 | } 30 | for { 31 | r := &ListStackResourcesResponse{} 32 | values := Values{ 33 | "StackName": params.StackName, 34 | "PhysicalResourceId": params.PhysicalResourceId, 35 | "LogicalResourceId": params.LogicalResourceId, 36 | "NextToken": NextToken, 37 | } 38 | e := client.loadCloudFormationResource("ListStackResources", values, r) 39 | if e != nil { 40 | return nil, e 41 | } 42 | 43 | result.StackResources = append(result.StackResources, r.InternalListStackResourcesResult.StackResources...) 44 | if r.InternalListStackResourcesResult.NextToken == nil { 45 | break 46 | } else { 47 | NextToken = *r.InternalListStackResourcesResult.NextToken 48 | } 49 | } 50 | return result, nil 51 | } 52 | -------------------------------------------------------------------------------- /aws/cloudformation/list_stacks.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | ) 7 | 8 | type ListStacksResponse struct { 9 | XMLName xml.Name `xml:"ListStacksResponse"` 10 | ListStacksResult *ListStacksResult `xml:"ListStacksResult"` 11 | } 12 | 13 | type ListStacksResult struct { 14 | Stacks []*Stack `xml:"StackSummaries>member"` 15 | } 16 | 17 | type ListStacksParameters struct { 18 | NextToken string 19 | StackStatusFilters []string 20 | } 21 | 22 | func (c *Client) ListStacks(params *ListStacksParameters) (*ListStacksResponse, error) { 23 | r := &ListStacksResponse{} 24 | if params == nil { 25 | params = &ListStacksParameters{} 26 | } 27 | v := Values{ 28 | "NextToken": params.NextToken, 29 | } 30 | for i, filter := range params.StackStatusFilters { 31 | v["StackStatusFilter.member."+strconv.Itoa(i+1)] = filter 32 | } 33 | e := c.loadCloudFormationResource("ListStacks", v, r) 34 | return r, e 35 | } 36 | -------------------------------------------------------------------------------- /aws/cloudformation/main.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/dynport/gocloud/aws" 12 | ) 13 | 14 | type Client struct { 15 | *aws.Client 16 | } 17 | 18 | var httpClient = newHttpClient() 19 | 20 | func newHttpClient() *http.Client { 21 | return &http.Client{ 22 | Transport: &http.Transport{ 23 | DisableKeepAlives: true, 24 | }, 25 | } 26 | } 27 | 28 | func NewFromEnv() *Client { 29 | return &Client{Client: aws.NewFromEnv()} 30 | } 31 | 32 | func (client *Client) Endpoint() string { 33 | prefix := "https://cloudformation" 34 | if client.Client.Region != "" { 35 | prefix += "." + client.Client.Region 36 | } 37 | return prefix + ".amazonaws.com" 38 | } 39 | 40 | func (client *Client) loadCloudFormationResource(action string, params Values, i interface{}) error { 41 | req, e := client.signedCloudFormationRequest(action, params) 42 | 43 | rsp, e := httpClient.Do(req) 44 | if e != nil { 45 | return e 46 | } 47 | defer rsp.Body.Close() 48 | b, e := ioutil.ReadAll(rsp.Body) 49 | if e != nil { 50 | return e 51 | } 52 | switch rsp.StatusCode { 53 | case 404: 54 | return ErrorNotFound 55 | case 200: 56 | if i != nil { 57 | return xml.Unmarshal(b, i) 58 | } 59 | return nil 60 | default: 61 | ersp := &ErrorResponse{} 62 | dbg.Printf("ERROR=%q", string(b)) 63 | e = xml.Unmarshal(b, ersp) 64 | if e != nil { 65 | return fmt.Errorf("expected status 2xx but got %s (%s)", rsp.Status, string(b)) 66 | 67 | } 68 | if strings.Contains(ersp.Error.Message, "does not exist") { 69 | return ErrorNotFound 70 | } 71 | return fmt.Errorf(ersp.Error.Message) 72 | } 73 | } 74 | 75 | var ( 76 | ErrorNotFound = fmt.Errorf("Error not found") 77 | ) 78 | 79 | func (client *Client) signedCloudFormationRequest(action string, values Values) (*http.Request, error) { 80 | values["Version"] = "2010-05-15" 81 | values["Action"] = action 82 | theUrl := client.Endpoint() + "?" + values.Encode() 83 | req, e := http.NewRequest("GET", theUrl, nil) 84 | if e != nil { 85 | return nil, e 86 | } 87 | client.SignAwsRequestV2(req, time.Now()) 88 | return req, nil 89 | } 90 | -------------------------------------------------------------------------------- /aws/cloudformation/network_interface.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type NetworkInterface struct { 4 | AssociatePublicIpAddress bool `json:"AssociatePublicIpAddress,omitempty"` // : Boolean, 5 | DeleteOnTermination bool `json:"DeleteOnTermination,omitempty"` // : Boolean, 6 | Description string `json:"Description,omitempty"` // : String, 7 | DeviceIndex string `json:"DeviceIndex,omitempty"` // : String, 8 | GroupSet []interface{} `json:"GroupSet,omitempty"` // : [ String, ... ], 9 | NetworkInterfaceId string `json:"NetworkInterfaceId,omitempty"` // : String, 10 | PrivateIpAddress string `json:"PrivateIpAddress,omitempty"` // : String, 11 | PrivateIpAddresses []string `json:"PrivateIpAddresses,omitempty"` // : [ PrivateIpAddressSpecification, ... ], 12 | SecondaryPrivateIpAddressCount int `json:"SecondaryPrivateIpAddressCount,omitempty"` // : Integer, 13 | SubnetId interface{} `json:"SubnetId,omitempty"` // : String 14 | } 15 | -------------------------------------------------------------------------------- /aws/cloudformation/route.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type Route struct { 4 | DestinationCidrBlock interface{} `json:"DestinationCidrBlock,omitempty"` 5 | GatewayId interface{} `json:"GatewayId,omitempty"` 6 | RouteTableId interface{} `json:"RouteTableId,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /aws/cloudformation/route53.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type RecordSet struct { 4 | AliasTarget interface{} `json:"AliasTarget,omitempty"` // AliasTarget, 5 | Comment interface{} `json:"Comment,omitempty"` // String, 6 | HostedZoneId interface{} `json:"HostedZoneId,omitempty"` // String, 7 | HostedZoneName interface{} `json:"HostedZoneName,omitempty"` // String, 8 | Name interface{} `json:"Name,omitempty"` // String, 9 | Region interface{} `json:"Region,omitempty"` // String, 10 | ResourceRecords []interface{} `json:"ResourceRecords,omitempty"` // [ String ], 11 | SetIdentifier interface{} `json:"SetIdentifier,omitempty"` // String, 12 | TTL interface{} `json:"TTL,omitempty"` // String, 13 | Type interface{} `json:"Type,omitempty"` // String, 14 | Weight interface{} `json:"Weight,omitempty"` // Integer 15 | } 16 | -------------------------------------------------------------------------------- /aws/cloudformation/route_table.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type RouteTable struct { 4 | VpcId interface{} `json:"VpcId,omitempty"` 5 | Tags []*Tag `json:"Tags,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /aws/cloudformation/subnet_route_table_association.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | func NewSubnetRouteTableAssociation(routeTableId interface{}, subnetId interface{}) *Resource { 4 | return NewResource("AWS::EC2::SubnetRouteTableAssociation", 5 | &SubnetRouteTableAssociation{RouteTableId: routeTableId, SubnetId: subnetId}, 6 | ) 7 | } 8 | 9 | type SubnetRouteTableAssociation struct { 10 | RouteTableId interface{} `json:"RouteTableId,omitempty"` 11 | SubnetId interface{} `json:"SubnetId,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /aws/cloudformation/update_stack.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import "fmt" 4 | 5 | type UpdateStackParameters struct { 6 | BaseParameters 7 | StackPolicyDuringUpdateBody string 8 | StackPolicyDuringUpdateURL string 9 | UsePreviousTemplate bool 10 | } 11 | 12 | func (p *UpdateStackParameters) values() Values { 13 | v := p.BaseParameters.values() 14 | v["StackPolicyDuringUpdateBody"] = p.StackPolicyDuringUpdateBody 15 | v["StackPolicyDuringUpdateURL"] = p.StackPolicyDuringUpdateURL 16 | if p.UsePreviousTemplate { 17 | v["UsePreviousTemplate"] = "true" 18 | } 19 | return v 20 | } 21 | 22 | type UpdateStackResponse struct { 23 | UpdateStackResult *UpdateStackResult `xml:"UpdateStackResult"` 24 | } 25 | 26 | type UpdateStackResult struct { 27 | StackId string `xml:"StackId"` 28 | } 29 | 30 | type UpdateStack struct { 31 | Parameters []*StackParameter 32 | Capabilities []string 33 | StackName string 34 | StackPolicyBody string 35 | StackPolicyURL string 36 | TemplateBody string 37 | TemplateURL string 38 | StackPolicyDuringUpdateBody string 39 | StackPolicyDuringUpdateURL string 40 | UsePreviousTemplate bool 41 | } 42 | 43 | func (update *UpdateStack) values() Values { 44 | v := Values{ 45 | "StackName": update.StackName, 46 | "StackPolicyBody": update.StackPolicyBody, 47 | "StackPolicyURL": update.StackPolicyURL, 48 | "TemplateBody": update.TemplateBody, 49 | "TemplateURL": update.TemplateURL, 50 | "StackPolicyDuringUpdateBody": update.StackPolicyDuringUpdateBody, 51 | "StackPolicyDuringUpdateURL": update.StackPolicyDuringUpdateURL, 52 | } 53 | if update.UsePreviousTemplate { 54 | v["UsePreviousTemplate"] = "true" 55 | } 56 | v.updateCapabilities(update.Capabilities) 57 | v.updateParameters(update.Parameters) 58 | return v 59 | } 60 | 61 | func (update *UpdateStack) Execute(client *Client) (*UpdateStackResponse, error) { 62 | r := &UpdateStackResponse{} 63 | e := client.loadCloudFormationResource("UpdateStack", update.values(), r) 64 | return r, e 65 | } 66 | 67 | const errorNoUpdate = "No updates are to be performed." 68 | 69 | var ErrorNoUpdate = fmt.Errorf(errorNoUpdate) 70 | 71 | func (c *Client) UpdateStack(params UpdateStackParameters) (stackId string, e error) { 72 | r := &UpdateStackResponse{} 73 | e = c.loadCloudFormationResource("UpdateStack", params.values(), r) 74 | if e != nil { 75 | dbg.Printf("error updating stack %T: %q", e, e) 76 | if e.Error() == errorNoUpdate { 77 | return "", ErrorNoUpdate 78 | } 79 | return "", e 80 | } 81 | return r.UpdateStackResult.StackId, nil 82 | } 83 | -------------------------------------------------------------------------------- /aws/cloudformation/utils.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type Reference struct { 4 | Ref string `json:"Ref,omitempty"` 5 | } 6 | 7 | func Ref(name string) *Reference { 8 | return &Reference{Ref: name} 9 | } 10 | 11 | func NewSubnet(az string, cidr string) *Resource { 12 | return NewResource("AWS::EC2::Subnet", 13 | &Subnet{ 14 | AvailabilityZone: az, 15 | CidrBlock: cidr, 16 | VpcId: &Reference{Ref: "vpc"}, 17 | }, 18 | ) 19 | } 20 | 21 | type Subnet struct { 22 | AvailabilityZone string `json:"AvailabilityZone,omitempty"` 23 | CidrBlock string `json:"CidrBlock,omitempty"` 24 | Tags []*Tag `json:"Tags,omitempty"` 25 | VpcId interface{} `json:"VpcId,omitempty"` 26 | } 27 | -------------------------------------------------------------------------------- /aws/cloudformation/vpc.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type Vpc struct { 4 | CidrBlock string `json:"CidrBlock,omitempty"` 5 | EnableDnsSupport bool `json:"EnableDnsSupport,omitempty"` 6 | EnableDnsHostnames bool `json:"EnableDnsHostnames,omitempty"` 7 | InstanceTenancy string `json:"InstanceTenancy,omitempty"` 8 | Tags []*Tag `json:"Tags,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /aws/cloudformation/vpc_gateway_attachment.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | type VPCGatewayAttachment struct { 4 | InternetGatewayId interface{} `json:"InternetGatewayId,omitempty"` 5 | VpcId interface{} `json:"VpcId,omitempty"` 6 | VpnGatewayId string `json:"VpnGatewayId,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /aws/cloudwatch/list_metrics.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "encoding/xml" 5 | "log" 6 | "net/url" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/dynport/gocloud/aws/ec2" 11 | ) 12 | 13 | type ListMetrics struct { 14 | Dimensions []*Dimension 15 | MetricName string 16 | Namespace string 17 | NextToken string 18 | } 19 | 20 | type Values map[string]string 21 | 22 | func (values Values) addDimensions(prefix string, dimensions []*Dimension) { 23 | for i, d := range dimensions { 24 | dimPrefix := prefix + "Dimensions.member." + strconv.Itoa(i+1) + "." 25 | values.Add(dimPrefix+"Name", d.Name) 26 | values.Add(dimPrefix+"Value", d.Value) 27 | } 28 | } 29 | 30 | func (values Values) Add(key, value string) { 31 | values[key] = value 32 | } 33 | 34 | func (values Values) Encode() string { 35 | ret := url.Values{} 36 | for k, v := range values { 37 | if v != "" { 38 | ret.Set(k, v) 39 | } 40 | } 41 | return ret.Encode() 42 | } 43 | 44 | var logger = log.New(os.Stderr, "", 0) 45 | 46 | func (action *ListMetrics) Execute(client *ec2.Client) (*ListMetricsResponse, error) { 47 | rsp, e := client.DoSignedRequest("GET", endpoint(client.Client), action.query(), nil) 48 | if e != nil { 49 | return nil, e 50 | } 51 | o := &ListMetricsResponse{} 52 | e = xml.Unmarshal(rsp.Content, o) 53 | return o, e 54 | } 55 | 56 | func (action *ListMetrics) query() string { 57 | values := Values{ 58 | "Version": VERSION, 59 | "Action": "ListMetrics", 60 | "MetricName": action.MetricName, 61 | "Namespace": action.Namespace, 62 | "NextToken": action.NextToken, 63 | } 64 | 65 | for i, d := range action.Dimensions { 66 | prefix := "Dimensions.member." + strconv.Itoa(i+1) + "." 67 | values.Add(prefix+"Name", d.Name) 68 | values.Add(prefix+"Value", d.Value) 69 | } 70 | return values.Encode() 71 | } 72 | -------------------------------------------------------------------------------- /aws/cloudwatch/metric.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "encoding/xml" 5 | "net/url" 6 | 7 | "github.com/dynport/gocloud/aws" 8 | ) 9 | 10 | type Dimension struct { 11 | Name string `xml:"Name"` 12 | Value string `xml:"Value"` 13 | } 14 | 15 | type Metric struct { 16 | Dimensions []*Dimension `xml:"Dimensions>member"` 17 | MetricName string `xml:"MetricName"` 18 | Namespace string `xml:"Namespace"` 19 | } 20 | 21 | type ListMetricsResponse struct { 22 | XMLName xml.Name `xml:"ListMetricsResponse"` 23 | Metrics []*Metric `xml:"ListMetricsResult>Metrics>member"` 24 | NextToken string `xml:"ListMetricsResult>NextToken"` 25 | } 26 | 27 | const ( 28 | VERSION = "2010-08-01" 29 | ) 30 | 31 | func endpoint(client *aws.Client) string { 32 | return "https://monitoring." + client.Region + ".amazonaws.com" 33 | } 34 | 35 | type Client struct { 36 | *aws.Client 37 | } 38 | 39 | func (client *Client) Endpoint() string { 40 | prefix := "https://monitoring" 41 | if client.Client.Region != "" { 42 | prefix += "." + client.Client.Region 43 | } 44 | return prefix + ".amazonaws.com" 45 | } 46 | 47 | type ListMetricsOptions struct { 48 | NextToken string 49 | } 50 | 51 | type ListMetricsOption func(*ListMetricsOptions) 52 | 53 | func OptNextToken(i string) ListMetricsOption { 54 | return func(o *ListMetricsOptions) { 55 | o.NextToken = i 56 | } 57 | } 58 | 59 | func (client *Client) ListMetrics(funcs ...ListMetricsOption) (rsp *ListMetricsResponse, e error) { 60 | values := &url.Values{} 61 | values.Add("Version", VERSION) 62 | values.Add("Action", "ListMetrics") 63 | o := &ListMetricsOptions{} 64 | for _, f := range funcs { 65 | f(o) 66 | } 67 | if o.NextToken != "" { 68 | values.Add("NextToken", o.NextToken) 69 | } 70 | logger.Printf("%#v", values.Encode()) 71 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 72 | if e != nil { 73 | return nil, e 74 | } 75 | e = xml.Unmarshal(raw.Content, &rsp) 76 | return rsp, e 77 | } 78 | -------------------------------------------------------------------------------- /aws/cloudwatch/put_metric_data.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/dynport/gocloud/aws" 11 | ) 12 | 13 | // http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html 14 | type PutMetricData struct { 15 | Namespace string 16 | MetricData []*MetricData 17 | } 18 | 19 | // http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html 20 | type MetricData struct { 21 | Dimensions []*Dimension 22 | MetricName string 23 | StatisticValues *StatisticValues 24 | Timestamp time.Time 25 | Unit string 26 | Value float64 27 | } 28 | 29 | type StatisticValues struct { 30 | Maximum float64 31 | Minimum float64 32 | SampleCount float64 33 | Sum float64 34 | } 35 | 36 | func (p *PutMetricData) Execute(client *aws.Client) error { 37 | q := p.query() 38 | theUrl := endpoint(client) + "?" + q 39 | req, e := http.NewRequest("POST", theUrl, nil) 40 | if e != nil { 41 | return e 42 | } 43 | now := time.Now() 44 | client.SignAwsRequestV2(req, now) 45 | rsp, e := http.DefaultClient.Do(req) 46 | if e != nil { 47 | return e 48 | } 49 | b, e := ioutil.ReadAll(rsp.Body) 50 | if e != nil { 51 | return e 52 | } 53 | if rsp.Status[0] != '2' { 54 | return fmt.Errorf("expected status 2xx, got %s. %s", rsp.Status, string(b)) 55 | } 56 | return nil 57 | } 58 | 59 | func (p *PutMetricData) query() string { 60 | values := Values{ 61 | "Version": VERSION, 62 | "Action": "PutMetricData", 63 | "Namespace": p.Namespace, 64 | } 65 | 66 | for i, d := range p.MetricData { 67 | prefix := "MetricData.member." + strconv.Itoa(i+1) + "." 68 | values[prefix+"MetricName"] = d.MetricName 69 | values[prefix+"Unit"] = d.Unit 70 | values[prefix+"Value"] = strconv.FormatFloat(d.Value, 'E', 10, 64) 71 | if !d.Timestamp.IsZero() { 72 | values[prefix+"Timestamp"] = d.Timestamp.UTC().Format(time.RFC3339) 73 | } 74 | values.addDimensions(prefix, d.Dimensions) 75 | } 76 | return values.Encode() 77 | } 78 | -------------------------------------------------------------------------------- /aws/cloudwatch/put_metric_data_test.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "testing" 5 | . "github.com/smartystreets/goconvey/convey" 6 | ) 7 | 8 | func TestPutMetricData(t *testing.T) { 9 | Convey("Query", t, func() { 10 | So(1, ShouldEqual, 1) 11 | p := &PutMetricData{ 12 | MetricData: []*MetricData{ 13 | { 14 | Value: 10, 15 | Dimensions: []*Dimension{ 16 | {Name: "InstanceId", Value: "test"}, 17 | }, 18 | }, 19 | }, 20 | } 21 | query := p.query() 22 | So(query, ShouldNotEqual, "") 23 | So(query, ShouldContainSubstring, "Value=1.0") 24 | So(query, ShouldNotContainSubstring, "Unit") 25 | So(query, ShouldContainSubstring, "MetricData.member.1.Dimensions.member.1.Name=InstanceId") 26 | t.Log(query) 27 | }) 28 | 29 | Convey("List Metrics", t, func() { 30 | So((&ListMetrics{}).query(), ShouldNotEqual, "") 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /aws/dbg.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[gocloud/aws/DEBUG] ", 0) 18 | -------------------------------------------------------------------------------- /aws/dynamodb/debugger.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | ) 8 | 9 | var debugger = log.New(debugWriter(), "DEBUG:", log.Llongfile) 10 | 11 | type NullWriter struct { 12 | } 13 | 14 | func (w *NullWriter) Write([]byte) (int, error) { 15 | return 0, nil 16 | } 17 | 18 | func debugWriter() io.Writer { 19 | if os.Getenv("DEBUG") == "true" { 20 | return os.Stderr 21 | } 22 | return &NullWriter{} 23 | } 24 | -------------------------------------------------------------------------------- /aws/dynamodb/describe_table.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "github.com/dynport/gocloud/aws" 4 | 5 | type DescribeTable struct { 6 | TableName string 7 | } 8 | 9 | func (d *DescribeTable) Execute(client *aws.Client) (*DescribeTableResponse, error) { 10 | rsp := &DescribeTableResponse{} 11 | e := loadAction(client, "DescribeTable", d, rsp) 12 | return rsp, e 13 | } 14 | 15 | type DescribeTableResponse struct { 16 | Table *Table `json:"Table,omitempty"` 17 | } 18 | 19 | type Table struct { 20 | TableStatus string `json:"TableStatus,omitempty"` 21 | TableSizesBytes int `json:"TableSizesBytes,omitempty"` 22 | TableName string `json:"TableName,omitempty"` 23 | ProvisionedThroughput *ProvisionedThroughput `json:"ProvisionedThroughput,omitempty"` 24 | KeySchema []*KeySchema `json:"KeySchema"` 25 | ItemCount int `json:"ItemCount,omitempty"` 26 | CreationDateTime float64 `json:"CreationDateTime,omitempty"` 27 | AttributeDefinitions []*AttributeDefinition `json:"AttributeDefinitions,omitempty"` 28 | } 29 | 30 | type AttributeDefinition struct { 31 | AttributeType string 32 | AtributeName string 33 | } 34 | 35 | type ProvisionedThroughput struct { 36 | WriteCapacityUnits int 37 | ReadCapacityUnits int 38 | NumberOfDecreasesToday int 39 | } 40 | 41 | type KeySchema struct { 42 | KeyType string 43 | AttributeName string 44 | } 45 | -------------------------------------------------------------------------------- /aws/dynamodb/get_item.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "github.com/dynport/gocloud/aws" 4 | 5 | type GetItem struct { 6 | AttributesToGet []string `json:"AttributesToGet,omitempty"` 7 | ConsistentRead string `json:"ConsistentRead,omitempty"` 8 | Key *Item `json:"Key,omitempty"` 9 | ReturnConsumedCapacity string `json:"ReturnConsumedCapacity,omitempty"` 10 | TableName string `json:"TableName,omitempty"` 11 | } 12 | 13 | func (g *GetItem) Execute(client *aws.Client) (*GetItemResponse, error) { 14 | rsp := &GetItemResponse{} 15 | e := loadAction(client, "GetItem", g, rsp) 16 | return rsp, e 17 | } 18 | 19 | type GetItemResponse struct { 20 | ConsumedCapacity *ConsumedCapacity `json:"ConsumedCapacity,omitempty"` 21 | Item Item `json:"Item,omitempty"` 22 | } 23 | 24 | type ConsumedCapacity struct { 25 | CapacityUnits float64 `json:"CapacityUnits,omitempty"` 26 | TableName string `json:"TableName,omitempty"` 27 | } 28 | -------------------------------------------------------------------------------- /aws/dynamodb/list_tables.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "github.com/dynport/gocloud/aws" 4 | 5 | type ListTables struct { 6 | ExclusiveStartTableName string `json:"ExclusiveStartTableName,omitempty"` 7 | Limit int `json:"Limit,omitempty"` 8 | } 9 | 10 | func (l *ListTables) Execute(client *aws.Client) (*ListTablesResponse, error) { 11 | rsp := &ListTablesResponse{} 12 | e := loadAction(client, "ListTables", l, rsp) 13 | return rsp, e 14 | } 15 | 16 | type ListTablesResponse struct { 17 | LastEvaluatedTableName string `json:"LastEvaluatedTableName,omitempty"` 18 | TableNames []string `json:"TableNames,omitempty"` 19 | } 20 | -------------------------------------------------------------------------------- /aws/dynamodb/main.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | 12 | "github.com/dynport/gocloud/aws" 13 | ) 14 | 15 | const targetPrefix = "DynamoDB_20120810" 16 | 17 | func loadAction(client *aws.Client, action string, payload interface{}, rsp interface{}) error { 18 | u, e := url.Parse("http://dynamodb." + client.Region + ".amazonaws.com/") 19 | if e != nil { 20 | return e 21 | } 22 | b, e := json.Marshal(payload) 23 | if e != nil { 24 | return e 25 | } 26 | debugger.Printf("PAYLOAD: %s", string(b)) 27 | req := &aws.RequestV4{ 28 | Service: "dynamodb", 29 | Region: client.Region, 30 | Method: "POST", 31 | URL: u, 32 | Time: time.Now(), 33 | Payload: b, 34 | Key: client.Key, 35 | Secret: client.Secret, 36 | } 37 | req.SetHeader("X-Amz-Target", targetPrefix+"."+action) 38 | req.SetHeader("Content-Type", "application/x-amz-json-1.0") 39 | 40 | r, e := req.Request() 41 | if e != nil { 42 | return e 43 | } 44 | 45 | started := time.Now() 46 | httpResponse, e := http.DefaultClient.Do(r) 47 | if e != nil { 48 | return e 49 | } 50 | defer httpResponse.Body.Close() 51 | debugger.Printf("RESPONSE: status=%s time=%.6f", httpResponse.Status, time.Since(started).Seconds()) 52 | buf := &bytes.Buffer{} 53 | reader := io.TeeReader(httpResponse.Body, buf) 54 | e = json.NewDecoder(reader).Decode(rsp) 55 | debugger.Print("BODY: " + buf.String()) 56 | if e != nil { 57 | return e 58 | } else if httpResponse.Status[0] != '2' { 59 | er := &ApiError{} 60 | e = json.Unmarshal(buf.Bytes(), er) 61 | if e != nil { 62 | return fmt.Errorf("status=%s error=%q", httpResponse.Status, e.Error()) 63 | } 64 | return fmt.Errorf("status=%s type=%q error=%q", httpResponse.Status, er.Type, er.Message) 65 | } 66 | return nil 67 | } 68 | 69 | type ApiError struct { 70 | Type string `json:"__type"` 71 | Message string `json:"message"` 72 | } 73 | -------------------------------------------------------------------------------- /aws/dynamodb/put_item.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "github.com/dynport/gocloud/aws" 4 | 5 | type PutItem struct { 6 | TableName string `json:"TableName,omitempty"` 7 | Expected map[string]*Expected `json:"Expected,omitempty"` 8 | Item Item `json:"Item,omitempty"` 9 | ReturnValues string `json:"ReturnValues,omitempty"` 10 | ReturnConsumedCapacity string `json:"ReturnConsumedCapacity,omitempty"` 11 | ReturnItemCollectionMetrics string `json:"ReturnItemCollectionMetrics,omitempty"` 12 | } 13 | 14 | type Expected struct { 15 | Exists string `json:"Exists,omitempty"` 16 | Value *ItemDefinition `json:"Value,omitempty"` 17 | } 18 | 19 | func (p *PutItem) Execute(client *aws.Client) (*PutItemResponse, error) { 20 | rsp := &PutItemResponse{} 21 | e := loadAction(client, "PutItem", p, rsp) 22 | return rsp, e 23 | } 24 | 25 | type PutItemResponse struct{} 26 | 27 | type Item map[string]*ItemDefinition 28 | 29 | type ItemDefinition struct { 30 | S string `json:"S,omitempty"` 31 | SS []string `json:"SS,omitempty"` 32 | B string `json:"B,omitempty"` 33 | BS []string `json:"BS,omitempty"` 34 | N string `json:"N,omitempty"` 35 | NS []string `json:"NS,omitempty"` 36 | } 37 | -------------------------------------------------------------------------------- /aws/dynamodb/scan.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "github.com/dynport/gocloud/aws" 4 | 5 | type Scan struct { 6 | TableName string `json:"TableName,omitempty"` 7 | TotalSegments string `json:"TotalSegments,omitempty"` 8 | AttributesToGet []string `json:"AttributesToGet,omitempty"` 9 | ExclusiveStartKey Item `json:"ExclusiveStartKey,omitempty"` 10 | Limit int `json:"Limit,omitempty"` 11 | ReturnConsumedCapacity string `json:"ReturnConsumedCapacity,omitempty"` // INDEXES, TOTAL or NONE 12 | ScanFilter map[string]*ScanFilter `json:"ScanFilter,omitempty"` 13 | Segment string `json:"Segment,omitempty"` 14 | Select string `json:"Select,omitempty"` 15 | } 16 | 17 | func (s *Scan) Execute(client *aws.Client) (*ScanResponse, error) { 18 | rsp := &ScanResponse{} 19 | e := loadAction(client, "Scan", s, rsp) 20 | return rsp, e 21 | } 22 | 23 | type ScanResponse struct { 24 | ConsumedCapacity *ConsumedCapacity 25 | Count int `json:"Count,omitempty"` 26 | ScannedCount int `json:"ScannedCount,omitempty"` 27 | Items []Item `json:"Items,omitempty"` 28 | LastEvaluatedKey Item `json:"tEvaluatedKey,omitempty"` 29 | } 30 | 31 | type ScanFilter struct { 32 | AttributeValueList []*ItemDefinition `json:"AttributeValueList,omitempty"` 33 | ComparisonOperator string `json:"parisonOperator,omitempty"` 34 | } 35 | -------------------------------------------------------------------------------- /aws/dynamodb/update_item.go: -------------------------------------------------------------------------------- 1 | package dynamodb 2 | 3 | import "reflect" 4 | 5 | type UpdateItem struct { 6 | AttributeUpdates map[string]*AttributeUpdate `json:"AttributeUpdate"` 7 | Expected *Expected `json:"Expected"` 8 | Key *Item `json:"Key"` 9 | ReturnConsumedCapacity string `json:"ReturnConsumedCapacity"` // "string", 10 | ReturnItemCollectionMetrics string `json:"ReturnItemCollectionMetrics"` // "string", 11 | ReturnValues string `json:"ReturnValues"` // "string", 12 | TableName string `json:"TableName"` // "string" 13 | } 14 | 15 | type AttributeUpdate struct { 16 | Action string `json:"Action"` 17 | Value *ItemDefinition `json:"ItemDefinition,omitempty"` 18 | } 19 | 20 | func init() { 21 | ad := &AttributeDefinition{} 22 | reflect.ValueOf(ad).FieldByName("Action").Set(reflect.ValueOf("PUT")) 23 | } 24 | -------------------------------------------------------------------------------- /aws/ec2/Makefile: -------------------------------------------------------------------------------- 1 | default: test install 2 | 3 | install: 4 | go install github.com/dynport/gocloud/aws/ec2 5 | 6 | test: 7 | go test -v 8 | -------------------------------------------------------------------------------- /aws/ec2/addresses.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | type Address struct { 8 | PublicIp string `xml:"publicIp"` // 203.0.113.41 9 | AllocationId string `xml:"allocationId"` // eipalloc-08229861 10 | Domain string `xml:"domain"` // vpc 11 | InstanceId string `xml:"instanceId"` // i-64600030 12 | AssociationId string `xml:"associationId"` // eipassoc-f0229899 13 | NetworkInterfaceId string `xml:"networkInterfaceId"` // eni-ef229886 14 | NetworkInterfaceOwnerId string `xml:"networkInterfaceOwnerId"` // 053230519467 15 | PrivateIpAddress string `xml:"privateIpAddress"` // 10.0.0.228 16 | } 17 | 18 | type DescribeAddressesResponse struct { 19 | Addresses []*Address `xml:"addressesSet>item"` 20 | } 21 | 22 | func (client *Client) DescribeAddresses() (addresses []*Address, e error) { 23 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), queryForAction("DescribeAddresses"), nil) 24 | if e != nil { 25 | return addresses, e 26 | } 27 | rsp := &DescribeAddressesResponse{} 28 | e = xml.Unmarshal(raw.Content, rsp) 29 | if e != nil { 30 | return addresses, e 31 | } 32 | return rsp.Addresses, nil 33 | } 34 | -------------------------------------------------------------------------------- /aws/ec2/client_test.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestRunInstancesConfig(t *testing.T) { 10 | Convey("Values", t, func() { 11 | So(1, ShouldEqual, 1) 12 | c := &RunInstancesConfig{ImageId: "image-id", SubnetId: "subnet"} 13 | c.BlockDeviceMappings = []*BlockDeviceMapping{ 14 | { 15 | DeviceName: "/dev/something", 16 | Ebs: &Ebs{ 17 | VolumeSize: 50, 18 | VolumeType: VolumeTypeGp, 19 | }, 20 | }, 21 | } 22 | c.AddPublicIp() 23 | v, e := c.Values() 24 | So(e, ShouldBeNil) 25 | So(v, ShouldNotBeNil) 26 | So(v.Get("BlockDeviceMapping.0.DeviceName"), ShouldEqual, "/dev/something") 27 | So(v.Get("BlockDeviceMapping.0.Ebs.VolumeSize"), ShouldEqual, "50") 28 | So(v.Get("BlockDeviceMapping.0.Ebs.VolumeType"), ShouldEqual, "gp2") 29 | So(len(v), ShouldEqual, 9) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /aws/ec2/deregister_image.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type DeregisterImage struct { 10 | ImageId string 11 | } 12 | 13 | func (d *DeregisterImage) Execute(client *Client) error { 14 | if d.ImageId == "" { 15 | return fmt.Errorf("ImageId must be set") 16 | } 17 | values := url.Values{ 18 | "Version": {API_VERSIONS_EC2}, 19 | "Action": {"DeregisterImage"}, 20 | "ImageId": {d.ImageId}, 21 | } 22 | rsp, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 23 | if e != nil { 24 | return e 25 | } 26 | status := strconv.Itoa(rsp.StatusCode) 27 | if status[0] != '2' { 28 | return fmt.Errorf("expected status 2xx, got %s", status) 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /aws/ec2/describe_images.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type DescribeImages struct { 10 | ExecutableBys []string 11 | ImageIds []string 12 | Owners []string 13 | Filters []*Filter 14 | } 15 | 16 | func (action *DescribeImages) Execute(client *Client) (*DescribeImagesResponse, error) { 17 | values := url.Values{ 18 | "Version": {API_VERSIONS_EC2}, 19 | "Action": {"DescribeImages"}, 20 | } 21 | 22 | for i, v := range action.Owners { 23 | values.Set("Owner."+strconv.Itoa(i+1), v) 24 | } 25 | for i, v := range action.ImageIds { 26 | values.Set("ImageId."+strconv.Itoa(i+1), v) 27 | } 28 | for i, filter := range action.Filters { 29 | values.Set("Filter."+strconv.Itoa(i+1)+".Name", filter.Name) 30 | for j, value := range filter.Values { 31 | values.Set("Filter."+strconv.Itoa(i+1)+"."+strconv.Itoa(j+1), value) 32 | } 33 | } 34 | rsp, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 35 | if e != nil { 36 | return nil, e 37 | } 38 | dvr := &DescribeImagesResponse{} 39 | e = xml.Unmarshal(rsp.Content, dvr) 40 | return dvr, e 41 | } 42 | -------------------------------------------------------------------------------- /aws/ec2/describe_instances.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type DescribeInstances struct { 10 | InstanceIds []string 11 | Filters []*Filter 12 | } 13 | 14 | func (action *DescribeInstances) Execute(client *Client) (*DescribeInstancesResponse, error) { 15 | values := url.Values{"Version": {API_VERSIONS_EC2}, "Action": {"DescribeInstances"}} 16 | if len(action.InstanceIds) > 0 { 17 | for i, id := range action.InstanceIds { 18 | values.Add("InstanceId."+strconv.Itoa(i+1), id) 19 | } 20 | } 21 | applyFilters(values, action.Filters) 22 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 23 | if e != nil { 24 | return nil, e 25 | } 26 | rsp := &DescribeInstancesResponse{} 27 | e = xml.Unmarshal(raw.Content, rsp) 28 | return rsp, e 29 | } 30 | -------------------------------------------------------------------------------- /aws/ec2/describe_subnets.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "net/url" 7 | ) 8 | 9 | type DescribeSubnetsResponse struct { 10 | XMLName xml.Name `xml:"DescribeSubnetsResponse"` 11 | Subnets []*Subnet `xml:"subnetSet>item"` 12 | } 13 | 14 | type DescribeSubnetsParameters struct { 15 | Filters []*Filter 16 | } 17 | 18 | func (client *Client) DescribeSubnets(params *DescribeSubnetsParameters) (*DescribeSubnetsResponse, error) { 19 | query := url.Values{ 20 | "Action": {"DescribeSubnets"}, 21 | "Version": {API_VERSIONS_EC2}, 22 | } 23 | applyFilters(query, params.Filters) 24 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), query.Encode(), nil) 25 | if e != nil { 26 | return nil, e 27 | } 28 | rsp := &DescribeSubnetsResponse{} 29 | e = xml.Unmarshal(raw.Content, rsp) 30 | if e != nil { 31 | return nil, fmt.Errorf("%s (%s)", e.Error(), string(raw.Content)) 32 | } 33 | return rsp, e 34 | } 35 | -------------------------------------------------------------------------------- /aws/ec2/describe_volumes.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "net/url" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type DescribeVolumes struct { 11 | VolumeIds []string 12 | Filters []*Filter 13 | } 14 | 15 | type DescribeVolumesResponse struct { 16 | XMLName xml.Name `xml:"DescribeVolumesResponse"` 17 | Volumes []*Volume `xml:"volumeSet>item"` 18 | } 19 | 20 | type Volume struct { 21 | VolumeId string `xml:"volumeId"` // vol-1a2b3c4d 22 | Size string `xml:"size"` // 80 23 | SnapshotId string `xml:"snapshotId/"` // 24 | AvailabilityZone string `xml:"availabilityZone"` // us-east-1a 25 | Status string `xml:"status"` // in-use 26 | CreateTime time.Time `xml:"createTime"` // YYYY-MM-DDTHH:MM:SS.SSSZ 27 | 28 | Attachments []*Attachment `xml:"attachmentSet>item"` 29 | } 30 | 31 | func (action *DescribeVolumes) Execute(client *Client) (*DescribeVolumesResponse, error) { 32 | values := url.Values{ 33 | "Version": {API_VERSIONS_EC2}, 34 | "Action": {"DescribeVolumes"}, 35 | } 36 | for i, v := range action.VolumeIds { 37 | values.Set("VolumeId."+strconv.Itoa(i+1), v) 38 | } 39 | rsp, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 40 | if e != nil { 41 | return nil, e 42 | } 43 | dvr := &DescribeVolumesResponse{} 44 | e = xml.Unmarshal(rsp.Content, dvr) 45 | return dvr, e 46 | } 47 | 48 | type Attachment struct { 49 | VolumeId string `xml:"volumeId"` // vol-1a2b3c4d 50 | InstanceId string `xml:"instanceId"` // i-1a2b3c4d 51 | Device string `xml:"device"` // /dev/sdh 52 | Status string `xml:"status"` // attached 53 | AttachTime string `xml:"attachTime"` // YYYY-MM-DDTHH:MM:SS.SSSZ 54 | DeleteOnTermination string `xml:"deleteOnTermination"` // false 55 | } 56 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/create_image_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | ami-4fa54026 4 | 5 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_addresses.xml: -------------------------------------------------------------------------------- 1 | 2 | f7de5e98-491a-4c19-a92d-908d6EXAMPLE 3 | 4 | 5 | 203.0.113.41 6 | eipalloc-08229861 7 | vpc 8 | i-64600030 9 | eipassoc-f0229899 10 | eni-ef229886 11 | 053230519467 12 | 10.0.0.228 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_images.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | 4 | 5 | ami-1a2b3c4d 6 | amazon/getting-started 7 | available 8 | 111122223333 9 | true 10 | i386 11 | machine 12 | aki-1a2b3c4d 13 | ari-1a2b3c4d 14 | amazon 15 | getting-started 16 | Image Description 17 | ebs 18 | /dev/sda 19 | 20 | 21 | /dev/sda1 22 | 23 | snap-1a2b3c4d 24 | 15 25 | false 26 | standard 27 | 28 | 29 | 30 | paravirtual 31 | 32 | xen 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_key_pairs.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | 4 | 5 | my-key-pair 6 | 1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_security_groups.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | 4 | 5 | 111122223333 6 | sg-1a2b3c4d 7 | WebServers 8 | Web Servers 9 | 10 | 11 | 12 | tcp 13 | 80 14 | 80 15 | 16 | 17 | 18 | 0.0.0.0/0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 111122223333 27 | sg-2a2b3c4d 28 | RangedPortsBySource 29 | Group A 30 | 31 | 32 | tcp 33 | 6000 34 | 7000 35 | 36 | 37 | 111122223333 38 | sg-3a2b3c4d 39 | Group B 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_spot_price_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | 4 | 5 | m1.small 6 | Linux/UNIX 7 | 0.287 8 | 2009-12-04T20:56:05.000Z 9 | us-east-1a 10 | 11 | 12 | m1.small 13 | Windows 14 | 0.033 15 | 2009-12-04T22:33:47.000Z 16 | us-east-1a 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/describe_tags.xml: -------------------------------------------------------------------------------- 1 | 2 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 3 | 4 | 5 | ami-1a2b3c4d 6 | image 7 | webserver 8 | 9 | 10 | 11 | ami-1a2b3c4d 12 | image 13 | stack 14 | Production 15 | 16 | 17 | i-5f4e3d2a 18 | instance 19 | webserver 20 | 21 | 22 | 23 | i-5f4e3d2a 24 | instance 25 | stack 26 | Production 27 | 28 | 29 | i-12345678 30 | instance 31 | database_server 32 | 33 | 34 | 35 | i-12345678 36 | instance 37 | stack 38 | Test 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /aws/ec2/fixtures/terminate_instances_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 59dbff89-35bd-4eac-99ed-be587EXAMPLE 3 | 4 | 5 | i-3ea74257 6 | 7 | 32 8 | shutting-down 9 | 10 | 11 | 16 12 | running 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /aws/ec2/images.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | ) 9 | 10 | func (client *Client) DescribeImages() (images []*Image, e error) { 11 | return client.DescribeImagesWithFilter(&ImageFilter{}) 12 | } 13 | 14 | func (client *Client) DescribeImagesWithFilter(filter *ImageFilter) (images ImageList, e error) { 15 | values := url.Values{ 16 | "Version": {API_VERSIONS_EC2}, 17 | "Action": {"DescribeImages"}, 18 | } 19 | if filter.Owner != "" { 20 | values.Add("Owner.1", filter.Owner) 21 | } 22 | if filter.Name != "" { 23 | values.Add("Filter.1.Name", "name") 24 | values.Add("Filter.1.Value.0", filter.Name) 25 | } 26 | for i, id := range filter.ImageIds { 27 | values.Add("ImageId."+strconv.Itoa(i+1), id) 28 | } 29 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 30 | if e != nil { 31 | return 32 | } 33 | rsp := &DescribeImagesResponse{} 34 | e = xml.Unmarshal(raw.Content, rsp) 35 | if e != nil { 36 | return images, e 37 | } 38 | return rsp.Images, nil 39 | } 40 | 41 | type CreateImageOptions struct { 42 | InstanceId string // required 43 | Name string // required 44 | Description string 45 | NoReboot bool 46 | } 47 | 48 | type CreateImageResponse struct { 49 | XMLName xml.Name `xml:"CreateImageResponse"` 50 | RequestId string `xml:"requestId"` 51 | ImageId string `xml:"imageId"` 52 | } 53 | 54 | func (client *Client) CreateImage(opts *CreateImageOptions) (rsp *CreateImageResponse, e error) { 55 | if opts.InstanceId == "" { 56 | return nil, fmt.Errorf("InstanceId must be provided") 57 | } 58 | if opts.Name == "" { 59 | return nil, fmt.Errorf("InstanceId must be provided") 60 | } 61 | values := &url.Values{} 62 | values.Add("Version", API_VERSIONS_EC2) 63 | values.Add("Action", "CreateImage") 64 | values.Add("Name", opts.Name) 65 | values.Add("InstanceId", opts.InstanceId) 66 | if opts.Description != "" { 67 | values.Add("Description", opts.Description) 68 | } 69 | if opts.NoReboot { 70 | values.Add("NoReboot", "true") 71 | } 72 | 73 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), values.Encode(), nil) 74 | if e != nil { 75 | return nil, e 76 | } 77 | rsp = &CreateImageResponse{} 78 | e = xml.Unmarshal(raw.Content, rsp) 79 | return rsp, e 80 | } 81 | -------------------------------------------------------------------------------- /aws/ec2/network_interface.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | type CreateNetworkInterface struct { 4 | DeviceIndex int `json:",omitempty"` 5 | AssociatePublicIpAddress bool `json:",omitempty"` 6 | SubnetId string `json:",omitempty"` 7 | SecurityGroupIds []string `json:",omitempty"` 8 | } 9 | -------------------------------------------------------------------------------- /aws/ec2/security_groups.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type DescribeSecurityGroupsResponse struct { 10 | SecurityGroups []*SecurityGroup `xml:"securityGroupInfo>item"` 11 | } 12 | 13 | type IpPermission struct { 14 | IpProtocol string `xml:"ipProtocol,omitempty"` // tcp 15 | FromPort int `xml:"fromPort,omitempty"` // 80 16 | ToPort int `xml:"toPort,omitempty"` // 80 17 | Groups []*SecurityGroup `xml:"groups>item,omitempty"` // 18 | IpRanges []string `xml:"ipRanges>item>cidrIp"` 19 | } 20 | 21 | type SecurityGroup struct { 22 | OwnerId string `xml:"ownerId,omitempty"` // 111122223333 23 | GroupId string `xml:"groupId,omitempty"` // sg-1a2b3c4d 24 | GroupName string `xml:"groupName,omitempty"` // WebServers 25 | GroupDescription string `xml:"groupDescription,omitempty"` // Web Servers 26 | VpcId string `xml:"vpcId,omitempty"` // 27 | IpPermissions []*IpPermission `xml:"ipPermissions>item"` 28 | } 29 | 30 | type DescribeSecurityGroupsParameters struct { 31 | GroupNames []string 32 | GroupIds []string 33 | Filters []*Filter 34 | } 35 | 36 | func (d *DescribeSecurityGroupsParameters) query() string { 37 | v := url.Values{} 38 | for i, g := range d.GroupIds { 39 | v.Add("GroupId."+strconv.Itoa(i+1), g) 40 | } 41 | for i, g := range d.GroupNames { 42 | v.Add("GroupName."+strconv.Itoa(i+1), g) 43 | } 44 | if len(v) > 0 { 45 | return v.Encode() 46 | } 47 | return "" 48 | } 49 | 50 | func (client *Client) DescribeSecurityGroups(params *DescribeSecurityGroupsParameters) (groups []*SecurityGroup, e error) { 51 | if params == nil { 52 | params = &DescribeSecurityGroupsParameters{} 53 | } 54 | q := queryForAction("DescribeSecurityGroups") 55 | if search := params.query(); search != "" { 56 | q += "&" + search 57 | } 58 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), q, nil) 59 | if e != nil { 60 | return groups, e 61 | } 62 | rsp := &DescribeSecurityGroupsResponse{} 63 | e = xml.Unmarshal(raw.Content, rsp) 64 | if e != nil { 65 | return groups, e 66 | } 67 | return rsp.SecurityGroups, nil 68 | } 69 | -------------------------------------------------------------------------------- /aws/ec2/spot_instances.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "encoding/xml" 5 | "log" 6 | "net/url" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | type SpotPrice struct { 12 | InstanceType string `xml:"instanceType"` // m1.small 13 | ProductDescription string `xml:"productDescription"` // Linux/UNIX 14 | SpotPrice float64 `xml:"spotPrice"` // 0.287 15 | Timestamp time.Time `xml:"timestamp"` // 2009-12-04T20:56:05.000Z 16 | AvailabilityZone string `xml:"availabilityZone"` // us-east-1a 17 | } 18 | 19 | type DescribeSpotPriceHistoryResponse struct { 20 | SpotPrices []*SpotPrice `xml:"spotPriceHistorySet>item"` 21 | } 22 | 23 | const TIME_FORMAT = "2006-01-02T15:04:05.999Z" 24 | 25 | type SpotPriceFilter struct { 26 | InstanceTypes []string 27 | AvailabilityZones []string 28 | ProductDescriptions []string 29 | StartTime time.Time 30 | EndTime time.Time 31 | } 32 | 33 | const DESC_LINUX_UNIX = "Linux/UNIX" 34 | 35 | func (client *Client) DescribeSpotPriceHistory(filter *SpotPriceFilter) (prices []*SpotPrice, e error) { 36 | query := queryForAction("DescribeSpotPriceHistory") 37 | if filter == nil { 38 | filter = &SpotPriceFilter{} 39 | } 40 | values := url.Values{} 41 | for i, instanceType := range filter.InstanceTypes { 42 | values.Add("InstanceType."+strconv.Itoa(i+1), instanceType) 43 | } 44 | for i, desc := range filter.ProductDescriptions { 45 | values.Add("ProductDescription."+strconv.Itoa(i+1), desc) 46 | } 47 | 48 | if !filter.StartTime.IsZero() { 49 | values.Add("StartTime", filter.StartTime.Format(TIME_FORMAT)) 50 | } 51 | if !filter.EndTime.IsZero() { 52 | values.Add("EndTime", filter.EndTime.Format(TIME_FORMAT)) 53 | } 54 | query += "&" + values.Encode() 55 | log.Println(query) 56 | raw, e := client.DoSignedRequest("GET", client.Endpoint(), query, nil) 57 | if e != nil { 58 | return prices, e 59 | } 60 | rsp := &DescribeSpotPriceHistoryResponse{} 61 | e = xml.Unmarshal(raw.Content, rsp) 62 | if e != nil { 63 | return prices, e 64 | } 65 | return rsp.SpotPrices, nil 66 | } 67 | -------------------------------------------------------------------------------- /aws/ec2/subnets.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type Subnet struct { 9 | SubnetId string `xml:"subnetId"` 10 | State string `xml:"state"` 11 | VpcId string `xml:"vpcId"` 12 | CidrBlock string `xml:"cidrBlock"` 13 | AvailableIpAddressCount int `xml:"availableIpAddressCount"` 14 | AvailabilityZone string `xml:"availabilityZone"` 15 | DefaultForAz bool `xml:"defaultForAz"` 16 | MapPublicIpOnLaunch bool `xml:"mapPublicIpOnLaunch"` 17 | } 18 | 19 | type Filter struct { 20 | Name string 21 | Values []string 22 | } 23 | 24 | func applyFilters(values url.Values, filters []*Filter) { 25 | for n, filter := range filters { 26 | key := fmt.Sprintf("Filter.%d.Name", n+1) 27 | for m, value := range filter.Values { 28 | valueKey := fmt.Sprintf("Filter.%d.Value.%d", n+1, m+1) 29 | values.Add(key, filter.Name) 30 | values.Add(valueKey, value) 31 | } 32 | } 33 | } 34 | 35 | type DescribeSubnetsOptions struct { 36 | SubnetIds []string 37 | Filters []*Filter 38 | } 39 | -------------------------------------------------------------------------------- /aws/ec2/tags.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | type DescribeTagsResponse struct { 4 | Tags []*Tag `xml:"tagSet>item"` 5 | } 6 | -------------------------------------------------------------------------------- /aws/ec2/utils.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | func IpForSsh(instance *Instance) (openIp string, e error) { 11 | cnt := 0 12 | type status struct { 13 | ip string 14 | ok bool 15 | } 16 | c := make(chan *status) 17 | for _, i := range []string{instance.PrivateIpAddress, instance.IpAddress} { 18 | cnt++ 19 | go func(ip string, ch chan *status) { 20 | rsp := &status{ip: ip} 21 | defer func() { c <- rsp }() 22 | c, e := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, 22), 100*time.Millisecond) 23 | if e != nil { 24 | return 25 | } 26 | defer c.Close() 27 | rsp.ok = true 28 | }(i, c) 29 | 30 | } 31 | 32 | timeout := time.After(2 * time.Second) 33 | for cnt > 0 { 34 | select { 35 | case s := <-c: 36 | cnt-- 37 | if s.ok { 38 | return s.ip, nil 39 | } 40 | case <-timeout: 41 | return "", fmt.Errorf("timed out") 42 | } 43 | } 44 | return "", nil 45 | } 46 | 47 | func waitForSsh(instance *Instance, timeoutDuration time.Duration) (string, error) { 48 | ticker := time.Tick(1 * time.Second) 49 | timeout := time.After(timeoutDuration) 50 | for { 51 | select { 52 | case <-ticker: 53 | ip, e := IpForSsh(instance) 54 | if e == nil { 55 | return ip, nil 56 | } 57 | case <-timeout: 58 | return "", fmt.Errorf("timeout waiting for ssh") 59 | } 60 | } 61 | } 62 | 63 | func WaitForInstances(instances []*Instance, timeout time.Duration) error { 64 | cnt := 0 65 | type result struct { 66 | Error error 67 | Ip string 68 | } 69 | finished := make(chan *result) 70 | for _, i := range instances { 71 | cnt++ 72 | go func(inst *Instance) { 73 | ip, e := waitForSsh(inst, timeout) 74 | finished <- &result{Error: e, Ip: ip} 75 | }(i) 76 | } 77 | 78 | errors := []string{} 79 | for cnt > 0 { 80 | select { 81 | case r := <-finished: 82 | cnt-- 83 | if r.Error != nil { 84 | errors = append(errors, r.Ip+": "+r.Error.Error()) 85 | } 86 | } 87 | } 88 | if len(errors) > 0 { 89 | return fmt.Errorf(strings.Join(errors, ", ")) 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /aws/elasticache/dbg.go: -------------------------------------------------------------------------------- 1 | package elasticache 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[DEBUG] ", 0) 18 | -------------------------------------------------------------------------------- /aws/elb/elb_test.go: -------------------------------------------------------------------------------- 1 | package elb 2 | 3 | import ( 4 | "encoding/xml" 5 | "io/ioutil" 6 | "testing" 7 | 8 | . "github.com/smartystreets/goconvey/convey" 9 | ) 10 | 11 | func mustReadFixture(t *testing.T, name string) []byte { 12 | b, e := ioutil.ReadFile("fixtures/" + name) 13 | if e != nil { 14 | t.Fatal("fixture " + name + " does not exist") 15 | } 16 | return b 17 | } 18 | 19 | func TestElb(t *testing.T) { 20 | Convey("Elb", t, func() { 21 | Convey("Marshalling", func() { 22 | f := mustReadFixture(t, "describe_load_balancers.xml") 23 | rsp := &DescribeLoadBalancersResponse{} 24 | e := xml.Unmarshal(f, rsp) 25 | So(e, ShouldBeNil) 26 | lbs := rsp.LoadBalancers 27 | So(len(lbs), ShouldEqual, 1) 28 | lb := lbs[0] 29 | So(lb.LoadBalancerName, ShouldEqual, "MyLoadBalancer") 30 | So(lb.CreatedTime.Unix(), ShouldEqual, 1369430131) 31 | So(lb.CanonicalHostedZoneName, ShouldEqual, "MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com") 32 | So(len(lb.AvailabilityZones), ShouldEqual, 1) 33 | So(lb.AvailabilityZones[0], ShouldEqual, "us-east-1a") 34 | So(len(lb.Subnets), ShouldEqual, 0) 35 | So(lb.HealthCheckTarget, ShouldEqual, "HTTP:80/") 36 | So(lb.HealthCheckInterval, ShouldEqual, 90) 37 | So(len(lb.Listeners), ShouldEqual, 1) 38 | So(lb.SourceSecurityGroupOwnerAlias, ShouldEqual, "amazon-elb") 39 | listener := lb.Listeners[0] 40 | So(listener.Protocol, ShouldEqual, "HTTP") 41 | 42 | So(len(lb.Instances), ShouldEqual, 1) 43 | So(lb.Instances[0], ShouldEqual, "i-e4cbe38d") 44 | 45 | }) 46 | 47 | Convey("MarshalInstanceHealth", func() { 48 | f := mustReadFixture(t, "describe_instances_health.xml") 49 | rsp := &DescribeInstanceHealthResponse{} 50 | e := xml.Unmarshal(f, rsp) 51 | So(e, ShouldBeNil) 52 | So(rsp, ShouldNotBeNil) 53 | So(len(rsp.InstanceStates), ShouldEqual, 1) 54 | 55 | state := rsp.InstanceStates[0] 56 | So(state.Description, ShouldEqual, "Instance registration is still in progress.") 57 | So(state.InstanceId, ShouldEqual, "i-315b7e51") 58 | 59 | }) 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /aws/elb/fixtures/describe_instances_health.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Instance registration is still in progress. 6 | i-315b7e51 7 | OutOfService 8 | ELB 9 | 10 | 11 | 12 | 13 | 1549581b-12b7-11e3-895e-1334aEXAMPLE 14 | 15 | 16 | -------------------------------------------------------------------------------- /aws/elb/fixtures/describe_load_balancers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MyLoadBalancer 7 | 2013-05-24T21:15:31.280Z 8 | 9 | 90 10 | HTTP:80/ 11 | 2 12 | 60 13 | 10 14 | 15 | 16 | 17 | 18 | 19 | HTTP 20 | 80 21 | HTTP 22 | 80 23 | 24 | 25 | 26 | 27 | 28 | i-e4cbe38d 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | us-east-1a 38 | 39 | ZZZZZZZZZZZ123X 40 | MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com 41 | internet-facing 42 | 43 | amazon-elb 44 | amazon-elb-sg 45 | 46 | MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com 47 | 48 | 49 | 50 | 51 | 52 | 53 | 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE 54 | 55 | 56 | -------------------------------------------------------------------------------- /aws/elb/fixtures/register_instances_with_load_balancer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | i-315b7e51 6 | 7 | 8 | 9 | 10 | 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE 11 | 12 | 13 | -------------------------------------------------------------------------------- /aws/generic.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "reflect" 7 | ) 8 | 9 | // beging marker types 10 | type Version struct { 11 | } 12 | 13 | type Action struct { 14 | } 15 | 16 | // end marker types 17 | 18 | func ParamsForAction(i interface{}) (url.Values, error) { 19 | v := reflect.ValueOf(i) 20 | if v.Kind() == reflect.Ptr { 21 | v = v.Elem() 22 | } 23 | t := v.Type() 24 | values := url.Values{} 25 | for i := 0; i < v.NumField(); i++ { 26 | fieldType := t.Field(i) 27 | awsTag := fieldType.Tag.Get("aws") 28 | if awsTag != "" { 29 | name := awsTag 30 | switch casted := v.Field(i).Interface().(type) { 31 | case int: 32 | if casted > 0 { 33 | values.Add(name, fmt.Sprintf("%d", casted)) 34 | } 35 | case string: 36 | if casted != "" { 37 | values.Add(name, casted) 38 | } 39 | case Version: 40 | values.Add("Version", awsTag) 41 | case Action: 42 | values.Add("Action", awsTag) 43 | default: 44 | return nil, fmt.Errorf("unable to handle %s (%T)", casted, casted) 45 | } 46 | } 47 | } 48 | return values, nil 49 | } 50 | -------------------------------------------------------------------------------- /aws/generic_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dynport/dgtk/expect" 7 | ) 8 | 9 | type dummyAction struct { 10 | Version Version `aws:"2011-06-15"` 11 | Action Action `aws:"GetSessionToken"` 12 | DurationSeconds int `aws:"DurationSeconds"` 13 | SerialNumber string `aws:"SerialNumber"` 14 | TokenCode string `aws:"TokenCode"` 15 | } 16 | 17 | func TestUrlForAction(t *testing.T) { 18 | expect := expect.New(t) 19 | pr := &dummyAction{DurationSeconds: 3601} 20 | expect(pr).ToNotBeNil() 21 | 22 | params, err := ParamsForAction(pr) 23 | expect(err).ToBeNil() 24 | expect(params).ToNotBeNil() 25 | expect(params["DurationSeconds"]).ToContain("3601") 26 | expect(params["Version"]).ToContain("2011-06-15") 27 | expect(params["Action"]).ToContain("GetSessionToken") 28 | 29 | } 30 | -------------------------------------------------------------------------------- /aws/iam/fixtures/get_account_summary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Groups 6 | 31 7 | 8 | 9 | GroupsQuota 10 | 50 11 | 12 | 13 | UsersQuota 14 | 150 15 | 16 | 17 | Users 18 | 35 19 | 20 | 21 | GroupPolicySizeQuota 22 | 10240 23 | 24 | 25 | AccessKeysPerUserQuota 26 | 2 27 | 28 | 29 | GroupsPerUserQuota 30 | 10 31 | 32 | 33 | UserPolicySizeQuota 34 | 10240 35 | 36 | 37 | SigningCertificatesPerUserQuota 38 | 2 39 | 40 | 41 | ServerCertificates 42 | 0 43 | 44 | 45 | ServerCertificatesQuota 46 | 10 47 | 48 | 49 | AccountMFAEnabled 50 | 0 51 | 52 | 53 | MFADevicesInUse 54 | 10 55 | 56 | 57 | MFADevices 58 | 20 59 | 60 | 61 | 62 | 63 | f1e38443-f1ad-11df-b1ef-a9265EXAMPLE 64 | 65 | 66 | -------------------------------------------------------------------------------- /aws/iam/fixtures/get_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /division_abc/subdivision_xyz/ 5 | Bob 6 | AIDACKCEVSQ6C2EXAMPLE 7 | 8 | arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob 9 | 10 | 11 | 12 | 13 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 14 | 15 | 16 | -------------------------------------------------------------------------------- /aws/iam/fixtures/list_account_aliases.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | foocorporation 6 | 7 | 8 | 9 | c5a076e9-f1b0-11df-8fbe-45274EXAMPLE 10 | 11 | 12 | -------------------------------------------------------------------------------- /aws/iam/fixtures/list_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /division_abc/subdivision_xyz/engineering/ 6 | Andrew 7 | AID2MAB8DPLSRHEXAMPLE 8 | arn:aws:iam::123456789012:user 9 | /division_abc/subdivision_xyz/engineering/Andrew 10 | 11 | 12 | /division_abc/subdivision_xyz/engineering/ 13 | Jackie 14 | AIDIODR4TAW7CSEXAMPLE 15 | arn:aws:iam::123456789012:user 16 | /division_abc/subdivision_xyz/engineering/Jackie 17 | 18 | 19 | false 20 | 21 | 22 | 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE 23 | 24 | 25 | -------------------------------------------------------------------------------- /aws/iam/iam_test.go: -------------------------------------------------------------------------------- 1 | package iam 2 | 3 | import ( 4 | "encoding/xml" 5 | "io/ioutil" 6 | "strings" 7 | "testing" 8 | 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | func mustReadFixture(t *testing.T, name string) []byte { 13 | b, e := ioutil.ReadFile("fixtures/" + name) 14 | if e != nil { 15 | t.Fatal("fixture " + name + " does not exist") 16 | } 17 | return b 18 | } 19 | 20 | func TestIam(t *testing.T) { 21 | Convey("IAM", t, func() { 22 | Convey("GetUser", func() { 23 | f := mustReadFixture(t, "get_user.xml") 24 | rsp := &GetUserResponse{} 25 | e := xml.Unmarshal(f, rsp) 26 | So(e, ShouldBeNil) 27 | So(f, ShouldNotBeNil) 28 | user := rsp.User 29 | So(user.Path, ShouldEqual, "/division_abc/subdivision_xyz/") 30 | So(user.UserName, ShouldEqual, "Bob") 31 | So(strings.TrimSpace(user.Arn), ShouldEqual, "arn:aws:iam::123456789012:user/division_abc/subdivision_xyz/Bob") 32 | }) 33 | 34 | Convey("AccountSummary", func() { 35 | f := mustReadFixture(t, "get_account_summary.xml") 36 | rsp := &GetAccountSummaryResponse{} 37 | e := xml.Unmarshal(f, rsp) 38 | So(e, ShouldBeNil) 39 | So(f, ShouldNotBeNil) 40 | m := rsp.SummaryMap 41 | So(len(m.Entries), ShouldEqual, 14) 42 | 43 | entry := m.Entries[0] 44 | So(entry.Key, ShouldEqual, "Groups") 45 | So(entry.Value, ShouldEqual, "31") 46 | 47 | }) 48 | 49 | }) 50 | 51 | Convey("ListUsers", t, func() { 52 | f := mustReadFixture(t, "list_users.xml") 53 | rsp := &ListUsersResponse{} 54 | e := xml.Unmarshal(f, rsp) 55 | So(e, ShouldBeNil) 56 | So(len(rsp.Users), ShouldEqual, 2) 57 | So(rsp.Users[0].UserName, ShouldEqual, "Andrew") 58 | 59 | }) 60 | 61 | Convey("ListAccountAliases", t, func() { 62 | f := mustReadFixture(t, "list_account_aliases.xml") 63 | rsp := &ListAccountAliasesResponse{} 64 | e := xml.Unmarshal(f, rsp) 65 | So(e, ShouldBeNil) 66 | So(len(rsp.AccountAliases), ShouldEqual, 1) 67 | So(rsp.AccountAliases[0], ShouldEqual, "foocorporation") 68 | 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /aws/iam_credentials.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type awsCredentials struct { 14 | Code string `json:"Code,omitempty"` 15 | LastUpdated time.Time `json:"LastUpdated,omitempty"` 16 | Type string `json:"Type,omitempty"` 17 | AccessKeyId string `json:"AccessKeyId,omitempty"` 18 | SecretAccessKey string `json:"SecretAccessKey,omitempty"` 19 | Token string `json:"Token,omitempty"` 20 | Expiration time.Time `json:"Expiration,omitempty"` 21 | } 22 | 23 | func CurrentAwsRegion() (string, error) { 24 | s, e := CurrentAwsAvailabilityZone() 25 | if e != nil { 26 | return "", e 27 | } 28 | if len(s) > 4 { 29 | return s[0 : len(s)-1], nil 30 | } 31 | return "", nil 32 | } 33 | 34 | func CurrentAwsAvailabilityZone() (string, error) { 35 | rsp, e := http.Get("http://169.254.169.254/latest/meta-data/placement/availability-zone") 36 | if e != nil { 37 | return "", e 38 | } 39 | defer rsp.Body.Close() 40 | b, e := ioutil.ReadAll(rsp.Body) 41 | return string(b), e 42 | } 43 | 44 | func loadAwsCredentials(theUrl string) (*awsCredentials, error) { 45 | r := &awsCredentials{} 46 | rsp, e := http.Get(theUrl) 47 | if e != nil { 48 | return nil, e 49 | } 50 | defer rsp.Body.Close() 51 | b, e := ioutil.ReadAll(rsp.Body) 52 | if e != nil { 53 | return nil, e 54 | } 55 | e = json.Unmarshal(b, r) 56 | return r, e 57 | } 58 | 59 | func newFromIam() (*Client, error) { 60 | con, e := net.DialTimeout("tcp", metadataIp+":80", 10*time.Millisecond) 61 | if e != nil { 62 | return nil, e 63 | } 64 | defer con.Close() 65 | 66 | rsp, e := http.Get(securityCredentialsUrl) 67 | if e != nil { 68 | return nil, e 69 | } 70 | defer rsp.Body.Close() 71 | if rsp.Status[0] != '2' { 72 | return nil, fmt.Errorf("expected status 2xx, got %s", rsp.Status) 73 | } 74 | 75 | b, e := ioutil.ReadAll(rsp.Body) 76 | if e != nil { 77 | return nil, e 78 | } 79 | 80 | rolesString := string(b) 81 | if rolesString == "" { 82 | return nil, nil 83 | } 84 | 85 | roles := strings.Fields(string(b)) 86 | if len(roles) > 0 { 87 | role := roles[0] 88 | r, e := loadAwsCredentials(securityCredentialsUrl + role) 89 | if e != nil { 90 | return nil, e 91 | } 92 | client := &Client{Key: r.AccessKeyId, Secret: r.SecretAccessKey, SecurityToken: r.Token} 93 | client.Region, _ = CurrentAwsRegion() 94 | return client, nil 95 | } 96 | return nil, nil 97 | } 98 | -------------------------------------------------------------------------------- /aws/pricing/.gitignore: -------------------------------------------------------------------------------- 1 | compiled_assets.go 2 | -------------------------------------------------------------------------------- /aws/pricing/Makefile: -------------------------------------------------------------------------------- 1 | test: build 2 | go test -v 3 | 4 | build: assets 5 | go get . 6 | 7 | assets: install_goassets 8 | rm -f ./assets.go 9 | goassets assets 10 | 11 | install_goassets: 12 | which goassets || go get github.com/dynport/dgtk/goassets 13 | -------------------------------------------------------------------------------- /aws/pricing/assets/fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | go get github.com/dynport/gocloud/aws/pricing/aws-dump-instance-types 4 | 5 | aws-dump-instance-types > instance_types.json 6 | 7 | base_url=http://aws-assets-pricing-prod.s3.amazonaws.com/pricing/ec2 8 | 9 | for name in linux-ri-heavy linux-ri-medium linux-ri-light linux-od; do 10 | echo "fetching $name" 11 | curl -s $base_url/$name.js | sed 's/^callback.*//' | sed 's/^)//' > ${name}.json 12 | done 13 | -------------------------------------------------------------------------------- /aws/pricing/configs.go: -------------------------------------------------------------------------------- 1 | package pricing 2 | 3 | import "strings" 4 | 5 | type InstanceTypeConfig struct { 6 | Name string 7 | Cpus int 8 | Memory float64 9 | Storage string 10 | NetworkPerformance string 11 | ClockSpeed float64 12 | 13 | Turbo bool 14 | AVX bool 15 | AES bool 16 | PhysicalProcessor string 17 | EbsOptimizable bool 18 | EnhancedNetworking bool 19 | } 20 | 21 | func (c *InstanceTypeConfig) Family() string { 22 | return strings.Split(c.Name, ".")[0] 23 | } 24 | -------------------------------------------------------------------------------- /aws/pricing/main_test.go: -------------------------------------------------------------------------------- 1 | package pricing 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | _ "launchpad.net/xmlpath" 9 | ) 10 | 11 | func mustReadFile(t *testing.T, path string) []byte { 12 | b, e := ioutil.ReadFile(path) 13 | if e != nil { 14 | t.Fatal(e.Error()) 15 | } 16 | return b 17 | } 18 | 19 | func TestLoadPricing(t *testing.T) { 20 | Convey("loadPricesFor", t, func() { 21 | prices, e := loadPricesFor("linux-od.json") 22 | if e != nil { 23 | t.Fatal(e) 24 | } 25 | regions := prices.RegionNames() 26 | So(len(regions), ShouldEqual, 8) 27 | if len(regions) < 1 { 28 | t.Fatal("at least 1 region must be found") 29 | } 30 | So(regions[0], ShouldEqual, "us-east") 31 | if len(prices.Config.Regions) < 1 { 32 | t.Fatal("at least 1 region must be found") 33 | } 34 | 35 | region := prices.Config.Regions[0] 36 | types := region.InstanceTypes 37 | if len(types) < 1 { 38 | t.Fatal("no types found") 39 | } 40 | So(len(types), ShouldEqual, 10) 41 | instanceType := types[0] 42 | 43 | size := instanceType.Sizes[0] 44 | So(size.Size, ShouldEqual, "m3.medium") 45 | vc := size.ValueColumns[0] 46 | So(vc.Name, ShouldEqual, "linux") 47 | So(vc.Prices["USD"], ShouldEqual, "0.113") 48 | }) 49 | } 50 | 51 | func TestValueColumnes(t *testing.T) { 52 | Convey("Value Columns", t, func() { 53 | Convey("on demand instances", func() { 54 | vcs := ValueColumns{ 55 | {Name: "linux", Prices: map[string]string{"USD": "0.450"}}, 56 | } 57 | So(vcs, ShouldNotBeNil) 58 | So(len(vcs.Prices()), ShouldEqual, 1) 59 | 60 | price := vcs.Prices()[0] 61 | So(price.PerHour, ShouldEqual, 0.45) 62 | So(price.TotalPerHour(), ShouldEqual, 0.45) 63 | So(price.Upfront, ShouldEqual, 0) 64 | 65 | }) 66 | Convey("reserved instances", func() { 67 | vcs := ValueColumns{ 68 | {Name: "yrTerm1Hourly", Rate: "perhr", Prices: map[string]string{"USD": "0.028"}}, 69 | {Name: "yrTerm1", Prices: map[string]string{"USD": "338"}}, 70 | {Name: "yrTerm3Hourly", Rate: "perhr", Prices: map[string]string{"USD": "0.023"}}, 71 | {Name: "yrTerm3", Prices: map[string]string{"USD": "514"}}, 72 | } 73 | So(vcs, ShouldNotBeNil) 74 | So(len(vcs.Prices()), ShouldEqual, 2) 75 | 76 | price := vcs.Prices()[0] 77 | So(price.Upfront, ShouldEqual, 338) 78 | So(price.PerHour, ShouldEqual, 0.028) 79 | So(price.TotalPerHour(), ShouldBeBetween, 0.06658, 0.06659) 80 | 81 | price = vcs.Prices()[1] 82 | So(price.Upfront, ShouldEqual, 514) 83 | So(price.PerHour, ShouldEqual, 0.023) 84 | So(price.TotalPerHour(), ShouldBeBetween, 0.042558, 0.042559) 85 | }) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /aws/pricing/parse.rb: -------------------------------------------------------------------------------- 1 | require "nokogiri" 2 | 3 | data = File.read("fixtures/instance_types.html") 4 | 5 | doc = Nokogiri::HTML(data) 6 | 7 | 8 | tables = [] 9 | doc.css("table").each do |table| 10 | rows = [] 11 | table.search("tr").each do |tr| 12 | row = [] 13 | tr.search("td").each do |td| 14 | row << td.inner_text.gsub(/\s+/, " ").strip 15 | end 16 | rows << row 17 | end 18 | tables << rows 19 | end 20 | 21 | tables.each do |table| 22 | table.each do |row| 23 | puts row[1..-1].join("\t") 24 | end 25 | break 26 | end 27 | 28 | puts "-" * 100 29 | -------------------------------------------------------------------------------- /aws/pricing/value_columns.go: -------------------------------------------------------------------------------- 1 | package pricing 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ValueColumns []*ValueColumn 8 | 9 | type ValueColumn struct { 10 | Name string `json:"name"` 11 | Rate string `json:"rate"` 12 | Prices Prices `json:"prices"` 13 | } 14 | 15 | type Price struct { 16 | Name string 17 | PerHour float64 18 | Upfront float64 19 | Currency string 20 | Duration time.Duration 21 | } 22 | 23 | func (price *Price) TotalPerHour() float64 { 24 | duration := price.Duration 25 | if duration == 0 { 26 | duration = OneYear 27 | } 28 | hours := duration.Hours() 29 | return (price.Upfront + (price.PerHour * hours)) / hours 30 | } 31 | 32 | func (vc *ValueColumns) ValueColumnMap() map[string]*ValueColumn { 33 | mapped := map[string]*ValueColumn{} 34 | for _, c := range *vc { 35 | mapped[c.Name] = c 36 | } 37 | return mapped 38 | } 39 | 40 | // prefix should be either yrTerm1 or yrTerm3 41 | func (vc *ValueColumns) priceForPrefix(prefix string) *Price { 42 | mapped := vc.ValueColumnMap() 43 | if upfront := mapped[prefix]; upfront != nil { 44 | if upfront.Name == "linux" { 45 | p := &Price{} 46 | p.Currency = "USD" 47 | ok := false 48 | if p.PerHour, ok = upfront.Prices.USD(); ok { 49 | return p 50 | } 51 | } 52 | if perHour := mapped[prefix+"Hourly"]; perHour != nil { 53 | p := &Price{} 54 | ok := false 55 | if p.PerHour, ok = perHour.Prices.USD(); ok { 56 | p.Currency = "USD" 57 | if p.Upfront, ok = upfront.Prices.USD(); ok { 58 | return p 59 | } 60 | } 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | const ( 67 | DaysPerYear = 365 68 | HoursPerDay = 24 69 | OneYear = time.Hour * HoursPerDay * DaysPerYear 70 | ) 71 | 72 | type PriceList []*Price 73 | 74 | func (list PriceList) Len() int { 75 | return len(list) 76 | } 77 | 78 | func (list PriceList) Swap(a, b int) { 79 | list[a], list[b] = list[b], list[a] 80 | } 81 | 82 | func (list PriceList) Less(a, b int) bool { 83 | return list[a].TotalPerHour() < list[b].TotalPerHour() 84 | } 85 | 86 | func (vc *ValueColumns) Prices() PriceList { 87 | prices := []*Price{} 88 | if p := vc.priceForPrefix("yrTerm1"); p != nil { 89 | p.Duration = 1 * OneYear 90 | p.Name = "1-year" 91 | prices = append(prices, p) 92 | } 93 | if p := vc.priceForPrefix("yrTerm3"); p != nil { 94 | p.Duration = 3 * OneYear 95 | p.Name = "3-year" 96 | prices = append(prices, p) 97 | } 98 | if p := vc.priceForPrefix("linux"); p != nil { 99 | p.Duration = 1 * OneYear 100 | p.Name = "on-demand" 101 | prices = append(prices, p) 102 | } 103 | return prices 104 | } 105 | -------------------------------------------------------------------------------- /aws/rds/db_security_group.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import "encoding/xml" 4 | 5 | type CreateDBSecurityGroup struct { 6 | DBSecurityGroupName string `xml:"DBSecurityGroupName"` 7 | DBSecurityGroupDescription string `xml:"DBSecurityGroupDescription"` 8 | } 9 | 10 | type CreateDBSecurityGroupResponse struct { 11 | XMLName xml.Name `xml:"CreateDBSecurityGroupResponse"` 12 | Result *CreateDBSecurityGroupResult `xml:"CreateDBSecurityGroupResult"` 13 | } 14 | 15 | type CreateDBSecurityGroupResult struct { 16 | XMLName xml.Name `xml:"CreateDBSecurityGroupResult"` 17 | DBSecurityGroup *DBSecurityGroup `xml:"DBSecurityGroup"` 18 | } 19 | 20 | func (action *CreateDBSecurityGroup) Execute(client *Client) (res *CreateDBSecurityGroupResponse, e error) { 21 | v := newAction("CreateDBSecurityGroup") 22 | if e = loadValues(v, action); e != nil { 23 | return nil, e 24 | } 25 | 26 | res = &CreateDBSecurityGroupResponse{} 27 | return res, client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, res) 28 | } 29 | 30 | type AuthorizeDBSecurityGroupIngress struct { 31 | DBSecurityGroupName string `xml:"DBSecurityGroupName"` 32 | CIDRIP string `xml:"CIDRIP"` 33 | } 34 | 35 | type AuthorizeDBSecurityGroupIngressResponse struct { 36 | XMLName xml.Name `xml:"AuthorizeDBSecurityGroupIngressResponse"` 37 | Result *AuthorizeDBSecurityGroupResult `xml:"AuthorizeDBSecurityGroupIngressResult"` 38 | } 39 | type AuthorizeDBSecurityGroupResult struct { 40 | XMLName xml.Name `xml:"AuthorizeDBSecurityGroupIngressResult"` 41 | DBSecurityGroup *DBSecurityGroup `xml:"DBSecurityGroup"` 42 | } 43 | 44 | func (action *AuthorizeDBSecurityGroupIngress) Execute(client *Client) (res *AuthorizeDBSecurityGroupIngressResponse, e error) { 45 | v := newAction("AuthorizeDBSecurityGroupIngress") 46 | if e = loadValues(v, action); e != nil { 47 | return nil, e 48 | } 49 | 50 | res = &AuthorizeDBSecurityGroupIngressResponse{} 51 | return res, client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, res) 52 | } 53 | 54 | type DeleteDBSecurityGroup struct { 55 | DBSecurityGroupName string `xml:"DBSecurityGroupName"` 56 | } 57 | 58 | func (action *DeleteDBSecurityGroup) Execute(client *Client) (e error) { 59 | v := newAction("DeleteDBSecurityGroup") 60 | if e = loadValues(v, action); e != nil { 61 | return e 62 | } 63 | res := &struct { 64 | XMLName xml.Name `xml:"DeleteDBSecurityGroupResponse"` 65 | }{} 66 | return client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, res) 67 | } 68 | -------------------------------------------------------------------------------- /aws/rds/dbg.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 18 | -------------------------------------------------------------------------------- /aws/rds/delete_db_instance.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import "encoding/xml" 4 | 5 | type DeleteDBInstance struct { 6 | DBInstanceIdentifier string `xml:",omitempty"` 7 | // Don't create an instance final snapshot. 8 | SkipFinalSnapshot bool `xml:",omitempty"` 9 | // Name of the final instance snapshot. 10 | FinalDBSnapshotIdentifier string `xml:",omitempty"` 11 | } 12 | 13 | type DeleteDBInstanceResponse struct { 14 | XMLName xml.Name `xml:"DeleteDBInstanceResponse"` 15 | DeleteDBInstanceResult *DeleteDBInstanceResult `xml:"DeleDescribeDBInstancesResult"` 16 | } 17 | 18 | type DeleteDBInstanceResult struct { 19 | Instance *DBInstance `xml:"DBInstance"` 20 | } 21 | 22 | func (r *DeleteDBInstance) Execute(client *Client) (*DeleteDBInstanceResponse, error) { 23 | v := newAction("DeleteDBInstance") 24 | if e := loadValues(v, r); e != nil { 25 | return nil, e 26 | } 27 | 28 | resp := &DeleteDBInstanceResponse{} 29 | return resp, client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, resp) 30 | } 31 | -------------------------------------------------------------------------------- /aws/rds/describe_db_engine_versions.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "github.com/dynport/gocloud/aws/ec2" 7 | ) 8 | 9 | type DescribeDBEngineVersionsResponse struct { 10 | XMLName xml.Name `xml:"DescribeDBEngineVersionsResponse"` 11 | DBEngineVersions []*DBEngineVersion `xml:"DescribeDBEngineVersionsResult>DBEngineVersions>DBEngineVersion"` 12 | } 13 | 14 | type DBEngineVersion struct { 15 | DBParameterGroupFamily string `xml:"DBParameterGroupFamily"` // oracle-se1-11.2 16 | Engine string `xml:"Engine"` // oracle-se1 17 | DBEngineDescription string `xml:"DBEngineDescription"` // Oracle Database Server SE1 18 | EngineVersion string `xml:"EngineVersion"` // 11.2.0.2.v3 19 | DBEngineVersionDescription string `xml:"DBEngineVersionDescription"` // Oracle SE1 release 20 | } 21 | 22 | type DescribeDBEngineVersions struct { 23 | DBParameterGroupFamily string 24 | DefaultOnly string 25 | Engine string 26 | EngineVerion string 27 | MaxRecords int 28 | Marker string 29 | ListSupportedCharacterSets bool 30 | Filters []*ec2.Filter 31 | } 32 | 33 | const Version = "2013-05-15" 34 | 35 | func (d *DescribeDBEngineVersions) Execute(client *Client) (*DescribeDBEngineVersionsResponse, error) { 36 | v := newAction("DescribeDBEngineVersions") 37 | e := loadValues(v, d) 38 | if e != nil { 39 | return nil, e 40 | } 41 | r := &DescribeDBEngineVersionsResponse{} 42 | e = client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, r) 43 | return r, e 44 | } 45 | -------------------------------------------------------------------------------- /aws/rds/describe_db_snapshots.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "encoding/xml" 5 | "time" 6 | ) 7 | 8 | type DescribeDBSnapshots struct { 9 | DBInstanceIdentifier string `xml:",omitempty"` 10 | DBSnapshotIdentifier string `xml:",omitempty"` 11 | Filters []*Filter `xml:"Filters>member,omitempty"` 12 | Marker string `xml:",omitempty"` 13 | MaxRecords int `xml:",omitempty"` 14 | SnapshotType string `xml:",omitempty"` 15 | } 16 | 17 | type DescribeDBSnapshotsResponse struct { 18 | XMLName xml.Name `xml:"DescribeDBSnapshotsResponse"` 19 | DescribeDBSnapshotsResult *DescribeDBSnapshotsResult `xml:"DescribeDBSnapshotsResult"` 20 | } 21 | 22 | type DescribeDBSnapshotsResult struct { 23 | Snapshots []*DBSnapshot `xml:"DBSnapshots>DBSnapshot"` 24 | } 25 | 26 | type Filter struct { 27 | Name string `xml:",omitempty"` 28 | Values []string `xml:",omitempty"` 29 | } 30 | 31 | type DBSnapshot struct { 32 | AllocatedStorage int `xml:",omitempty"` 33 | AvailabilityZone string `xml:",omitempty"` 34 | DBInstanceIdentifier string `xml:",omitempty"` 35 | DBSnapshotIdentifier string `xml:",omitempty"` 36 | Engine string `xml:",omitempty"` 37 | EngineVersion string `xml:",omitempty"` 38 | InstanceCreateTime time.Time `xml:",omitempty"` 39 | Iops int `xml:",omitempty"` 40 | LicenseModel string `xml:",omitempty"` 41 | MasterUsername string `xml:",omitempty"` 42 | OptionGroupName string `xml:",omitempty"` 43 | PercentProgress int `xml:",omitempty"` 44 | Port int `xml:",omitempty"` 45 | SnapshotCreateTime time.Time `xml:",omitempty"` 46 | SnapshotType string `xml:",omitempty"` 47 | SourceRegion string `xml:",omitempty"` 48 | Status string `xml:",omitempty"` 49 | VpcId string `xml:",omitempty"` 50 | } 51 | 52 | func (d *DescribeDBSnapshots) Execute(client *Client) (*DescribeDBSnapshotsResponse, error) { 53 | v := newAction("DescribeDBSnapshots") 54 | e := loadValues(v, d) 55 | if e != nil { 56 | return nil, e 57 | } 58 | r := &DescribeDBSnapshotsResponse{} 59 | e = client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, r) 60 | return r, e 61 | } 62 | -------------------------------------------------------------------------------- /aws/rds/main.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "encoding/xml" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/dynport/gocloud/aws" 12 | "github.com/dynport/gocloud/aws/ec2" 13 | ) 14 | 15 | type Client struct { 16 | *aws.Client 17 | CustomRegion string 18 | } 19 | 20 | func (client *Client) Endpoint() string { 21 | prefix := "https://rds" 22 | if client.Client.Region != "" { 23 | prefix += "." + client.Client.Region 24 | } 25 | return prefix + ".amazonaws.com" 26 | } 27 | 28 | func NewFromEnv() *Client { 29 | return &Client{Client: aws.NewFromEnv()} 30 | } 31 | 32 | type DescribeDBInstances struct { 33 | DBInstanceIdentifier string 34 | Filters []*ec2.Filter 35 | Marker string 36 | MaxRecords int 37 | } 38 | 39 | func newAction(name string) url.Values { 40 | return url.Values{"Action": {name}, "Version": {Version}} 41 | } 42 | 43 | type Error struct { 44 | Code string `xml:"Code"` 45 | Message string `xml:"Message"` 46 | } 47 | 48 | func (e Error) Error() string { 49 | return e.Code + ": " + e.Message 50 | } 51 | 52 | type ErrorResponse struct { 53 | XMLName xml.Name `xml:"ErrorResponse"` 54 | RequestID string `xml:"RequestID"` 55 | Error Error `xml:"Error"` 56 | } 57 | 58 | func (client *Client) loadResource(method string, url string, r io.Reader, i interface{}) error { 59 | dbg.Printf("executing method=%s to url=%s", method, url) 60 | req, e := http.NewRequest(method, url, r) 61 | if e != nil { 62 | return e 63 | } 64 | client.SignAwsRequestV2(req, time.Now()) 65 | rsp, e := http.DefaultClient.Do(req) 66 | if e != nil { 67 | return e 68 | } 69 | defer rsp.Body.Close() 70 | dbg.Printf("got status %s", rsp.Status) 71 | 72 | b, e := ioutil.ReadAll(rsp.Body) 73 | if e != nil { 74 | return e 75 | } 76 | dbg.Printf("got response %s", b) 77 | 78 | if rsp.Status[0] != '2' { 79 | resp := &ErrorResponse{} 80 | if e = xml.Unmarshal(b, resp); e != nil { 81 | return e 82 | } 83 | return resp.Error 84 | } 85 | return xml.Unmarshal(b, i) 86 | } 87 | -------------------------------------------------------------------------------- /aws/rds/modify_db_instance.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import "encoding/xml" 4 | 5 | type ModifyDBInstance struct { 6 | DBInstanceIdentifier string 7 | DBSecurityGroups []string `xml:"DBSecurityGroups>member,omitempty"` 8 | VpcSecurityGroupIds []string `xml:"VpcSecurityGroupIds>member,omitempty"` 9 | } 10 | 11 | type ModifyDBInstanceResponse struct { 12 | XMLName xml.Name `xml:"ModifyDBInstanceResponse"` 13 | ModifyDBInstanceResult *ModifyDBInstanceResult `xml:"ModifyDBInstanceResult,omitempty"` 14 | } 15 | 16 | type ModifyDBInstanceResult struct { 17 | XMLName xml.Name `xml:"ModifyDBInstanceResult"` 18 | DBInstance *DBInstance `xml:"DBInstance"` 19 | } 20 | 21 | func (action *ModifyDBInstance) Execute(client *Client) (res *ModifyDBInstanceResponse, e error) { 22 | v := newAction("ModifyDBInstance") 23 | if e = loadValues(v, action); e != nil { 24 | return nil, e 25 | } 26 | 27 | res = &ModifyDBInstanceResponse{} 28 | return res, client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, res) 29 | } 30 | -------------------------------------------------------------------------------- /aws/rds/restore_db_snapshot.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import "encoding/xml" 4 | 5 | type RestoreDBSnapshot struct { 6 | DBInstanceClass string `xml:",omitempty"` 7 | DBInstanceIdentifier string `xml:",omitempty"` 8 | DBSnapshotIdentifier string `xml:",omitempty"` 9 | } 10 | 11 | type RestoreDBSnapshotResponse struct { 12 | XMLName xml.Name `xml:"RestoreDBInstanceFromDBSnapshotResponse"` 13 | RestoreDBSnapshotResult *RestoreDBSnapshotResult `xml:"RestoreDBInstanceFromDBSnapshotResult"` 14 | } 15 | 16 | type RestoreDBSnapshotResult struct { 17 | Instance *DBInstance `xml:"DBInstance"` 18 | } 19 | 20 | func (r *RestoreDBSnapshot) Execute(client *Client) (*RestoreDBSnapshotResponse, error) { 21 | v := newAction("RestoreDBInstanceFromDBSnapshot") 22 | if e := loadValues(v, r); e != nil { 23 | return nil, e 24 | } 25 | 26 | resp := &RestoreDBSnapshotResponse{} 27 | return resp, client.loadResource("GET", client.Endpoint()+"?"+v.Encode(), nil, resp) 28 | } 29 | -------------------------------------------------------------------------------- /aws/rds/utils.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "reflect" 7 | ) 8 | 9 | func loadValues(v url.Values, i interface{}) error { 10 | value := reflect.ValueOf(i) 11 | if value.Kind() == reflect.Ptr { 12 | value = value.Elem() 13 | } 14 | t := value.Type() 15 | for i := 0; i < value.NumField(); i++ { 16 | value := value.Field(i) 17 | name := t.Field(i).Name 18 | switch casted := value.Interface().(type) { 19 | case string: 20 | if casted != "" { 21 | v.Set(name, casted) 22 | } 23 | case bool: 24 | if casted { 25 | v.Set(name, "true") 26 | } 27 | case int64: 28 | if casted != 0 { 29 | v.Set(name, fmt.Sprintf("%d", casted)) 30 | } 31 | case int: 32 | if casted != 0 { 33 | v.Set(name, fmt.Sprintf("%d", casted)) 34 | } 35 | case []string: 36 | if len(casted) != 0 { 37 | for i, val := range casted { 38 | v.Set(fmt.Sprintf("%s.member.%d", name, i+1), val) 39 | } 40 | } 41 | } 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /aws/rds/utils_test.go: -------------------------------------------------------------------------------- 1 | package rds 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "sort" 7 | "strings" 8 | "testing" 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | func TestValuesFor(t *testing.T) { 13 | Convey("Values For", t, func() { 14 | type action struct { 15 | String string 16 | Bool bool 17 | Integer int 18 | Int64 int64 19 | //Filters []*ec2.Filter 20 | } 21 | 22 | type test struct { 23 | Action *action 24 | Expected string 25 | } 26 | tests := []*test{ 27 | {Action: &action{String: "test"}, Expected: "String=test"}, 28 | {Action: &action{Bool: true}, Expected: "Bool=true"}, 29 | {Action: &action{Integer: 10}, Expected: "Integer=10"}, 30 | {Action: &action{Int64: int64(64)}, Expected: "Int64=64"}, 31 | } 32 | for _, test := range tests { 33 | v := url.Values{} 34 | out := []string{} 35 | e := loadValues(v, test.Action) 36 | So(e, ShouldBeNil) 37 | for k, v := range v { 38 | out = append(out, fmt.Sprintf("%s=%s", k, strings.Join(v, ","))) 39 | } 40 | sort.Strings(out) 41 | So(strings.Join(out, "&"), ShouldEqual, test.Expected) 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /aws/route53/fixtures/get_hosted_zone_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /hostedzone/Z1PA6795UKMFR9 5 | example.com. 6 | myUniqueIdentifier 7 | 8 | This is my first hosted zone. 9 | 10 | 17 11 | 12 | 13 | 14 | ns-2048.awsdns-64.com 15 | ns-2049.awsdns-65.net 16 | ns-2050.awsdns-66.org 17 | ns-2051.awsdns-67.co.uk 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /aws/route53/fixtures/list_hosted_zones.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /hostedzone/Z111111QQQQQQQ 6 | example2.com. 7 | MyUniqueIdentifier2 8 | 9 | This is my second hosted zone. 10 | 11 | 42 12 | 13 | 14 | true 15 | Z222222VVVVVVV 16 | 1 17 | 18 | -------------------------------------------------------------------------------- /aws/route53/fixtures/list_resource_record_sets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | example.com. 6 | NS 7 | 172800 8 | 9 | 10 | ns-2048.awsdns-64.com. 11 | 12 | 13 | ns-2049.awsdns-65.net. 14 | 15 | 16 | ns-2050.awsdns-66.org. 17 | 18 | 19 | ns-2051.awsdns-67.co.uk. 20 | 21 | 22 | 23 | 24 | false 25 | 10 26 | 27 | -------------------------------------------------------------------------------- /aws/s3/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go get github.com/dynport/gocloud/aws/s3 3 | -------------------------------------------------------------------------------- /aws/s3/acl.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | type AccessControlPolicy struct { 12 | XMLname xml.Name `xml:"AccessControlPolicy"` 13 | Owner *User `xml:"Owner"` 14 | AccessControlList *AccessControlList `xml:"AccessControlList"` 15 | } 16 | 17 | type AccessControlList struct { 18 | Grants []*Grant `xml:"Grant"` 19 | } 20 | 21 | type Grant struct { 22 | Grantee *User `xml:"Grantee"` 23 | Permission string `xml:"Permission"` 24 | } 25 | 26 | type User struct { 27 | ID string `xml:"ID"` // Owner-canonical-user-ID 28 | DisplayName string `xml:"DisplayName"` // display-name 29 | URI string `xml:"URI"` 30 | } 31 | 32 | type Acl struct { 33 | Bucket string 34 | Key string 35 | } 36 | 37 | func (acl *Acl) Load(client *Client) (*AccessControlPolicy, error) { 38 | if acl.Bucket == "" { 39 | return nil, fmt.Errorf("Bucket must be set") 40 | } 41 | path := acl.Bucket 42 | if acl.Key != "" { 43 | path += "/" + acl.Key 44 | } 45 | u := "https://" + client.EndpointHost() + "/" + path + "?acl" 46 | req, e := http.NewRequest("GET", u, nil) 47 | client.SignS3Request(req, acl.Bucket) 48 | rsp, e := http.DefaultClient.Do(req) 49 | if e != nil { 50 | return nil, e 51 | } 52 | defer rsp.Body.Close() 53 | 54 | buf := &bytes.Buffer{} 55 | 56 | r := io.TeeReader(rsp.Body, buf) 57 | 58 | acp := &AccessControlPolicy{} 59 | e = xml.NewDecoder(r).Decode(acp) 60 | return acp, e 61 | 62 | } 63 | -------------------------------------------------------------------------------- /aws/s3/client_test.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | . "github.com/smartystreets/goconvey/convey" 5 | "net/url" 6 | "testing" 7 | ) 8 | 9 | func TestSignRequest(t *testing.T) { 10 | Convey("Sign request", t, func() { 11 | u, e := url.Parse("http://127.0.0.1/?uploads&a=1") 12 | if e != nil { 13 | t.Fatal(e.Error()) 14 | } 15 | v := normalizeParams(u) 16 | So(v, ShouldEqual, "uploads") 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /aws/s3/fixtures/list_bucket.xml: -------------------------------------------------------------------------------- 1 | 2 | bucket 3 | 4 | 5 | 1000 6 | false 7 | 8 | my-image.jpg 9 | 2009-10-12T17:50:30.000Z 10 | "fba9dede5f27731c9771645a39863328" 11 | 434234 12 | STANDARD 13 | 14 | 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a 15 | mtd@amazon.com 16 | 17 | 18 | 19 | my-third-image.jpg 20 | 2009-10-12T17:50:30.000Z 21 | "1b2cf535f27731c974343645a3985328" 22 | 64994 23 | STANDARD 24 | 25 | 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a 26 | mtd@amazon.com 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /aws/s3/fixtures/service.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bcaf1ffd86f461ca5fb16fd081034f 5 | webfile 6 | 7 | 8 | 9 | quotes 10 | 2006-02-03T16:45:09.000Z 11 | 12 | 13 | samples 14 | 2006-02-03T16:41:58.000Z 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /aws/s3/list_bucket.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | type ListBucketOptions struct { 11 | Marker string 12 | Prefix string 13 | } 14 | 15 | func (client *Client) ListBucket(bucket string) (r *ListBucketResult, e error) { 16 | return client.ListBucketWithOptions(bucket, nil) 17 | } 18 | 19 | func (client *Client) ListBucketWithOptions(bucket string, opts *ListBucketOptions) (r *ListBucketResult, e error) { 20 | u := "https://" + client.EndpointHost() + "/" + bucket 21 | if opts != nil { 22 | v := &url.Values{} 23 | if opts.Marker != "" { 24 | v.Add("marker", opts.Marker) 25 | } 26 | if opts.Prefix != "" { 27 | v.Add("prefix", opts.Prefix) 28 | } 29 | if len(*v) > 0 { 30 | u += "?" + v.Encode() 31 | } 32 | } 33 | req, e := http.NewRequest("GET", u, nil) 34 | if e != nil { 35 | return r, e 36 | } 37 | req.Header.Add("Host", bucket+"."+client.EndpointHost()) 38 | _, b, e := client.signAndDoRequest(bucket, req) 39 | if e != nil { 40 | return nil, e 41 | } 42 | r = &ListBucketResult{} 43 | e = xml.Unmarshal(b, r) 44 | if e != nil { 45 | return r, fmt.Errorf("ERROR: %s (%s)", e.Error(), string(b)) 46 | } 47 | return r, e 48 | } 49 | -------------------------------------------------------------------------------- /aws/s3/policy.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | type PolicyRequest struct { 12 | Bucket string 13 | Key string 14 | } 15 | 16 | type Policy struct { 17 | Id string `json:"Id,omitempty"` 18 | Version string `json:"Version,omitempty"` 19 | Statements []*PolicyStatement `json:"Statement,omitempty"` 20 | } 21 | 22 | type PolicyStatement struct { 23 | Condition interface{} `json:"Condition"` 24 | Resource string `json:"Resource,omitempty"` 25 | Action string `json:"Action,omitempty"` 26 | Principal interface{} `json:"Principal,omitempty"` 27 | Effect string `json:"Effect,omitempty"` 28 | Sid string `json:"Sid,omitempty"` 29 | } 30 | 31 | func (pr *PolicyRequest) Load(client *Client) (*AccessControlPolicy, error) { 32 | if pr.Bucket == "" { 33 | return nil, fmt.Errorf("Bucket must be set") 34 | } 35 | path := pr.Bucket 36 | if pr.Key != "" { 37 | path += "/" + pr.Key 38 | } 39 | u := "https://" + client.EndpointHost() + "/" + path + "?policy" 40 | req, e := http.NewRequest("GET", u, nil) 41 | client.SignS3Request(req, pr.Bucket) 42 | rsp, e := http.DefaultClient.Do(req) 43 | if e != nil { 44 | return nil, e 45 | } 46 | defer rsp.Body.Close() 47 | 48 | buf := &bytes.Buffer{} 49 | 50 | r := io.TeeReader(rsp.Body, buf) 51 | 52 | acp := &AccessControlPolicy{} 53 | e = xml.NewDecoder(r).Decode(acp) 54 | return acp, e 55 | 56 | } 57 | -------------------------------------------------------------------------------- /aws/s3/put_bucket.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html 8 | func (client *Client) PutBucket(name string) error { 9 | req, e := http.NewRequest("PUT", "http://"+client.EndpointHost()+"/", nil) 10 | if e != nil { 11 | return e 12 | } 13 | req.Header.Add("Host", name+"."+client.EndpointHost()) 14 | req.Header.Add("Content-Length", "0") 15 | rsp, b, e := client.signAndDoRequest("", req) 16 | if e != nil { 17 | return e 18 | } 19 | if rsp.Status[0] != '2' { 20 | return NewApiError("Unable to create bucket "+name, req, rsp, b) 21 | 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /aws/s3/util.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "crypto/md5" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | func contentMd5(s string) (ret string, e error) { 12 | digest := md5.New() 13 | _, e = io.Copy(digest, strings.NewReader(s)) 14 | if e != nil { 15 | return "", e 16 | } 17 | sum := digest.Sum(nil) 18 | return b64.EncodeToString(sum), nil 19 | } 20 | 21 | var logger = log.New(os.Stdout, "", 0) 22 | -------------------------------------------------------------------------------- /aws/sts/fixtures/get_session_token_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/L 6 | To6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3z 7 | rkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtp 8 | Z3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE 9 | 10 | 11 | wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY 12 | 13 | 2011-07-11T19:55:29.611Z 14 | AKIAIOSFODNN7EXAMPLE 15 | 16 | 17 | 18 | 58c5dbae-abef-11e0-8cfe-09039844ac7d 19 | 20 | 21 | -------------------------------------------------------------------------------- /aws/sts/get_session_token.go: -------------------------------------------------------------------------------- 1 | package sts 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/dynport/gocloud/aws" 11 | 12 | "time" 13 | ) 14 | 15 | type GetSessionToken struct { 16 | Version aws.Version `aws:"2011-06-15"` 17 | Action aws.Action `aws:"GetSessionToken"` 18 | DurationSeconds int `aws:"DurationSeconds"` 19 | SerialNumber string `aws:"SerialNumber"` 20 | TokenCode string `aws:"TokenCode"` 21 | } 22 | 23 | func (a *GetSessionToken) Execute(client *aws.Client) (*GetSessionTokenResponse, error) { 24 | params, err := aws.ParamsForAction(a) 25 | if err != nil { 26 | return nil, err 27 | } 28 | req, err := http.NewRequest("GET", "https://sts.amazonaws.com/?"+params.Encode(), nil) 29 | tim := time.Now().UTC() 30 | client.SignAwsRequestV2(req, tim) 31 | for k, v := range req.Header { 32 | log.Printf("%s: %s", k, v) 33 | } 34 | rsp, err := http.DefaultClient.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | defer rsp.Body.Close() 39 | if rsp.Status[0] != '2' { 40 | b, _ := ioutil.ReadAll(rsp.Body) 41 | return nil, fmt.Errorf("expected status 2xx, got %s. %s", rsp.Status, string(b)) 42 | } 43 | var r *GetSessionTokenResponse 44 | return r, xml.NewDecoder(rsp.Body).Decode(&r) 45 | } 46 | 47 | type GetSessionTokenResponse struct { 48 | XMLName xml.Name `xml:"GetSessionTokenResponse"` 49 | Credentials *Credentials `xml:"GetSessionTokenResult>Credentials"` 50 | } 51 | 52 | type Credentials struct { 53 | AccessKeyID string `xml:"AccessKeyId,omitempty"` 54 | Expiration time.Time `xml:",omitempty"` 55 | SecretAccessKey string `xml:",omitempty"` 56 | SessionToken string `xml:",omitempty"` 57 | } 58 | -------------------------------------------------------------------------------- /aws/sts/get_session_token_test.go: -------------------------------------------------------------------------------- 1 | package sts 2 | 3 | import ( 4 | "encoding/xml" 5 | 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestGetSessionTokenParse(t *testing.T) { 11 | f, err := os.Open("fixtures/get_session_token_response.xml") 12 | if err != nil { 13 | t.Fatal("error opening fixture", err) 14 | } 15 | defer f.Close() 16 | var rsp *GetSessionTokenResponse 17 | err = xml.NewDecoder(f).Decode(&rsp) 18 | if err != nil { 19 | t.Fatal("error decoding response", err) 20 | } 21 | creds := rsp.Credentials 22 | tests := []struct { 23 | Name string 24 | Value interface{} 25 | Expected interface{} 26 | }{ 27 | {"Expiration", creds.Expiration.Format("2006-01-02"), "2011-07-11"}, 28 | {"AccessKeyID", creds.AccessKeyID, "AKIAIOSFODNN7EXAMPLE"}, 29 | } 30 | 31 | for _, tst := range tests { 32 | if tst.Expected != tst.Value { 33 | t.Errorf("expected %s to be %#v, was %#v", tst.Name, tst.Expected, tst.Value) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /aws/util.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | const ( 9 | CONTENT_MD5 = "Content-Md5" 10 | CONTENT_TYPE = "Content-Type" 11 | CONTENT_EXPIRES = "Expires" 12 | CONTENT_ACL = "x-amz-acl" 13 | ENV_AWS_ACCESS_KEY = "AWS_ACCESS_KEY_ID" 14 | ENV_AWS_SECRET_KEY = "AWS_SECRET_ACCESS_KEY" 15 | ENV_AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" 16 | ) 17 | 18 | func abortWith(message string) { 19 | fmt.Println("ERROR: " + message) 20 | os.Exit(1) 21 | } 22 | 23 | // copied from https://launchpad.net/goamz 24 | var unreserved = make([]bool, 128) 25 | var hex = "0123456789ABCDEF" 26 | 27 | func init() { 28 | // RFC3986 29 | u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~" 30 | for _, c := range u { 31 | unreserved[c] = true 32 | } 33 | } 34 | 35 | func Encode(s string) string { 36 | encode := false 37 | for i := 0; i != len(s); i++ { 38 | c := s[i] 39 | if c > 127 || !unreserved[c] { 40 | encode = true 41 | break 42 | } 43 | } 44 | if !encode { 45 | return s 46 | } 47 | e := make([]byte, len(s)*3) 48 | ei := 0 49 | for i := 0; i != len(s); i++ { 50 | c := s[i] 51 | if c > 127 || !unreserved[c] { 52 | e[ei] = '%' 53 | e[ei+1] = hex[c>>4] 54 | e[ei+2] = hex[c&0xF] 55 | ei += 3 56 | } else { 57 | e[ei] = c 58 | ei += 1 59 | } 60 | } 61 | return string(e[:ei]) 62 | } 63 | -------------------------------------------------------------------------------- /cli/aws/cloudformation/parameters_describe.go: -------------------------------------------------------------------------------- 1 | package cloudformation 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/aws/cloudformation" 8 | ) 9 | 10 | type ParametersDescribe struct { 11 | Name string `cli:"arg required"` 12 | Full bool `cli:"opt --full"` 13 | } 14 | 15 | func (a *ParametersDescribe) Run() error { 16 | client := cloudformation.NewFromEnv() 17 | tpl := cloudformation.DescribeStacks{ 18 | StackName: a.Name, 19 | } 20 | rsp, e := tpl.Execute(client) 21 | if e != nil { 22 | return e 23 | } 24 | stacks := rsp.DescribeStacksResult.Stacks 25 | if len(stacks) != 1 { 26 | return fmt.Errorf("expected 1 stack for %q, got %d", a.Name, len(stacks)) 27 | } 28 | stack := stacks[0] 29 | t := gocli.NewTable() 30 | for _, p := range stack.Parameters { 31 | value := p.ParameterValue 32 | if len(value) > 64 && !a.Full { 33 | value = value[0:64] + "... (truncated)" 34 | } 35 | t.Add(p.ParameterKey, value) 36 | } 37 | fmt.Println(t) 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /cli/aws/cloudwatch/cloudwatch.go: -------------------------------------------------------------------------------- 1 | package cloudwatch 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/dgtk/cli" 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/aws" 8 | "github.com/dynport/gocloud/aws/cloudwatch" 9 | ) 10 | 11 | func Register(router *cli.Router) { 12 | router.RegisterFunc("aws/cloudwatch", cloudwatchList, "List Cloudwatch metrics") 13 | } 14 | 15 | func cloudwatchList() error { 16 | client := cloudwatch.Client{Client: aws.NewFromEnv()} 17 | rsp, e := client.ListMetrics() 18 | if e != nil { 19 | return e 20 | } 21 | table := gocli.NewTable() 22 | for _, m := range rsp.Metrics { 23 | table.Add(m.Namespace, m.MetricName) 24 | for _, d := range m.Dimensions { 25 | table.Add("", d.Name, d.Value) 26 | } 27 | } 28 | fmt.Println(table) 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /cli/aws/ec2/ec2-prices/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dynport/dgtk/cli" 5 | "github.com/dynport/gocloud/cli/aws/ec2" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | router := cli.NewRouter() 12 | router.Register("prices", &ec2.Prices{Region: os.Getenv("AWS_DEFAULT_REGION")}, "List ec2 prices") 13 | e := router.Run(append([]string{"prices"}, os.Args[1:]...)...) 14 | if e != nil { 15 | log.Fatal(e.Error()) 16 | } 17 | } 18 | 19 | func init() { 20 | log.SetFlags(0) 21 | } 22 | -------------------------------------------------------------------------------- /cli/aws/ec2/prices.go: -------------------------------------------------------------------------------- 1 | package ec2 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/dynport/gocli" 8 | "github.com/dynport/gocloud/aws/pricing" 9 | ) 10 | 11 | type Prices struct { 12 | Region string `cli:"type=opt short=r default=eu-ireland"` 13 | Heavy bool `cli:"type=opt long=heavy"` 14 | Detailed bool `cli:"type=opt long=detailed"` 15 | } 16 | 17 | func (a *Prices) Run() error { 18 | configs, e := pricing.AllInstanceTypeConfigs() 19 | if e != nil { 20 | return e 21 | } 22 | sort.Sort(configs) 23 | var pr *pricing.Pricing 24 | regionName := a.Region 25 | typ := "od" 26 | if a.Heavy { 27 | regionName = normalizeRegion(regionName) 28 | typ = "ri-heavy" 29 | pr, e = pricing.LinuxReservedHeavy() 30 | } else { 31 | regionName = normalizeRegionForOd(regionName) 32 | pr, e = pricing.LinuxOnDemand() 33 | } 34 | if e != nil { 35 | return e 36 | } 37 | priceMapping := map[string]pricing.PriceList{} 38 | region := pr.FindRegion(regionName) 39 | if region == nil { 40 | return fmt.Errorf("could not find prices for reagion %q. Known regions are %v", regionName, pr.RegionNames()) 41 | } 42 | for _, t := range region.InstanceTypes { 43 | for _, size := range t.Sizes { 44 | priceMapping[size.Size] = size.ValueColumns.Prices() 45 | } 46 | } 47 | if a.Detailed { 48 | printConfigsDetailed(regionName, typ, priceMapping, configs) 49 | } else { 50 | printConfigs(regionName, typ, priceMapping, configs) 51 | } 52 | return nil 53 | } 54 | 55 | func printConfigsDetailed(regionName string, typ string, priceMapping map[string]pricing.PriceList, configs pricing.InstanceTypeConfigs) { 56 | table := gocli.NewTable() 57 | for _, config := range configs { 58 | table.Add("Type", config.Name) 59 | table.Add("CPUs", config.Cpus) 60 | table.Add("Storage", config.Storage) 61 | table.Add("-------", "") 62 | } 63 | fmt.Println(table) 64 | } 65 | 66 | func printConfigs(regionName string, typ string, priceMapping map[string]pricing.PriceList, configs pricing.InstanceTypeConfigs) { 67 | table := gocli.NewTable() 68 | table.Add("Type", "Cores", "GB RAM", "Region", "Type", "$/Hour", "$/Month", "$/Core", "$/GB") 69 | for _, config := range configs { 70 | cols := []interface{}{ 71 | config.Name, config.Cpus, config.Memory, 72 | } 73 | if prices, ok := priceMapping[config.Name]; ok { 74 | cols = append(cols, normalizeRegion(regionName), typ) 75 | if len(prices) > 0 { 76 | sort.Sort(prices) 77 | price := prices[0].TotalPerHour() 78 | perMonth := price * HOURS_PER_MONTH 79 | perCore := perMonth / float64(config.Cpus) 80 | perGb := perMonth / config.Memory 81 | cols = append(cols, fmt.Sprintf("%.03f", price), monthlyPrice(perMonth), monthlyPrice(perCore), monthlyPrice(perGb)) 82 | } 83 | } 84 | table.Add(cols...) 85 | } 86 | fmt.Println(table) 87 | } 88 | -------------------------------------------------------------------------------- /cli/aws/elb/elb.go: -------------------------------------------------------------------------------- 1 | package elb 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/dgtk/cli" 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/aws/elb" 8 | "log" 9 | "strings" 10 | ) 11 | 12 | func Register(router *cli.Router) { 13 | router.RegisterFunc("aws/elb/lbs/list", elbListLoadBalancers, "Describe load balancers") 14 | router.Register("aws/elb/lbs/describe", &elbDescribeLoadBalancer{}, "Describe load balancers") 15 | router.Register("aws/elb/lbs/deregister", &elbDeregisterInstances{}, "Deregister instances with load balancer") 16 | router.Register("aws/elb/lbs/register", &elbRegisterInstances{}, "Register instances with load balancer") 17 | } 18 | 19 | type elbDescribeLoadBalancer struct { 20 | Name string `cli:"type=arg required=true"` 21 | } 22 | 23 | func (a *elbDescribeLoadBalancer) Run() error { 24 | elbClient := elb.NewFromEnv() 25 | states, e := elbClient.DescribeInstanceHealth(a.Name) 26 | if e != nil { 27 | return e 28 | } 29 | table := gocli.NewTable() 30 | for _, state := range states { 31 | stateString := "" 32 | if state.State == "InService" { 33 | stateString = gocli.Green(state.State) 34 | } else { 35 | stateString = gocli.Red(state.State) 36 | } 37 | table.Add(state.InstanceId, stateString) 38 | } 39 | fmt.Println(table) 40 | return nil 41 | } 42 | 43 | type elbRegisterInstances struct { 44 | LbId string `cli:"type=arg required=true"` 45 | InstanceIds []string `cli:"type=arg required=true"` 46 | } 47 | 48 | func (a *elbRegisterInstances) Run() error { 49 | return elb.NewFromEnv().RegisterInstancesWithLoadBalancer(a.LbId, a.InstanceIds) 50 | } 51 | 52 | type elbDeregisterInstances struct { 53 | LbId string `cli:"type=arg required=true"` 54 | InstanceIds []string `cli:"type=arg required=true"` 55 | } 56 | 57 | func (a *elbDeregisterInstances) Run() error { 58 | return elb.NewFromEnv().DeregisterInstancesWithLoadBalancer(a.LbId, a.InstanceIds) 59 | } 60 | 61 | func elbListLoadBalancers() error { 62 | elbClient := elb.NewFromEnv() 63 | log.Print("describing load balancers") 64 | lbs, e := elbClient.DescribeLoadBalancers() 65 | if e != nil { 66 | return e 67 | } 68 | table := gocli.NewTable() 69 | table.Add("Name", "DnsName", "Instances") 70 | for _, lb := range lbs { 71 | table.Add( 72 | lb.LoadBalancerName, 73 | lb.DNSName, 74 | strings.Join(lb.Instances, ", "), 75 | ) 76 | } 77 | fmt.Print(table) 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /cli/aws/iam/iam.go: -------------------------------------------------------------------------------- 1 | package iam 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/dgtk/cli" 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/aws/iam" 8 | "strings" 9 | ) 10 | 11 | func Register(router *cli.Router) { 12 | router.RegisterFunc("aws/iam/users/get", iamGetUser, "Get user information") 13 | router.RegisterFunc("aws/iam/users/list", iamListUsers, "List users") 14 | router.RegisterFunc("aws/iam/account-summary", iamGetAccountSummary, "Get account summary") 15 | router.RegisterFunc("aws/iam/account-aliases/list", iamListAccountAliases, "List account aliases") 16 | } 17 | 18 | func iamGetUser() error { 19 | client := iam.NewFromEnv() 20 | user, e := client.GetUser("") 21 | if e != nil { 22 | return e 23 | } 24 | table := gocli.NewTable() 25 | table.Add("Id", user.UserId) 26 | table.Add("Name", user.UserName) 27 | table.Add("Arn", strings.TrimSpace(user.Arn)) 28 | table.Add("Path", user.Path) 29 | fmt.Println(table) 30 | return nil 31 | } 32 | 33 | func iamGetAccountSummary() error { 34 | client := iam.NewFromEnv() 35 | summary, e := client.GetAccountSummary() 36 | if e != nil { 37 | return e 38 | } 39 | table := gocli.NewTable() 40 | for _, entry := range summary.Entries { 41 | table.Add(entry.Key, entry.Value) 42 | } 43 | fmt.Println(table) 44 | return nil 45 | } 46 | 47 | func iamListUsers() error { 48 | client := iam.NewFromEnv() 49 | rsp, e := client.ListUsers() 50 | if e != nil { 51 | return e 52 | } 53 | table := gocli.NewTable() 54 | for _, user := range rsp.Users { 55 | table.Add(user.UserId, user.UserName, strings.TrimSpace(user.Arn)) 56 | } 57 | fmt.Println(table) 58 | return nil 59 | } 60 | 61 | func iamListAccountAliases() error { 62 | client := iam.NewFromEnv() 63 | rsp, e := client.ListAccountAliases() 64 | if e != nil { 65 | return e 66 | } 67 | for _, alias := range rsp.AccountAliases { 68 | fmt.Println(alias) 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /cli/aws/route53/route53.go: -------------------------------------------------------------------------------- 1 | package route53 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/dgtk/cli" 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/aws/route53" 8 | "log" 9 | ) 10 | 11 | func Register(router *cli.Router) { 12 | router.RegisterFunc("aws/route53/hosted-zones/list", route53ListHostedZones, "List Hosted Zones") 13 | router.Register("aws/route53/rrs/list", &route53ListResourceRecordSet{}, "List Resource Record Set") 14 | } 15 | 16 | func route53ListHostedZones() error { 17 | log.Print("describing hosted zones") 18 | client := route53.NewFromEnv() 19 | zones, e := client.ListHostedZones() 20 | if e != nil { 21 | return e 22 | } 23 | table := gocli.NewTable() 24 | table.Add("Name", "record_set_count", "rrs count") 25 | for _, zone := range zones { 26 | table.Add(zone.Code(), zone.Name, zone.ResourceRecordSetCount) 27 | } 28 | fmt.Println(table) 29 | return nil 30 | } 31 | 32 | type route53ListResourceRecordSet struct { 33 | HostedZone string `cli:"type=arg required=true"` 34 | } 35 | 36 | func (r *route53ListResourceRecordSet) Run() error { 37 | client := route53.NewFromEnv() 38 | rrsets, e := client.ListResourceRecordSets(r.HostedZone) 39 | if e != nil { 40 | return e 41 | } 42 | table := gocli.NewTable() 43 | table.Add("name", "type", "ttl", "weight", "id", "hc id", "value") 44 | maxLen := 64 45 | for _, rrs := range rrsets { 46 | weight := "" 47 | if rrs.Weight > 0 { 48 | weight = fmt.Sprintf("%d", rrs.Weight) 49 | } 50 | col := []string{ 51 | rrs.Name, rrs.Type, fmt.Sprintf("%d", rrs.TTL), rrs.SetIdentifier, weight, rrs.HealthCheckId, 52 | } 53 | for i, record := range rrs.ResourceRecords { 54 | v := record.Value 55 | if len(v) > maxLen { 56 | v = v[0:maxLen] 57 | } 58 | if i == 0 { 59 | col = append(col, v) 60 | table.AddStrings(col) 61 | } else { 62 | table.Add("", "", "", "", "", "", v) 63 | } 64 | } 65 | } 66 | fmt.Println(table) 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /cli/hetzner/hetzner.go: -------------------------------------------------------------------------------- 1 | package hetzner 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/dynport/dgtk/cli" 9 | "github.com/dynport/gocli" 10 | "github.com/dynport/gocloud/hetzner" 11 | ) 12 | 13 | var logger = log.New(os.Stderr, "", 0) 14 | 15 | func Register(router *cli.Router) { 16 | router.RegisterFunc("hetzner/servers/list", ListServers, "list servers") 17 | router.Register("hetzner/servers/describe", &DescribeServer{}, "describe server") 18 | router.Register("hetzner/servers/rename", &RenameServer{}, "rename server") 19 | } 20 | 21 | type DescribeServer struct { 22 | IP string `cli:"type=arg required=true"` 23 | } 24 | 25 | func (a *DescribeServer) Run() error { 26 | account, e := hetzner.AccountFromEnv() 27 | if e != nil { 28 | return e 29 | } 30 | 31 | server, e := account.LoadServer(a.IP) 32 | if e != nil { 33 | return e 34 | } 35 | table := gocli.NewTable() 36 | table.Add("IP", server.ServerIp) 37 | table.Add("Number", server.ServerNumber) 38 | table.Add("Name", server.ServerName) 39 | table.Add("Product", server.Product) 40 | table.Add("DataCenter", server.Dc) 41 | table.Add("Status", server.Status) 42 | table.Add("Reset", server.Reset) 43 | table.Add("Rescue", server.Rescue) 44 | table.Add("VNC", server.Vnc) 45 | fmt.Println(table) 46 | return nil 47 | } 48 | 49 | func ListServers() error { 50 | account, e := hetzner.AccountFromEnv() 51 | if e != nil { 52 | return e 53 | } 54 | servers, e := account.Servers() 55 | if e != nil { 56 | return e 57 | } 58 | table := gocli.NewTable() 59 | table.Add("Number", "Name", "Product", "DC", "Ip", "Status") 60 | for _, server := range servers { 61 | table.Add(server.ServerNumber, server.ServerName, server.Product, server.Dc, server.ServerIp, server.Status) 62 | } 63 | fmt.Println(table) 64 | return nil 65 | } 66 | 67 | type RenameServer struct { 68 | Ip string `cli:"type=arg required=true"` 69 | NewName string `cli:"type=arg required=true"` 70 | } 71 | 72 | func (a *RenameServer) Run() error { 73 | account, e := hetzner.AccountFromEnv() 74 | if e != nil { 75 | return e 76 | } 77 | logger.Printf("renaming servers %s to %s", a.Ip, a.NewName) 78 | return account.RenameServer(a.Ip, a.NewName) 79 | } 80 | -------------------------------------------------------------------------------- /cli/profitbricks/profitbricks.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "github.com/dynport/dgtk/cli" 5 | "github.com/dynport/gocloud/profitbricks/actions" 6 | ) 7 | 8 | func Register(router *cli.Router) { 9 | router.Register("pb/dcs/describe", &actions.DescribeDataCenterHandler{}, "Describe Data Center") 10 | router.RegisterFunc("pb/dcs/list", actions.ListAllDataCentersHandler, "List All DataCenters") 11 | router.Register("pb/servers/start", &actions.StartServer{}, "Start Server") 12 | router.Register("pb/servers/stop", &actions.StopServer{}, "Stop Server") 13 | router.Register("pb/servers/delete", &actions.DeleteServer{}, "Delete Server") 14 | router.RegisterFunc("pb/servers/list", actions.ListAllServersHandler, "List All Servers") 15 | router.Register("pb/servers/create", &actions.CreateServer{}, "Create Server") 16 | router.Register("pb/storages/delete", &actions.DeleteStorage{}, "Delete Storage") 17 | router.Register("pb/storages/create", &actions.CreateStorage{}, "Create Storage") 18 | router.RegisterFunc("pb/storages/list", actions.ListAllStorages, "List All Storages") 19 | router.RegisterFunc("pb/snapshots/list", actions.ListAllSnapshotsHandler, "List all snapshots") 20 | router.Register("pb/snapshots/rollback", &actions.RollbackSnapshotHandler{}, "Rollback Snapshot") 21 | router.RegisterFunc("pb/images/list", actions.ListAllImagesHandler, "List images") 22 | } 23 | -------------------------------------------------------------------------------- /cmd/digo2/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go get ./... 3 | -------------------------------------------------------------------------------- /cmd/digo2/droplet_create.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/dynport/gocloud/digitalocean/v2/digitalocean" 7 | ) 8 | 9 | type dropletCreate struct { 10 | Name string `cli:"opt --name required"` // required 11 | Region string `cli:"opt --region required"` // required 12 | Size string `cli:"opt --size required"` // required 13 | Image string `cli:"opt --image required"` // required 14 | SshKeys string `cli:"opt --ssh-keys"` 15 | Backups bool `cli:"opt --backups"` 16 | IPv6 bool `cli:"opt --ipv6"` 17 | PrivateNetworking bool `cli:"opt --private-networking"` 18 | } 19 | 20 | func (r *dropletCreate) Run() error { 21 | cl, e := client() 22 | if e != nil { 23 | return e 24 | } 25 | a := digitalocean.CreateDroplet{ 26 | Name: r.Name, 27 | Region: r.Region, 28 | Size: r.Size, 29 | Image: r.Image, 30 | Backups: r.Backups, 31 | IPv6: r.IPv6, 32 | PrivateNetworking: r.PrivateNetworking, 33 | } 34 | if r.SshKeys != "" { 35 | a.SshKeys = strings.Split(r.SshKeys, ",") 36 | } 37 | 38 | rsp, e := a.Execute(cl) 39 | if e != nil { 40 | return e 41 | } 42 | printDroplet(rsp.Droplet) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /cmd/digo2/droplet_delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type dropletDelete struct { 4 | Id string `cli:"arg required"` 5 | } 6 | 7 | func (r *dropletDelete) Run() error { 8 | cl, e := client() 9 | if e != nil { 10 | return e 11 | } 12 | return cl.DropletDelete(r.Id) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/digo2/droplet_reboot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type dropletReboot struct { 4 | ID string `cli:"arg required"` 5 | } 6 | 7 | func (r *dropletReboot) Run() error { 8 | cl, err := client() 9 | if err != nil { 10 | return err 11 | } 12 | return cl.RebootDroplet(r.ID) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/digo2/droplet_show.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dynport/gocli" 7 | "github.com/dynport/gocloud/digitalocean/v2/digitalocean" 8 | ) 9 | 10 | type dropletShow struct { 11 | Id string `cli:"arg required"` 12 | } 13 | 14 | func (r *dropletShow) Run() error { 15 | cl, e := client() 16 | if e != nil { 17 | return e 18 | } 19 | rsp, e := cl.Droplet(r.Id) 20 | if e != nil { 21 | return e 22 | } 23 | printDroplet(rsp.Droplet) 24 | return nil 25 | } 26 | 27 | func printDroplet(droplet *digitalocean.Droplet) { 28 | t := gocli.NewTable() 29 | t.Add("Id", droplet.Id) 30 | t.Add("Name", droplet.Name) 31 | t.Add("Status", droplet.Status) 32 | t.Add("Locked", fmt.Sprintf("%t", droplet.Locked)) 33 | t.Add("CreatedAt", droplet.CreatedAt.Format("2006-01-02 15:04:05")) 34 | t.Add("Size", droplet.Size.Slug) 35 | t.Add("Region", droplet.Region.Name) 36 | t.Add("Image", droplet.Image.Name) 37 | for i, ip := range droplet.Networks.V4 { 38 | t.Add(fmt.Sprintf("IP %d", i+1), ip.IpAddress, ip.Type) 39 | } 40 | fmt.Println(t) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/digo2/droplets_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dynport/gocli" 7 | ) 8 | 9 | type dropletsList struct { 10 | } 11 | 12 | func (r *dropletsList) Run() error { 13 | cl, e := client() 14 | if e != nil { 15 | return e 16 | } 17 | rsp, e := cl.Droplets() 18 | if e != nil { 19 | return e 20 | } 21 | t := gocli.NewTable() 22 | t.Add("Id", "Status", "IP", "Private IP", "Name", "Region", "Size", "ImageId:ImageName (ImageSlug)", "CreatedAt") 23 | for _, d := range rsp.Droplets { 24 | imageName := fmt.Sprintf("%d:%s", d.Image.Id, d.Image.Name) 25 | if d.Image.Slug != "" { 26 | imageName += " (" + d.Image.Slug + ")" 27 | } 28 | var public, private string 29 | if d.Networks != nil { 30 | for _, i := range d.Networks.V4 { 31 | switch i.Type { 32 | case "public": 33 | public = i.IpAddress 34 | case "private": 35 | private = i.IpAddress 36 | 37 | } 38 | } 39 | } 40 | reg := func() string { 41 | if d.Region != nil { 42 | return d.Region.Slug 43 | } 44 | return "" 45 | }() 46 | created := func() string { 47 | if !d.CreatedAt.IsZero() { 48 | return d.CreatedAt.Format("2006-01-02 15:04:05") 49 | } 50 | return "" 51 | }() 52 | size := func() string { 53 | if d.Size != nil { 54 | return d.Size.Slug 55 | } 56 | return d.SizeSlug 57 | }() 58 | t.Add(d.Id, d.Status, public, private, d.Name, reg, size, imageName, created) 59 | } 60 | fmt.Println(t) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/digo2/images_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dynport/gocli" 8 | ) 9 | 10 | type imagesList struct { 11 | Page int `cli:"opt --page"` 12 | } 13 | 14 | func (r *imagesList) Run() error { 15 | cl, e := client() 16 | if e != nil { 17 | return e 18 | } 19 | rsp, e := cl.Images(r.Page) 20 | if e != nil { 21 | return e 22 | } 23 | t := gocli.NewTable() 24 | for _, i := range rsp.Images { 25 | t.Add(i.Id, i.Slug, i.Name, strings.Join(i.Regions, ",")) 26 | } 27 | fmt.Println(t) 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /cmd/digo2/keys_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dynport/gocli" 7 | ) 8 | 9 | type keysList struct { 10 | } 11 | 12 | func (r *keysList) Run() error { 13 | cl, e := client() 14 | if e != nil { 15 | return e 16 | } 17 | rsp, e := cl.Keys() 18 | if e != nil { 19 | return e 20 | } 21 | t := gocli.NewTable() 22 | for _, k := range rsp.SshKeys { 23 | t.Add(k.Id, k.Name, fmt.Sprintf("%.64q...", k.PublicKey)) 24 | } 25 | fmt.Println(t) 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /cmd/digo2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/dynport/dgtk/cli" 8 | "github.com/dynport/gocloud/digitalocean/v2/digitalocean" 9 | ) 10 | 11 | var logger = log.New(os.Stderr, "", 0) 12 | 13 | func main() { 14 | router := cli.NewRouter() 15 | router.Register("images/list", &imagesList{}, "List Images") 16 | router.Register("regions/list", ®ionsList{}, "List Regions") 17 | router.Register("keys/list", &keysList{}, "List Keys") 18 | router.Register("droplets/delete", &dropletDelete{}, "Delete Droplet") 19 | router.Register("droplets/list", &dropletsList{}, "List Droplets") 20 | router.Register("droplets/show", &dropletShow{}, "Show Droplet") 21 | router.Register("droplets/create", &dropletCreate{}, "Create Droplet") 22 | router.Register("droplets/reboot", &dropletReboot{}, "Reboot Droplet") 23 | switch e := router.RunWithArgs(); e { 24 | case nil, cli.ErrorHelpRequested, cli.ErrorNoRoute: 25 | // ignore 26 | return 27 | default: 28 | logger.Fatal(e) 29 | } 30 | } 31 | 32 | func client() (*digitalocean.Client, error) { 33 | return digitalocean.NewFromEnv() 34 | } 35 | -------------------------------------------------------------------------------- /cmd/digo2/regions_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dynport/gocli" 7 | ) 8 | 9 | type regionsList struct { 10 | } 11 | 12 | func (r *regionsList) Run() error { 13 | cl, e := client() 14 | if e != nil { 15 | return e 16 | } 17 | rsp, e := cl.Regions() 18 | if e != nil { 19 | return e 20 | } 21 | t := gocli.NewTable() 22 | for _, r := range rsp.Regions { 23 | t.Add(r.Slug, r.Name, r.Available) 24 | } 25 | fmt.Println(t) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /digitalocean/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | go install github.com/dynport/gocloud/digitalocean 3 | -------------------------------------------------------------------------------- /digitalocean/account_test.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestAccount(t *testing.T) { 10 | Convey("Account", t, func() { 11 | account := &Account{RegionId: 1122, SizeId: 10} 12 | So(account, ShouldNotBeNil) 13 | droplet := account.DefaultDroplet() 14 | So(droplet, ShouldNotBeNil) 15 | So(account.RegionId, ShouldEqual, 1122) 16 | So(account.SizeId, ShouldEqual, 10) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /digitalocean/config.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type Config struct { 11 | Accounts []*Account 12 | } 13 | 14 | func LoadAccount(name string) (account *Account, e error) { 15 | config, e := LoadConfig() 16 | if e != nil { 17 | return nil, e 18 | } 19 | return config.Account(name) 20 | } 21 | 22 | func LoadConfig() (config *Config, e error) { 23 | b, e := ioutil.ReadFile(os.Getenv("HOME") + "/.digitalocean") 24 | if e != nil { 25 | return 26 | } 27 | config = &Config{} 28 | e = json.Unmarshal(b, config) 29 | return config, e 30 | } 31 | 32 | func (c *Config) Account(name string) (account *Account, e error) { 33 | for _, account := range c.Accounts { 34 | if account.Name == name { 35 | return account, nil 36 | } 37 | } 38 | return nil, errors.New("no account found for " + name) 39 | } 40 | -------------------------------------------------------------------------------- /digitalocean/constants.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | var ( 4 | GITCOMMIT string 5 | ) 6 | 7 | const ( 8 | VERSION = "0.1.3" 9 | IMAGE_UBUNTU_13_04_64BIT = 350076 10 | REGION_SF1 = 3 11 | SIZE_512M = 66 12 | ) 13 | -------------------------------------------------------------------------------- /digitalocean/digital_ocean.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | //"os" 5 | //"github.com/dynport/gologger" 6 | ) 7 | 8 | const API_ROOT = "https://api.digitalocean.com" 9 | -------------------------------------------------------------------------------- /digitalocean/env.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | var logger = log.New(os.Stderr, "", 0) 11 | 12 | func debugStream() io.Writer { 13 | if os.Getenv("DEBUG") == "true" { 14 | return os.Stderr 15 | } 16 | return ioutil.Discard 17 | } 18 | 19 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 20 | -------------------------------------------------------------------------------- /digitalocean/image.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | type Image struct { 10 | Id int `json:"id"` 11 | Name string `json:"name"` 12 | Distribution string `json:"distribution"` 13 | } 14 | 15 | type ImagesReponse struct { 16 | Status string `json:"status"` 17 | Images []*Image `json:"images"` 18 | } 19 | 20 | type ImageResponse struct { 21 | Status string `json:"status"` 22 | Image *Image `json:"image"` 23 | } 24 | 25 | func (self *Account) Images() (images []*Image, e error) { 26 | imagesReponse := &ImagesReponse{} 27 | e = self.loadResource("/images", imagesReponse, cacheFor(24*time.Hour)) 28 | if e != nil { 29 | return nil, e 30 | } 31 | images = imagesReponse.Images 32 | return images, nil 33 | } 34 | 35 | var cachedPath = os.Getenv("HOME") + "/.gocloud/cache/digitalocean" 36 | 37 | func (self *Account) GetImage(id int) (image *Image, e error) { 38 | imageReponse := &ImageResponse{} 39 | e = self.loadResource("/images/"+strconv.Itoa(id), imageReponse, cacheFor(24*time.Hour)) 40 | image = imageReponse.Image 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /digitalocean/pricing.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "github.com/dynport/gocloud" 5 | ) 6 | 7 | var megaToGiga = 1024 8 | 9 | var Plans = []*gocloud.Plan{ 10 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 0.7}, MemoryInMB: 512, Cores: 1, DiskInGB: 20, TrafficInTB: 1}, 11 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 1.5}, MemoryInMB: 1 * megaToGiga, Cores: 1, DiskInGB: 30, TrafficInTB: 2}, 12 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 3}, MemoryInMB: 2 * megaToGiga, Cores: 2, DiskInGB: 40, TrafficInTB: 3}, 13 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 6}, MemoryInMB: 4 * megaToGiga, Cores: 2, DiskInGB: 60, TrafficInTB: 4}, 14 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 11.9}, MemoryInMB: 8 * megaToGiga, Cores: 4, DiskInGB: 80, TrafficInTB: 5}, 15 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 23.8}, MemoryInMB: 16 * megaToGiga, Cores: 8, DiskInGB: 160, TrafficInTB: 6}, 16 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 47.6}, MemoryInMB: 32 * megaToGiga, Cores: 12, DiskInGB: 320, TrafficInTB: 7}, 17 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 70.5}, MemoryInMB: 48 * megaToGiga, Cores: 16, DiskInGB: 480, TrafficInTB: 8}, 18 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 94.1}, MemoryInMB: 64 * megaToGiga, Cores: 20, DiskInGB: 640, TrafficInTB: 9}, 19 | {Price: &gocloud.Price{PerHour: true, Currency: gocloud.USD, Amount: 141.1}, MemoryInMB: 96 * megaToGiga, Cores: 24, DiskInGB: 960, TrafficInTB: 10}, 20 | } 21 | -------------------------------------------------------------------------------- /digitalocean/region.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type RegionResponse struct { 8 | Status string `json:"status"` 9 | Regions []*Region `json:"regions"` 10 | } 11 | 12 | type Region struct { 13 | Id int `json:"id"` 14 | Name string `json:"name"` 15 | } 16 | 17 | func (self *Account) Regions() (regions []*Region, e error) { 18 | regionResponse := &RegionResponse{} 19 | e = self.loadResource("/regions", regionResponse, cacheFor(24*time.Hour)) 20 | regions = regionResponse.Regions 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /digitalocean/resource.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "path" 11 | "time" 12 | ) 13 | 14 | type resource struct { 15 | Url string 16 | CachedAt time.Time 17 | Content []byte 18 | } 19 | 20 | func (r *resource) load(opts *fetchOptions) (e error) { 21 | if opts != nil { 22 | if e := r.loadCached(opts); e == nil { 23 | dbg.Printf("loaded cached resource (expires in %s)", r.CachedAt.Add(opts.ttl).Sub(time.Now()).String()) 24 | return nil 25 | } else { 26 | dbg.Printf(e.Error()) 27 | } 28 | } 29 | rsp, e := http.Get(r.Url) 30 | if e != nil { 31 | return e 32 | } 33 | defer rsp.Body.Close() 34 | r.Content, e = ioutil.ReadAll(rsp.Body) 35 | if e != nil { 36 | return e 37 | } 38 | if opts != nil { 39 | if e := r.store(); e != nil { 40 | logger.Printf("err=%q", e) 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | func (r *resource) store() error { 47 | r.CachedAt = time.Now() 48 | p := r.path() 49 | os.Remove(p) 50 | if e := os.MkdirAll(path.Dir(p), 0755); e != nil { 51 | return e 52 | } 53 | f, e := os.Create(p) 54 | if e != nil { 55 | return e 56 | } 57 | defer f.Close() 58 | return json.NewEncoder(f).Encode(r) 59 | } 60 | 61 | func (r *resource) path() string { 62 | hash := md5.New() 63 | hash.Write([]byte(r.Url)) 64 | sum := fmt.Sprintf("%x", hash.Sum(nil)) 65 | return cachedPath + "/" + sum + ".json" 66 | } 67 | 68 | func (r *resource) loadCached(opts *fetchOptions) error { 69 | p := r.path() 70 | stat, e := os.Stat(p) 71 | if e != nil { 72 | return e 73 | } 74 | f, e := os.Open(p) 75 | if e != nil { 76 | return e 77 | } 78 | defer f.Close() 79 | e = json.NewDecoder(f).Decode(r) 80 | if e != nil { 81 | return e 82 | } 83 | dbg.Printf("%v %v", r.CachedAt, opts.ttl.String()) 84 | if time.Now().After(stat.ModTime().Add(opts.ttl)) { 85 | dbg.Printf("resource expired") 86 | return fmt.Errorf("expired") 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /digitalocean/size.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type Size struct { 4 | Id int `json:"id"` 5 | Name string `json:"name"` 6 | } 7 | 8 | type SizeResponse struct { 9 | Status string `json:"status"` 10 | Sizes []*Size `json:"sizes"` 11 | } 12 | -------------------------------------------------------------------------------- /digitalocean/ssh_key.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type SshKey struct { 4 | Id int `json:"id"` 5 | Name string `json:"name"` 6 | } 7 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/client.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type DropletsList struct { 13 | } 14 | 15 | type Client struct { 16 | *http.Client 17 | } 18 | 19 | var root = "https://api.digitalocean.com" 20 | 21 | func (c *Client) loadResponse(path string, i interface{}) error { 22 | rsp, e := c.Get(root + "/" + strings.TrimPrefix(path, "/")) 23 | if e != nil { 24 | return e 25 | } 26 | defer rsp.Body.Close() 27 | b, e := ioutil.ReadAll(rsp.Body) 28 | if e != nil { 29 | return e 30 | } 31 | if rsp.Status[0] != '2' { 32 | return fmt.Errorf("expected status 2xx, got %s: %s", rsp.Status, string(b)) 33 | } 34 | dbg.Printf("%s", string(b)) 35 | return json.Unmarshal(b, &i) 36 | } 37 | 38 | func New(token string) (*Client, error) { 39 | if token == "" { 40 | return nil, fmt.Errorf("token must be set") 41 | } 42 | return &Client{Client: &http.Client{Transport: &transport{apiToken: token}}}, nil 43 | } 44 | 45 | func NewFromEnv() (*Client, error) { 46 | token := os.Getenv("DIGITAL_OCEAN_API_KEY") 47 | if token == "" { 48 | return nil, fmt.Errorf("DIGITAL_OCEAN_API_KEY must be set in env") 49 | } 50 | return New(token) 51 | } 52 | 53 | type transport struct { 54 | apiToken string 55 | } 56 | 57 | func (c *transport) RoundTrip(r *http.Request) (*http.Response, error) { 58 | r.Header.Set("Authorization", "Bearer "+c.apiToken) 59 | return http.DefaultClient.Do(r) 60 | } 61 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/dbg.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 18 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/domains.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type Domain struct { 4 | Name string `json:"name,omitempty"` 5 | Ttl string `json:"ttl,omitempty"` 6 | ZoneFile string `json:"zone_file,omitempty"` 7 | } 8 | 9 | type DomainsResponse struct { 10 | Domains []*Domain `json:"domain,omitempty"` 11 | Meta *Meta `json:"meta,omitempty"` 12 | } 13 | 14 | func (client *Client) Domains() (*DomainsResponse, error) { 15 | rsp := &DomainsResponse{} 16 | e := client.loadResponse("/v2/domains", &rsp) 17 | return rsp, e 18 | } 19 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/images.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import "fmt" 4 | 5 | type ImagesResponse struct { 6 | Images []*Image `json:"images,omitempty"` 7 | Meta *Meta `json:"meta,omitempty"` 8 | } 9 | 10 | type ImageResponse struct { 11 | Image *Image `json:"image,omitempty"` 12 | } 13 | 14 | func (client *Client) Images(page int) (*ImagesResponse, error) { 15 | rsp := &ImagesResponse{} 16 | p := "/v2/images" 17 | if page > 1 { 18 | p += fmt.Sprintf("?page=%d", page) 19 | } 20 | e := client.loadResponse(p, rsp) 21 | return rsp, e 22 | } 23 | 24 | func (client *Client) Image(idOrSlug string) (*ImageResponse, error) { 25 | rsp := &ImageResponse{} 26 | e := client.loadResponse("/v2/images/"+idOrSlug, rsp) 27 | return rsp, e 28 | } 29 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/keys.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type KeysResponse struct { 4 | SshKeys []*SshKey `json:"ssh_keys"` 5 | } 6 | 7 | type SshKey struct { 8 | Id int `json:"id,omitempty"` 9 | Fingerprint string `json:"fingerprint,omitempty"` 10 | PublicKey string `json:"public_key,omitempty"` 11 | Name string `json:"name,omitempty"` 12 | } 13 | 14 | func (client *Client) Keys() (*KeysResponse, error) { 15 | rsp := &KeysResponse{} 16 | e := client.loadResponse("/v2/account/keys", rsp) 17 | return rsp, e 18 | } 19 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/reboot_droplet.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func (c *Client) RebootDroplet(id string) error { 11 | req, err := http.NewRequest("POST", root+"/v2/droplets/"+id+"/actions", strings.NewReader(`{"type": "reboot"}`)) 12 | if err != nil { 13 | return err 14 | } 15 | req.Header.Set("Content-Type", "application/json") 16 | rsp, err := c.Do(req) 17 | if err != nil { 18 | return err 19 | } 20 | defer rsp.Body.Close() 21 | if rsp.Status[0] != '2' { 22 | b, _ := ioutil.ReadAll(rsp.Body) 23 | return fmt.Errorf("got status %s but expected 2x. body=%s", rsp.Status, string(b)) 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/regions.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type RegionsResponse struct { 4 | Regions []*Region `json:"regions,omitempty"` 5 | } 6 | 7 | func (client *Client) Regions() (*RegionsResponse, error) { 8 | rsp := &RegionsResponse{} 9 | e := client.loadResponse("/v2/regions", rsp) 10 | return rsp, e 11 | } 12 | -------------------------------------------------------------------------------- /digitalocean/v2/digitalocean/sizes.go: -------------------------------------------------------------------------------- 1 | package digitalocean 2 | 3 | type SizesResponse struct { 4 | Sizes []*Size `json:"sizes,omitempty"` 5 | } 6 | -------------------------------------------------------------------------------- /gocloud/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go get github.com/dynport/gocloud/gocloud 3 | -------------------------------------------------------------------------------- /gocloud/compare.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/gocli" 6 | aws "github.com/dynport/gocloud/aws/pricing" 7 | "github.com/dynport/gocloud/digitalocean" 8 | _ "github.com/dynport/gocloud/jiffybox" 9 | _ "github.com/dynport/gocloud/profitbricks" 10 | ) 11 | 12 | func init() { 13 | router.RegisterFunc("compare", Compare, "Compare Cloud Providers") 14 | } 15 | 16 | func Compare() error { 17 | table := gocli.NewTable() 18 | for _, plan := range digitalocean.Plans { 19 | table.Add(plan.Cores, fmt.Sprintf("%.1f GB", float64(plan.MemoryInMB)/1024.0)) 20 | } 21 | 22 | pricing, e := aws.LinuxOnDemand() 23 | if e != nil { 24 | return e 25 | } 26 | counts := map[string]int{} 27 | for _, region := range pricing.Config.Regions { 28 | for _, typ := range region.InstanceTypes { 29 | for _, size := range typ.Sizes { 30 | for _, vc := range size.ValueColumns { 31 | counts[size.Size]++ 32 | table.Add(region.Region, typ.Type, size.Size, vc.Name, vc.Prices["USD"]) 33 | } 34 | } 35 | } 36 | } 37 | fmt.Println(table) 38 | fmt.Println(counts) 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /gocloud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/dynport/dgtk/cli" 8 | "github.com/dynport/gocloud/cli/aws/cloudformation" 9 | "github.com/dynport/gocloud/cli/aws/cloudwatch" 10 | "github.com/dynport/gocloud/cli/aws/ec2" 11 | "github.com/dynport/gocloud/cli/aws/elb" 12 | "github.com/dynport/gocloud/cli/aws/iam" 13 | "github.com/dynport/gocloud/cli/aws/route53" 14 | "github.com/dynport/gocloud/cli/digitalocean" 15 | "github.com/dynport/gocloud/cli/hetzner" 16 | "github.com/dynport/gocloud/cli/jiffybox" 17 | "github.com/dynport/gocloud/cli/profitbricks" 18 | ) 19 | 20 | var router = cli.NewRouter() 21 | 22 | func init() { 23 | ec2.Register(router) 24 | cloudformation.Register(router) 25 | elb.Register(router) 26 | digitalocean.Register(router) 27 | hetzner.Register(router) 28 | jiffybox.Register(router) 29 | route53.Register(router) 30 | iam.Register(router) 31 | cloudwatch.Register(router) 32 | profitbricks.Register(router) 33 | } 34 | 35 | func init() { 36 | log.SetFlags(0) 37 | } 38 | 39 | func main() { 40 | if e := router.RunWithArgs(); e != nil { 41 | if e != cli.ErrorNoRoute { 42 | log.Println("ERROR: " + e.Error()) 43 | } 44 | os.Exit(1) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /hetzner/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go get github.com/dynport/gocloud/hetzner 3 | -------------------------------------------------------------------------------- /hetzner/plans.go: -------------------------------------------------------------------------------- 1 | package hetzner 2 | 3 | import ( 4 | "github.com/dynport/gocloud" 5 | ) 6 | 7 | func NewHetznerPrice(setup, amount float64) *gocloud.Price { 8 | return &gocloud.Price{ 9 | Amount: amount, 10 | Setup: setup, 11 | Currency: gocloud.EUR, 12 | } 13 | } 14 | 15 | var Plans = []*gocloud.Plan{ 16 | {HyperThreading: true, Name: "EX40", Cpu: "i7-4770", Price: NewHetznerPrice(49, 49), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 4096}, 17 | {HyperThreading: true, Name: "EX40-SSD", Cpu: "i7-4770", Price: NewHetznerPrice(59, 59), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 480}, 18 | {HyperThreading: true, Name: "EX60", Cpu: "i7-920", Price: NewHetznerPrice(59, 0), Cores: 4, MemoryInMB: 1024 * 48, DiskInGB: 1024 * 4096}, 19 | {HyperThreading: true, Name: "PX60", Cpu: "E3-1270v3", Price: NewHetznerPrice(69, 99), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 4096}, 20 | {HyperThreading: true, Name: "PX60-SSD", Cpu: "E3-1270v3", Price: NewHetznerPrice(79, 99), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 480}, 21 | {HyperThreading: true, Name: "PX70", Cpu: "E3-1270v3", Price: NewHetznerPrice(79, 99), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 8192}, 22 | {HyperThreading: true, Name: "PX70-SSD", Cpu: "E3-1270v3", Price: NewHetznerPrice(99, 99), Cores: 4, MemoryInMB: 1024 * 32, DiskInGB: 1024 * 960}, 23 | {HyperThreading: true, Name: "PX90", Cpu: "E5-1650v2", Price: NewHetznerPrice(99, 99), Cores: 6, MemoryInMB: 1024 * 64, DiskInGB: 1024 * 4096}, 24 | {HyperThreading: true, Name: "PX90-SSD", Cpu: "E5-1650v2", Price: NewHetznerPrice(109, 109), Cores: 6, MemoryInMB: 1024 * 64, DiskInGB: 1024 * 480}, 25 | {HyperThreading: true, Name: "PX120", Cpu: "E5-1650v2", Price: NewHetznerPrice(129, 129), Cores: 6, MemoryInMB: 1024 * 128, DiskInGB: 1024 * 4096}, 26 | {HyperThreading: true, Name: "PX120-SSD", Cpu: "E5-1650v2", Price: NewHetznerPrice(139, 139), Cores: 6, MemoryInMB: 1024 * 128, DiskInGB: 1024 * 480}, 27 | {HyperThreading: true, Name: "DX150", Cpu: "E5-2620", Price: NewHetznerPrice(159, 199), Cores: 6, MemoryInMB: 1024 * 64, DiskInGB: 1024 * 0}, 28 | {HyperThreading: true, Name: "DX290", Cpu: "E5-2620", Price: NewHetznerPrice(299, 199), Cores: 12, MemoryInMB: 1024 * 128, DiskInGB: 1024 * 0}, 29 | } 30 | -------------------------------------------------------------------------------- /jiffybox/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -v 3 | -------------------------------------------------------------------------------- /jiffybox/backups.go: -------------------------------------------------------------------------------- 1 | package jiffybox 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | type Backup struct { 9 | ServerId int 10 | Key string 11 | Id string `json:"id"` 12 | Created int64 `json:"created"` 13 | } 14 | 15 | func (backup *Backup) CreatedAt() time.Time { 16 | return time.Unix(backup.Created, 0) 17 | } 18 | 19 | type BackupsResponse struct { 20 | Message []*Message `json:"message"` 21 | Result map[string]map[string]*Backup `json:"result"` 22 | } 23 | 24 | func (client *Client) CreateBackup(id int) error { 25 | httpResponse, e := client.PostForm("backups/"+strconv.Itoa(id), nil) 26 | defer httpResponse.Response.Body.Close() 27 | if e != nil { 28 | return e 29 | } 30 | defer httpResponse.Response.Body.Close() 31 | rsp := &ErrorResponse{} 32 | e = client.unmarshal(httpResponse.Content, rsp) 33 | if e != nil { 34 | return e 35 | } 36 | return nil 37 | } 38 | 39 | func (client *Client) Backups() (backups []*Backup, e error) { 40 | rsp := &BackupsResponse{} 41 | e = client.LoadResource("backups", rsp) 42 | if e != nil { 43 | return backups, e 44 | } 45 | for server, backupsHash := range rsp.Result { 46 | for key, backup := range backupsHash { 47 | backup.ServerId, _ = strconv.Atoi(server) 48 | backup.Key = key 49 | backups = append(backups, backup) 50 | } 51 | } 52 | return backups, e 53 | } 54 | 55 | type BackupsForServerResponse struct { 56 | Message []*Message `json:"message"` 57 | Result map[string]*Backup `json:"result"` 58 | } 59 | 60 | func (client *Client) BackupsForServer(id int) (backups []*Backup, e error) { 61 | rsp := BackupsForServerResponse{} 62 | e = client.LoadResource(client.BaseUrl()+"/backups/"+strconv.Itoa(id), rsp) 63 | if e != nil { 64 | return backups, e 65 | } 66 | for key, backup := range rsp.Result { 67 | backup.ServerId = id 68 | backup.Key = key 69 | backups = append(backups, backup) 70 | } 71 | return backups, e 72 | } 73 | -------------------------------------------------------------------------------- /jiffybox/distributions.go: -------------------------------------------------------------------------------- 1 | package jiffybox 2 | 3 | type Distribution struct { 4 | Key string 5 | MinDiskSizeMB int `json:"minDiskSizeMB"` 6 | Name string `json:"name"` 7 | RootdiskMode string `json:"rootdiskMode"` 8 | DefaultKernel string `json:"defaultKernel"` 9 | } 10 | 11 | type DistributionsResponse struct { 12 | DistributionsMap map[string]*Distribution `json:"result"` 13 | } 14 | 15 | type DistributionResponse struct { 16 | Distribution *Distribution `json:"result"` 17 | } 18 | 19 | func (rsp *DistributionsResponse) Distributions() (distributions []*Distribution) { 20 | for key, distribution := range rsp.DistributionsMap { 21 | distribution.Key = key 22 | distributions = append(distributions, distribution) 23 | } 24 | return distributions 25 | } 26 | 27 | func (client *Client) Distributions() (dists []*Distribution, e error) { 28 | rsp := &DistributionsResponse{} 29 | e = client.LoadResource("distributions", rsp) 30 | if e != nil { 31 | return dists, e 32 | } 33 | return rsp.Distributions(), nil 34 | } 35 | 36 | func (client *Client) Distribution(id string) (dist *Distribution, e error) { 37 | rsp := &DistributionResponse{} 38 | e = client.LoadResource("distribution/"+id, rsp) 39 | if e != nil { 40 | return dist, e 41 | } 42 | return rsp.Distribution, nil 43 | } 44 | -------------------------------------------------------------------------------- /jiffybox/distributions_test.go: -------------------------------------------------------------------------------- 1 | package jiffybox 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestDistributions(t *testing.T) { 11 | Convey("Distributions", t, func() { 12 | f := mustReadFixture(t, "distributions.json") 13 | rsp := &DistributionsResponse{} 14 | e := json.Unmarshal(f, rsp) 15 | So(e, ShouldBeNil) 16 | So(rsp, ShouldNotBeNil) 17 | 18 | So(len(rsp.DistributionsMap), ShouldEqual, 2) 19 | So(len(rsp.Distributions()), ShouldEqual, 2) 20 | 21 | dist := rsp.Distributions()[0] 22 | So(dist.Name, ShouldEqual, "CentOS 5.4") 23 | So(dist.Key, ShouldEqual, "centos_5_4_32bit") 24 | 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /jiffybox/fixtures/distributions.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [], 3 | "result": { 4 | "centos_5_4_32bit": { 5 | "minDiskSizeMB": 1024, 6 | "name": "CentOS 5.4", 7 | "rootdiskMode": "ro", 8 | "defaultKernel": "xen-current" 9 | }, 10 | "centos_5_4_64bit": { 11 | "minDiskSizeMB": 1024, 12 | "name": "CentOS 5.4 64-Bit", 13 | "rootdiskMode": "ro", 14 | "defaultKernel": "xen-current-x86_64" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jiffybox/fixtures/error_creating_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages":[ 3 | {"type":"error","message":"Der Name existiert bereits."} 4 | ], 5 | "result":null 6 | } 7 | -------------------------------------------------------------------------------- /jiffybox/fixtures/jiffyBoxes.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [], 3 | "result": { 4 | "12345": { 5 | "id": 12345, 6 | "name": "Test", 7 | "ips": { 8 | "public": ["188.93.14.176"], 9 | "private": ["10.93.14.175"] 10 | }, 11 | "status": "READY", 12 | "created": 1234567890, 13 | "recoverymodeActive": false, 14 | "manualBackupRunning": false, 15 | "isBeingCopied": false, 16 | "running": false, 17 | "host": "vmhost-testsys-2-2-9-1", 18 | "plan": { 19 | "id":22, 20 | "name":"CloudLevel 3", 21 | "diskSizeInMB":307200, 22 | "ramInMB":8192, 23 | "pricePerHour":0.07, 24 | "pricePerHourFrozen":0.02, 25 | "cpus":6 26 | }, 27 | "metadata": { 28 | "createdby": "JiffyBoxTeam" 29 | }, 30 | "activeProfile": { 31 | "name": "Standard", 32 | "created": 1234567890, 33 | "runlevel": "default", 34 | "kernel": "xen-current", 35 | "rootdisk": "\/dev\/xvda", 36 | "rootdiskMode": "ro", 37 | "status": "READY", 38 | "disks": { 39 | "xvda": { 40 | "name": "CentOS 5.4", 41 | "filesystem": "ext3", 42 | "sizeInMB": 81408, 43 | "created": 1234567890, 44 | "status": "READY", 45 | "distribution": "centos_5_4_32bit" 46 | }, 47 | "xvdb": { 48 | "name":" Swap", 49 | "filesystem": "swap", 50 | "sizeInMB": 512, 51 | "created": 1234567890, 52 | "status": "READY" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jiffybox/fixtures/no_module_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [ 3 | { 4 | "type": "error", 5 | "message": "Das Modul test existiert nicht" 6 | } 7 | ], 8 | "result": false 9 | } 10 | -------------------------------------------------------------------------------- /jiffybox/jiffybox_test.go: -------------------------------------------------------------------------------- 1 | package jiffybox 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | 8 | . "github.com/smartystreets/goconvey/convey" 9 | ) 10 | 11 | func mustReadFixture(t *testing.T, name string) []byte { 12 | b, e := ioutil.ReadFile("fixtures/" + name) 13 | if e != nil { 14 | t.Fatal("fixture " + name + " does not exist") 15 | } 16 | return b 17 | } 18 | 19 | func TestJiffyBoxes(t *testing.T) { 20 | Convey("JiffyBoxes", t, func() { 21 | 22 | f := mustReadFixture(t, "jiffyBoxes.json") 23 | 24 | rsp := &JiffyBoxesResponse{} 25 | e := json.Unmarshal(f, rsp) 26 | So(e, ShouldBeNil) 27 | So(len(rsp.Messages), ShouldEqual, 0) 28 | 29 | So(len(rsp.Servers()), ShouldEqual, 1) 30 | 31 | server := rsp.Server() 32 | So(server.Id, ShouldEqual, 12345) 33 | So(server.Name, ShouldEqual, "Test") 34 | So(len(server.Ips), ShouldEqual, 2) 35 | 36 | public := server.Ips["public"] 37 | 38 | So(public[0], ShouldEqual, "188.93.14.176") 39 | So(server.Status, ShouldEqual, "READY") 40 | 41 | plan := server.Plan 42 | So(plan.Id, ShouldEqual, 22) 43 | So(plan.Name, ShouldEqual, "CloudLevel 3") 44 | So(plan.RamInMB, ShouldEqual, 8192) 45 | 46 | So(server.Metadata["createdby"], ShouldEqual, "JiffyBoxTeam") 47 | ap := server.ActiveProfile 48 | So(ap.Name, ShouldEqual, "Standard") 49 | So(ap.Created, ShouldEqual, 1234567890) 50 | 51 | So(len(ap.DisksHash), ShouldEqual, 2) 52 | So(len(ap.Disks()), ShouldEqual, 2) 53 | 54 | disk := ap.DisksHash["xvda"] 55 | 56 | So(disk.Name, ShouldEqual, "CentOS 5.4") 57 | So(disk.SizeInMB, ShouldEqual, 81408) 58 | }) 59 | } 60 | 61 | func TestUnmarshalling(t *testing.T) { 62 | Convey("Unmarshalling", t, func() { 63 | f := mustReadFixture(t, "error_creating_response.json") 64 | rsp := &ErrorResponse{} 65 | e := json.Unmarshal(f, rsp) 66 | So(e, ShouldBeNil) 67 | 68 | f = mustReadFixture(t, "no_module_response.json") 69 | rsp = &ErrorResponse{} 70 | e = json.Unmarshal(f, rsp) 71 | So(e, ShouldBeNil) 72 | t.Log(rsp.Result) 73 | So(rsp, ShouldNotBeNil) 74 | }) 75 | 76 | } 77 | -------------------------------------------------------------------------------- /jiffybox/plans.go: -------------------------------------------------------------------------------- 1 | package jiffybox 2 | 3 | type Plan struct { 4 | Id int `json:"id"` //22, 5 | Name string `json:"name"` //"CloudLevel 3", 6 | DiskSizeInMB int `json:"diskSizeInMB"` //307200, 7 | RamInMB int `json:"ramInMB"` //8192, 8 | PricePerHour float64 `json:"pricePerHour"` //0.07, 9 | PricePerHourFrozen float64 `json:"pricePerHourFrozen"` //0.02, 10 | Cpus int `json:"cpus"` //6 11 | } 12 | 13 | type PlansResponse struct { 14 | Messages []string `json:"messages"` 15 | PlansMap map[string]*Plan `json:"result"` 16 | } 17 | 18 | type PlanResponse struct { 19 | Messages []string `json:"messages"` 20 | Plan *Plan `json:"result"` 21 | } 22 | 23 | func (rsp *PlansResponse) Plans() (plans []*Plan) { 24 | for _, plan := range rsp.PlansMap { 25 | plans = append(plans, plan) 26 | } 27 | return plans 28 | } 29 | 30 | func (client *Client) Plans() (plans []*Plan, e error) { 31 | rsp := &PlansResponse{} 32 | e = client.LoadResource("plans", rsp) 33 | if e != nil { 34 | return plans, e 35 | } 36 | return rsp.Plans(), e 37 | } 38 | 39 | func (client *Client) Plan(id string) (plan *Plan, e error) { 40 | rsp := &PlanResponse{} 41 | e = client.LoadResource("plans/"+id, rsp) 42 | if e != nil { 43 | return plan, e 44 | } 45 | return rsp.Plan, nil 46 | } 47 | -------------------------------------------------------------------------------- /plans.go: -------------------------------------------------------------------------------- 1 | package gocloud 2 | 3 | const USD = "USD" 4 | const EUR = "EUR" 5 | 6 | type Price struct { 7 | Amount float64 8 | Currency string 9 | PerHour bool 10 | Setup float64 11 | } 12 | 13 | type Plan struct { 14 | Name string 15 | MemoryInMB int 16 | Cores int 17 | DiskInGB int 18 | TrafficInTB int 19 | Cpu string 20 | HyperThreading bool 21 | Price *Price 22 | } 23 | 24 | func (plan *Plan) PricePerCore() *Price { 25 | return plan.pricePer(plan.Cores) 26 | } 27 | 28 | func (plan *Plan) PricePerGbRam() *Price { 29 | return plan.pricePer(plan.MemoryInMB / 1024.0) 30 | } 31 | 32 | func (plan *Plan) pricePer(value int) *Price { 33 | return &Price{ 34 | Amount: plan.Price.Amount / float64(value), 35 | PerHour: plan.Price.PerHour, 36 | Currency: plan.Price.Currency, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /profitbricks/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go build 3 | 4 | test: 5 | go test -v 6 | -------------------------------------------------------------------------------- /profitbricks/actions.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type UpdateServerRequest struct { 4 | } 5 | 6 | type GetAllDataCenters struct { 7 | } 8 | -------------------------------------------------------------------------------- /profitbricks/actions/dcs.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/gocli" 6 | "github.com/dynport/gocloud/profitbricks" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | ENV_PROFITBRICKS_DEFAULT_DATA_CENTER_ID = "PROFITBRICKS_DEFAULT_DC_ID" 12 | ) 13 | 14 | var DescribeDataCenter *gocli.Action 15 | 16 | type DescribeDataCenterHandler struct { 17 | DataCenterId string `cli:"type=arg required=true"` 18 | } 19 | 20 | func (a *DescribeDataCenterHandler) Run() error { 21 | client := profitbricks.NewFromEnv() 22 | dc, e := client.GetDataCenter(a.DataCenterId) 23 | if e != nil { 24 | return e 25 | } 26 | 27 | table := gocli.NewTable() 28 | table.Add("Id", dc.DataCenterId) 29 | table.Add("Name", dc.DataCenterName) 30 | table.Add("Region", dc.Region) 31 | table.Add("State", dc.ProvisioningState) 32 | table.Add("Version", dc.DataCenterVersion) 33 | fmt.Println(table) 34 | fmt.Println("\nServers:") 35 | if len(dc.Servers) > 0 { 36 | table = gocli.NewTable() 37 | table.Add("Id", "Created", "Name", "Lans", "Ip", "AZ", "ProvState", "VMState", "Ram", "Cores", "Internet") 38 | for _, server := range dc.Servers { 39 | table.Add(server.ServerId, server.CreationTime.Format("2006-01-02T15:04"), server.ServerName, server.Lans(), strings.Join(server.Ips, ","), server.AvailabilityZone, server.ProvisioningState, server.VirtualMachineState, server.Ram, server.Cores, server.InternetAccess) 40 | } 41 | fmt.Println(table) 42 | } else { 43 | fmt.Println("* None *") 44 | } 45 | 46 | fmt.Println("\nStorages:") 47 | if len(dc.Storages) > 0 { 48 | table = gocli.NewTable() 49 | table.Add("Id", "Name", "Size") 50 | for _, storage := range dc.Storages { 51 | table.Add(storage.StorageId, storage.StorageName, storage.Size) 52 | } 53 | fmt.Println(table) 54 | } else { 55 | fmt.Println("* None *") 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /profitbricks/actions/server.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/gocli" 6 | "github.com/dynport/gocloud/profitbricks" 7 | "strings" 8 | ) 9 | 10 | func ListAllServersHandler() error { 11 | client := profitbricks.NewFromEnv() 12 | servers, e := client.GetAllServers() 13 | if e != nil { 14 | return e 15 | } 16 | table := gocli.NewTable() 17 | table.Add("Id", "Name", "ProvisioningState", "VmState", "Ips", "Lans") 18 | for _, server := range servers { 19 | table.Add(server.ServerId, server.ServerName, server.ProvisioningState, server.VirtualMachineState, strings.Join(server.Ips, ","), server.Lans()) 20 | } 21 | fmt.Println(table) 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /profitbricks/actions/snapshot.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/gocli" 6 | "github.com/dynport/gocloud/profitbricks" 7 | ) 8 | 9 | func init() { 10 | //ListAllSnapshots = &gocli.Action{Handler: ListAllSnapshotsHandler, Description: "List Snapshots"} 11 | 12 | args := gocli.NewArgs(nil) 13 | args.RegisterString(CLI_ROLLBACK_SNAPSHOT_STORAGE_ID, "storage_id", true, "", "Storage ID") 14 | args.RegisterString(CLI_ROLLBACK_SNAPSHOT_SNAPSHOT_ID, "snapshot_id", true, "", "Snapshot ID") 15 | 16 | //RollbackSnapshot = &gocli.Action{Handler: RollbackSnapshotHandler, Description: "Rollback Snapshot", Args: args} 17 | } 18 | 19 | type RollbackSnapshotHandler struct { 20 | StorageId string `cli:"type=arg required=true"` 21 | SnapshotId string `cli:"type=arg required=true"` 22 | } 23 | 24 | func (a *RollbackSnapshotHandler) Run() error { 25 | req := &profitbricks.RollbackSnapshotRequest{ 26 | StorageId: a.StorageId, 27 | SnapshotId: a.SnapshotId, 28 | } 29 | return profitbricks.NewFromEnv().RollbackSnapshot(req) 30 | } 31 | 32 | func ListAllSnapshotsHandler() error { 33 | client := profitbricks.NewFromEnv() 34 | snapshots, e := client.GetAllSnapshots() 35 | if e != nil { 36 | return e 37 | } 38 | table := gocli.NewTable() 39 | table.Add("Id", "OsType", "Name", "Size", "State") 40 | for _, snapshot := range snapshots { 41 | table.Add(snapshot.SnapshotId, snapshot.OsType, snapshot.SnapshotName, snapshot.SnapshotSize, snapshot.ProvisioningState) 42 | } 43 | fmt.Println(table) 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /profitbricks/actions/storage.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dynport/gocli" 6 | "github.com/dynport/gocloud/profitbricks" 7 | "strings" 8 | ) 9 | 10 | func ListAllStorages() error { 11 | client := profitbricks.NewFromEnv() 12 | storages, e := client.GetAllStorages() 13 | if e != nil { 14 | return e 15 | } 16 | table := gocli.NewTable() 17 | table.Add("Id", "Name", "ProvisioningState", "Servers", "Image Name", "Image ID") 18 | for _, storage := range storages { 19 | table.Add(storage.StorageId, storage.StorageName, storage.ProvisioningState, strings.Join(storage.ServerIds, ","), storage.ImageName, storage.ImageId) 20 | } 21 | fmt.Println(table) 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /profitbricks/client_test.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | . "github.com/smartystreets/goconvey/convey" 6 | "testing" 7 | ) 8 | 9 | func TestMarshlling(t *testing.T) { 10 | req := &CreateStorageRequest{} 11 | b, e := xml.Marshal(req) 12 | Convey("serializing", t, func() { 13 | Convey("the error should not be nil", func() { 14 | So(e, ShouldBeNil) 15 | }) 16 | Convey("the serialized xml should have the correct format", func() { 17 | So(string(b), ShouldStartWith, "") 18 | }) 19 | }) 20 | } 21 | 22 | func TestMultiRequest(t *testing.T) { 23 | s := marshalMultiRequest("createStorage", &CreateServerRequest{DataCenterId: "Id"}) 24 | Convey("serialize multi request", t, func() { 25 | So(s, ShouldContainSubstring, "") 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /profitbricks/connected_storage.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type ConnectedStorage struct { 4 | BootDevice bool `xml:"bootDevice"` // >true 5 | BusType string `xml:"busType"` // >VIRTIO 6 | DeviceNumber int `xml:"deviceNumber"` // >1 7 | Size int `xml:"size"` // >10 8 | StorageId string `xml:"storageId"` // >dd24d153-6cf7-4476-aab3-0b1d32d5e15c 9 | StorageName string `xml:"storageName"` // >Ubuntu 10 | } 11 | -------------------------------------------------------------------------------- /profitbricks/data_center.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type DataCenter struct { 4 | DataCenterId string `xml:"dataCenterId"` 5 | DataCenterName string `xml:"dataCenterName"` 6 | DataCenterVersion int `xml:"dataCenterVersion"` 7 | Servers []*Server `xml:"servers"` 8 | Storages []*Storage `xml:"storages"` 9 | ProvisioningState string `xml:"provisioningState"` 10 | Region string `xml:"region"` 11 | } 12 | -------------------------------------------------------------------------------- /profitbricks/firewall.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type Firewall struct { 4 | Active string `xml:"active"` // >false 5 | FirewallId string `xml:"firewallId"` // >29f279de-027e-40e4-99f2-4f09aa086903 6 | NicId string `xml:"nicId"` // >910910a2-a4c5-45fe-aeff-fe7ec4d8d4d9 7 | ProvisioningState string `xml:"provisioningState"` // >AVAILABLE 8 | } 9 | -------------------------------------------------------------------------------- /profitbricks/fixtures/error_response.xml: -------------------------------------------------------------------------------- 1 | S:ServerAn unexpected error occured. Please ask for support. Please refer to Request Id : 881846 UNEXPECTED503An unexpected error occured. Please ask for support. Please refer to Request Id : 881846 881846 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_all_data_center_request.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_all_data_center_response.xml: -------------------------------------------------------------------------------- 1 | eb394325-b2f1-418c-93e4-377697e3c597Production4 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_all_snapshots.xml: -------------------------------------------------------------------------------- 1 | b0488204-40e2-42b2-a9ca-ca1cc1c44ae5Created from “Storage 1” in Data Center “Production”10240Storage 1-Snapshot-11/16/2013AVAILABLEfalseLINUXfalsefalsefalsefalseEUROPE3518aa6c-b200-4624-8291-ca682032eaa210240AVAILABLEfalseUNKNOWNfalsefalsefalsefalseEUROPE 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_all_storages.xml: -------------------------------------------------------------------------------- 1 | eb394325-b2f1-418c-93e4-377697e3c59735618e0875-4348-4d0a-bd0f-8efc379d7b5c10Storage 2b0488204-40e2-42b2-a9ca-ca1cc1c44ae5GMAVAILABLE2013-11-25T10:08:22.068Z2013-11-25T10:08:22.068Zeb394325-b2f1-418c-93e4-377697e3c59735cd1c609e-330c-4948-9324-ade31c3aa52e10Storage 1b0488204-40e2-42b2-a9ca-ca1cc1c44ae5GM18d0599c-c1c4-437d-9c2f-c2ad94658f4bAVAILABLE2013-11-25T10:08:22.068Z2013-11-25T10:08:22.068Zeb394325-b2f1-418c-93e4-377697e3c5973543abb20b-c323-4c04-a019-8a18be38c6bc10Storage 4b0488204-40e2-42b2-a9ca-ca1cc1c44ae5GMAVAILABLE2013-11-25T13:27:00.469Z2013-11-25T13:27:00.469Z 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_data_center_request.xml: -------------------------------------------------------------------------------- 1 | eb394325-b2f1-418c-93e4-377697e3c597 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_data_center_response.xml: -------------------------------------------------------------------------------- 1 | 878312eb394325-b2f1-418c-93e4-377697e3c5974Productioneb394325-b2f1-418c-93e4-377697e3c59745d0a9936-94c0-44a8-85cc-9a9cae082202Server 111024true46.16.78.173trueVIRTIO110dd24d153-6cf7-4476-aab3-0b1d32d5e15cUbuntueb394325-b2f1-418c-93e4-377697e3c5974910910a2-a4c5-45fe-aeff-fe7ec4d8d4d91true5d0a9936-94c0-44a8-85cc-9a9cae08220246.16.78.17302:01:0f:f6:2c:b5false29f279de-027e-40e4-99f2-4f09aa086903910910a2-a4c5-45fe-aeff-fe7ec4d8d4d9AVAILABLEtrue46.16.78.1AVAILABLEAVAILABLERUNNING2013-11-14T16:21:42.325Z2013-11-14T16:24:39.253ZLINUXAUTOeb394325-b2f1-418c-93e4-377697e3c5974dd24d153-6cf7-4476-aab3-0b1d32d5e15c10Ubuntu3e90d37c-c87a-11e2-b188-0025901dfe2aubuntu-13.04-server-amd64-05.28.13.img5d0a9936-94c0-44a8-85cc-9a9cae082202AVAILABLE2013-11-14T17:10:28.997Z2013-11-14T17:10:28.997ZAVAILABLEEUROPE 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_server_request.xml: -------------------------------------------------------------------------------- 1 | 5d0a9936-94c0-44a8-85cc-9a9cae082202 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_server_response.xml: -------------------------------------------------------------------------------- 1 | 881128eb394325-b2f1-418c-93e4-377697e3c59765d0a9936-94c0-44a8-85cc-9a9cae082202Server 111024true46.16.78.222trueVIRTIO120dd24d153-6cf7-4476-aab3-0b1d32d5e15cUbuntueb394325-b2f1-418c-93e4-377697e3c5976910910a2-a4c5-45fe-aeff-fe7ec4d8d4d91true5d0a9936-94c0-44a8-85cc-9a9cae08220246.16.78.22202:01:0f:f6:2c:b5false29f279de-027e-40e4-99f2-4f09aa086903910910a2-a4c5-45fe-aeff-fe7ec4d8d4d9AVAILABLEtrue46.16.78.1AVAILABLEAVAILABLERUNNING2013-11-14T16:21:42.325Z2013-11-14T16:24:39.253ZLINUXAUTO 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_storage_request.xml: -------------------------------------------------------------------------------- 1 | dd24d153-6cf7-4476-aab3-0b1d32d5e15c 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/get_storage_response.xml: -------------------------------------------------------------------------------- 1 | 881140eb394325-b2f1-418c-93e4-377697e3c5976dd24d153-6cf7-4476-aab3-0b1d32d5e15c20Ubuntu3e90d37c-c87a-11e2-b188-0025901dfe2aubuntu-13.04-server-amd64-05.28.13.img5d0a9936-94c0-44a8-85cc-9a9cae082202AVAILABLE2013-11-14T17:10:28.997Z2013-11-15T11:22:07.236Z 2 | -------------------------------------------------------------------------------- /profitbricks/fixtures/rollback_snapshot_response.xml: -------------------------------------------------------------------------------- 1 | 1197645eb394325-b2f1-418c-93e4-377697e3c59732 2 | -------------------------------------------------------------------------------- /profitbricks/image.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type Image struct { 4 | CpuHotpluggable bool `xml:"cpuHotpluggable"` // type="xs:boolean" minOccurs="0"/> 5 | ImageId string `xml:"imageId"` // type="xs:string" minOccurs="0"/> 6 | ImageName string `xml:"imageName"` // type="xs:string" minOccurs="0"/> 7 | ImageSize int `xml:"imageSize"` // type="xs:long" minOccurs="0"/> 8 | ImageType string `xml:"imageType"` // type="tns:imageType" minOccurs="0"/> 9 | MemoryHotpluggable bool `xml:"memoryHotpluggable"` // type="xs:boolean" minOccurs="0"/> 10 | OsType string `xml:"osType"` // type="tns:osType" minOccurs="0"/> 11 | Region string `xml:"region"` // type="tns:region" minOccurs="0"/> 12 | ServerIds string `xml:"serverIds"` // type="xs:string" nillable="true" minOccurs="0" maxOccurs="unbounded"/> 13 | Writeable bool `xml:"writeable"` // type="xs:boolean" minOccurs="0"/> 14 | } 15 | -------------------------------------------------------------------------------- /profitbricks/nic.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type Nic struct { 4 | DataCenterId string `xml:"dataCenterId"` // eb394325-b2f1-418c-93e4-377697e3c597 5 | DataCenterVersion int `xml:"dataCenterVersion"` // 4 6 | NicId string `xml:"nicId"` // 910910a2-a4c5-45fe-aeff-fe7ec4d8d4d9 7 | LanId int `xml:"lanId"` // 1 8 | InternetAccess bool `xml:"internetAccess"` // true 9 | ServerId string `xml:"serverId"` // 5d0a9936-94c0-44a8-85cc-9a9cae082202 10 | Ips string `xml:"ips"` // 46.16.78.173 11 | MacAddress string `xml:"macAddress"` // 02:01:0f:f6:2c:b5 12 | Firewall *Firewall `xml:"firewall"` 13 | DhcpActive bool `xml:"dhcpActive"` // >true 14 | GatewayIp string `xml:"gatewayIp"` // >46.16.78.1 15 | ProvisioningState string `xml:"provisioningState"` // >AVAILABLE 16 | } 17 | -------------------------------------------------------------------------------- /profitbricks/pricing.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | type Pricing struct { 4 | CpuPerHour float64 5 | GbRamPerHour float64 6 | GbStoragePerMonth float64 7 | } 8 | 9 | var ( 10 | HoursPerMonth float64 = 24 * 30 11 | 12 | DE = &Pricing{ 13 | CpuPerHour: 2, 14 | GbRamPerHour: 0.45, 15 | GbStoragePerMonth: 9, 16 | } 17 | 18 | US = &Pricing{ 19 | CpuPerHour: 2.5, 20 | GbRamPerHour: 0.75, 21 | GbStoragePerMonth: 9, 22 | } 23 | ) 24 | 25 | type Price struct { 26 | Cpu float64 27 | Ram float64 28 | Storage float64 29 | } 30 | 31 | func (price *Price) TotalPrice() float64 { 32 | return price.Cpu + price.Ram + price.Storage 33 | } 34 | 35 | func (pricing *Pricing) Calculate(cpus int, ram int, storage int) *Price { 36 | return &Price{ 37 | Cpu: float64(cpus) * HoursPerMonth * pricing.CpuPerHour, 38 | Ram: float64(ram) * HoursPerMonth * pricing.GbRamPerHour, 39 | Storage: float64(storage) * pricing.GbStoragePerMonth, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /profitbricks/server.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type Server struct { 11 | ServerId string `xml:"serverId"` 12 | ServerName string `xml:"serverName"` 13 | Cores int `xml:"cores"` 14 | Ram int `xml:"ram"` 15 | InternetAccess bool `xml:"internetAccess"` 16 | ProvisioningState string `xml:"provisioningState"` 17 | Ips []string `xml:"ips"` 18 | LastModificationTime time.Time `xml:"lastModificationTime"` 19 | CreationTime time.Time `xml:"creationTime"` 20 | VirtualMachineState string `xml:"virtualMachineState"` 21 | AvailabilityZone string `xml:"availabilityZone"` 22 | Region string `xml:"region"` 23 | Nics []*Nic `xml:"nics"` 24 | ConnectedStorages []*ConnectedStorage `xml:"connectedStorages"` 25 | } 26 | 27 | func (server *Server) Lans() string { 28 | lans := make([]string, 0, len(server.Nics)) 29 | for _, nic := range server.Nics { 30 | lans = append(lans, strconv.Itoa(nic.LanId)) 31 | } 32 | return strings.Join(lans, ",") 33 | } 34 | 35 | type CreateServerRequest struct { 36 | XMLName xml.Name `xml:"request"` 37 | DataCenterId string `xml:"dataCenterId,omitempty"` 38 | Cores int `xml:"cores,omitempty"` 39 | Ram int `xml:"ram,omitempty"` 40 | ServerName string `xml:"serverName,omitempty"` 41 | BootFromStorageId string `xml:"bootFromStorageId,omitempty"` 42 | BootFromImageId string `xml:"bootFromImageId,omitempty"` 43 | InternetAccess bool `xml:"internetAccess,omitempty"` 44 | LanId int `xml:"lanId,omitempty"` 45 | OsType string `xml:"osType,omitempty"` 46 | AvailabilityZone string `xml:"availabilityZone,omitempty"` 47 | } 48 | 49 | func (client *Client) CreateServer(req *CreateServerRequest) error { 50 | _, e := client.loadSoapRequest(marshalMultiRequest("createServer", req)) 51 | return e 52 | } 53 | 54 | func (client *Client) GetAllServers() (servers []*Server, e error) { 55 | env, e := client.loadSoapRequest("") 56 | if e != nil { 57 | return nil, e 58 | } 59 | return env.Body.GetAllServersResponse, nil 60 | } 61 | -------------------------------------------------------------------------------- /profitbricks/snapshot.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | type Snapshot struct { 8 | SnapshotId string `xml:"snapshotId"` // type="xs:string"/> 9 | Description string `xml:"description"` // type="xs:string" minOccurs="0"/> 10 | SnapshotSize int `xml:"snapshotSize"` // type="xs:long"/> 11 | SnapshotName string `xml:"snapshotName"` // type="xs:string" minOccurs="0"/> 12 | ProvisioningState string `xml:"provisioningState"` // type="tns:provisioningState"/> 13 | Bootable bool `xml:"bootable"` // type="xs:boolean" minOccurs="0"/> 14 | OsType string `xml:"osType"` // type="tns:osType" minOccurs="0"/> 15 | CpuHotPlug bool `xml:"cpuHotPlug"` // type="xs:boolean" minOccurs="0"/> 16 | RamHotPlug bool `xml:"ramHotPlug"` // type="xs:boolean" minOccurs="0"/> 17 | NicHotPlug bool `xml:"nicHotPlug"` // type="xs:boolean" minOccurs="0"/> 18 | NicHotUnPlug bool `xml:"nicHotUnPlug"` // type="xs:boolean" minOccurs="0"/> 19 | Region string `xml:"region"` // type="tns:region"/> 20 | } 21 | 22 | type UpdateSnapshotRequest struct { 23 | XMLName xml.Name `xml:"request"` 24 | SnapshotId string `xml:"snapshotId"` // type="xs:string"/> 25 | Description string `xml:"description"` // type="xs:string" minOccurs="0"/> 26 | SnapshotName string `xml:"snapshotName"` // type="xs:string" minOccurs="0"/> 27 | Bootable bool `xml:"bootable"` // type="xs:boolean" minOccurs="0"/> 28 | OsType string `xml:"osType"` // type="tns:osType" minOccurs="0"/> 29 | CpuHotPlug string `xml:"cpuHotPlug"` // type="xs:boolean" minOccurs="0"/> 30 | RamHotPlug string `xml:"ramHotPlug"` // type="xs:boolean" minOccurs="0"/> 31 | NicHotPlug string `xml:"nicHotPlug"` // type="xs:boolean" minOccurs="0"/> 32 | NicHotUnPlug string `xml:"nicHotUnPlug"` // type="xs:boolean" minOccurs="0"/> 33 | } 34 | 35 | type RollbackSnapshotRequest struct { 36 | XMLName xml.Name `xml:"request"` 37 | SnapshotId string `xml:"snapshotId"` 38 | StorageId string `xml:"storageId"` 39 | } 40 | 41 | func (client *Client) RollbackSnapshot(req *RollbackSnapshotRequest) error { 42 | _, e := client.loadSoapRequest(marshalMultiRequest("rollbackSnapshot", req)) 43 | return e 44 | } 45 | 46 | func (client *Client) CreateSnapshot(req *UpdateSnapshotRequest) error { 47 | _, e := client.loadSoapRequest(marshalMultiRequest("createSnapshot", req)) 48 | return e 49 | } 50 | 51 | func (client *Client) UpdateSnapshot(req *UpdateSnapshotRequest) error { 52 | _, e := client.loadSoapRequest(marshalMultiRequest("updateSnapshot", req)) 53 | return e 54 | } 55 | -------------------------------------------------------------------------------- /profitbricks/soap.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | ) 6 | 7 | const ( 8 | SOAP_HEADER = `` 9 | SOAP_FOOTER = `` 10 | ) 11 | 12 | type Envelope struct { 13 | XMLName xml.Name `xml:"Envelope"` 14 | Body *Body `xml:"Body"` 15 | } 16 | 17 | type Body struct { 18 | XMLName xml.Name `xml:"Body"` 19 | GetAllDataCenters []*DataCenter `xml:"getAllDataCentersResponse>return"` 20 | GetDataCenterResponse *GetDataCenterResponse `xml:"getDataCenterResponse>return"` 21 | GetAllImagesResponse []*Image `xml:"getAllImagesResponse>return"` 22 | GetAllSnapshotsResponse []*Snapshot `xml:"getAllSnapshotsResponse>return"` 23 | GetAllStoragesResponse []*Storage `xml:"getAllStoragesResponse>return"` 24 | GetAllServersResponse []*Server `xml:"getAllServersResponse>return"` 25 | } 26 | 27 | func NewSoapRequest(body string) string { 28 | return SOAP_HEADER + body + SOAP_FOOTER 29 | } 30 | -------------------------------------------------------------------------------- /profitbricks/storage.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | "time" 6 | ) 7 | 8 | type Storage struct { 9 | DataCenterId string `xml:"dataCenterId,omitempty"` 10 | DataCenterVersion string `xml:"dataCenterVersion,omitempty"` 11 | StorageId string `xml:"storageId"` 12 | Size int `xml:"size"` 13 | StorageName string `xml:"storageName"` 14 | BootDevice bool `xml:"bootDevice"` 15 | BusType string `xml:"busType"` 16 | DeviceNumber int `xml:"deviceNumber"` 17 | ImageId string `xml:"mountImage>imageId,omitempty"` 18 | ImageName string `xml:"mountImage>imageName,omitempty"` 19 | ServerIds []string `xml:"serverIds,omitempty"` 20 | ProvisioningState string `xml:"provisioningState,omitempty"` 21 | CreationTime time.Time `xml:"creationTime,omitempty"` 22 | LastModificationTime time.Time `xml:"lastModificationTime,omitempty"` 23 | } 24 | 25 | type CreateStorageRequest struct { 26 | XMLName xml.Name `xml:"request"` 27 | DataCenterId string `xml:"dataCenterId,omitempty"` 28 | StorageName string `xml:"storageName,omitempty"` 29 | Size int `xml:"size,omitempty"` 30 | MountImageId string `xml:"mountImageId,omitempty"` 31 | ProfitBricksImagePassword string `xml:"profitBricksImagePassword,omitempty"` 32 | } 33 | -------------------------------------------------------------------------------- /profitbricks/util.go: -------------------------------------------------------------------------------- 1 | package profitbricks 2 | 3 | import ( 4 | "encoding/xml" 5 | "strings" 6 | ) 7 | 8 | func marshalMultiRequest(name string, requests ...interface{}) string { 9 | lines := make([]string, 0, len(requests)) 10 | for _, req := range requests { 11 | b, e := xml.Marshal(req) 12 | if e != nil { 13 | panic(e.Error()) 14 | } 15 | lines = append(lines, string(b)) 16 | } 17 | return "" + strings.Join(lines, "\n") + "" 18 | } 19 | --------------------------------------------------------------------------------