From 0027e5f2fe12c65a1b5d3528e750b7f560238c22 Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Tue, 3 Oct 2023 10:23:07 +0300 Subject: [PATCH] Remove API and "serve" subcommand Signed-off-by: Kimmo Lehto --- cmd/bootloose/root.go | 1 - cmd/bootloose/serve.go | 80 ----------------- cmd/bootloose/serve_test.go | 29 ------ go.mod | 1 - go.sum | 2 - pkg/api/api.go | 92 ------------------- pkg/api/cluster.go | 94 -------------------- pkg/api/db.go | 148 ------------------------------ pkg/api/key.go | 65 -------------- pkg/api/machine.go | 99 --------------------- pkg/api/response.go | 55 ------------ pkg/client/client.go | 173 ------------------------------------ pkg/client/client_test.go | 111 ----------------------- 13 files changed, 950 deletions(-) delete mode 100644 cmd/bootloose/serve.go delete mode 100644 cmd/bootloose/serve_test.go delete mode 100644 pkg/api/api.go delete mode 100644 pkg/api/cluster.go delete mode 100644 pkg/api/db.go delete mode 100644 pkg/api/key.go delete mode 100644 pkg/api/machine.go delete mode 100644 pkg/api/response.go delete mode 100644 pkg/client/client.go delete mode 100644 pkg/client/client_test.go diff --git a/cmd/bootloose/root.go b/cmd/bootloose/root.go index 294cd03..50d7d81 100644 --- a/cmd/bootloose/root.go +++ b/cmd/bootloose/root.go @@ -50,7 +50,6 @@ func NewRootCommand(ctx context.Context) *cobra.Command { // hide config flag from commands that do not need it for _, configlessCmd := range []*cobra.Command{ - NewServeCommand(), NewVersionCommand(), } { cmd.AddCommand(configlessCmd) diff --git a/cmd/bootloose/serve.go b/cmd/bootloose/serve.go deleted file mode 100644 index 6944337..0000000 --- a/cmd/bootloose/serve.go +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package bootloose - -import ( - "fmt" - "net" - "net/http" - - log "github.com/sirupsen/logrus" - - "github.com/k0sproject/bootloose/pkg/api" - "github.com/k0sproject/bootloose/pkg/cluster" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -type serveOptions struct { - listen string - keyStorePath string - debug bool -} - -// defaultKeyStore is the path where to store the public keys. -const defaultKeyStorePath = "keys" -const defaultListenPort = 2444 - -func NewServeCommand() *cobra.Command { - opts := &serveOptions{} - cmd := &cobra.Command{ - Use: "serve", - Short: "Launch a bootloose server", - RunE: opts.serve, - } - cmd.Flags().StringVarP(&opts.listen, "listen", "l", fmt.Sprintf(":%d", defaultListenPort), "Listen address") - cmd.Flags().StringVar(&opts.keyStorePath, "keystore-path", defaultKeyStorePath, "Path of the public keys store") - cmd.Flags().BoolVar(&opts.debug, "debug", false, "Enable debug") - - return cmd -} - -func baseURI(addr string) (string, error) { - host, port, err := net.SplitHostPort(addr) - if err != nil { - return "", err - } - if host == "" || host == "0.0.0.0" || host == "[::]" { - host = "localhost" - } - return fmt.Sprintf("http://%s:%s", host, port), nil -} - -func (opts *serveOptions) serve(cmd *cobra.Command, args []string) error { - baseURI, err := baseURI(opts.listen) - if err != nil { - return errors.Wrapf(err, "invalid listen address '%s'", opts.listen) - } - - log.Infof("Starting server on: %s\n", opts.listen) - - keyStore := cluster.NewKeyStore(opts.keyStorePath) - if err := keyStore.Init(); err != nil { - return errors.Wrapf(err, "could not init keystore") - } - - log.Infof("Key store successfully initialized in path: %s\n", opts.keyStorePath) - - api := api.New(baseURI, keyStore, opts.debug) - router := api.Router() - - err = http.ListenAndServe(opts.listen, router) - if err != nil { - log.Fatalf("Unable to start server: %s", err) - } - - return nil -} - diff --git a/cmd/bootloose/serve_test.go b/cmd/bootloose/serve_test.go deleted file mode 100644 index 56574c4..0000000 --- a/cmd/bootloose/serve_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package bootloose - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBaseURI(t *testing.T) { - tests := []struct { - valid bool - input, expected string - }{ - {true, ":2444", "http://localhost:2444"}, - } - - for _, test := range tests { - uri, err := baseURI(test.input) - if !test.valid { - assert.Error(t, err) - } - assert.Equal(t, test.expected, uri) - } -} - diff --git a/go.mod b/go.mod index 673c5cd..6d5f09a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/carlmjohnson/versioninfo v0.22.5 github.com/docker/docker v24.0.6+incompatible github.com/ghodss/yaml v1.0.0 - github.com/gorilla/mux v1.8.0 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index bd5c7d7..f90722f 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/pkg/api/api.go b/pkg/api/api.go deleted file mode 100644 index ce19cc3..0000000 --- a/pkg/api/api.go +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "encoding/json" - "net/http" - - log "github.com/sirupsen/logrus" - - "github.com/gorilla/mux" - "github.com/k0sproject/bootloose/pkg/cluster" -) - -// API represents the bootloose REST API. -type API struct { - BaseURI string - db db - keyStore *cluster.KeyStore - router *mux.Router -} - -// New creates a new object able to answer bootloose REST API. -func New(baseURI string, keyStore *cluster.KeyStore, debug bool) *API { - if debug { - log.SetLevel(log.DebugLevel) - } - - api := &API{ - BaseURI: baseURI, - keyStore: keyStore, - router: mux.NewRouter(), - } - api.db.init() - return api -} - -func httpLogger(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Debugln(r.RequestURI, r.Method) - next.ServeHTTP(w, r) - }) -} - -func (a *API) createDocs(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - response := map[string][]string{} - - _ = a.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { - path, _ := route.GetPathTemplate() - methods, _ := route.GetMethods() - - if _, ok := response[path]; ok { - response[path] = append(response[path], methods[0]) - } else { - response[path] = methods - } - - return nil - }) - - payload, err := json.Marshal(response) - if err != nil { - log.Fatalln(err) - } - - sendResponse(w, payload) -} - -func (a *API) initRouter() { - a.router.HandleFunc("/api/keys", a.createPublicKey).Methods("POST") - a.router.HandleFunc("/api/keys/{key}", a.getPublicKey).Methods("GET") - a.router.HandleFunc("/api/keys/{key}", a.deletePublicKey).Methods("DELETE") - a.router.HandleFunc("/api/clusters", a.createCluster).Methods("POST") - a.router.HandleFunc("/api/clusters/{cluster}", a.deleteCluster).Methods("DELETE") - a.router.HandleFunc("/api/clusters/{cluster}/machines", a.createMachine).Methods("POST") - a.router.HandleFunc("/api/clusters/{cluster}/machines/{machine}", a.getMachine).Methods("GET") - a.router.HandleFunc("/api/clusters/{cluster}/machines/{machine}", a.deleteMachine).Methods("DELETE") - - a.router.HandleFunc("/", a.createDocs).Methods("GET") - - a.router.Use(httpLogger) -} - -// Router returns the API request router. -func (a *API) Router() *mux.Router { - a.initRouter() - return a.router -} diff --git a/pkg/api/cluster.go b/pkg/api/cluster.go deleted file mode 100644 index b3cfd56..0000000 --- a/pkg/api/cluster.go +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/k0sproject/bootloose/pkg/cluster" - "github.com/k0sproject/bootloose/pkg/config" -) - -// ClusterURI returns the URI identifying a cluster in the REST API. -func (a *API) ClusterURI(c *cluster.Cluster) string { - return fmt.Sprintf("%s/api/clusters/%s", a.BaseURI, c.Name()) -} - -// createCluster creates a cluster. -func (a *API) createCluster(w http.ResponseWriter, r *http.Request) { - var def config.Cluster - if err := json.NewDecoder(r.Body).Decode(&def); err != nil { - sendError(w, http.StatusBadRequest, errors.Wrap(err, "could not decode body")) - return - } - if def.Name == "" { - sendError(w, http.StatusBadRequest, errors.New("no cluster name provided")) - return - } - - cluster, err := cluster.New(config.Config{Cluster: def}) - if err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - cluster.SetKeyStore(a.keyStore) - - if err := a.db.addCluster(def.Name, cluster); err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - if err := cluster.Create(); err != nil { - _, _ = a.db.removeCluster(def.Name) - sendError(w, http.StatusInternalServerError, err) - return - } - sendCreated(w, a.ClusterURI((cluster))) -} - -// deleteCluster deletes a cluster. -func (a *API) deleteCluster(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - c, err := a.db.cluster(vars["cluster"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - // Starts by deleting the machines associated with the cluster. - machines, err := a.db.machines(vars["cluster"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - for _, m := range machines { - if err := c.DeleteMachine(m, 0); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - if _, err := a.db.removeMachine(vars["cluster"], m.Hostname()); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - } - - // Delete cluster. - if err := c.Delete(); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - _, err = a.db.removeCluster(vars["cluster"]) - if err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - sendOK(w) -} diff --git a/pkg/api/db.go b/pkg/api/db.go deleted file mode 100644 index c4207cb..0000000 --- a/pkg/api/db.go +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "sync" - - "github.com/pkg/errors" - "github.com/k0sproject/bootloose/pkg/cluster" -) - -type entry struct { - cluster *cluster.Cluster - machines map[string]*cluster.Machine -} - -type db struct { - sync.Mutex - - clusters map[string]entry -} - -func (db *db) init() { - db.clusters = make(map[string]entry) -} - -func (db *db) entry(name string) *entry { - db.Lock() - defer db.Unlock() - - entry, ok := db.clusters[name] - if !ok { - return nil - } - return &entry -} - -func (db *db) cluster(name string) (*cluster.Cluster, error) { - entry := db.entry(name) - if entry == nil { - return nil, errors.Errorf("unknown cluster '%s'", name) - } - return entry.cluster, nil -} - -func (db *db) addCluster(name string, c *cluster.Cluster) error { - db.Lock() - defer db.Unlock() - - if _, ok := db.clusters[name]; ok { - return errors.Errorf("cluster '%s' has already been added", name) - } - db.clusters[name] = entry{ - cluster: c, - machines: make(map[string]*cluster.Machine), - } - return nil -} - -func (db *db) removeCluster(name string) (*cluster.Cluster, error) { - db.Lock() - defer db.Unlock() - - var entry entry - var ok bool - if entry, ok = db.clusters[name]; !ok { - return nil, errors.Errorf("unknown cluster '%s'", name) - } - // It is an error to remove the cluster from the db before removing all of its - // machines. - if len(entry.machines) != 0 { - return nil, errors.Errorf("cluster has machines associated with it") - } - delete(db.clusters, name) - return entry.cluster, nil -} - -func (db *db) machine(clusterName, machineName string) (*cluster.Machine, error) { - entry := db.entry(clusterName) - if entry == nil { - return nil, errors.Errorf("unknown cluster '%s'", clusterName) - } - - db.Lock() - defer db.Unlock() - - var m *cluster.Machine - var ok bool - if m, ok = entry.machines[machineName]; !ok { - return nil, errors.Errorf("unknown machine '%s' for cluster '%s'", machineName, clusterName) - } - return m, nil -} - -func (db *db) machines(clusterName string) ([]*cluster.Machine, error) { - entry := db.entry(clusterName) - if entry == nil { - return nil, errors.Errorf("unknown cluster '%s'", clusterName) - } - - db.Lock() - defer db.Unlock() - - var machines []*cluster.Machine - for _, m := range entry.machines { - machines = append(machines, m) - } - return machines, nil -} - -func (db *db) addMachine(cluster string, m *cluster.Machine) error { - entry := db.entry(cluster) - if entry == nil { - return errors.Errorf("unknown cluster '%s'", cluster) - } - - db.Lock() - defer db.Unlock() - - // Hostname is really the machine unique name as we don't allow setting a - // different hostname. - if _, ok := entry.machines[m.Hostname()]; ok { - return errors.Errorf("machine '%s' has already been added", m.Hostname()) - - } - entry.machines[m.Hostname()] = m - return nil -} - -func (db *db) removeMachine(clusterName, machineName string) (*cluster.Machine, error) { - entry := db.entry(clusterName) - if entry == nil { - return nil, errors.Errorf("unknown cluster '%s'", clusterName) - } - - db.Lock() - defer db.Unlock() - - var m *cluster.Machine - var ok bool - if m, ok = entry.machines[machineName]; !ok { - return nil, errors.Errorf("unknown machine '%s' for cluster '%s'", machineName, clusterName) - } - delete(entry.machines, machineName) - return m, nil -} diff --git a/pkg/api/key.go b/pkg/api/key.go deleted file mode 100644 index 3c0f1f3..0000000 --- a/pkg/api/key.go +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/k0sproject/bootloose/pkg/config" -) - -func (a *API) keyURI(name string) string { - return fmt.Sprintf("%s/keys/%s", a.BaseURI, name) -} - -func (a *API) createPublicKey(w http.ResponseWriter, r *http.Request) { - var def config.PublicKey - if err := json.NewDecoder(r.Body).Decode(&def); err != nil { - sendError(w, http.StatusBadRequest, errors.Wrap(err, "could not decode body")) - return - } - if def.Name == "" { - sendError(w, http.StatusBadRequest, errors.New("no key name provided")) - return - } - - if err := a.keyStore.Store(def.Name, def.Key); err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - sendCreated(w, a.keyURI(def.Name)) -} - -func (a *API) getPublicKey(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - data, err := a.keyStore.Get(vars["key"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - key := config.PublicKey{ - Name: vars["key"], - Key: string(data), - } - if err := json.NewEncoder(w).Encode(&key); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } -} - -func (a *API) deletePublicKey(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - if err := a.keyStore.Remove(vars["key"]); err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - sendOK(w) -} diff --git a/pkg/api/machine.go b/pkg/api/machine.go deleted file mode 100644 index c37fbc0..0000000 --- a/pkg/api/machine.go +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/k0sproject/bootloose/pkg/cluster" - "github.com/k0sproject/bootloose/pkg/config" -) - -// MachineURI returns the URI identifying a machine in the REST API. -func (a *API) MachineURI(c *cluster.Cluster, m *cluster.Machine) string { - return fmt.Sprintf("%s/api/clusters/%s/machines/%s", a.BaseURI, c.Name(), m.Hostname()) -} - -// createMachine creates a machine. -func (a *API) createMachine(w http.ResponseWriter, r *http.Request) { - var def config.Machine - if err := json.NewDecoder(r.Body).Decode(&def); err != nil { - sendError(w, http.StatusBadRequest, errors.Wrap(err, "could not decode body")) - return - } - if def.Name == "" { - sendError(w, http.StatusBadRequest, errors.New("no machine name provided")) - return - } - - vars := mux.Vars(r) - c, err := a.db.cluster(vars["cluster"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - m := c.NewMachine(&def) - - if err := c.CreateMachine(m, 0); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - if err := a.db.addMachine(vars["cluster"], m); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - sendCreated(w, a.MachineURI(c, m)) -} - -// getMachine returns a machine object -func (a *API) getMachine(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - m, err := a.db.machine(vars["cluster"], vars["machine"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - formatter := new(cluster.JSONFormatter) - if err := formatter.FormatSingle(w, m); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } -} - -// deleteMachine deletes a machine. -func (a *API) deleteMachine(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - c, err := a.db.cluster(vars["cluster"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - m, err := a.db.machine(vars["cluster"], vars["machine"]) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } - - if err := c.DeleteMachine(m, 0); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - _, err = a.db.removeMachine(vars["cluster"], vars["machine"]) - if err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - - sendOK(w) -} diff --git a/pkg/api/response.go b/pkg/api/response.go deleted file mode 100644 index 9f620d8..0000000 --- a/pkg/api/response.go +++ /dev/null @@ -1,55 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" -) - -func sendOK(w http.ResponseWriter) { - w.WriteHeader(http.StatusOK) -} - -// DocsResponse returns basic API docs. -type DocsResponse struct { - DOCS []byte `json:"docs"` -} - -func sendResponse(w http.ResponseWriter, body []byte) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - resp := DocsResponse{ - DOCS: body, - } - - _ = json.NewEncoder(w).Encode(&resp) -} - -// ErrorResponse is the response API entry points return when they encountered an error. -type ErrorResponse struct { - Error string `json:"error"` -} - -func sendError(w http.ResponseWriter, status int, err error) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - resp := ErrorResponse{ - Error: err.Error(), - } - _ = json.NewEncoder(w).Encode(&resp) -} - -// CreatedResponse is the response POST entry points return when a resource has been -// successfully created. -type CreatedResponse struct { - URI string `json:"uri"` -} - -func sendCreated(w http.ResponseWriter, URI string) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - resp := CreatedResponse{ - URI: URI, - } - _ = json.NewEncoder(w).Encode(&resp) -} diff --git a/pkg/client/client.go b/pkg/client/client.go deleted file mode 100644 index 537cb03..0000000 --- a/pkg/client/client.go +++ /dev/null @@ -1,173 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package client - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/pkg/errors" - "github.com/k0sproject/bootloose/pkg/api" - "github.com/k0sproject/bootloose/pkg/cluster" - "github.com/k0sproject/bootloose/pkg/config" -) - -// Client is a object able to talk a remote bootloose API server. -type Client struct { - baseURI *url.URL - client *http.Client -} - -// New creates a new Client. -func New(baseURI string) *Client { - u, err := url.Parse(baseURI) - if err != nil { - panic(err) - } - return &Client{ - baseURI: u, - client: &http.Client{}, - } -} - -func (c *Client) uriFromPath(path string) string { - u, err := url.Parse(path) - if err != nil { - panic(err) - } - return c.baseURI.ResolveReference(u).String() -} - -func (c *Client) publicKeyURI(name string) string { - return c.uriFromPath(fmt.Sprintf("/api/keys/%s", name)) -} - -func (c *Client) clusterURI(name string) string { - return c.uriFromPath(fmt.Sprintf("/api/clusters/%s", name)) -} - -func (c *Client) machineURI(clusterName, name string) string { - return c.uriFromPath(fmt.Sprintf("/api/clusters/%s/machines/%s", clusterName, name)) -} - -func apiError(resp *http.Response) error { - e := api.ErrorResponse{} - if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { - return errors.New("could not decode error response") - } - return errors.New(e.Error) -} - -func (c *Client) create(uri string, data interface{}) error { - jsonData, err := json.Marshal(data) - if err != nil { - return errors.Wrap(err, "json marshal") - } - - req, err := http.NewRequest("POST", uri, bytes.NewBuffer(jsonData)) - if err != nil { - return errors.Wrapf(err, "new POST request to %q", uri) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.client.Do(req) - if err != nil { - return errors.Wrap(err, "http request") - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - return errors.Wrapf(apiError(resp), "POST status %d", resp.StatusCode) - } - return nil -} - -func (c *Client) get(uri string, data interface{}) error { - req, err := http.NewRequest("GET", uri, http.NoBody) - if err != nil { - return errors.Wrapf(err, "new GET request to %q", uri) - } - - resp, err := c.client.Do(req) - if err != nil { - return errors.Wrap(err, "http request") - } - - if resp.StatusCode != http.StatusOK { - return errors.Wrapf(apiError(resp), "GET status %d", resp.StatusCode) - } - - if err := json.NewDecoder(resp.Body).Decode(data); err != nil { - return errors.Errorf("could not decode GET response: %v", err) - } - return nil -} - -func (c *Client) delete(uri string) error { - req, err := http.NewRequest("DELETE", uri, http.NoBody) - if err != nil { - return errors.Wrapf(err, "new DELETE request to %q", uri) - } - - resp, err := c.client.Do(req) - if err != nil { - return errors.Wrap(err, "http request") - } - - if resp.StatusCode != http.StatusOK { - return errors.Wrapf(apiError(resp), "DELETE status %d", resp.StatusCode) - } - return nil -} - -// CreatePublicKey creates a new public key. -func (c *Client) CreatePublicKey(def *config.PublicKey) error { - return c.create(c.uriFromPath("/api/keys"), def) -} - -// GetPublicKey retrieves a public key. -func (c *Client) GetPublicKey(name string) (*config.PublicKey, error) { - data := config.PublicKey{} - err := c.get(c.publicKeyURI(name), &data) - return &data, err -} - -// DeletePublicKey deletes a public key. -func (c *Client) DeletePublicKey(name string) error { - return c.delete(c.publicKeyURI(name)) -} - -// CreateCluster creates a new cluster. -func (c *Client) CreateCluster(def *config.Cluster) error { - return c.create(c.uriFromPath("/api/clusters"), def) -} - -// DeleteCluster deletes a cluster and all its associated machines. -func (c *Client) DeleteCluster(name string) error { - return c.delete(c.clusterURI(name)) -} - -// CreateMachine creates a new machine. -func (c *Client) CreateMachine(cluster string, def *config.Machine) error { - return c.create(c.uriFromPath(fmt.Sprintf("/api/clusters/%s/machines", cluster)), def) -} - -// GetMachine retrieves the machine details. -// -// XXX: This API isn't stable and will change in the future as we refine what -// the machine spec and status objects should be. -func (c *Client) GetMachine(clusterName, machine string) (*cluster.MachineStatus, error) { - status := cluster.MachineStatus{} - err := c.get(c.machineURI(clusterName, machine), &status) - return &status, err -} - -// DeleteMachine deletes a machine. -func (c *Client) DeleteMachine(cluster, machine string) error { - return c.delete(c.machineURI(cluster, machine)) -} diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go deleted file mode 100644 index 2b412e4..0000000 --- a/pkg/client/client_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Weaveworks Ltd. -// SPDX-FileCopyrightText: 2023 bootloose authors -// SPDX-License-Identifier: Apache-2.0 - -package client - -import ( - "net/http/httptest" - "net/url" - "testing" - - "github.com/k0sproject/bootloose/pkg/api" - "github.com/k0sproject/bootloose/pkg/cluster" - "github.com/k0sproject/bootloose/pkg/config" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type env struct { - server *httptest.Server - client Client -} - -func (e *env) Close() { - e.server.Close() -} - -func newEnv() *env { - // Create an API server - server := httptest.NewUnstartedServer(nil) - baseURI := "http://" + server.Listener.Addr().String() - keyStore := cluster.NewKeyStore(".") - api := api.New(baseURI, keyStore, false) - server.Config.Handler = api.Router() - server.Start() - - u, _ := url.Parse(server.URL) - - return &env{ - server: server, - client: Client{ - baseURI: u, - client: server.Client(), - }, - } -} - -const publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDT3IG4sRIpLaoAtQSXBYaVLZTXh3Pl95ONm9oe9+nJ08qrUOFEJuKMTnqSgbC+R6v3T6fcgu1HgZtQyqB15rlA5U6rybKEa631+2Y+STBdCtBover2/c59QqfEyXWoPeq0EWRCt/ixVJdcTZqxNpZQUBoUQAIl1T/+lqEsefI4H/fFCeuqDyZfjWQXpoIh8fTpYleS6rmzvKTBhxg149LdmI96mo8Wzh2nSuXxxrk4ItvjUkNP/+s/I1xBZ6OKkO5a1Ngjuv4Yi0HM3SwZcIEP4P8QnFJtTUZjz7NyyPUthJy7QPIRMmimCg+yyRwkMhnbb6bNY6QIbQmrRw4rbGyd31eY/xXXLk6DLVGaoacVD5VuPjSEVjn9lzgaQoO1HJLYnAfgJB+3L/eKG5C8iE4gwnNbKMazLr2iVa6VdeACqyzTyx3uv/4TY2Q3Aqq+LPzOda0nbeaeIaq6xpA1iBsdNM/j88SOGJtYufUngVMql7nZGsxHt4oEw0OOGtshWcR27bKMJsuOkghnHJzs9o9uRBvBStZFLpEyA6TEIeNfTn6Mzdag/T+0NeisXUKSEvrMaxEVAnX7uvkMr5UNUeT/TDbVhAtFHm4YDFEnSupmMsAKiuiTA+XhBuY+FzsGTDGcVZRj6ERZl6u0A+Oo8p/h7TizP3ct7dXVD02dmfJGAQ== cluster@bootloose.mail" - -func TestCreateDeletePublicKey(t *testing.T) { - env := newEnv() - defer env.Close() - - err := env.client.CreatePublicKey(&config.PublicKey{ - Name: "testpublickey", - Key: publicKey, - }) - require.NoError(t, err) - - data, err := env.client.GetPublicKey("testpublickey") - require.NoError(t, err) - assert.Equal(t, "testpublickey", data.Name) - assert.Equal(t, publicKey, data.Key) - - err = env.client.DeletePublicKey("testpublickey") - require.NoError(t, err) -} - -func TestCreateDeleteCluster(t *testing.T) { - env := newEnv() - defer env.Close() - - err := env.client.CreateCluster(&config.Cluster{ - Name: "testcluster", - PrivateKey: "testcluster-key", - }) - require.NoError(t, err) - - err = env.client.DeleteCluster("testcluster") - require.NoError(t, err) -} - -func TestCreateDeleteMachine(t *testing.T) { - env := newEnv() - defer env.Close() - - err := env.client.CreateCluster(&config.Cluster{ - Name: "testcluster", - PrivateKey: "testcluster-key", - }) - require.NoError(t, err) - - err = env.client.CreateMachine("testcluster", &config.Machine{ - Name: "testmachine", - Image: "quay.io/k0sproject/bootloose-ubuntu20.04:latest", - PortMappings: []config.PortMapping{ - {ContainerPort: 22}, - }, - }) - require.NoError(t, err) - - status, err := env.client.GetMachine("testcluster", "testmachine") - require.NoError(t, err) - assert.Equal(t, "testmachine", status.Spec.Name) - - err = env.client.DeleteMachine("testcluster", "testmachine") - require.NoError(t, err) - - err = env.client.DeleteCluster("testcluster") - require.NoError(t, err) -}