├── .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 | [](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 |
--------------------------------------------------------------------------------