diff --git a/satellite/admin.go b/satellite/admin.go
index 53bc98eff..443f1d7a2 100644
--- a/satellite/admin.go
+++ b/satellite/admin.go
@@ -215,10 +215,28 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
return nil, err
}
+ placement, err := config.Placement.Parse()
+ if err != nil {
+ return nil, err
+ }
+
adminConfig := config.Admin
adminConfig.AuthorizationToken = config.Console.AuthToken
- peer.Admin.Server = admin.NewServer(log.Named("admin"), peer.Admin.Listener, peer.DB, peer.Buckets.Service, peer.REST.Keys, peer.FreezeAccounts.Service, peer.Analytics.Service, peer.Payments.Accounts, config.Console, adminConfig)
+ peer.Admin.Server = admin.NewServer(
+ log.Named("admin"),
+ peer.Admin.Listener,
+ peer.DB,
+ peer.Buckets.Service,
+ peer.REST.Keys,
+ peer.FreezeAccounts.Service,
+ peer.Analytics.Service,
+ peer.Payments.Accounts,
+ placement,
+ config.Console,
+ adminConfig,
+ )
+
peer.Servers.Add(lifecycle.Item{
Name: "admin",
Run: peer.Admin.Server.Run,
diff --git a/satellite/admin/back-office/api-docs.gen.md b/satellite/admin/back-office/api-docs.gen.md
index 2745decc6..cb5bf9871 100644
--- a/satellite/admin/back-office/api-docs.gen.md
+++ b/satellite/admin/back-office/api-docs.gen.md
@@ -6,20 +6,24 @@
List of Endpoints
-* Example
- * [Get examples](#example-get-examples)
+* PlacementManagement
+ * [Get placements](#placementmanagement-get-placements)
-
+
-Get a list with the names of the all available examples
+Gets placement rule IDs and their locations
-`GET /back-office/api/v1/example/examples`
+`GET /back-office/api/v1/placements/`
**Response body:**
```typescript
[
-string
+ {
+ id: number
+ location: string
+ }
+
]
```
diff --git a/satellite/admin/back-office/gen/main.go b/satellite/admin/back-office/gen/main.go
index 85a03ec2d..7ecf0d287 100644
--- a/satellite/admin/back-office/gen/main.go
+++ b/satellite/admin/back-office/gen/main.go
@@ -5,6 +5,8 @@
// source code of the API server handlers and clients and the documentation markdown document.
package main
+//go:generate go run $GOFILE
+
import (
"os"
"path"
@@ -22,24 +24,21 @@ func main() {
BasePath: path.Join(backoffice.PathPrefix, "/api"),
}
- // This is an example and must be deleted when we define the first real endpoint.
- group := api.Group("Example", "example")
+ group := api.Group("PlacementManagement", "placements")
- group.Get("/examples", &apigen.Endpoint{
- Name: "Get examples",
- Description: "Get a list with the names of the all available examples",
- GoName: "GetExamples",
- TypeScriptName: "getExamples",
- Response: []string{},
- ResponseMock: []string{"example-1", "example-2", "example-3"},
- NoCookieAuth: false,
- NoAPIAuth: false,
+ group.Get("/", &apigen.Endpoint{
+ Name: "Get placements",
+ Description: "Gets placement rule IDs and their locations",
+ GoName: "GetPlacements",
+ TypeScriptName: "getPlacements",
+ Response: []backoffice.PlacementInfo{},
+ NoCookieAuth: true,
+ NoAPIAuth: true,
})
modroot := findModuleRootDir()
api.MustWriteGo(filepath.Join(modroot, "satellite", "admin", "back-office", "handlers.gen.go"))
api.MustWriteTS(filepath.Join(modroot, "satellite", "admin", "back-office", "ui", "src", "api", "client.gen.ts"))
- api.MustWriteTSMock(filepath.Join(modroot, "satellite", "admin", "back-office", "ui", "src", "api", "client-mock.gen.ts"))
api.MustWriteDocs(filepath.Join(modroot, "satellite", "admin", "back-office", "api-docs.gen.md"))
}
diff --git a/satellite/admin/back-office/handlers.gen.go b/satellite/admin/back-office/handlers.gen.go
index 8e6702f94..5e6be6448 100644
--- a/satellite/admin/back-office/handlers.gen.go
+++ b/satellite/admin/back-office/handlers.gen.go
@@ -16,49 +16,42 @@ import (
"storj.io/storj/private/api"
)
-var ErrExampleAPI = errs.Class("admin example api")
+var ErrPlacementsAPI = errs.Class("admin placements api")
-type ExampleService interface {
- GetExamples(ctx context.Context) ([]string, api.HTTPError)
+type PlacementManagementService interface {
+ GetPlacements(ctx context.Context) ([]PlacementInfo, api.HTTPError)
}
-// ExampleHandler is an api handler that implements all Example API endpoints functionality.
-type ExampleHandler struct {
+// PlacementManagementHandler is an api handler that implements all PlacementManagement API endpoints functionality.
+type PlacementManagementHandler struct {
log *zap.Logger
mon *monkit.Scope
- service ExampleService
+ service PlacementManagementService
auth api.Auth
}
-func NewExample(log *zap.Logger, mon *monkit.Scope, service ExampleService, router *mux.Router, auth api.Auth) *ExampleHandler {
- handler := &ExampleHandler{
+func NewPlacementManagement(log *zap.Logger, mon *monkit.Scope, service PlacementManagementService, router *mux.Router, auth api.Auth) *PlacementManagementHandler {
+ handler := &PlacementManagementHandler{
log: log,
mon: mon,
service: service,
auth: auth,
}
- exampleRouter := router.PathPrefix("/back-office/api/v1/example").Subrouter()
- exampleRouter.HandleFunc("/examples", handler.handleGetExamples).Methods("GET")
+ placementsRouter := router.PathPrefix("/back-office/api/v1/placements").Subrouter()
+ placementsRouter.HandleFunc("/", handler.handleGetPlacements).Methods("GET")
return handler
}
-func (h *ExampleHandler) handleGetExamples(w http.ResponseWriter, r *http.Request) {
+func (h *PlacementManagementHandler) handleGetPlacements(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer h.mon.Task()(&ctx)(&err)
w.Header().Set("Content-Type", "application/json")
- ctx, err = h.auth.IsAuthenticated(ctx, r, true, true)
- if err != nil {
- h.auth.RemoveAuthCookie(w)
- api.ServeError(h.log, w, http.StatusUnauthorized, err)
- return
- }
-
- retVal, httpErr := h.service.GetExamples(ctx)
+ retVal, httpErr := h.service.GetPlacements(ctx)
if httpErr.Err != nil {
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
return
@@ -66,6 +59,6 @@ func (h *ExampleHandler) handleGetExamples(w http.ResponseWriter, r *http.Reques
err = json.NewEncoder(w).Encode(retVal)
if err != nil {
- h.log.Debug("failed to write json GetExamples response", zap.Error(ErrExampleAPI.Wrap(err)))
+ h.log.Debug("failed to write json GetPlacements response", zap.Error(ErrPlacementsAPI.Wrap(err)))
}
}
diff --git a/satellite/admin/back-office/placements.go b/satellite/admin/back-office/placements.go
new file mode 100644
index 000000000..a846734da
--- /dev/null
+++ b/satellite/admin/back-office/placements.go
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 Storj Labs, Inc.
+// See LICENSE for copying information.
+
+package admin
+
+import (
+ "context"
+
+ "storj.io/common/storj"
+ "storj.io/storj/private/api"
+ "storj.io/storj/satellite/nodeselection"
+)
+
+// PlacementInfo contains the ID and location of a placement rule.
+type PlacementInfo struct {
+ ID storj.PlacementConstraint `json:"id"`
+ Location string `json:"location"`
+}
+
+// GetPlacements returns IDs and locations of placement rules.
+func (s *Server) GetPlacements(ctx context.Context) ([]PlacementInfo, api.HTTPError) {
+ var err error
+ defer mon.Task()(&ctx)(&err)
+
+ placements := s.placement.SupportedPlacements()
+ infos := make([]PlacementInfo, 0, len(placements))
+ for _, placement := range placements {
+ filter := s.placement.CreateFilters(placement)
+ infos = append(infos, PlacementInfo{
+ ID: placement,
+ Location: nodeselection.GetAnnotation(filter, nodeselection.Location),
+ })
+ }
+
+ return infos, api.HTTPError{}
+}
diff --git a/satellite/admin/back-office/server.go b/satellite/admin/back-office/server.go
index 730c7b4f4..057e84772 100644
--- a/satellite/admin/back-office/server.go
+++ b/satellite/admin/back-office/server.go
@@ -10,20 +10,26 @@ import (
"net/http"
"github.com/gorilla/mux"
+ "github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"storj.io/common/errs2"
ui "storj.io/storj/satellite/admin/back-office/ui"
+ "storj.io/storj/satellite/overlay"
)
// PathPrefix is the path that will be prefixed to the router passed to the NewServer constructor.
// This is temporary until this server will replace the storj.io/storj/satellite/admin/server.go.
const PathPrefix = "/back-office/"
-// Error is the error class that wraps all the errors returned by this package.
-var Error = errs.Class("satellite-admin")
+var (
+ // Error is the error class that wraps all the errors returned by this package.
+ Error = errs.Class("satellite-admin")
+
+ mon = monkit.Package()
+)
// Config defines configuration for the satellite administration server.
type Config struct {
@@ -38,23 +44,25 @@ type Config struct {
// Server serves the API endpoints and the web application to allow preforming satellite
// administration tasks.
type Server struct {
- log *zap.Logger
-
- listener net.Listener
- server http.Server
+ log *zap.Logger
+ listener net.Listener
+ placement *overlay.PlacementDefinitions
config Config
+
+ server http.Server
}
// NewServer creates a satellite administration server instance with the provided dependencies and
// configurations.
//
// When listener is nil, Server.Run is a noop.
-func NewServer(log *zap.Logger, listener net.Listener, root *mux.Router, config Config) *Server {
+func NewServer(log *zap.Logger, listener net.Listener, placement *overlay.PlacementDefinitions, root *mux.Router, config Config) *Server {
server := &Server{
- log: log,
- listener: listener,
- config: config,
+ log: log,
+ listener: listener,
+ placement: placement,
+ config: config,
}
if root == nil {
@@ -63,7 +71,8 @@ func NewServer(log *zap.Logger, listener net.Listener, root *mux.Router, config
// API endpoints.
// API generator already add the PathPrefix.
- // _ := NewExample(log, mon, nil, root, nil)
+ auth := &apiAuth{server}
+ NewPlacementManagement(log, mon, server, root, auth)
root = root.PathPrefix(PathPrefix).Subrouter()
// Static assets for the web interface.
@@ -108,3 +117,18 @@ func (server *Server) Run(ctx context.Context) error {
func (server *Server) Close() error {
return Error.Wrap(server.server.Close())
}
+
+// apiAuth exposes methods to control the authentication process for each generated API endpoint.
+type apiAuth struct {
+ server *Server
+}
+
+// IsAuthenticated checks if request is performed with all needed authorization credentials.
+// This method is inert because the satellite admin back office uses neither cookies nor API keys to authenticate.
+func (a *apiAuth) IsAuthenticated(ctx context.Context, r *http.Request, isCookieAuth, isKeyAuth bool) (_ context.Context, err error) {
+ return ctx, nil
+}
+
+// RemoveAuthCookie indicates to the client that the authentication cookie should be removed.
+// This method is inert because the satellite admin back office doesn't use authentication cookies.
+func (a *apiAuth) RemoveAuthCookie(w http.ResponseWriter) {}
diff --git a/satellite/admin/back-office/ui/src/api/client-mock.gen.ts b/satellite/admin/back-office/ui/src/api/client-mock.gen.ts
deleted file mode 100644
index 88e6998d6..000000000
--- a/satellite/admin/back-office/ui/src/api/client-mock.gen.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-// AUTOGENERATED BY private/apigen
-// DO NOT EDIT.
-
-class APIError extends Error {
- constructor(
- public readonly msg: string,
- public readonly responseStatusCode?: number,
- ) {
- super(msg);
- }
-}
-
-export class ExampleHttpApiV1 {
- public readonly respStatusCode: number;
-
- // When respStatuscode is passed, the client throws an APIError on each method call
- // with respStatusCode as HTTP status code.
- // respStatuscode must be equal or greater than 400
- constructor(respStatusCode?: number) {
- if (typeof respStatusCode === 'undefined') {
- this.respStatusCode = 0;
- return;
- }
-
- if (respStatusCode < 400) {
- throw new Error('invalid response status code for API Error, it must be greater or equal than 400');
- }
-
- this.respStatusCode = respStatusCode;
- }
-
- public async getExamples(): Promise {
- if (this.respStatusCode !== 0) {
- throw new APIError('mock error message: ' + this.respStatusCode, this.respStatusCode);
- }
-
- return JSON.parse('["example-1","example-2","example-3"]') as string[];
- }
-}
diff --git a/satellite/admin/back-office/ui/src/api/client.gen.ts b/satellite/admin/back-office/ui/src/api/client.gen.ts
index 999bcc194..825f15e57 100644
--- a/satellite/admin/back-office/ui/src/api/client.gen.ts
+++ b/satellite/admin/back-office/ui/src/api/client.gen.ts
@@ -3,6 +3,11 @@
import { HttpClient } from '@/utils/httpClient';
+export class PlacementInfo {
+ id: number;
+ location: string;
+}
+
class APIError extends Error {
constructor(
public readonly msg: string,
@@ -12,15 +17,15 @@ class APIError extends Error {
}
}
-export class ExampleHttpApiV1 {
+export class PlacementManagementHttpApiV1 {
private readonly http: HttpClient = new HttpClient();
- private readonly ROOT_PATH: string = '/back-office/api/v1/example';
+ private readonly ROOT_PATH: string = '/back-office/api/v1/placements';
- public async getExamples(): Promise {
- const fullPath = `${this.ROOT_PATH}/examples`;
+ public async getPlacements(): Promise {
+ const fullPath = `${this.ROOT_PATH}/`;
const response = await this.http.get(fullPath);
if (response.ok) {
- return response.json().then((body) => body as string[]);
+ return response.json().then((body) => body as PlacementInfo[]);
}
const err = await response.json();
throw new APIError(err.error, response.status);
diff --git a/satellite/admin/server.go b/satellite/admin/server.go
index e5cb58704..64844345f 100644
--- a/satellite/admin/server.go
+++ b/satellite/admin/server.go
@@ -29,6 +29,7 @@ import (
"storj.io/storj/satellite/console/consoleweb"
"storj.io/storj/satellite/console/restkeys"
"storj.io/storj/satellite/oidc"
+ "storj.io/storj/satellite/overlay"
"storj.io/storj/satellite/payments"
"storj.io/storj/satellite/payments/stripe"
)
@@ -38,6 +39,9 @@ const (
UnauthorizedNotInGroup = "User must be a member of one of these groups to conduct this operation: %s"
// AuthorizationNotEnabled - message for when authorization is disabled.
AuthorizationNotEnabled = "Authorization not enabled."
+
+ // BackOfficePathPrefix is the path prefix used for the back office router.
+ BackOfficePathPrefix = "/back-office"
)
// Config defines configuration for debug server.
@@ -102,6 +106,7 @@ func NewServer(
freezeAccounts *console.AccountFreezeService,
analyticsService *analytics.Service,
accounts payments.Accounts,
+ placement *overlay.PlacementDefinitions,
console consoleweb.Config,
config Config,
) *Server {
@@ -178,7 +183,7 @@ func NewServer(
// NewServer adds the backoffice.PahtPrefix for the static assets, but not for the API because the
// generator already add the PathPrefix to router when the API handlers are hooked.
- _ = backoffice.NewServer(log.Named("back-office"), nil, root, config.BackOffice)
+ _ = backoffice.NewServer(log.Named("back-office"), nil, placement, root, config.BackOffice)
// This handler must be the last one because it uses the root as prefix,
// otherwise will try to serve all the handlers set after this one.