/* Copyright © 2021-2022 Ettore Di Giacinto Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package client import ( "context" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net" "net/http" "strings" "time" "github.com/mudler/edgevpn/api" "github.com/mudler/edgevpn/pkg/blockchain" "github.com/mudler/edgevpn/pkg/types" ) type ( Client struct { host string httpClient *http.Client } ) func WithHost(host string) func(c *Client) error { return func(c *Client) error { c.host = host if strings.HasPrefix(host, "unix://") { socket := strings.ReplaceAll(host, "unix://", "") c.host = "http://unix" c.httpClient = &http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { return net.Dial("unix", socket) }, }, } } return nil } } func WithTimeout(d time.Duration) func(c *Client) error { return func(c *Client) error { c.httpClient.Timeout = d return nil } } func WithHTTPClient(cl *http.Client) func(c *Client) error { return func(c *Client) error { c.httpClient = cl return nil } } type Option func(c *Client) error func NewClient(o ...Option) *Client { c := &Client{ httpClient: &http.Client{}, } for _, oo := range o { oo(c) } return c } func (c *Client) do(method, endpoint string, params map[string]string) (*http.Response, error) { baseURL := fmt.Sprintf("%s%s", c.host, endpoint) req, err := http.NewRequest(method, baseURL, nil) if err != nil { return nil, err } req.Header.Add("Content-Type", "application/json") q := req.URL.Query() for key, val := range params { q.Set(key, val) } req.URL.RawQuery = q.Encode() return c.httpClient.Do(req) } // Get methods (Services, Users, Files, Ledger, Blockchain, Machines) func (c *Client) Services() (resp []types.Service, err error) { res, err := c.do(http.MethodGet, api.ServiceURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return resp, err } if err = json.Unmarshal(body, &resp); err != nil { return resp, err } return } func (c *Client) Files() (data []types.File, err error) { res, err := c.do(http.MethodGet, api.FileURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return data, err } if err = json.Unmarshal(body, &data); err != nil { return data, err } return } func (c *Client) Users() (data []types.User, err error) { res, err := c.do(http.MethodGet, api.UsersURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return data, err } if err = json.Unmarshal(body, &data); err != nil { return data, err } return } func (c *Client) Ledger() (data map[string]map[string]blockchain.Data, err error) { res, err := c.do(http.MethodGet, api.LedgerURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return data, err } if err = json.Unmarshal(body, &data); err != nil { return data, err } return } func (c *Client) Summary() (data types.Summary, err error) { res, err := c.do(http.MethodGet, api.SummaryURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return data, err } if err = json.Unmarshal(body, &data); err != nil { return data, err } return } func (c *Client) Blockchain() (data blockchain.Block, err error) { res, err := c.do(http.MethodGet, api.BlockchainURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return data, err } if err = json.Unmarshal(body, &data); err != nil { return data, err } return } func (c *Client) Machines() (resp []types.Machine, err error) { res, err := c.do(http.MethodGet, api.MachineURL, nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return resp, err } if err = json.Unmarshal(body, &resp); err != nil { return resp, err } return } func (c *Client) GetBucket(b string) (resp map[string]blockchain.Data, err error) { res, err := c.do(http.MethodGet, fmt.Sprintf("%s/%s", api.LedgerURL, b), nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return resp, err } if err = json.Unmarshal(body, &resp); err != nil { return resp, err } return } func (c *Client) GetBucketKeys(b string) (resp []string, err error) { d, err := c.GetBucket(b) if err != nil { return resp, err } for k := range d { resp = append(resp, k) } return } func (c *Client) GetBuckets() (resp []string, err error) { d, err := c.Ledger() if err != nil { return resp, err } for k := range d { resp = append(resp, k) } return } func (c *Client) GetBucketKey(b, k string) (resp blockchain.Data, err error) { res, err := c.do(http.MethodGet, fmt.Sprintf("%s/%s/%s", api.LedgerURL, b, k), nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return resp, err } var r string if err = json.Unmarshal(body, &r); err != nil { return resp, err } if err = json.Unmarshal([]byte(r), &r); err != nil { return resp, err } d, err := base64.URLEncoding.DecodeString(r) if err != nil { return resp, err } resp = blockchain.Data(string(d)) return } func (c *Client) Put(b, k string, v interface{}) (err error) { s := struct{ State string }{} dat, err := json.Marshal(v) if err != nil { return } d := base64.URLEncoding.EncodeToString(dat) res, err := c.do(http.MethodPut, fmt.Sprintf("%s/%s/%s/%s", api.LedgerURL, b, k, d), nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } if err = json.Unmarshal(body, &s); err != nil { return err } if s.State != "Announcing" { return fmt.Errorf("unexpected state '%s'", s.State) } return } func (c *Client) Delete(b, k string) (err error) { s := struct{ State string }{} res, err := c.do(http.MethodDelete, fmt.Sprintf("%s/%s/%s", api.LedgerURL, b, k), nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } if err = json.Unmarshal(body, &s); err != nil { return err } if s.State != "Announcing" { return fmt.Errorf("unexpected state '%s'", s.State) } return } func (c *Client) DeleteBucket(b string) (err error) { s := struct{ State string }{} res, err := c.do(http.MethodDelete, fmt.Sprintf("%s/%s", api.LedgerURL, b), nil) if err != nil { return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return err } if err = json.Unmarshal(body, &s); err != nil { return err } if s.State != "Announcing" { return fmt.Errorf("unexpected state '%s'", s.State) } return }