From 85e19c546fd356488da8206b1e5439aef921b515 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Tue, 17 Apr 2018 21:26:05 -0400 Subject: [PATCH 01/15] Provide base houston integration --- Gopkg.lock | 8 +- Gopkg.toml | 4 + config/config.go | 15 +++ config/types.go | 4 + houston/houston.go | 115 ++++++++++++++++++ houston/http.go | 102 ++++++++++++++++ houston/types.go | 29 +++++ vendor/github.com/shurcooL/go/browser/LICENSE | 27 ++++ .../github.com/shurcooL/go/ctxhttp/ctxhttp.go | 78 ++++++++++++ 9 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 houston/houston.go create mode 100644 houston/http.go create mode 100644 houston/types.go create mode 100644 vendor/github.com/shurcooL/go/browser/LICENSE create mode 100644 vendor/github.com/shurcooL/go/ctxhttp/ctxhttp.go diff --git a/Gopkg.lock b/Gopkg.lock index 11fc82be9..3523e018a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -325,6 +325,12 @@ ] revision = "282c8707aa210456a825798969cc27edda34992a" +[[projects]] + branch = "master" + name = "github.com/shurcooL/go" + packages = ["ctxhttp"] + revision = "47fa5b7ceee66c60ac3a281416089035bf526a3c" + [[projects]] name = "github.com/sirupsen/logrus" packages = ["."] @@ -439,6 +445,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e9fc06cdc2748ad4d6c7199e3f6ad1d19c416801086d914f2252edb70464ece7" + inputs-digest = "f3633d08b4ae352ae9054c1f956b70b7666a6ce524011d6ee0b1a89e3bfd7c76" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 7ed06fc64..2291d0b5f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -68,3 +68,7 @@ [[override]] name = "github.com/xeipuuv/gojsonschema" revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70" + +[[constraint]] + branch = "master" + name = "github.com/shurcooL/go" diff --git a/config/config.go b/config/config.go index f04ebc20b..be60a7667 100644 --- a/config/config.go +++ b/config/config.go @@ -38,6 +38,10 @@ var ( RegistryUser: newCfg("docker.registry.user", true, "admin"), RegistryPassword: newCfg("docker.registry.password", true, "admin"), ProjectName: newCfg("project.name", true, ""), + APIHostName: newCfg("api.hostname", true, "localhost"), + APIProtocol: newCfg("api.protocol", true, "http"), + APIPort: newCfg("api.port", true, "8870"), + APIVersion: newCfg("api.version", true, "v1"), } // viperHome is the viper object in the users home directory @@ -175,3 +179,14 @@ func saveConfig(v *viper.Viper, file string) error { } return nil } + +// APIURL will return a full qualified API url +func APIURL() string { + return fmt.Sprintf( + "%s://%s:%s/%s", + CFG.APIProtocol.GetString(), + CFG.APIHostName.GetString(), + CFG.APIPort.GetString(), + CFG.APIVersion.GetString(), + ) +} diff --git a/config/types.go b/config/types.go index 276da68ad..19f4c5f1a 100644 --- a/config/types.go +++ b/config/types.go @@ -9,6 +9,10 @@ type cfg struct { // cfgs houses all configurations for an astro project type cfgs struct { + APIHostName cfg + APIProtocol cfg + APIPort cfg + APIVersion cfg PostgresUser cfg PostgresPassword cfg PostgresHost cfg diff --git a/houston/houston.go b/houston/houston.go new file mode 100644 index 000000000..c15989060 --- /dev/null +++ b/houston/houston.go @@ -0,0 +1,115 @@ +package houston + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/astronomerio/astro-cli/config" + "github.com/pkg/errors" + // "github.com/sirupsen/logrus" +) + +var ( + createTokenRequest = ` + mutation createToken { + createToken(username:"%s", password:"%s") { + success + message + token + decoded { + id + sU + } + } + } + ` + + // log = logrus.WithField("package", "houston") +) + +// Client containers the logger and HTTPClient used to communicate with the HoustonAPI +type Client struct { + HTTPClient *HTTPClient +} + +type HoustonResponse struct { + Raw *http.Response + Body string +} + +// NewHoustonClient returns a new Client with the logger and HTTP client setup. +func NewHoustonClient(HTTPClient *HTTPClient) *Client { + return &Client{ + HTTPClient: HTTPClient, + } +} + +type GraphQLQuery struct { + Query string `json:"query"` +} + +func (c *Client) QueryHouston(query string) (HoustonResponse, error) { + // logger := log.WithField("function", "QueryHouston") + doOpts := DoOptions{ + Data: GraphQLQuery{query}, + Headers: map[string]string{ + "Accept": "application/json", + }, + } + + // set headers + // if config.GetString(config.AuthTokenCFG) != "" { + // doOpts.Headers["authorization"] = config.GetString(config.AuthTokenCFG) + // } + + // if config.GetString(config.OrgIDCFG) != "" { + // doOpts.Headers["organization"] = config.GetString(config.OrgIDCFG) + // } + + var response HoustonResponse + httpResponse, err := c.HTTPClient.Do("POST", config.APIURL(), &doOpts) + if err != nil { + return response, err + } + defer httpResponse.Body.Close() + + // strings.NewReader(jsonStream) + body, err := ioutil.ReadAll(httpResponse.Body) + if err != nil { + return response, err + } + + response = HoustonResponse{httpResponse, string(body)} + + // logger.Debug(query) + // logger.Debug(response.Body) + + return response, nil +} + +// CreateToken will request a new token from Huston, passing the users e-mail and password. +// Returns a CreateTokenResponse structure with the users ID and Token inside. +func (c *Client) CreateToken(email string, password string) (*CreateTokenResponse, error) { + // logger := log.WithField("method", "CreateToken") + // logger.Debug("Entered CreateToken") + + request := fmt.Sprintf(createTokenRequest, email, password) + + response, err := c.QueryHouston(request) + if err != nil { + // logger.Error(err) + return nil, errors.Wrap(err, "CreateToken Failed") + } + + var body CreateTokenResponse + err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) + + if err != nil { + // logger.Error(err) + return nil, errors.Wrap(err, "CreateToken JSON decode failed") + } + return &body, nil +} diff --git a/houston/http.go b/houston/http.go new file mode 100644 index 000000000..d8ccc55a1 --- /dev/null +++ b/houston/http.go @@ -0,0 +1,102 @@ +package houston + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" + "github.com/shurcooL/go/ctxhttp" +) + +// HTTPClient returns an HTTP Client struct that can execute HTTP requests +type HTTPClient struct { + HTTPClient *http.Client +} + +// DoOptions are options passed to the HTTPClient.Do function +type DoOptions struct { + Data interface{} + Context context.Context + Headers map[string]string + ForceJSON bool +} + +// NewHTTPClient returns a new HTTP Client +func NewHTTPClient() *HTTPClient { + return &HTTPClient{ + HTTPClient: &http.Client{}, + } +} + +// Do executes the given HTTP request and returns the HTTP Response +func (c *HTTPClient) Do(method, path string, doOptions *DoOptions) (*http.Response, error) { + var body io.Reader + if doOptions.Data != nil || doOptions.ForceJSON { + buf, err := json.Marshal(doOptions.Data) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(buf) + } + + req, err := http.NewRequest(method, path, body) + if err != nil { + return nil, err + } + if doOptions.Data != nil { + req.Header.Set("Content-Type", "application/json") + } + + for k, v := range doOptions.Headers { + req.Header.Set(k, v) + } + + ctx := doOptions.Context + if ctx == nil { + ctx = context.Background() + } + + resp, err := ctxhttp.Do(ctx, c.HTTPClient, req) + if err != nil { + return nil, errors.Wrap(chooseError(ctx, err), "HTTP DO Failed") + } + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + return nil, newError(resp) + } + return resp, nil +} + +// if error in context, return that instead of generic http error +func chooseError(ctx context.Context, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return err + } +} + +// Error is a custom HTTP error structure +type Error struct { + Status int + Message string +} + +func newError(resp *http.Response) *Error { + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)} + } + return &Error{Status: resp.StatusCode, Message: string(data)} +} + +// Error implemented to match Error interface +func (e *Error) Error() string { + return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) +} diff --git a/houston/types.go b/houston/types.go new file mode 100644 index 000000000..b4d6483bc --- /dev/null +++ b/houston/types.go @@ -0,0 +1,29 @@ +package houston + +type CreateTokenResponse struct { + Data struct { + CreateToken Token `json:"createToken"` + } `json:"data"` +} + +type Token struct { + Success bool `json:"success"` + Message string `json:"message"` + Token string `json:"token"` + Decoded Decoded `json:"decoded"` +} + +type Deployment struct { + Id string `json:"uuid"` + Type string `json:"type"` + Title string `json:"title"` + ReleaseName string `json:"release_name"` + Version string `json:"version"` +} + +type Decoded struct { + ID string `json:"id"` + SU bool `json:"sU"` + Iat int `json:"iat"` + Exp int `json:"exp"` +} diff --git a/vendor/github.com/shurcooL/go/browser/LICENSE b/vendor/github.com/shurcooL/go/browser/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/github.com/shurcooL/go/browser/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/shurcooL/go/ctxhttp/ctxhttp.go b/vendor/github.com/shurcooL/go/ctxhttp/ctxhttp.go new file mode 100644 index 000000000..3cf40a264 --- /dev/null +++ b/vendor/github.com/shurcooL/go/ctxhttp/ctxhttp.go @@ -0,0 +1,78 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +// Package ctxhttp provides helper functions for performing context-aware HTTP requests. +// +// It's a copy of "golang.org/x/net/context/ctxhttp" with pre-1.7 support dropped, +// and "golang.org/x/net/context" import replaced with "context". +// It exists temporarily until "golang.org/x/net/context/ctxhttp" is updated, +// which will happen "in a couple releases" according to https://golang.org/cl/24620. +package ctxhttp + +import ( + "context" + "io" + "net/http" + "net/url" + "strings" +) + +// Do sends an HTTP request with the provided http.Client and returns +// an HTTP response. +// +// If the client is nil, http.DefaultClient is used. +// +// The provided ctx must be non-nil. If it is canceled or times out, +// ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + resp, err := client.Do(req.WithContext(ctx)) + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + if err != nil { + select { + case <-ctx.Done(): + err = ctx.Err() + default: + } + } + return resp, err +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} From 9731937bbfb35a1b26632619f41a0e663e5c53b1 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Tue, 17 Apr 2018 21:59:25 -0400 Subject: [PATCH 02/15] Add houston auth flow --- auth/auth.go | 23 +++++++++++++++++++---- config/config.go | 5 ++--- config/types.go | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index b98af432c..81fdfccd4 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -5,6 +5,7 @@ import ( "github.com/astronomerio/astro-cli/config" "github.com/astronomerio/astro-cli/docker" + "github.com/astronomerio/astro-cli/houston" "github.com/astronomerio/astro-cli/utils" ) @@ -14,14 +15,28 @@ func Login() { username := utils.InputText("Username: ") password, _ := utils.InputPassword("Password: ") - err := docker.ExecLogin(registry, username, password) - if err != nil { + HTTP := houston.NewHTTPClient() + API := houston.NewHoustonClient(HTTP) + + // authenticate with houston + body, houstonErr := API.CreateToken(username, password) + if houstonErr != nil { + panic(houstonErr) + } else if body.Data.CreateToken.Success != true { + fmt.Println(body.Data.CreateToken.Message) + return + } + + config.CFG.APIAuthToken.SetProjectString(body.Data.CreateToken.Token) + + //authenticate with registry + dockerErr := docker.ExecLogin(registry, username, password) + if dockerErr != nil { // Println instead of panic to prevent excessive error logging to stdout on a failed login - fmt.Println(err) + fmt.Println(dockerErr) return } - // pass successful credentials to config config.CFG.RegistryUser.SetProjectString(username) config.CFG.RegistryPassword.SetProjectString(password) } diff --git a/config/config.go b/config/config.go index be60a7667..480fc3b0d 100644 --- a/config/config.go +++ b/config/config.go @@ -41,7 +41,7 @@ var ( APIHostName: newCfg("api.hostname", true, "localhost"), APIProtocol: newCfg("api.protocol", true, "http"), APIPort: newCfg("api.port", true, "8870"), - APIVersion: newCfg("api.version", true, "v1"), + APIAuthToken: newCfg("api.authToken", true, ""), } // viperHome is the viper object in the users home directory @@ -183,10 +183,9 @@ func saveConfig(v *viper.Viper, file string) error { // APIURL will return a full qualified API url func APIURL() string { return fmt.Sprintf( - "%s://%s:%s/%s", + "%s://%s:%s/v1", CFG.APIProtocol.GetString(), CFG.APIHostName.GetString(), CFG.APIPort.GetString(), - CFG.APIVersion.GetString(), ) } diff --git a/config/types.go b/config/types.go index 19f4c5f1a..b1e95a9d8 100644 --- a/config/types.go +++ b/config/types.go @@ -12,7 +12,7 @@ type cfgs struct { APIHostName cfg APIProtocol cfg APIPort cfg - APIVersion cfg + APIAuthToken cfg PostgresUser cfg PostgresPassword cfg PostgresHost cfg From 662ed2ee6036bd1674f551e8412de485e5707193 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Tue, 17 Apr 2018 23:04:30 -0400 Subject: [PATCH 03/15] Add airflow create deployment flow --- airflow/airflow.go | 13 ++++++++++++- cmd/airflow.go | 3 ++- houston/houston.go | 47 +++++++++++++++++++++++++++++++++++++++++----- houston/types.go | 9 +++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/airflow/airflow.go b/airflow/airflow.go index 3a7499195..05e24568f 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -6,6 +6,7 @@ import ( "path/filepath" "github.com/astronomerio/astro-cli/airflow/include" + "github.com/astronomerio/astro-cli/houston" "github.com/astronomerio/astro-cli/utils" ) @@ -81,6 +82,16 @@ func Init(path string) error { } // Create new airflow deployment -func Create() error { +func Create(title string) error { + HTTP := houston.NewHTTPClient() + API := houston.NewHoustonClient(HTTP) + + // authenticate with houston + body, houstonErr := API.CreateDeployment(title) + if houstonErr != nil { + panic(houstonErr) + } + + fmt.Println(body.Data.CreateDeployment.Message) return nil } diff --git a/cmd/airflow.go b/cmd/airflow.go index f8ccad589..a10f006b4 100644 --- a/cmd/airflow.go +++ b/cmd/airflow.go @@ -36,6 +36,7 @@ var ( Use: "create", Short: "Create a new airflow deployment", Long: "Create a new airflow deployment", + Args: cobra.ExactArgs(1), RunE: airflowCreate, } @@ -154,7 +155,7 @@ func airflowInit(cmd *cobra.Command, args []string) error { } func airflowCreate(cmd *cobra.Command, args []string) error { - return airflow.Create() + return airflow.Create(args[0]) } func airflowDeploy(cmd *cobra.Command, args []string) error { diff --git a/houston/houston.go b/houston/houston.go index c15989060..443b9dcfb 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -27,6 +27,21 @@ var ( } ` + createDeploymentRequest = ` + mutation CreateDeployment { + createDeployment( + title: "%s", + organizationUuid: "", + teamUuid: "", + version: "") { + success, + message, + id, + code + } + } + ` + // log = logrus.WithField("package", "houston") ) @@ -61,9 +76,9 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { } // set headers - // if config.GetString(config.AuthTokenCFG) != "" { - // doOpts.Headers["authorization"] = config.GetString(config.AuthTokenCFG) - // } + if config.CFG.APIAuthToken.GetString() != "" { + doOpts.Headers["authorization"] = config.CFG.APIAuthToken.GetString() + } // if config.GetString(config.OrgIDCFG) != "" { // doOpts.Headers["organization"] = config.GetString(config.OrgIDCFG) @@ -90,7 +105,7 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { return response, nil } -// CreateToken will request a new token from Huston, passing the users e-mail and password. +// CreateToken will request a new token from Houston, passing the users e-mail and password. // Returns a CreateTokenResponse structure with the users ID and Token inside. func (c *Client) CreateToken(email string, password string) (*CreateTokenResponse, error) { // logger := log.WithField("method", "CreateToken") @@ -106,10 +121,32 @@ func (c *Client) CreateToken(email string, password string) (*CreateTokenRespons var body CreateTokenResponse err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) - if err != nil { // logger.Error(err) return nil, errors.Wrap(err, "CreateToken JSON decode failed") } return &body, nil } + +// CreateDeployment will send request to Houston to create a new AirflowDeployment +// Returns a CreateDeploymentResponse which contains the unique id of deployment +func (c *Client) CreateDeployment(title string) (*CreateDeploymentResponse, error) { + // logger := log.WithField("method", "CreateDeployment") + // logger.Debug("Entered CreateDeployment") + + request := fmt.Sprintf(createDeploymentRequest, title) + + response, err := c.QueryHouston(request) + if err != nil { + // logger.Error(err) + return nil, errors.Wrap(err, "CreateDeployment Failed") + } + + var body CreateDeploymentResponse + err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) + if err != nil { + // logger.Error(key) + return nil, errors.Wrap(err, "CreateDeployment JSON decode failed") + } + return &body, nil +} diff --git a/houston/types.go b/houston/types.go index b4d6483bc..49d4d895b 100644 --- a/houston/types.go +++ b/houston/types.go @@ -1,5 +1,11 @@ package houston +type CreateDeploymentResponse struct { + Data struct { + CreateDeployment Deployment `json:"createDeployment"` + } `json:"data"` +} + type CreateTokenResponse struct { Data struct { CreateToken Token `json:"createToken"` @@ -14,6 +20,9 @@ type Token struct { } type Deployment struct { + Success bool `json:"success"` + Message string `json:"message"` + Code string `json:"code"` Id string `json:"uuid"` Type string `json:"type"` Title string `json:"title"` From 3d0540f3aed4db7fb385076002eb0be609f9ecbc Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Wed, 18 Apr 2018 00:10:30 -0400 Subject: [PATCH 04/15] Add fetch airflow deployments flow --- airflow/airflow.go | 20 ++++++++++-- cmd/airflow.go | 14 ++++++++ houston/houston.go | 80 +++++++++++++++++++++++++++++++++------------- houston/types.go | 18 ++++++++--- 4 files changed, 103 insertions(+), 29 deletions(-) diff --git a/airflow/airflow.go b/airflow/airflow.go index 05e24568f..51fd6d9f8 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -86,12 +86,28 @@ func Create(title string) error { HTTP := houston.NewHTTPClient() API := houston.NewHoustonClient(HTTP) - // authenticate with houston body, houstonErr := API.CreateDeployment(title) if houstonErr != nil { - panic(houstonErr) + return houstonErr } fmt.Println(body.Data.CreateDeployment.Message) return nil } + +// List all airflow deployments +func List() error { + HTTP := houston.NewHTTPClient() + API := houston.NewHoustonClient(HTTP) + + body, houstonErr := API.FetchDeployments() + if houstonErr != nil { + return houstonErr + } + + for _, d := range body.Data.FetchDeployments { + rowTmp := "Title: %s\nId: %s\nRelease: %s\nVersion: %s\n\n" + fmt.Printf(rowTmp, d.Title, d.Id, d.ReleaseName, d.Version) + } + return nil +} diff --git a/cmd/airflow.go b/cmd/airflow.go index a10f006b4..7ba84401d 100644 --- a/cmd/airflow.go +++ b/cmd/airflow.go @@ -40,6 +40,13 @@ var ( RunE: airflowCreate, } + airflowListCmd = &cobra.Command{ + Use: "list", + Short: "List airflow clusters", + Long: "List all created airflow clusters", + RunE: airflowList, + } + airflowDeployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy an airflow project", @@ -96,6 +103,9 @@ func init() { // Airflow create airflowRootCmd.AddCommand(airflowCreateCmd) + // Airflow list + airflowRootCmd.AddCommand(airflowListCmd) + // Airflow deploy airflowRootCmd.AddCommand(airflowDeployCmd) @@ -158,6 +168,10 @@ func airflowCreate(cmd *cobra.Command, args []string) error { return airflow.Create(args[0]) } +func airflowList(cmd *cobra.Command, args []string) error { + return airflow.List() +} + func airflowDeploy(cmd *cobra.Command, args []string) error { return airflow.Deploy(projectRoot, args[0]) } diff --git a/houston/houston.go b/houston/houston.go index 443b9dcfb..57f137c5a 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -13,6 +13,21 @@ import ( ) var ( + createDeploymentRequest = ` + mutation CreateDeployment { + createDeployment( + title: "%s", + organizationUuid: "", + teamUuid: "", + version: "") { + success, + message, + id, + code + } + } + ` + createTokenRequest = ` mutation createToken { createToken(username:"%s", password:"%s") { @@ -27,20 +42,16 @@ var ( } ` - createDeploymentRequest = ` - mutation CreateDeployment { - createDeployment( - title: "%s", - organizationUuid: "", - teamUuid: "", - version: "") { - success, - message, - id, - code + fetchDeploymentsRequest = ` + query FetchAllDeployments { + fetchDeployments { + uuid + type + title + release_name + version } - } - ` + }` // log = logrus.WithField("package", "houston") ) @@ -105,6 +116,29 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { return response, nil } +// CreateDeployment will send request to Houston to create a new AirflowDeployment +// Returns a CreateDeploymentResponse which contains the unique id of deployment +func (c *Client) CreateDeployment(title string) (*CreateDeploymentResponse, error) { + // logger := log.WithField("method", "CreateDeployment") + // logger.Debug("Entered CreateDeployment") + + request := fmt.Sprintf(createDeploymentRequest, title) + + response, err := c.QueryHouston(request) + if err != nil { + // logger.Error(err) + return nil, errors.Wrap(err, "CreateDeployment Failed") + } + + var body CreateDeploymentResponse + err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) + if err != nil { + // logger.Error(key) + return nil, errors.Wrap(err, "CreateDeployment JSON decode failed") + } + return &body, nil +} + // CreateToken will request a new token from Houston, passing the users e-mail and password. // Returns a CreateTokenResponse structure with the users ID and Token inside. func (c *Client) CreateToken(email string, password string) (*CreateTokenResponse, error) { @@ -128,25 +162,25 @@ func (c *Client) CreateToken(email string, password string) (*CreateTokenRespons return &body, nil } -// CreateDeployment will send request to Houston to create a new AirflowDeployment -// Returns a CreateDeploymentResponse which contains the unique id of deployment -func (c *Client) CreateDeployment(title string) (*CreateDeploymentResponse, error) { - // logger := log.WithField("method", "CreateDeployment") - // logger.Debug("Entered CreateDeployment") +// FetchDeployments will request all airflow deployments from Houston +// Returns a FetchDeploymentResponse structure with deployment details +func (c *Client) FetchDeployments() (*FetchDeploymentsResponse, error) { + // logger := log.WithField("method", "FetchDeployments") + // logger.Debug("Entered FetchDeployments") - request := fmt.Sprintf(createDeploymentRequest, title) + request := fetchDeploymentsRequest response, err := c.QueryHouston(request) if err != nil { // logger.Error(err) - return nil, errors.Wrap(err, "CreateDeployment Failed") + return nil, errors.Wrap(err, "FetchDeployments Failed") } - var body CreateDeploymentResponse + var body FetchDeploymentsResponse err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) if err != nil { - // logger.Error(key) - return nil, errors.Wrap(err, "CreateDeployment JSON decode failed") + //logger.Error(err) + return nil, errors.Wrap(err, "FetchDeployments JSON decode failed") } return &body, nil } diff --git a/houston/types.go b/houston/types.go index 49d4d895b..816c9eebe 100644 --- a/houston/types.go +++ b/houston/types.go @@ -2,7 +2,7 @@ package houston type CreateDeploymentResponse struct { Data struct { - CreateDeployment Deployment `json:"createDeployment"` + CreateDeployment CreatedDeployment `json:"createDeployment"` } `json:"data"` } @@ -12,6 +12,12 @@ type CreateTokenResponse struct { } `json:"data"` } +type FetchDeploymentsResponse struct { + Data struct { + FetchDeployments []Deployment `json:"fetchDeployments"` + } `json:"data"` +} + type Token struct { Success bool `json:"success"` Message string `json:"message"` @@ -19,10 +25,14 @@ type Token struct { Decoded Decoded `json:"decoded"` } +type CreatedDeployment struct { + Success bool `json:"success"` + Message string `json:"message"` + Code string `json:"code"` + Id string `json:"uuid"` +} + type Deployment struct { - Success bool `json:"success"` - Message string `json:"message"` - Code string `json:"code"` Id string `json:"uuid"` Type string `json:"type"` Title string `json:"title"` From 0b51c6606864aa421c2f147423ca865e690f2928 Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 25 Apr 2018 09:46:31 -0400 Subject: [PATCH 05/15] =?UTF-8?q?Added=20IDE=E2=80=99s=20to=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index eabd68dc5..c75ecf970 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ Dockerfile meta/ dist/ packages.txt -requirements.txt \ No newline at end of file +requirements.txt + +# IDEs +.idea/ +.vscode/ \ No newline at end of file From 352905960a116fb698af978093fca2c413e06e34 Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 25 Apr 2018 09:49:21 -0400 Subject: [PATCH 06/15] Changed some config vars around --- config/config.go | 16 ++++++++-------- config/types.go | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/config.go b/config/config.go index 480fc3b0d..07bd94094 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,9 @@ var ( // CFG Houses configuration meta CFG = cfgs{ + CloudDomain: newCfg("cloud.domain", true, ""), + CloudAPIProtocol: newCfg("cloud.api.protocol", true, "https"), + CloudAPIPort: newCfg("cloud.api.port", true, "443"), PostgresUser: newCfg("postgres.user", true, "postgres"), PostgresPassword: newCfg("postgres.password", true, "postgres"), PostgresHost: newCfg("postgres.host", true, "postgres"), @@ -38,10 +41,7 @@ var ( RegistryUser: newCfg("docker.registry.user", true, "admin"), RegistryPassword: newCfg("docker.registry.password", true, "admin"), ProjectName: newCfg("project.name", true, ""), - APIHostName: newCfg("api.hostname", true, "localhost"), - APIProtocol: newCfg("api.protocol", true, "http"), - APIPort: newCfg("api.port", true, "8870"), - APIAuthToken: newCfg("api.authToken", true, ""), + UserAPIAuthToken: newCfg("user.apiAuthToken", true, ""), } // viperHome is the viper object in the users home directory @@ -183,9 +183,9 @@ func saveConfig(v *viper.Viper, file string) error { // APIURL will return a full qualified API url func APIURL() string { return fmt.Sprintf( - "%s://%s:%s/v1", - CFG.APIProtocol.GetString(), - CFG.APIHostName.GetString(), - CFG.APIPort.GetString(), + "%s://houston.%s:%s/v1", + CFG.CloudAPIProtocol.GetString(), + CFG.CloudDomain.GetString(), + CFG.CloudAPIPort.GetString(), ) } diff --git a/config/types.go b/config/types.go index b1e95a9d8..79e46694b 100644 --- a/config/types.go +++ b/config/types.go @@ -9,10 +9,9 @@ type cfg struct { // cfgs houses all configurations for an astro project type cfgs struct { - APIHostName cfg - APIProtocol cfg - APIPort cfg - APIAuthToken cfg + CloudDomain cfg + CloudAPIProtocol cfg + CloudAPIPort cfg PostgresUser cfg PostgresPassword cfg PostgresHost cfg @@ -21,6 +20,7 @@ type cfgs struct { RegistryUser cfg RegistryPassword cfg ProjectName cfg + UserAPIAuthToken cfg } // Creates a new cfg struct From d1730bab81b8314a36dfb0e1bc1f5e80c4a0959d Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 25 Apr 2018 09:49:36 -0400 Subject: [PATCH 07/15] Update GQL queries --- houston/houston.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/houston/houston.go b/houston/houston.go index 57f137c5a..27def066b 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -19,6 +19,7 @@ var ( title: "%s", organizationUuid: "", teamUuid: "", + type: "airflow", version: "") { success, message, @@ -30,7 +31,7 @@ var ( createTokenRequest = ` mutation createToken { - createToken(username:"%s", password:"%s") { + createToken(identity:"%s", password:"%s") { success message token From 8f0048d5253d1c5f648801df9bf62e51c7856cd7 Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 25 Apr 2018 09:55:12 -0400 Subject: [PATCH 08/15] Updated config value for token --- auth/auth.go | 2 +- houston/houston.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 81fdfccd4..daf5aee8c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -27,7 +27,7 @@ func Login() { return } - config.CFG.APIAuthToken.SetProjectString(body.Data.CreateToken.Token) + config.CFG.UserAPIAuthToken.SetProjectString(body.Data.CreateToken.Token) //authenticate with registry dockerErr := docker.ExecLogin(registry, username, password) diff --git a/houston/houston.go b/houston/houston.go index 27def066b..3c02d7084 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -88,8 +88,8 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { } // set headers - if config.CFG.APIAuthToken.GetString() != "" { - doOpts.Headers["authorization"] = config.CFG.APIAuthToken.GetString() + if config.CFG.UserAPIAuthToken.GetString() != "" { + doOpts.Headers["authorization"] = config.CFG.UserAPIAuthToken.GetString() } // if config.GetString(config.OrgIDCFG) != "" { From e4ce64908faca90aceb9f9ef7d5ae875707f63b1 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Wed, 25 Apr 2018 11:20:21 -0400 Subject: [PATCH 09/15] Coalesce registry url through config chain --- cmd/auth.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/auth.go b/cmd/auth.go index 160704b4b..343bfb893 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/astronomerio/astro-cli/auth" "github.com/astronomerio/astro-cli/config" "github.com/pkg/errors" @@ -44,11 +46,32 @@ func init() { func authLogin(cmd *cobra.Command, args []string) error { if len(registryOverride) > 0 { - config.CFG.RegistryAuthority.SetProjectString(registryOverride) + } - if config.CFG.RegistryAuthority.GetHomeString() == "" && - registryOverride == "" { + projectRegistry := config.CFG.RegistryAuthority.GetProjectString() + projectCloudDomain := config.CFG.CloudDomain.GetProjectString() + globalCloudDomain := config.CFG.CloudDomain.GetHomeString() + globalRegistry := config.CFG.RegistryAuthority.GetHomeString() + + // checks for registry in all the expected places + // prompts user for any implicit behavior + switch { + case registryOverride != "": + config.CFG.RegistryAuthority.SetProjectString(registryOverride) + case projectRegistry != "": + // Don't prompt user, using project config is default expected behavior + break + case projectCloudDomain != "": + config.CFG.RegistryAuthority.SetProjectString(fmt.Sprintf("registry.%s", projectCloudDomain)) + fmt.Printf("No registry set, using default: registry.%s\n", projectCloudDomain) + case globalCloudDomain != "": + config.CFG.RegistryAuthority.SetProjectString(fmt.Sprintf("registry.%s", globalCloudDomain)) + fmt.Printf("No registry set, using default: registry.%s\n", globalCloudDomain) + case globalRegistry != "": + // Don't prompt user, falling back to global config is default expected behavior + break + default: return errors.New("No registry set. Use -r to pass a custom registry\n\nEx.\nastro auth login -r registry.EXAMPLE_DOMAIN.com\n ") } From 8b7a1ba86d253b02b7acd1c36c7dedce5d404ece Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Thu, 26 Apr 2018 08:20:34 -0400 Subject: [PATCH 10/15] Use pkg convention for standalone utils --- airflow/airflow.go | 8 +++--- auth/auth.go | 6 ++--- cmd/airflow.go | 4 +-- config/config.go | 12 ++++----- pkg/README.md | 1 + pkg/fileutil/files.go | 53 ++++++++++++++++++++++++++++++++++++ pkg/fileutil/paths.go | 62 +++++++++++++++++++++++++++++++++++++++++++ pkg/input/input.go | 41 ++++++++++++++++++++++++++++ 8 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 pkg/README.md create mode 100644 pkg/fileutil/files.go create mode 100644 pkg/fileutil/paths.go create mode 100644 pkg/input/input.go diff --git a/airflow/airflow.go b/airflow/airflow.go index 51fd6d9f8..20ace64b6 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -7,7 +7,7 @@ import ( "github.com/astronomerio/astro-cli/airflow/include" "github.com/astronomerio/astro-cli/houston" - "github.com/astronomerio/astro-cli/utils" + "github.com/astronomerio/astro-cli/pkg/fileutil" ) func initDirs(root string, dirs []string) bool { @@ -20,7 +20,7 @@ func initDirs(root string, dirs []string) bool { fullpath := filepath.Join(root, dir) // Move on if already exists - if utils.Exists(fullpath) { + if fileutil.Exists(fullpath) { exists = true continue } @@ -44,13 +44,13 @@ func initFiles(root string, files map[string]string) bool { fullpath := filepath.Join(root, file) // Move on if already exiss - if utils.Exists(fullpath) { + if fileutil.Exists(fullpath) { exists = true continue } // Write files out - if err := utils.WriteStringToFile(fullpath, content); err != nil { + if err := fileutil.WriteStringToFile(fullpath, content); err != nil { fmt.Println(err) } } diff --git a/auth/auth.go b/auth/auth.go index daf5aee8c..bedd18b71 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -6,14 +6,14 @@ import ( "github.com/astronomerio/astro-cli/config" "github.com/astronomerio/astro-cli/docker" "github.com/astronomerio/astro-cli/houston" - "github.com/astronomerio/astro-cli/utils" + "github.com/astronomerio/astro-cli/pkg/input" ) // Login logs a user into the docker registry. Will need to login to Houston next. func Login() { registry := config.CFG.RegistryAuthority.GetString() - username := utils.InputText("Username: ") - password, _ := utils.InputPassword("Password: ") + username := input.InputText("Username: ") + password, _ := input.InputPassword("Password: ") HTTP := houston.NewHTTPClient() API := houston.NewHoustonClient(HTTP) diff --git a/cmd/airflow.go b/cmd/airflow.go index 7ba84401d..99bcda3d6 100644 --- a/cmd/airflow.go +++ b/cmd/airflow.go @@ -10,7 +10,7 @@ import ( "github.com/astronomerio/astro-cli/airflow" "github.com/astronomerio/astro-cli/config" - "github.com/astronomerio/astro-cli/utils" + "github.com/astronomerio/astro-cli/pkg/fileutil" "github.com/iancoleman/strcase" "github.com/spf13/cobra" ) @@ -132,7 +132,7 @@ func ensureProjectDir(cmd *cobra.Command, args []string) { // Use project name for image name func airflowInit(cmd *cobra.Command, args []string) error { // Grab working directory - path := utils.GetWorkingDir() + path := fileutil.GetWorkingDir() // Validate project name if len(projectName) != 0 { diff --git a/config/config.go b/config/config.go index 07bd94094..27e286abd 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/astronomerio/astro-cli/utils" + "github.com/astronomerio/astro-cli/pkg/fileutil" "github.com/pkg/errors" "github.com/spf13/viper" ) @@ -21,7 +21,7 @@ var ( ConfigDir = ".astro" // HomeConfigPath is the path to the users global directory - HomeConfigPath = filepath.Join(utils.GetHomeDir(), ConfigDir) + HomeConfigPath = filepath.Join(fileutil.GetHomeDir(), ConfigDir) // HomeConfigFile is the global config file HomeConfigFile = filepath.Join(HomeConfigPath, ConfigFileNameWithExt) @@ -70,7 +70,7 @@ func initHome() { } // If home config does not exist, create it - if !utils.Exists(HomeConfigFile) { + if !fileutil.Exists(HomeConfigFile) { err := CreateConfig(viperHome, HomeConfigPath, HomeConfigFile) if err != nil { fmt.Printf("Error creating default config in home dir: %s", err) @@ -94,7 +94,7 @@ func initProject() { viperProject.SetConfigName(ConfigFileName) viperProject.SetConfigType(ConfigFileType) - configPath, searchErr := utils.FindDirInPath(ConfigDir) + configPath, searchErr := fileutil.FindDirInPath(ConfigDir) if searchErr != nil { fmt.Printf("Error searching for project dir: %v\n", searchErr) return @@ -104,7 +104,7 @@ func initProject() { projectConfigFile := filepath.Join(configPath, ConfigFileNameWithExt) // If path is empty or config file does not exist, just return - if len(configPath) == 0 || configPath == HomeConfigPath || !utils.Exists(projectConfigFile) { + if len(configPath) == 0 || configPath == HomeConfigPath || !fileutil.Exists(projectConfigFile) { return } @@ -161,7 +161,7 @@ func ProjectConfigExists() bool { // ProjectRoot returns the path to the nearest project root func ProjectRoot() (string, error) { - configPath, searchErr := utils.FindDirInPath(ConfigDir) + configPath, searchErr := fileutil.FindDirInPath(ConfigDir) if searchErr != nil { return "", searchErr } diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 000000000..18edc8da5 --- /dev/null +++ b/pkg/README.md @@ -0,0 +1 @@ +pkg/ is a collection of utility packages used by astro-cli without being specific to astro-cli itself. A package belongs here only if it could possibly be moved out into its own repository in the future. diff --git a/pkg/fileutil/files.go b/pkg/fileutil/files.go new file mode 100644 index 000000000..90aeba9ae --- /dev/null +++ b/pkg/fileutil/files.go @@ -0,0 +1,53 @@ +package fileutil + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// Exists returns a boolean indicating if the givin path already exists +func Exists(path string) bool { + if path == "" { + return false + } + + _, err := os.Stat(path) + if err == nil { + return true + } + + if !os.IsNotExist(err) { + fmt.Println(err) + os.Exit(1) + } + + return false +} + +// WriteStringToFile write a string to a file +func WriteStringToFile(path string, s string) error { + return WriteToFile(path, strings.NewReader(s)) +} + +// WriteToFile writes an io.Reader to a file if it does not exst +func WriteToFile(path string, r io.Reader) error { + dir := filepath.Dir(path) + if dir != "" { + if err := os.MkdirAll(dir, 0777); err != nil { + return err + } + } + + file, err := os.Create(path) + if err != nil { + return err + } + + defer file.Close() + + _, err = io.Copy(file, r) + return err +} diff --git a/pkg/fileutil/paths.go b/pkg/fileutil/paths.go new file mode 100644 index 000000000..77b8e1154 --- /dev/null +++ b/pkg/fileutil/paths.go @@ -0,0 +1,62 @@ +package fileutil + +import ( + "fmt" + "os" + "path" + "path/filepath" + + homedir "github.com/mitchellh/go-homedir" +) + +// GetWorkingDir returns the curent working directory +func GetWorkingDir() string { + work, err := os.Getwd() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + return work +} + +// GetHomeDir returns the home directory +func GetHomeDir() string { + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + return home +} + +// FindDirInPath walks up the current directory looking for the .astro folder +func FindDirInPath(search string) (string, error) { + // Start in our current directory + workingDir := GetWorkingDir() + + // Recursively walk up the filesystem tree + for true { + // Return if we're in root + if workingDir == "/" { + return "", nil + } + + // If searching home path, stop at home root + if workingDir == GetHomeDir() { + return "", nil + } + + // Check if our file exists + exists := Exists(filepath.Join(workingDir, search)) + + // Return where we found it + if exists { + return filepath.Join(workingDir, search), nil + } + + // Set the directory, and try again + workingDir = path.Dir(workingDir) + } + + return "", nil +} diff --git a/pkg/input/input.go b/pkg/input/input.go new file mode 100644 index 000000000..c8df9c4d9 --- /dev/null +++ b/pkg/input/input.go @@ -0,0 +1,41 @@ +package input + +import ( + "bufio" + "fmt" + "os" + "strings" + "syscall" + + "golang.org/x/crypto/ssh/terminal" +) + +// InputText requests a user for input text and returns it +func InputText(promptText string) string { + reader := bufio.NewReader(os.Stdin) + if promptText != "" { + fmt.Print(promptText) + } + text, _ := reader.ReadString('\n') + return strings.Trim(text, "\r\n") +} + +// InputPassword requests a users passord, does not print out what they entered, and returns it +func InputPassword(promptText string) (string, error) { + fmt.Print(promptText) + bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", err + } + fmt.Print("\n") + return string(bytePassword), nil +} + +// InputConfirm requests a user to confirm their input +func InputConfirm(promptText string) (bool, error) { + reader := bufio.NewReader(os.Stdin) + fmt.Printf("%s (y/n) ", promptText) + + text, _ := reader.ReadString('\n') + return strings.Trim(text, "\r\n") == "y", nil +} From 8f1bfdf48926b6d5ff22a1fd407ab2d8770d7c56 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Thu, 26 Apr 2018 08:23:28 -0400 Subject: [PATCH 11/15] Remove straggling files --- utils/files.go | 53 ------------------------------------------ utils/input.go | 41 --------------------------------- utils/paths.go | 62 -------------------------------------------------- 3 files changed, 156 deletions(-) delete mode 100644 utils/files.go delete mode 100644 utils/input.go delete mode 100644 utils/paths.go diff --git a/utils/files.go b/utils/files.go deleted file mode 100644 index efa6c0c45..000000000 --- a/utils/files.go +++ /dev/null @@ -1,53 +0,0 @@ -package utils - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" -) - -// Exists returns a boolean indicating if the givin path already exists -func Exists(path string) bool { - if path == "" { - return false - } - - _, err := os.Stat(path) - if err == nil { - return true - } - - if !os.IsNotExist(err) { - fmt.Println(err) - os.Exit(1) - } - - return false -} - -// WriteStringToFile write a string to a file -func WriteStringToFile(path string, s string) error { - return WriteToFile(path, strings.NewReader(s)) -} - -// WriteToFile writes an io.Reader to a file if it does not exst -func WriteToFile(path string, r io.Reader) error { - dir := filepath.Dir(path) - if dir != "" { - if err := os.MkdirAll(dir, 0777); err != nil { - return err - } - } - - file, err := os.Create(path) - if err != nil { - return err - } - - defer file.Close() - - _, err = io.Copy(file, r) - return err -} diff --git a/utils/input.go b/utils/input.go deleted file mode 100644 index cc874e372..000000000 --- a/utils/input.go +++ /dev/null @@ -1,41 +0,0 @@ -package utils - -import ( - "bufio" - "fmt" - "os" - "strings" - "syscall" - - "golang.org/x/crypto/ssh/terminal" -) - -// InputText requests a user for input text and returns it -func InputText(promptText string) string { - reader := bufio.NewReader(os.Stdin) - if promptText != "" { - fmt.Print(promptText) - } - text, _ := reader.ReadString('\n') - return strings.Trim(text, "\r\n") -} - -// InputPassword requests a users passord, does not print out what they entered, and returns it -func InputPassword(promptText string) (string, error) { - fmt.Print(promptText) - bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) - if err != nil { - return "", err - } - fmt.Print("\n") - return string(bytePassword), nil -} - -// InputConfirm requests a user to confirm their input -func InputConfirm(promptText string) (bool, error) { - reader := bufio.NewReader(os.Stdin) - fmt.Printf("%s (y/n) ", promptText) - - text, _ := reader.ReadString('\n') - return strings.Trim(text, "\r\n") == "y", nil -} diff --git a/utils/paths.go b/utils/paths.go deleted file mode 100644 index c458a5f57..000000000 --- a/utils/paths.go +++ /dev/null @@ -1,62 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "path" - "path/filepath" - - homedir "github.com/mitchellh/go-homedir" -) - -// GetWorkingDir returns the curent working directory -func GetWorkingDir() string { - work, err := os.Getwd() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - return work -} - -// GetHomeDir returns the home directory -func GetHomeDir() string { - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - return home -} - -// FindDirInPath walks up the current directory looking for the .astro folder -func FindDirInPath(search string) (string, error) { - // Start in our current directory - workingDir := GetWorkingDir() - - // Recursively walk up the filesystem tree - for true { - // Return if we're in root - if workingDir == "/" { - return "", nil - } - - // If searching home path, stop at home root - if workingDir == GetHomeDir() { - return "", nil - } - - // Check if our file exists - exists := Exists(filepath.Join(workingDir, search)) - - // Return where we found it - if exists { - return filepath.Join(workingDir, search), nil - } - - // Set the directory, and try again - workingDir = path.Dir(workingDir) - } - - return "", nil -} From 74111fb4a8260f4cee9d0d747e5b840e1b7ca1e9 Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Thu, 26 Apr 2018 13:55:51 -0400 Subject: [PATCH 12/15] Separate generic http into it's own pkg --- airflow/airflow.go | 7 ++++-- auth/auth.go | 6 ++++- houston/houston.go | 38 +++++++++++++++---------------- {houston => pkg/httputil}/http.go | 8 ++++++- 4 files changed, 36 insertions(+), 23 deletions(-) rename {houston => pkg/httputil}/http.go (94%) diff --git a/airflow/airflow.go b/airflow/airflow.go index 20ace64b6..06b3d632d 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -8,6 +8,11 @@ import ( "github.com/astronomerio/astro-cli/airflow/include" "github.com/astronomerio/astro-cli/houston" "github.com/astronomerio/astro-cli/pkg/fileutil" + "github.com/astronomerio/astro-cli/pkg/httputil" +) + +var ( + HTTP = httputil.NewHTTPClient() ) func initDirs(root string, dirs []string) bool { @@ -83,7 +88,6 @@ func Init(path string) error { // Create new airflow deployment func Create(title string) error { - HTTP := houston.NewHTTPClient() API := houston.NewHoustonClient(HTTP) body, houstonErr := API.CreateDeployment(title) @@ -97,7 +101,6 @@ func Create(title string) error { // List all airflow deployments func List() error { - HTTP := houston.NewHTTPClient() API := houston.NewHoustonClient(HTTP) body, houstonErr := API.FetchDeployments() diff --git a/auth/auth.go b/auth/auth.go index bedd18b71..041ad551b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -6,16 +6,20 @@ import ( "github.com/astronomerio/astro-cli/config" "github.com/astronomerio/astro-cli/docker" "github.com/astronomerio/astro-cli/houston" + "github.com/astronomerio/astro-cli/pkg/httputil" "github.com/astronomerio/astro-cli/pkg/input" ) +var ( + HTTP = httputil.NewHTTPClient() +) + // Login logs a user into the docker registry. Will need to login to Houston next. func Login() { registry := config.CFG.RegistryAuthority.GetString() username := input.InputText("Username: ") password, _ := input.InputPassword("Password: ") - HTTP := houston.NewHTTPClient() API := houston.NewHoustonClient(HTTP) // authenticate with houston diff --git a/houston/houston.go b/houston/houston.go index 3c02d7084..1702fc489 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -4,10 +4,10 @@ import ( "encoding/json" "fmt" "io/ioutil" - "net/http" "strings" "github.com/astronomerio/astro-cli/config" + "github.com/astronomerio/astro-cli/pkg/httputil" "github.com/pkg/errors" // "github.com/sirupsen/logrus" ) @@ -57,30 +57,27 @@ var ( // log = logrus.WithField("package", "houston") ) -// Client containers the logger and HTTPClient used to communicate with the HoustonAPI -type Client struct { - HTTPClient *HTTPClient -} - -type HoustonResponse struct { - Raw *http.Response - Body string +// HoustonClient containers the logger and HTTPClient used to communicate with the HoustonAPI +type HoustonClient struct { + HTTPClient *httputil.HTTPClient } // NewHoustonClient returns a new Client with the logger and HTTP client setup. -func NewHoustonClient(HTTPClient *HTTPClient) *Client { - return &Client{ - HTTPClient: HTTPClient, +func NewHoustonClient(c *httputil.HTTPClient) *HoustonClient { + return &HoustonClient{ + HTTPClient: c, } } +// GraphQLQuery wraps a graphql query string type GraphQLQuery struct { Query string `json:"query"` } -func (c *Client) QueryHouston(query string) (HoustonResponse, error) { +// QueryHouston executes a query against the Houston API +func (c *HoustonClient) QueryHouston(query string) (httputil.HTTPResponse, error) { // logger := log.WithField("function", "QueryHouston") - doOpts := DoOptions{ + doOpts := httputil.DoOptions{ Data: GraphQLQuery{query}, Headers: map[string]string{ "Accept": "application/json", @@ -96,7 +93,7 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { // doOpts.Headers["organization"] = config.GetString(config.OrgIDCFG) // } - var response HoustonResponse + var response httputil.HTTPResponse httpResponse, err := c.HTTPClient.Do("POST", config.APIURL(), &doOpts) if err != nil { return response, err @@ -109,7 +106,10 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { return response, err } - response = HoustonResponse{httpResponse, string(body)} + response = httputil.HTTPResponse{ + Raw: httpResponse, + Body: string(body), + } // logger.Debug(query) // logger.Debug(response.Body) @@ -119,7 +119,7 @@ func (c *Client) QueryHouston(query string) (HoustonResponse, error) { // CreateDeployment will send request to Houston to create a new AirflowDeployment // Returns a CreateDeploymentResponse which contains the unique id of deployment -func (c *Client) CreateDeployment(title string) (*CreateDeploymentResponse, error) { +func (c *HoustonClient) CreateDeployment(title string) (*CreateDeploymentResponse, error) { // logger := log.WithField("method", "CreateDeployment") // logger.Debug("Entered CreateDeployment") @@ -142,7 +142,7 @@ func (c *Client) CreateDeployment(title string) (*CreateDeploymentResponse, erro // CreateToken will request a new token from Houston, passing the users e-mail and password. // Returns a CreateTokenResponse structure with the users ID and Token inside. -func (c *Client) CreateToken(email string, password string) (*CreateTokenResponse, error) { +func (c *HoustonClient) CreateToken(email string, password string) (*CreateTokenResponse, error) { // logger := log.WithField("method", "CreateToken") // logger.Debug("Entered CreateToken") @@ -165,7 +165,7 @@ func (c *Client) CreateToken(email string, password string) (*CreateTokenRespons // FetchDeployments will request all airflow deployments from Houston // Returns a FetchDeploymentResponse structure with deployment details -func (c *Client) FetchDeployments() (*FetchDeploymentsResponse, error) { +func (c *HoustonClient) FetchDeployments() (*FetchDeploymentsResponse, error) { // logger := log.WithField("method", "FetchDeployments") // logger.Debug("Entered FetchDeployments") diff --git a/houston/http.go b/pkg/httputil/http.go similarity index 94% rename from houston/http.go rename to pkg/httputil/http.go index d8ccc55a1..cd91c816d 100644 --- a/houston/http.go +++ b/pkg/httputil/http.go @@ -1,4 +1,4 @@ -package houston +package httputil import ( "bytes" @@ -18,6 +18,12 @@ type HTTPClient struct { HTTPClient *http.Client } +// HTTPResponse houses respnse object +type HTTPResponse struct { + Raw *http.Response + Body string +} + // DoOptions are options passed to the HTTPClient.Do function type DoOptions struct { Data interface{} From d4a3f93884db63430352a3223e673133e147929d Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Thu, 26 Apr 2018 15:47:01 -0400 Subject: [PATCH 13/15] Houston response updates, deploy lists --- Makefile | 29 ++++++++++++ airflow/airflow.go | 28 ++++++------ airflow/docker.go | 37 +++++++++++++-- auth/auth.go | 8 ++-- cmd/airflow.go | 8 +++- cmd/auth.go | 12 +++-- houston/houston.go | 109 ++++++++++++++++++++++++++------------------- houston/types.go | 10 +++-- 8 files changed, 166 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 3828c7b0a..d9186cc98 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,35 @@ dep: build: go build -o ${OUTPUT} -ldflags "${LDFLAGS_VERSION} ${LDFLAGS_GIT_COMMIT}" main.go +format: + @echo "--> Running go fmt" + @go fmt $(GOFILES) + +vet: + @echo "--> Running go vet" + @go vet $(GOFILES); if [ $$? -eq 1 ]; then \ + echo ""; \ + echo "Vet found suspicious constructs. Please check the reported constructs"; \ + echo "and fix them if necessary before submitting the code for review."; \ + exit 1; \ + fi + +style: + @echo ">> checking code style" + @! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^' + +staticcheck: + @echo ">> running staticcheck" + @staticcheck $(GOFILES) + +gosimple: + @echo ">> running gosimple" + @gosimple $(GOFILES) + +tools: + @echo ">> installing some extra tools" + @go get -u -v honnef.co/go/tools/... + install: build $(eval DESTDIR ?= $(GOBIN)) mkdir -p $(DESTDIR) diff --git a/airflow/airflow.go b/airflow/airflow.go index 06b3d632d..3d7244e84 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -9,10 +9,12 @@ import ( "github.com/astronomerio/astro-cli/houston" "github.com/astronomerio/astro-cli/pkg/fileutil" "github.com/astronomerio/astro-cli/pkg/httputil" + "github.com/astronomerio/astro-cli/config" ) var ( - HTTP = httputil.NewHTTPClient() + http = httputil.NewHTTPClient() + api = houston.NewHoustonClient(http) ) func initDirs(root string, dirs []string) bool { @@ -88,27 +90,27 @@ func Init(path string) error { // Create new airflow deployment func Create(title string) error { - API := houston.NewHoustonClient(HTTP) - - body, houstonErr := API.CreateDeployment(title) - if houstonErr != nil { - return houstonErr + response, err := api.CreateDeployment(title) + if err != nil { + return err } + deployment, err := api.FetchDeployment(response.Id) - fmt.Println(body.Data.CreateDeployment.Message) + fmt.Println(response.Message) + fmt.Printf("\nAirflow Dashboard: https://%s-airflow.%s\n", deployment.ReleaseName, config.CFG.CloudDomain.GetString()) + fmt.Printf("Flower Dashboard: https://%s-flower.%s\n", deployment.ReleaseName, config.CFG.CloudDomain.GetString()) + fmt.Printf("Grafana Dashboard: https://%s-grafana.%s\n", deployment.ReleaseName, config.CFG.CloudDomain.GetString()) return nil } // List all airflow deployments func List() error { - API := houston.NewHoustonClient(HTTP) - - body, houstonErr := API.FetchDeployments() - if houstonErr != nil { - return houstonErr + deployments, err := api.FetchDeployments() + if err != nil { + return err } - for _, d := range body.Data.FetchDeployments { + for _, d := range deployments { rowTmp := "Title: %s\nId: %s\nRelease: %s\nVersion: %s\n\n" fmt.Printf(rowTmp, d.Title, d.Id, d.ReleaseName, d.Version) } diff --git a/airflow/docker.go b/airflow/docker.go index 158e86e0a..ac844709c 100644 --- a/airflow/docker.go +++ b/airflow/docker.go @@ -11,14 +11,17 @@ import ( "strings" "text/tabwriter" - "github.com/astronomerio/astro-cli/airflow/include" - "github.com/astronomerio/astro-cli/config" - docker "github.com/astronomerio/astro-cli/docker" dockercompose "github.com/docker/libcompose/docker" "github.com/docker/libcompose/docker/ctx" "github.com/docker/libcompose/project" "github.com/docker/libcompose/project/options" "github.com/pkg/errors" + + "github.com/astronomerio/astro-cli/airflow/include" + "github.com/astronomerio/astro-cli/config" + "github.com/astronomerio/astro-cli/docker" + "github.com/astronomerio/astro-cli/houston" + "github.com/astronomerio/astro-cli/pkg/input" ) const ( @@ -239,6 +242,34 @@ func PS(airflowHome string) error { // Deploy pushes a new docker image // TODO: Check for uncommitted git changes func Deploy(path, name string) error { + if name == "" { + deployments, err := api.FetchDeployments() + if err != nil { + return err + } + + if len(deployments) == 0 { + return errors.New("No airflow deployments found") + } + + deployMap := map[string]houston.Deployment{} + fmt.Println("Select which airflow deployment you want to deploy to:") + for i, deployment := range deployments { + index := i + 1 + deployMap[strconv.Itoa(index)] = deployment + fmt.Printf("%d) %s (%s)\n", index, deployment.Title, deployment.ReleaseName) + } + + choice := input.InputText("") + selected, ok := deployMap[choice] + if !ok { + return errors.New("Invalid deployment selection") + } + name = selected.ReleaseName + } + fmt.Printf("Deploying: %s\n", name) + fmt.Println(repositoryName(name)) + // Get a list of tags in the repository for this image tags, err := docker.ListRepositoryTags(repositoryName(name)) if err != nil { diff --git a/auth/auth.go b/auth/auth.go index 041ad551b..a24f54ffc 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -23,15 +23,15 @@ func Login() { API := houston.NewHoustonClient(HTTP) // authenticate with houston - body, houstonErr := API.CreateToken(username, password) + token, houstonErr := API.CreateToken(username, password) if houstonErr != nil { panic(houstonErr) - } else if body.Data.CreateToken.Success != true { - fmt.Println(body.Data.CreateToken.Message) + } else if token.Success != true { + fmt.Println(token.Message) return } - config.CFG.UserAPIAuthToken.SetProjectString(body.Data.CreateToken.Token) + config.CFG.UserAPIAuthToken.SetProjectString(token.Token) //authenticate with registry dockerErr := docker.ExecLogin(registry, username, password) diff --git a/cmd/airflow.go b/cmd/airflow.go index 99bcda3d6..47ba9503a 100644 --- a/cmd/airflow.go +++ b/cmd/airflow.go @@ -51,7 +51,7 @@ var ( Use: "deploy", Short: "Deploy an airflow project", Long: "Deploy an airflow project to a given deployment", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), PreRun: ensureProjectDir, RunE: airflowDeploy, } @@ -173,7 +173,11 @@ func airflowList(cmd *cobra.Command, args []string) error { } func airflowDeploy(cmd *cobra.Command, args []string) error { - return airflow.Deploy(projectRoot, args[0]) + releaseName := "" + if len(args) > 0 { + releaseName = args[0] + } + return airflow.Deploy(projectRoot, releaseName) } // Start an airflow cluster diff --git a/cmd/auth.go b/cmd/auth.go index 343bfb893..76486482a 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -11,6 +11,7 @@ import ( var ( registryOverride string + domainOverride string authRootCmd = &cobra.Command{ Use: "auth", @@ -40,13 +41,14 @@ func init() { // Auth login authRootCmd.AddCommand(authLoginCmd) authLoginCmd.Flags().StringVarP(®istryOverride, "registry", "r", "", "pass a custom project registry for authentication") + authLoginCmd.Flags().StringVarP(&domainOverride, "domain", "d", "", "pass the cluster domain for authentication") // Auth logout authRootCmd.AddCommand(authLogoutCmd) } func authLogin(cmd *cobra.Command, args []string) error { - if len(registryOverride) > 0 { - + if domainOverride != "" { + config.CFG.CloudDomain.SetProjectString(domainOverride) } projectRegistry := config.CFG.RegistryAuthority.GetProjectString() @@ -64,7 +66,9 @@ func authLogin(cmd *cobra.Command, args []string) error { break case projectCloudDomain != "": config.CFG.RegistryAuthority.SetProjectString(fmt.Sprintf("registry.%s", projectCloudDomain)) - fmt.Printf("No registry set, using default: registry.%s\n", projectCloudDomain) + if domainOverride == "" { + fmt.Printf("No registry set, using default: registry.%s\n", projectCloudDomain) + } case globalCloudDomain != "": config.CFG.RegistryAuthority.SetProjectString(fmt.Sprintf("registry.%s", globalCloudDomain)) fmt.Printf("No registry set, using default: registry.%s\n", globalCloudDomain) @@ -72,7 +76,7 @@ func authLogin(cmd *cobra.Command, args []string) error { // Don't prompt user, falling back to global config is default expected behavior break default: - return errors.New("No registry set. Use -r to pass a custom registry\n\nEx.\nastro auth login -r registry.EXAMPLE_DOMAIN.com\n ") + return errors.New("No domain specified (`cloud.domain` in config.yaml). Use -d to pass your cluster domain\n\nEx.\nastro auth login -d EXAMPLE_DOMAIN.com\n ") } auth.Login() diff --git a/houston/houston.go b/houston/houston.go index 1702fc489..d06ab2062 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -1,15 +1,15 @@ package houston import ( - "encoding/json" "fmt" + "encoding/json" "io/ioutil" "strings" + "github.com/pkg/errors" + // "github.com/sirupsen/logrus" "github.com/astronomerio/astro-cli/config" "github.com/astronomerio/astro-cli/pkg/httputil" - "github.com/pkg/errors" - // "github.com/sirupsen/logrus" ) var ( @@ -45,26 +45,37 @@ var ( fetchDeploymentsRequest = ` query FetchAllDeployments { - fetchDeployments { - uuid - type - title - release_name - version - } - }` + fetchDeployments { + uuid + type + title + release_name + version + } + }` + + fetchDeploymentRequest = ` + query FetchDeployment { + fetchDeployments(deploymentUuid: "%s") { + uuid + type + title + release_name + version + } + }` // log = logrus.WithField("package", "houston") ) -// HoustonClient containers the logger and HTTPClient used to communicate with the HoustonAPI -type HoustonClient struct { +// Client containers the logger and HTTPClient used to communicate with the HoustonAPI +type Client struct { HTTPClient *httputil.HTTPClient } // NewHoustonClient returns a new Client with the logger and HTTP client setup. -func NewHoustonClient(c *httputil.HTTPClient) *HoustonClient { - return &HoustonClient{ +func NewHoustonClient(c *httputil.HTTPClient) *Client { + return &Client{ HTTPClient: c, } } @@ -75,7 +86,7 @@ type GraphQLQuery struct { } // QueryHouston executes a query against the Houston API -func (c *HoustonClient) QueryHouston(query string) (httputil.HTTPResponse, error) { +func (c *Client) QueryHouston(query string) (*HoustonResponse, error) { // logger := log.WithField("function", "QueryHouston") doOpts := httputil.DoOptions{ Data: GraphQLQuery{query}, @@ -96,14 +107,14 @@ func (c *HoustonClient) QueryHouston(query string) (httputil.HTTPResponse, error var response httputil.HTTPResponse httpResponse, err := c.HTTPClient.Do("POST", config.APIURL(), &doOpts) if err != nil { - return response, err + return nil, err } defer httpResponse.Body.Close() // strings.NewReader(jsonStream) body, err := ioutil.ReadAll(httpResponse.Body) if err != nil { - return response, err + return nil, err } response = httputil.HTTPResponse{ @@ -114,12 +125,18 @@ func (c *HoustonClient) QueryHouston(query string) (httputil.HTTPResponse, error // logger.Debug(query) // logger.Debug(response.Body) - return response, nil + decode := HoustonResponse{} + err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&decode) + if err != nil { + //logger.Error(err) + return nil, errors.Wrap(err, "Failed to JSON decode Houston response") + } + return &decode, nil } // CreateDeployment will send request to Houston to create a new AirflowDeployment -// Returns a CreateDeploymentResponse which contains the unique id of deployment -func (c *HoustonClient) CreateDeployment(title string) (*CreateDeploymentResponse, error) { +// Returns a StatusResponse which contains the unique id of deployment +func (c *Client) CreateDeployment(title string) (*StatusResponse, error) { // logger := log.WithField("method", "CreateDeployment") // logger.Debug("Entered CreateDeployment") @@ -131,18 +148,12 @@ func (c *HoustonClient) CreateDeployment(title string) (*CreateDeploymentRespons return nil, errors.Wrap(err, "CreateDeployment Failed") } - var body CreateDeploymentResponse - err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) - if err != nil { - // logger.Error(key) - return nil, errors.Wrap(err, "CreateDeployment JSON decode failed") - } - return &body, nil + return response.Data.CreateDeployment, nil } // CreateToken will request a new token from Houston, passing the users e-mail and password. -// Returns a CreateTokenResponse structure with the users ID and Token inside. -func (c *HoustonClient) CreateToken(email string, password string) (*CreateTokenResponse, error) { +// Returns a Token structure with the users ID and Token inside. +func (c *Client) CreateToken(email string, password string) (*Token, error) { // logger := log.WithField("method", "CreateToken") // logger.Debug("Entered CreateToken") @@ -154,18 +165,12 @@ func (c *HoustonClient) CreateToken(email string, password string) (*CreateToken return nil, errors.Wrap(err, "CreateToken Failed") } - var body CreateTokenResponse - err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) - if err != nil { - // logger.Error(err) - return nil, errors.Wrap(err, "CreateToken JSON decode failed") - } - return &body, nil + return response.Data.CreateToken, nil } // FetchDeployments will request all airflow deployments from Houston -// Returns a FetchDeploymentResponse structure with deployment details -func (c *HoustonClient) FetchDeployments() (*FetchDeploymentsResponse, error) { +// Returns a []Deployment structure with deployment details +func (c *Client) FetchDeployments() ([]Deployment, error) { // logger := log.WithField("method", "FetchDeployments") // logger.Debug("Entered FetchDeployments") @@ -177,11 +182,25 @@ func (c *HoustonClient) FetchDeployments() (*FetchDeploymentsResponse, error) { return nil, errors.Wrap(err, "FetchDeployments Failed") } - var body FetchDeploymentsResponse - err = json.NewDecoder(strings.NewReader(response.Body)).Decode(&body) + return response.Data.FetchDeployments, nil +} + +// FetchDeployment will request a specific airflow deployments from Houston by uuid +// Returns a Deployment structure with deployment details +func (c *Client) FetchDeployment(deploymentUuid string) (*Deployment, error) { + // logger := log.WithField("method", "FetchDeployments") + // logger.Debug("Entered FetchDeployments") + + request := fmt.Sprintf(fetchDeploymentRequest, deploymentUuid) + + response, err := c.QueryHouston(request) if err != nil { - //logger.Error(err) - return nil, errors.Wrap(err, "FetchDeployments JSON decode failed") + // logger.Error(err) + return nil, errors.Wrap(err, "FetchDeployment Failed") } - return &body, nil -} + + if len(response.Data.FetchDeployments) == 0 { + return nil, fmt.Errorf("deployment not found for uuid \"%s\"", deploymentUuid) + } + return &response.Data.FetchDeployments[0], nil +} \ No newline at end of file diff --git a/houston/types.go b/houston/types.go index 816c9eebe..315918cdc 100644 --- a/houston/types.go +++ b/houston/types.go @@ -1,8 +1,10 @@ package houston -type CreateDeploymentResponse struct { +type HoustonResponse struct { Data struct { - CreateDeployment CreatedDeployment `json:"createDeployment"` + CreateDeployment *StatusResponse `json:"createDeployment,omitempty"` + CreateToken *Token `json:"createToken,omitempty"` + FetchDeployments []Deployment `json:"fetchDeployments"` } `json:"data"` } @@ -25,11 +27,11 @@ type Token struct { Decoded Decoded `json:"decoded"` } -type CreatedDeployment struct { +type StatusResponse struct { Success bool `json:"success"` Message string `json:"message"` Code string `json:"code"` - Id string `json:"uuid"` + Id string `json:"id"` } type Deployment struct { From 66c7ff27871cc67642196f95cead302cbe0d4e2f Mon Sep 17 00:00:00 2001 From: Andy Cooper Date: Tue, 8 May 2018 12:46:50 -0400 Subject: [PATCH 14/15] Fix linting and spelling errors --- houston/houston.go | 12 +++++------- houston/types.go | 11 +++++++++-- pkg/fileutil/files.go | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/houston/houston.go b/houston/houston.go index d06ab2062..af91893a0 100644 --- a/houston/houston.go +++ b/houston/houston.go @@ -1,13 +1,13 @@ package houston import ( - "fmt" "encoding/json" + "fmt" "io/ioutil" "strings" + "github.com/pkg/errors" // "github.com/sirupsen/logrus" - "github.com/astronomerio/astro-cli/config" "github.com/astronomerio/astro-cli/pkg/httputil" ) @@ -26,8 +26,7 @@ var ( id, code } - } - ` + }` createTokenRequest = ` mutation createToken { @@ -40,8 +39,7 @@ var ( sU } } - } - ` + }` fetchDeploymentsRequest = ` query FetchAllDeployments { @@ -203,4 +201,4 @@ func (c *Client) FetchDeployment(deploymentUuid string) (*Deployment, error) { return nil, fmt.Errorf("deployment not found for uuid \"%s\"", deploymentUuid) } return &response.Data.FetchDeployments[0], nil -} \ No newline at end of file +} diff --git a/houston/types.go b/houston/types.go index 315918cdc..5ee86fc48 100644 --- a/houston/types.go +++ b/houston/types.go @@ -1,25 +1,29 @@ package houston +// HoustonReasponse wraps all houston response structs used for json marashalling type HoustonResponse struct { Data struct { CreateDeployment *StatusResponse `json:"createDeployment,omitempty"` - CreateToken *Token `json:"createToken,omitempty"` - FetchDeployments []Deployment `json:"fetchDeployments"` + CreateToken *Token `json:"createToken,omitempty"` + FetchDeployments []Deployment `json:"fetchDeployments"` } `json:"data"` } +// CreateTokenResponse defines structure of a houston response CreateTokenResponse object type CreateTokenResponse struct { Data struct { CreateToken Token `json:"createToken"` } `json:"data"` } +// FetchDeploymentsResponse defines structure of a houston response FetchDeploymentsResponse object type FetchDeploymentsResponse struct { Data struct { FetchDeployments []Deployment `json:"fetchDeployments"` } `json:"data"` } +// Token defines structure of a houston response token object type Token struct { Success bool `json:"success"` Message string `json:"message"` @@ -27,6 +31,7 @@ type Token struct { Decoded Decoded `json:"decoded"` } +// StatusResponse defines structure of a houston response StatusResponse object type StatusResponse struct { Success bool `json:"success"` Message string `json:"message"` @@ -34,6 +39,7 @@ type StatusResponse struct { Id string `json:"id"` } +// Deployment defines structure of a houston response Deployment object type Deployment struct { Id string `json:"uuid"` Type string `json:"type"` @@ -42,6 +48,7 @@ type Deployment struct { Version string `json:"version"` } +// Decoded defines structure of a houston response Decoded object type Decoded struct { ID string `json:"id"` SU bool `json:"sU"` diff --git a/pkg/fileutil/files.go b/pkg/fileutil/files.go index 90aeba9ae..f1582edaf 100644 --- a/pkg/fileutil/files.go +++ b/pkg/fileutil/files.go @@ -8,7 +8,7 @@ import ( "strings" ) -// Exists returns a boolean indicating if the givin path already exists +// Exists returns a boolean indicating if the given path already exists func Exists(path string) bool { if path == "" { return false From c44c382357c5bdaeab88feb22805c0761ada72e7 Mon Sep 17 00:00:00 2001 From: Courtney Wurtz Date: Wed, 9 May 2018 11:10:01 -0400 Subject: [PATCH 15/15] Missed changed in last merge commit --- cmd/airflow.go | 4 ++-- pkg/git/git.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/airflow.go b/cmd/airflow.go index 48cd6e187..11ae16538 100644 --- a/cmd/airflow.go +++ b/cmd/airflow.go @@ -13,8 +13,8 @@ import ( "github.com/astronomerio/astro-cli/airflow" "github.com/astronomerio/astro-cli/config" + "github.com/astronomerio/astro-cli/pkg/git" "github.com/astronomerio/astro-cli/pkg/fileutil" - "github.com/astronomerio/astro-cli/utils" ) var ( @@ -181,7 +181,7 @@ func airflowDeploy(cmd *cobra.Command, args []string) error { if len(args) > 0 { releaseName = args[0] } - if utils.HasUncommitedChanges() && !forceDeploy { + if git.HasUncommitedChanges() && !forceDeploy { fmt.Println("Project directory has uncommmited changes, use `astro airflow deploy [releaseName] -f` to force deploy.") return nil } diff --git a/pkg/git/git.go b/pkg/git/git.go index ed16b4f1a..14e2a5853 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,4 +1,4 @@ -package utils +package git import ( "os/exec"