Giter Club home page Giter Club logo

godo's Introduction

Godo

GitHub Actions CI GoDoc

Godo is a Go client library for accessing the DigitalOcean V2 API.

You can view the client API docs here: http://godoc.org/github.com/digitalocean/godo

You can view DigitalOcean API docs here: https://docs.digitalocean.com/reference/api/api-reference/

Install

go get github.com/digitalocean/[email protected]

where X.Y.Z is the version you need.

or

go get github.com/digitalocean/godo

for non Go modules usage or latest version.

Usage

import "github.com/digitalocean/godo"

Create a new DigitalOcean client, then use the exposed services to access different parts of the DigitalOcean API.

Authentication

Currently, Personal Access Token (PAT) is the only method of authenticating with the API. You can manage your tokens at the DigitalOcean Control Panel Applications Page.

You can then use your token to create a new client:

package main

import (
    "github.com/digitalocean/godo"
)

func main() {
    client := godo.NewFromToken("my-digitalocean-api-token")
}

If you need to provide a context.Context to your new client, you should use godo.NewClient to manually construct a client instead.

Examples

To create a new Droplet:

dropletName := "super-cool-droplet"

createRequest := &godo.DropletCreateRequest{
    Name:   dropletName,
    Region: "nyc3",
    Size:   "s-1vcpu-1gb",
    Image: godo.DropletCreateImage{
        Slug: "ubuntu-20-04-x64",
    },
}

ctx := context.TODO()

newDroplet, _, err := client.Droplets.Create(ctx, createRequest)

if err != nil {
    fmt.Printf("Something bad happened: %s\n\n", err)
    return err
}

Pagination

If a list of items is paginated by the API, you must request pages individually. For example, to fetch all Droplets:

func DropletList(ctx context.Context, client *godo.Client) ([]godo.Droplet, error) {
    // create a list to hold our droplets
    list := []godo.Droplet{}

    // create options. initially, these will be blank
    opt := &godo.ListOptions{}
    for {
        droplets, resp, err := client.Droplets.List(ctx, opt)
        if err != nil {
            return nil, err
        }

        // append the current page's droplets to our list
        list = append(list, droplets...)

        // if we are at the last page, break out the for loop
        if resp.Links == nil || resp.Links.IsLastPage() {
            break
        }

        page, err := resp.Links.CurrentPage()
        if err != nil {
            return nil, err
        }

        // set the page we want for the next request
        opt.Page = page + 1
    }

    return list, nil
}

Some endpoints offer token based pagination. For example, to fetch all Registry Repositories:

func ListRepositoriesV2(ctx context.Context, client *godo.Client, registryName string) ([]*godo.RepositoryV2, error) {
    // create a list to hold our registries
    list := []*godo.RepositoryV2{}

    // create options. initially, these will be blank
    opt := &godo.TokenListOptions{}
    for {
        repositories, resp, err := client.Registry.ListRepositoriesV2(ctx, registryName, opt)
        if err != nil {
            return nil, err
        }

        // append the current page's registries to our list
        list = append(list, repositories...)

        // if we are at the last page, break out the for loop
        if resp.Links == nil || resp.Links.IsLastPage() {
            break
        }

        // grab the next page token
        nextPageToken, err := resp.Links.NextPageToken()
        if err != nil {
            return nil, err
        }

        // provide the next page token for the next request
        opt.Token = nextPageToken
    }

    return list, nil
}

Automatic Retries and Exponential Backoff

The Godo client can be configured to use automatic retries and exponentional backoff for requests that fail with 429 or 500-level response codes via go-retryablehttp. To configure Godo to enable usage of go-retryablehttp, the RetryConfig.RetryMax must be set.

tokenSrc := oauth2.StaticTokenSource(&oauth2.Token{
    AccessToken: "dop_v1_xxxxxx",
})

oauth_client := oauth2.NewClient(oauth2.NoContext, tokenSrc)

waitMax := godo.PtrTo(6.0)
waitMin := godo.PtrTo(3.0)

retryConfig := godo.RetryConfig{
    RetryMax:     3,
    RetryWaitMin: waitMin,
    RetryWaitMax: waitMax,
}

client, err := godo.New(oauth_client, godo.WithRetryAndBackoffs(retryConfig))

Please refer to the RetryConfig Godo documentation for more information.

Versioning

Each version of the client is tagged and the version is updated accordingly.

To see the list of past versions, run git tag.

Documentation

For a comprehensive list of examples, check out the API documentation.

For details on all the functionality in this library, see the GoDoc documentation.

Contributing

We love pull requests! Please see the contribution guidelines.

godo's People

Contributors

adamwg avatar andrewsomething avatar aqche avatar aybabtme avatar bentranter avatar bryanl avatar chiefmatestarbuck avatar danaelhe avatar dweinshenker avatar eddiezane avatar hilary avatar jcodybaker avatar kamaln7 avatar lxfontes avatar mauricio avatar mchitten avatar mikejholly avatar nanzhong avatar nicktate avatar phillbaker avatar rbutler avatar stephenvarela avatar sunny-b avatar timoreimann avatar varshavaradarajan avatar verolop avatar viola avatar xmudrii avatar zachgersh avatar zbarahal-do avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

godo's Issues

add ability to specify api url

please add ability to specify api url, because some providers have DO like api to creating vps, and previously packer use handmade lib to support DO and have this ability, but in last release it switches to this library and i miss this feature.

Missing GET /v2/droplets/12345/kernels,snapshots,backups,actions,neighbors

Invalid key identifiers for Droplet creation

Hi, there is no test for this case also, I tried many cases but all is invalid

And I don't know why we have to pass ssh keys as []interface{}

keys := make([]interface{}, 1)
keys[0] = godo.Key{
    ID:          <id>,
    Name:        <name>,
    Fingerprint: <fingerprint>,
}
POST https://api.digitalocean.com/v2/droplets: 422 {"id"=><id>, "name"=><name>, "fingerprint"=><fingerprint>} are invalid key identifiers for Droplet creation.

Create a test server

I think it will be good have a official test server to make easier tests with godo.
Today, to test something with godo, we need to start a fake server and mock digitalocean api.

If you guys agree I can implement it and submit a PR. :)

Fix assigned variable to networks ipv6 on loop

https://github.com/digitalocean/godo/blob/master/droplets.go#L92

// PublicIPv6 returns the private IPv6 address for the Droplet.
func (d *Droplet) PublicIPv6() (string, error) {
	if d.Networks == nil {
		return "", errNoNetworks
	}

	for _, v4 := range d.Networks.V6 {
		if v4.Type == "public" {
			return v4.IPAddress, nil
		}
	}

	return "", nil
}

The variable assigned on loop for networks ipv6 is a little bit confuse. A better name to this variable would be v6. I believe that this error was a typo, but follows my suggestion. 😃

// PublicIPv6 returns the private IPv6 address for the Droplet.
func (d *Droplet) PublicIPv6() (string, error) {
	if d.Networks == nil {
		return "", errNoNetworks
	}

	for _, v6 := range d.Networks.V6 {
		if v6.Type == "public" {
			return v6.IPAddress, nil
		}
	}

	return "", nil
}

Changing the User Agent doesn't work as expected

If you specify a custom user agent when creating a new Client:
c := godo.New(nil, godo.SetUserAgent("testing"))

c.UserAgent returns the expected user agent, but the c.NewRequest() method doesn't use it.

Here's a full working Go program to help reproduce the issue:

package main

import (
    "fmt"
    "log"

    "github.com/digitalocean/godo"
)

func main() {
    c, err := godo.New(nil, godo.SetUserAgent("testing"))
    if err != nil {
        log.Fatalf("Something bad happened: %s\n\n", err)
    }

    r, _ := c.NewRequest("GET", "/foo", nil)
    fmt.Printf("client User Agent: %s\n", c.UserAgent)
    fmt.Printf("request User Agent: %s\n", r.Header.Get("User-Agent"))
}

may be errors in marshaling DropletCreateRequest

I check how packer send request to do servers and find, that ssh_keys passed incorrectly, documentation says, that we need to pass array of ID or fingerprints, but godo struct have not []string array or something like this, but struct , so if i have valid request via json, i can't unmarshal it using godo.DropletCreateRequest

Issue with RebuildByImageID() API.

Hi,

I am trying to use RebuildByImageID(id, ImageID) for creating a droplet from snapshot image.

Problem I am facing is with id param passed in API, it gives error,

POST https://api.digitalocean.com/v2/droplets/6370882/actions: 404 The resource you were accessing could not be found

What should be the value passed in id ?

Metadata api support

The package currently provide access to all API expect the metadata API, do you plan adding support for that?
I don't need it for anything expect getting the droplet it, but maybe someone else need "the rest".

Regards Kristian

Droplet List/Get tests lack full json responses

I see now our List/Get examples aren't using full json responses only a small poriton (droplet id). We should probably expand that to be a full json response from the API. Could have detected #20 sooner.

Adding tag on droplet creation do not apply for firewall rules

Hello there,

I've open a ticket that was closed via DO support, so I'm trying here.

When creating a droplet with tags via the create droplet call:

createRequest := &godo.DropletCreateRequest{
	Name:   dropletName,
	Region: "tor1",
	Size:   "16gb",
	Image: godo.DropletCreateImage{
		Slug: "ubuntu-16-04-x64",
	},
	PrivateNetworking: true,
	SSHKeys:           []godo.DropletCreateSSHKey{godo.DropletCreateSSHKey{Fingerprint: os.Getenv("DO_FP")}},
	Tags:              []string{"rdmp", "rdmp-app-instances"},
}

The tag are visible on the dashboard but the firewall rules that are set based on those tags are not working when creating the droplet via the API.

Create the droplet via API without the tags and applying them via the dashboard works and the droplet can contact others droplet protected by the tag based firewall rules.

The DO support asked me to apply the tags separately (i.e. not via the creation call). That does not seems to be possible via this Go package.

Nonetheless, the tags that are applied via the droplet creation should do the same as when applying them via the dashboard regarding firewall rules no?

Hopefully I can get some help, we're currently unable to run a background process than ran all fine all until we decided to start using DO firewall and tags.

Thanks

Invalid request for Droplet Actions that require parameters

Droplet Actions that require parameters like Resize and Rename do not seem to work. For example:

package main

import "os"
import "fmt"
import "strconv"

import "github.com/digitaloceancloud/godo"
import "code.google.com/p/goauth2/oauth"

func main () {
    id, e  := strconv.Atoi(os.Args[1])
    if e != nil {
        fmt.Println(e)
    }
    newName := os.Args[2]

    pat := os.Getenv("DO_TOKEN")
    t := &oauth.Transport{
        Token: &oauth.Token{AccessToken: pat},
    }

    client := godo.NewClient(t.Client())

    newSnapshot, _, err := client.DropletActions.Rename(id, newName)

    if err != nil {
        fmt.Printf("Something bad happened: %s\n\n", err)
    } else {
        fmt.Printf("%v\n\n", godo.Stringify(newSnapshot))
    }
}

Always returns:

./rename 3146039 test-subject
Something bad happened: POST https://api.digitalocean.com/v2/droplets/3146039/actions: 422 Name Droplet must have a name

It looks to me that the requests get built like:

{"type":"rename","params":{"name":"test-subject"}}

DropletActions.Shutdown doesn't work for centos-6-x64 droplet

DropletActions.Shutdown doesn't work for centos-5-x64 and centos-6-x64 droplets.
the mystical issue described in hashicorp/packer#3919
this bug is located in godo library or on DigitalOcean API side, not in packer.

packer logic is straightforward and works fine for centos-7-x64 droplet, but doesn't for centos-6-x64, centos-5-x64
https://github.com/hashicorp/packer/blob/master/builder/digitalocean/step_shutdown.go#L22-L27

bug is stable reproducible, steps to reproduce:

  1. MacOS 10.12.4
  2. packer 1.0.0 from the official website or compiled from sources (master branch)
  3. packer config file content
{
    "builders": [{
        "type": "digitalocean",
        "droplet_name": "v-packer-centos-6-x64.ci.percona.com",
        "snapshot_name": "packer-centos-6-x64",
        "image": "centos-6-x64",
        "region": "nyc3",
        "size": "2gb",
        "ssh_username": "root"
    }],
    "provisioners": [{
        "type": "shell",
        "inline": [
            "sudo hostname v-packer-centos-6-x64.ci.percona.com && sudo service network restart"
        ]
    }]
}

commands:

% export DIGITALOCEAN_API_TOKEN=...
% packer build --only digitalocean packer.json

packer uses not the latest version of the library, but if you update library in the vendor dir - situation the same.

EditRecord call returns empty record

It seems that when I do an EditRecord call, it is returning an empty record. My code is here: https://github.com/jeffutter/digitalocean-dyndns/blob/master/cmd/digitalocean-dyndns/main.go#L131-L132

and the output from those two lines looks like this:

godo.Rate{Limit:0, Remaining:0, Reset:godo.Timestamp{0001-01-01 00:00:00 +0000 UTC}} godo.DomainRecord{ID:0, Type:"", Name:"", Data:"", Priority:0, Port:0, Weight:0}

I may just be doing something wrong, but I think I followed the documentation fairly closely.

Retry requests

Per #42, I can definitely see how you wouldn't want to retry every failing request. However, it would be good to have the option to do so.

In our case, the DO Terraform provider is choking on the intermittent 503s from the API, and aborting our entire deployment (in which DO is just one of the pieces) as a result.

It'd be great if the Terraform provider would retry a few times where it makes sense--i.e., definitely on reads, and maybe even on writes because the 503 response just means that the request didn't go through anyway. And if you're going to have that retry logic anyway, it's probably better to have it in the core SDK than to repeat it in everything that uses it.

Of course, ultimately, we don't want to get spurious 503 responses at all.

Firewall portrange inconsistent for create and list

When using the Firewalls.Create command, it doesn't accept TCP port range to be 0, needs to be all. Doing a list of firewalls, returns portRange to be "all", instead of 0. This makes it harder to reconcile, whether configuration is consistent and on the client side needs to create a mapping to create with portRange set to all, and expect return to be set to 0.

 Sample response after creating a firewall --
godo.InboundRule{Protocol:"tcp", PortRange:"0", Sources:godo.Sources{Addresses:["0.0.0.0/0"]}}]
 Sample request for creating a firewall --
godo.InboundRule{Protocol:"tcp", PortRange:"all", Sources:godo.Sources{Addresses:["0.0.0.0/0"]}}]

EditRecord returning empty respsonse

Same as #68, my call to EditRecord returns an empty DomainRecord. Example to reproduce:

updatedRecord, _, err := do.client.Domains.EditRecord(context.TODO(), "domain", id, update)

There also is no error produced.

godo.New(...) doesn't initialize the different services

If we create a client like this:
client, err := godo.New(aValidHTTPClientHere)

and we try to us it like this:
droplets, _, err := client.Droplets.List(nil)

then it gives an error like this:
panic: runtime error: invalid memory address or nil pointer dereference

because the DropletsServiceOp struct hasn't been initialized.

Here's a full working Go program to help reproduce the issue:

package main

import (
    "fmt"
    "log"

    "github.com/digitalocean/godo"
    "golang.org/x/oauth2"
)

type TokenSource struct {
    AccessToken string
}

func (t *TokenSource) Token() (*oauth2.Token, error) {
    token := &oauth2.Token{
        AccessToken: t.AccessToken,
    }
    return token, nil
}

func main() {
    pat := "YOUR_PERSONAL_ACCESS_TOKEN_HERE"
    tokenSource := &TokenSource{
        AccessToken: pat,
    }

    oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
    client, err := godo.New(oauthClient)
    if err != nil {
        log.Fatalf("Something bad happened: %s\n\n", err)
    }

    droplets, _, err := client.Droplets.List(nil)
    if err != nil {
        log.Fatalf("Something bad happened: %s\n\n", err)
    }

    fmt.Printf("Nb droplets: %d\n", len(droplets))
}

Are you looking for maintainers?

Hi, I'm able and wiling to help out if you are looking for maintainers for this project. I don't want to see it neglected. :) You can see my profile for Go experience.

Cheers.

Add debug logging for remote calls

It may be beneficial to have a logging flag for remote calls. I'm thinking that there could be an environment variable, GODO_DEBUG, that would print http traffic to stderr.

Implement tagging for all resources

Hi.

Some of the resources don't support tags atm. This makes it very difficult to group them together to a certain app and I have to either save their id somewhere or have them looked up via what droplet they are affiliated with which is cumbersome.

It would be very helpful if tags (note plural, not singular) could be used for all of them.

SSH Keys not attached to droplet on creation despite SSH keys being uploaded

Hello,
I am using Terraform to spin-up a server. When server is created, the ssh_fingerprint for a passphrase-less public/private key is attached to it.

resource "digitalocean_ssh_key" "root" {
  name       = "root" 
  public_key = "${chomp(file(var.root))}"
}

resource "digitalocean_droplet" "machine" {
  name                = "${var.name}"
  region              = "${var.region}"
  size                = "${var.size}"
  image               = "${var.image}" 
  ssh_keys            = ["${digitalocean_ssh_key.root.fingerprint}"] 
}

Terraform Version

// ♥ terraform -v
Terraform v0.11.10
+ provider.digitalocean v1.0.2

Affected Resource(s)

- digitalocean_ssh_key 
- digitalocean_droplet

Expectation

I am not prompted for a password when ssh -i path/to/private/key root@droplet_ip is run

Result

password prompt is displayed when ssh -i path/to/private/key root@droplet_ip is run.

I have read through the your API and Terraform provider docs and can't seem to find anything that might point to a missing step here.

Allow changing the base URL per-request

It should be possible to change the base URL to point to an endpoint of one's choice. This is currently possible for the whole client, but not on a per-request basis.

Failing tests

Hello!

We are getting failing tests in Debian:

--- FAIL: TestNewRequest_invalidJSON (0.00s)
    godo_test.go:147: Expected error to be returned.
    godo_test.go:150: Expected a JSON error; got (*json.UnsupportedTypeError)(nil).

Any idea how to solve this?

Mixed types on API v2 endpoint to create a droplet

Looking at the V2 API, to create a droplet, it says to either specify a number if using a private image, or a string if using a public image. The CreateDropletRequest.Image field is of type string. While I haven't tested to see if this will fail, I'm just pre-emptively filing this issue in case it does limit anyone from being able to create droplets from private images.

invalid character '<' looking for beginning of value

Hi,
we discovered an intermittent issue (every few days) in our nightly acceptance tests for DigitalOcean provider in Terraform.

From time to time the API, or more specifically CloudFlare which is apparently fronting the API replies 524 Origin Time-out with HTML body which godo can't process as it expects JSON.

Here's an example of request & response:
https://gist.github.com/radeksimko/412a3b2b0967ebf6a3fcb6ad609a4827

Unfortunately I cannot provide the request body because #140

I think godo should automatically retry in such case (and probably similar cases, like 503) and avoid parsing the body.

Add support for projects

Heya, any plans in adding support for "projects"?

It would be very helpful to create your resources contained and organised if you want to leverage the API resource creation better. Or am i missing something?
Currently i can't even find any references in the actual API docs?

I could potentially use tags, just to give me an idea of what resources belong where.

Expose a method to wait for actions to finish

There should be a way to make synchronous calls through the client. The API is currently asynchronous, while Go works better with synchronous clients. The client should handle the asynchronosity.

client.Droplets.List() only returns the first droplet

I used the example from the REDME which, after a bit of tweaking/fixing, compiles but I can't get the complete list of droplets, only the first one is returned.
I added debug statements to godo and I see two droplets in the json response, only one is decoded in godo.go:240

Ideas/suggestions?

I'm running Golang 1.4.1, here's the code:

package main

import (
    "fmt"
    "log"

    "code.google.com/p/goauth2/oauth"
    "godo"
)

func DropletList(client *godo.Client) ([]godo.Droplet, error) {
    // create a list to hold our droplets
    list := []godo.Droplet{}

    // create options. initially, these will be blank
    opt := &godo.ListOptions{}
    for {
        droplets, resp, err := client.Droplets.List(nil)
        if err != nil {
            return nil, err
        }

        // append the current page's droplets to our list
        for _, d := range droplets {
            list = append(list, d)
        }

        // if we are at the last page, break out the for loop
        if resp.Links != nil || resp.Links.IsLastPage() {
            break
        }

        page, err := resp.Links.CurrentPage()
        if err != nil {
            return nil, err
        }

        // set the page we want for the next request
        opt.Page = page + 1
    }

    return list, nil
}

func main() {

    token := "<<<TOKEN>>>"
    t := &oauth.Transport{
        Token: &oauth.Token{AccessToken: token},
    }
    client := godo.NewClient(t.Client())
    list, err := DropletList(client)
    if err != nil {
        log.Fatal("not good")
    }

    for _, d := range list {
        fmt.Printf("%s \n", d.Name)

    }
}

What is the status of Snapshot API in godo?

I have found out that Snapshots support Volumes and new unified API got exposed. It got announced in Add Volume Snapshots, October 5th.

I can't find it anywhere in code, neither I can find a Volumes Snapshot feature. Is it done and I miss it or is this something that needs to be done? I can fix this if needed, by I want to be sure that it isn't already done.

Creating a droplet on a region that does not support user data.

The Bug

When trying to create a droplet with a region that does not support user data. The server responds with 422 Region is not available, and there is no way of circumventing this issue through the interface provided. Aside from creating a request from scratch.
Taking a quick glance, it looks like the request is sending up

...
"user_data": "",
...

instead of

...
"user_data": null,
...

Steps to reproduce

At this moment in time, the region slug nyc2 does not support user data. Attempting to create a droplet through the godo interface client.Droplets.Create will result in an error.

Complexity to fix

One simple fix is to replace the string with a pointer to a string, the JSONMarshaler will interpret a nil pointer as a null value. Assigning a string would in turn marshal to the correct string.

Another simple fix is to just omitempty on that attribute, if your server handles that request properly.

I will create a pull request fulfilling the one I feel is a cleaner solution.

Predictable release cycle

First of all, thank you for all hard work has been done!

I'd like to clarify the state and plans of release frequency to improve visibility on this question. Currently there are some features, like #152 which was merged on Oct 4, it's 5 month ago. Current release, 1.1.1 was tagged on Sept 29, so we close to hit 6 month with it, which is half a year. I understand that you are busy there and this is an open-source that's why I'd suggest to improve release cycle to be predictable and then adhere to it.

While DigitalOcean plays important role in the modern era of cloud providers, it's hard to deny that this is a must to have. And that having an API for new feature to be propagated to the library for 5 month is not something that should lasts longer.

From $$$ perspective it's hard to sell business moving to DO partially because they see that there is a lot of place for improvements which ended up loosing a clients whose monthly budget varies from few $k to few $10k (they go to AWS if you would ask). For that money tagging releases with new DO features propageted to the tools should be a good choice in terms of ROI, right?

I really like DO and I'm a bit frustrated to see this gap in tooling is even increasing for that past few years. I'm pretty sure you are working on even better and exciting features, but please, do a bit more towards tooling around DigitalOcean.

Thank you for reading!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.