├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── auth └── auth.go ├── client └── upload.go ├── config └── config.go ├── glide.lock ├── glide.yaml └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | loom 2 | loom-linux* 3 | loom-osx* 4 | loom_release.tgz 5 | vendor/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.9 4 | - tip 5 | install: 6 | - go get github.com/Masterminds/glide 7 | - go get github.com/golang/lint/golint 8 | before_script: 9 | script: 10 | - glide install 11 | - go build -o loom-linux-x64-$TRAVIS_TAG . 12 | - GOOS=darwin GOARCH=amd64 go build -o loom-osx-x64-$TRAVIS_TAG . 13 | - chmod +x loom-osx-x64-$TRAVIS_TAG 14 | - chmod +x loom-linux-x64-$TRAVIS_TAG 15 | 16 | deploy: 17 | provider: releases 18 | skip_cleanup: true 19 | api_key: 20 | secure: NNb/vu3NGEC93dmyo/J6tpN86vh6XpQWjKGZtIlYRa1CmasHb/1C+sWtfUWV804PIwLkkOiC/yIdfCqOhWSmNYIuIgqvnZMPI8Kuw6NI4qyq2Q1eUhV1ETqYaA3CDxsn+IzCoaSVGgUKm6+ODsYKsVXmLKZOPwggxC1RGGnS8pH2kJjMeouTPZgopl47VVFi4QM70yOF9rNKP1GFQhuoScG+zQZe5PuvYtpBCsIQ3sbW8j97khkSDEEsRPC9l6vK2PVgNH3SYkBDeoQaWkthQOLY+ZHOEhgVHtK19P0ZWWWSjqn2xg5WxPFE8qnMVfPEwsgAMS5Avf6RXTivGMcPyhJ1JayDNYuMIUF6LbBJ/JShWX5800Y2biKLkXfX8U21gvxC81aovaXOBdXsYPHAjIgER1ZJDcManEcgsz0N/jMFlDqRvAo1sN4sMNK/XH4abR8BxqmQt7/8SWEs6HEmd6LSF3MgeoTr93Io0kKVOZgwhO68BlqEwNmbs+bFRXsNVFkD/EETGSfPIM2G5BGx1wxfazGtSK/J3VTfhP6kqJPqx/je45QF9lAR4yrKVzEqFnnvATKT/LaeFXQCP7Z0vOLZItY7qRg1XG3yl/iFGUspcMZAVghuKKfKxGd8AOL0RO52Xty71e/zLNMNDDf3D/zmyyuB4NeGRvA1OkQFkho= 21 | file: 22 | - loom-linux-x64-$TRAVIS_TAG 23 | - loom-osx-x64-$TRAVIS_TAG 24 | on: 25 | tags: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 loomnetwork 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EthDeploy Client [![Build Status](https://travis-ci.org/loomnetwork/client.svg?branch=master)](https://travis-ci.org/loomnetwork/client) 2 | 3 | *warning: EthDeploy is only lightly maintained as we're only focusing on our core product. This is not related to DAppchain tech.* 4 | 5 | Client for deploying apps on EthDeploy (private hosted sandbox-blockchains) 6 | For more info about EthDeploy and our other projects, please see [Loom Network](https://loomx.io) 7 | 8 | ``` 9 | loom login 10 | loom deploy application.zip application_name 11 | ``` 12 | 13 | Currently you can log into loom network with Github or Linkedin. 14 | 15 | ## Install via Homebrew (OSX) 16 | 17 | ``` 18 | brew install loomnetwork/homebrew-client/loom 19 | ``` 20 | 21 | ## Install via Wget (Linux/OSX) 22 | 23 | # Building 24 | 25 | ``` 26 | glide install 27 | go build -o loom . 28 | ``` 29 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "path" 13 | "strings" 14 | 15 | oauth2ns "github.com/loomnetwork/oauth2-noserver" 16 | "golang.org/x/oauth2" 17 | "golang.org/x/oauth2/github" 18 | "golang.org/x/oauth2/linkedin" 19 | ) 20 | 21 | type LoginAuth struct { 22 | Email string `json:"email,omitempty" form:"id"` 23 | ApiKey string `json:"apikey,omitempty" form:"id"` 24 | } 25 | 26 | func Login(network string, loomnetworkHost string) string { 27 | n := strings.ToLower(network) 28 | var c *http.Client 29 | if strings.Index(n, "linkedin") >= 0 { 30 | c = loginLinkedIn() 31 | } else if strings.Index(n, "github") >= 0 { 32 | c = loginGithub() 33 | } else { 34 | reader := bufio.NewReader(os.Stdin) 35 | fmt.Printf("Please enter which network you want (linkedin/github): \n") 36 | text, _ := reader.ReadString('\n') 37 | return Login(text, loomnetworkHost) 38 | } 39 | if c == nil { 40 | fmt.Printf("Failed logging into %s\n", network) 41 | } 42 | return validateLoomNetwork(loomnetworkHost, c, n) 43 | //return extractGithubEmail(c, "") 44 | } 45 | 46 | type GithubEmail struct { 47 | Email string `json:"email,omitempty"` 48 | Verified bool `json:"verified,omitempty""` 49 | Primary bool `json:"primary,omitempty""` 50 | } 51 | 52 | func extractGithubEmail(c *http.Client, auth string) string { 53 | githubEmailURL := "https://api.github.com/user/emails" 54 | req, err := http.NewRequest("GET", githubEmailURL, nil) 55 | if auth != "" { 56 | req.Header.Add("Authorization", auth) 57 | } 58 | resp, err := c.Do(req) 59 | if err != nil { 60 | fmt.Println("error:", err) 61 | return "" 62 | } 63 | defer resp.Body.Close() 64 | 65 | if resp.StatusCode != 200 { // OK 66 | fmt.Printf("bad response code %d\n", resp.StatusCode) 67 | } 68 | bodyBytes, err := ioutil.ReadAll(resp.Body) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | var gemails []GithubEmail 74 | err = json.Unmarshal(bodyBytes, &gemails) 75 | if err != nil { 76 | fmt.Println("error:", err) 77 | } 78 | if len(gemails) > 0 { 79 | for _, email := range gemails { 80 | if email.Verified == true && email.Primary == true { 81 | return email.Email 82 | } 83 | } 84 | } 85 | 86 | return "" 87 | } 88 | 89 | func validateLoomNetwork(loomnetworkHost string, c *http.Client, network string) string { 90 | u, err := url.Parse(loomnetworkHost) 91 | u.Path = path.Join(u.Path, fmt.Sprintf("/login_oauth")) 92 | 93 | req, err := http.NewRequest("POST", u.String(), nil) 94 | req.Header.Add("Loom-Oauth-Provider", network) 95 | req.Header.Add("accept", "application/json") 96 | resp, err := c.Do(req) 97 | 98 | defer resp.Body.Close() 99 | 100 | if resp.StatusCode != 200 { // OK 101 | fmt.Printf("bad response code %d\n", resp.StatusCode) 102 | } 103 | bodyBytes, err := ioutil.ReadAll(resp.Body) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | //TODO remove this 108 | bodyString := string(bodyBytes) 109 | fmt.Printf(bodyString) 110 | 111 | var lauth LoginAuth 112 | err = json.Unmarshal(bodyBytes, &lauth) 113 | if err != nil { 114 | fmt.Println("error:", err) 115 | return "" 116 | } 117 | 118 | return lauth.ApiKey 119 | } 120 | 121 | func loginLinkedIn() *http.Client { 122 | clientID := "86zs2w1g2j8hfu" 123 | clientSecret := "mBd0gDHQdEwSRgt8" 124 | 125 | scopes := []string{"r_emailaddress", "r_basicprofile"} 126 | 127 | conf := &oauth2.Config{ 128 | ClientID: clientID, // also known as slient key sometimes 129 | ClientSecret: clientSecret, // also known as secret key 130 | Scopes: scopes, 131 | Endpoint: linkedin.Endpoint, 132 | } 133 | r := oauth2ns.Authorize(conf) 134 | // use client.Get / client.Post for further requests, the token will automatically be there 135 | 136 | if len(r.Token.AccessToken) > 0 { 137 | return r.Client 138 | } 139 | return nil 140 | } 141 | 142 | func loginGithub() *http.Client { 143 | fmt.Printf("Attempting to login to Github\n") 144 | clientID := "57bc10263596c4739845" 145 | clientSecret := "952e625011098759a2f7ebc02e12c01cf1ef2e80" 146 | scopes := []string{"user:email"} 147 | 148 | conf := &oauth2.Config{ 149 | ClientID: clientID, // also known as slient key sometimes 150 | ClientSecret: clientSecret, // also known as secret key 151 | Scopes: scopes, 152 | Endpoint: github.Endpoint, 153 | } 154 | r := oauth2ns.Authorize(conf) 155 | // use client.Get / client.Post for further requests, the token will automatically be there 156 | 157 | if len(r.Token.AccessToken) > 0 { 158 | return r.Client 159 | } 160 | 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /client/upload.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "mime/multipart" 9 | "net/http" 10 | "os" 11 | 12 | "github.com/fatih/color" 13 | ) 14 | 15 | func UploadApp(loomHost string, apikey string, filename string, slug string) { 16 | targetUrl := fmt.Sprintf("%s/upload", loomHost) 17 | 18 | fmt.Printf("Deploying %s to Loom Network... \n", filename) 19 | fmt.Printf("DApp deployed to ") 20 | color.Blue("https://%s.loomapps.io\n", slug) 21 | 22 | postFile(filename, targetUrl, apikey, slug) 23 | } 24 | 25 | func postFile(filename, targetUrl, apikey, slug string) error { 26 | client := &http.Client{} 27 | 28 | var err error 29 | bodyBuf := &bytes.Buffer{} 30 | bodyWriter := multipart.NewWriter(bodyBuf) 31 | 32 | bodyWriter.Boundary() 33 | bodyWriter.FormDataContentType() 34 | 35 | if err = bodyWriter.WriteField("application_slug", slug); err != nil { 36 | return err 37 | } 38 | //Lets the backend know to create a new application if it doesn't already exist 39 | if err = bodyWriter.WriteField("auto_create", "true"); err != nil { 40 | return err 41 | } 42 | 43 | fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) 44 | if err != nil { 45 | fmt.Println("error writing to buffer") 46 | return err 47 | } 48 | 49 | // open file handle 50 | fh, err := os.Open(filename) 51 | if err != nil { 52 | fmt.Println("error opening file") 53 | return err 54 | } 55 | 56 | //iocopy 57 | _, err = io.Copy(fileWriter, fh) 58 | if err != nil { 59 | return err 60 | } 61 | bodyWriter.Close() 62 | 63 | req, err := http.NewRequest("POST", targetUrl, bodyBuf) 64 | req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) 65 | req.Header.Set("accept", "application/json") 66 | req.Header.Set("Loom-Api-Key", apikey) 67 | 68 | //requestDump, err := httputil.DumpRequest(req, true) 69 | //if err != nil { 70 | // fmt.Println(err) 71 | //} 72 | //fmt.Println(string(requestDump)) 73 | 74 | resp, err := client.Do(req) 75 | if err != nil { 76 | return err 77 | } 78 | defer resp.Body.Close() 79 | resp_body, err := ioutil.ReadAll(resp.Body) 80 | if err != nil { 81 | return err 82 | } 83 | fmt.Println(string(resp_body)) 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/user" 9 | "path" 10 | 11 | "gopkg.in/yaml.v2" 12 | ) 13 | 14 | type Config struct { 15 | Apikey string `yaml:"apikey"` 16 | HostName string `yaml:"hostname"` 17 | } 18 | 19 | func findOrCreateConfigFile() string { 20 | usr, err := user.Current() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | configFile := path.Join(usr.HomeDir, ".loom") 25 | 26 | return configFile 27 | } 28 | 29 | func ReadConfig() *Config { 30 | filename := findOrCreateConfigFile() 31 | b, err := ioutil.ReadFile(filename) // just pass the file name 32 | if err != nil { 33 | //If we dont have a file its fine, we can just ignore 34 | // fmt.Print(err) 35 | } 36 | 37 | t := &Config{} 38 | 39 | err = yaml.Unmarshal([]byte(b), &t) 40 | if err != nil { 41 | log.Fatalf("error parsing .loom config file: %v", err) 42 | } 43 | 44 | return t 45 | } 46 | 47 | func WriteConfig(apikey string) { 48 | filename := findOrCreateConfigFile() 49 | b, err := ioutil.ReadFile(filename) // just pass the file name 50 | if err != nil { 51 | fmt.Print(err) 52 | } 53 | 54 | t := Config{Apikey: apikey} 55 | 56 | err = yaml.Unmarshal([]byte(b), &t) 57 | 58 | d, err := yaml.Marshal(&t) 59 | if err != nil { 60 | log.Fatalf("failed creating config file: %v", err) 61 | } 62 | 63 | err = ioutil.WriteFile(filename, d, os.FileMode(0644)) 64 | if err != nil { 65 | log.Fatalf("failed saving config file: %v", err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: d67de7ad871a0b19f2c3c7115379e88789bfb0f415b83c7065eb74026718a873 2 | updated: 2017-10-14T14:25:23.585320087+07:00 3 | imports: 4 | - name: github.com/alecthomas/template 5 | version: a0175ee3bccc567396460bf5acd36800cb10c49c 6 | subpackages: 7 | - parse 8 | - name: github.com/alecthomas/units 9 | version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a 10 | - name: github.com/fatih/color 11 | version: 570b54cabe6b8eb0bc2dfce68d964677d63b5260 12 | - name: github.com/golang/protobuf 13 | version: 130e6b02ab059e7b717a096f397c5b60111cae74 14 | subpackages: 15 | - proto 16 | - name: github.com/loomnetwork/oauth2-noserver 17 | version: 540b1c7e167525d7d91d3c5b85ad7c788ff1b9ae 18 | - name: github.com/mattn/go-colorable 19 | version: 5411d3eea5978e6cdc258b30de592b60df6aba96 20 | repo: https://github.com/mattn/go-colorable 21 | - name: github.com/mattn/go-isatty 22 | version: 57fdcb988a5c543893cc61bce354a6e24ab70022 23 | repo: https://github.com/mattn/go-isatty 24 | - name: github.com/skratchdot/open-golang 25 | version: 75fb7ed4208cf72d323d7d02fd1a5964a7a9073c 26 | subpackages: 27 | - open 28 | - name: golang.org/x/net 29 | version: 41bba8d80bbfab43231ffdf4c210037baae5f6a3 30 | subpackages: 31 | - context 32 | - context/ctxhttp 33 | - name: golang.org/x/oauth2 34 | version: bb50c06baba3d0c76f9d125c0719093e315b5b44 35 | subpackages: 36 | - internal 37 | - name: golang.org/x/sys 38 | version: e24f485414aeafb646f6fca458b0bf869c0880a1 39 | repo: https://go.googlesource.com/sys 40 | subpackages: 41 | - unix 42 | - name: google.golang.org/appengine 43 | version: 24e4144ec923c2374f6b06610c0df16a9222c3d9 44 | subpackages: 45 | - internal 46 | - internal/base 47 | - internal/datastore 48 | - internal/log 49 | - internal/remote_api 50 | - internal/urlfetch 51 | - urlfetch 52 | - name: gopkg.in/alecthomas/kingpin.v2 53 | version: 1087e65c9441605df944fb12c33f0fe7072d18ca 54 | - name: gopkg.in/yaml.v2 55 | version: 4c78c975fe7c825c6d1466c42be594d1d6f3aba6 56 | testImports: [] 57 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/loomnetwork/client 2 | import: 3 | - package: gopkg.in/alecthomas/kingpin.v2 4 | version: v2.2.5 5 | - package: gopkg.in/yaml.v2 6 | - package: github.com/loomnetwork/oauth2-noserver 7 | - package: github.com/fatih/color 8 | version: ~1.5.0 9 | - package: github.com/skratchdot/open-golang 10 | subpackages: 11 | - open 12 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/loomnetwork/client/auth" 8 | "github.com/loomnetwork/client/client" 9 | "github.com/loomnetwork/client/config" 10 | 11 | kingpin "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | var ( 15 | app = kingpin.New("loom", "Loom network deployment tool.") 16 | debug = app.Flag("debug", "Enable debug mode.").Bool() 17 | 18 | login = app.Command("login", "Logins you into loom network. It also retrieves api key.") 19 | network = login.Arg("network", "Either Linkedin/Github. Leave blank for a prompt").String() 20 | 21 | upload = app.Command("upload", "Upload an application package.") 22 | slug = upload.Arg("appName", "The shortname for your application. Will create the domain .loomapps.io .").Required().String() 23 | zipfile = upload.Arg("zipfile", "File to upload.").Required().String() 24 | 25 | setapikey = app.Command("setapikey", "Set api key.") 26 | apikey = setapikey.Arg("key", "Key.").Required().String() 27 | ) 28 | 29 | var DEFAULT_HOST = "https://platform.loomx.io" 30 | 31 | func main() { 32 | c := config.ReadConfig() 33 | hostName := c.HostName 34 | if hostName == "" { 35 | hostName = DEFAULT_HOST 36 | } 37 | 38 | switch kingpin.MustParse(app.Parse(os.Args[1:])) { 39 | case login.FullCommand(): 40 | newapiKey := auth.Login(*network, hostName) 41 | if len(newapiKey) > 0 { 42 | config.WriteConfig(newapiKey) 43 | fmt.Println("Api successfully set!") 44 | } else { 45 | fmt.Println("Failed logging in.") 46 | } 47 | case upload.FullCommand(): 48 | if c.Apikey == "" || len(c.Apikey) < 3 { 49 | fmt.Println("Missing api key or api key is invalid. Please use the \"login\" command or set it with the \"setapikey\" command.") 50 | return 51 | } 52 | 53 | if *zipfile == "" { 54 | fmt.Println("Please supply a zipfile") 55 | return 56 | } 57 | client.UploadApp(hostName, c.Apikey, *zipfile, *slug) 58 | case setapikey.FullCommand(): 59 | if *apikey == "" || len(*apikey) < 3 { 60 | fmt.Println("Missing api key or api key is invalid") 61 | return 62 | } 63 | config.WriteConfig(*apikey) 64 | fmt.Println("Api successfully set!") 65 | } 66 | } 67 | --------------------------------------------------------------------------------