Skip to content

Commit

Permalink
add labels filter support to API.
Browse files Browse the repository at this point in the history
  • Loading branch information
m1k1o committed Sep 30, 2023
1 parent 7b38250 commit e7b9d39
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 59 deletions.
15 changes: 15 additions & 0 deletions OpenApi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ paths:
- rooms
summary: List all rooms
operationId: roomsList
parameters:
- in: query
name: labels
schema:
type: object
additionalProperties:
type: string
responses:
'200':
description: OK
Expand Down Expand Up @@ -318,6 +325,10 @@ components:
type: string
format: datetime
example: "2021-03-07T21:56:34Z"
labels:
type: object
additionalProperties:
type: string

RoomMount:
type: object
Expand Down Expand Up @@ -415,6 +426,10 @@ components:
type: string
example:
CUSTOM_ENV: custom value
labels:
type: object
additionalProperties:
type: string
mounts:
type: array
items:
Expand Down
36 changes: 29 additions & 7 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ export interface RoomEntry {
* @memberof RoomEntry
*/
'created'?: string;
/**
*
* @type {{ [key: string]: string; }}
* @memberof RoomEntry
*/
'labels'?: { [key: string]: string; };
}
/**
*
Expand Down Expand Up @@ -490,6 +496,12 @@ export interface RoomSettings {
* @memberof RoomSettings
*/
'envs'?: { [key: string]: string; };
/**
*
* @type {{ [key: string]: string; }}
* @memberof RoomSettings
*/
'labels'?: { [key: string]: string; };
/**
*
* @type {Array<RoomMount>}
Expand Down Expand Up @@ -1248,10 +1260,11 @@ export const RoomsApiAxiosParamCreator = function (configuration?: Configuration
/**
*
* @summary List all rooms
* @param {{ [key: string]: string; }} [labels]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
roomsList: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
roomsList: async (labels?: { [key: string]: string; }, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/rooms`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
Expand All @@ -1264,6 +1277,12 @@ export const RoomsApiAxiosParamCreator = function (configuration?: Configuration
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

if (labels !== undefined) {
for (let param of Object.keys(labels)) {
localVarQueryParameter[param] = labels?.[param];
}
}



setSearchParams(localVarUrlObj, localVarQueryParameter);
Expand Down Expand Up @@ -1387,11 +1406,12 @@ export const RoomsApiFp = function(configuration?: Configuration) {
/**
*
* @summary List all rooms
* @param {{ [key: string]: string; }} [labels]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async roomsList(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<RoomEntry>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.roomsList(options);
async roomsList(labels?: { [key: string]: string; }, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<RoomEntry>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.roomsList(labels, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
Expand Down Expand Up @@ -1497,11 +1517,12 @@ export const RoomsApiFactory = function (configuration?: Configuration, basePath
/**
*
* @summary List all rooms
* @param {{ [key: string]: string; }} [labels]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
roomsList(options?: any): AxiosPromise<Array<RoomEntry>> {
return localVarFp.roomsList(options).then((request) => request(axios, basePath));
roomsList(labels?: { [key: string]: string; }, options?: any): AxiosPromise<Array<RoomEntry>> {
return localVarFp.roomsList(labels, options).then((request) => request(axios, basePath));
},
};
};
Expand Down Expand Up @@ -1624,12 +1645,13 @@ export class RoomsApi extends BaseAPI {
/**
*
* @summary List all rooms
* @param {{ [key: string]: string; }} [labels]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof RoomsApi
*/
public roomsList(options?: AxiosRequestConfig) {
return RoomsApiFp(this.configuration).roomsList(options).then((request) => request(this.axios, this.basePath));
public roomsList(labels?: { [key: string]: string; }, options?: AxiosRequestConfig) {
return RoomsApiFp(this.configuration).roomsList(labels, options).then((request) => request(this.axios, this.basePath));
}
}

Expand Down
29 changes: 21 additions & 8 deletions client/src/components/RoomInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,27 @@
</template>
</v-simple-table>

<div class="my-3 headline">Mounts</div>
<v-simple-table>
<template v-slot:default>
<tbody>
<tr v-for="({ type, host_path, container_path }, index) in settings.mounts" :key="index"><td style="width:50%;">{{ host_path }} <v-chip small>{{ type }}</v-chip></td><td>{{ container_path }}</td></tr>
</tbody>
</template>
</v-simple-table>
<template v-if="settings.labels && Object.keys(settings.labels).length > 0">
<div class="my-3 headline">Labels</div>
<v-simple-table>
<template v-slot:default>
<tbody>
<tr v-for="(val, key) in settings.labels" :key="key"><th style="width:50%;">{{ key }}</th><td>{{ val }}</td></tr>
</tbody>
</template>
</v-simple-table>
</template>

<template v-if="settings.mounts && settings.mounts.length > 0">
<div class="my-3 headline">Mounts</div>
<v-simple-table>
<template v-slot:default>
<tbody>
<tr v-for="({ type, host_path, container_path }, index) in settings.mounts" :key="index"><td style="width:50%;">{{ host_path }} <v-chip small>{{ type }}</v-chip></td><td>{{ container_path }}</td></tr>
</tbody>
</template>
</v-simple-table>
</template>

<div class="my-3 headline">Resources</div>
<v-simple-table>
Expand Down
2 changes: 1 addition & 1 deletion docs/lables.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# custom labels

You can add custom lables for every room.
You can add custom labels for every room.

Example: Expose port 3000 from every room under `3000-(room-name)`:

Expand Down
16 changes: 15 additions & 1 deletion internal/api/rooms.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,28 @@ package api
import (
"encoding/json"
"net/http"
"strings"

"github.com/go-chi/chi/v5"

"github.com/m1k1o/neko-rooms/internal/room"
"github.com/m1k1o/neko-rooms/internal/types"
)

func (manager *ApiManagerCtx) roomsList(w http.ResponseWriter, r *http.Request) {
response, err := manager.rooms.List()
labelsMap := map[string]string{}
for key, value := range r.URL.Query() {
key = strings.ToLower(key)

if !room.CheckLabelKey(key) {
http.Error(w, "invalid label name, allowed characters: [a-z0-9.-]", 400)
return
}

labelsMap[key] = value[0]
}

response, err := manager.rooms.List(labelsMap)
if err != nil {
http.Error(w, err.Error(), 500)
return
Expand Down
35 changes: 9 additions & 26 deletions internal/room/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (manager *RoomManagerCtx) containerToEntry(container dockerTypes.Container)
Running: container.State == "running",
Status: container.Status,
Created: time.Unix(container.Created, 0),
Labels: labels.UserDefined,
}

if labels.Mux {
Expand All @@ -37,35 +38,23 @@ func (manager *RoomManagerCtx) containerToEntry(container dockerTypes.Container)
return entry, nil
}

func (manager *RoomManagerCtx) listContainers() ([]dockerTypes.Container, error) {
func (manager *RoomManagerCtx) listContainers(labels map[string]string) ([]dockerTypes.Container, error) {
args := filters.NewArgs(
filters.Arg("label", "m1k1o.neko_rooms.instance"),
filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", manager.config.InstanceName)),
)

containers, err := manager.client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{
for key, val := range labels {
args.Add("label", fmt.Sprintf("m1k1o.neko_rooms.x-%s=%s", key, val))
}

return manager.client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{
All: true,
Filters: args,
})

if err != nil {
return nil, err
}

result := []dockerTypes.Container{}
for _, container := range containers {
val, ok := container.Labels["m1k1o.neko_rooms.instance"]
if !ok || val != manager.config.InstanceName {
continue
}

result = append(result, container)
}

return result, nil
}

func (manager *RoomManagerCtx) containerFilter(args filters.Args) (*dockerTypes.Container, error) {
args.Add("label", "m1k1o.neko_rooms.instance")
args.Add("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", manager.config.InstanceName))

containers, err := manager.client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{
All: true,
Expand All @@ -81,12 +70,6 @@ func (manager *RoomManagerCtx) containerFilter(args filters.Args) (*dockerTypes.
}

container := containers[0]

val, ok := container.Labels["m1k1o.neko_rooms.instance"]
if !ok || val != manager.config.InstanceName {
return nil, fmt.Errorf("this container does not belong to neko_rooms")
}

return &container, nil
}

Expand Down
25 changes: 25 additions & 0 deletions internal/room/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package room

import (
"fmt"
"regexp"
"strconv"
"strings"

"github.com/m1k1o/neko-rooms/internal/types"
)

var labelRegex = regexp.MustCompile(`^[a-z0-9.-]+$`)

type RoomLabels struct {
Name string
URL string
Expand All @@ -15,6 +19,7 @@ type RoomLabels struct {
NekoImage string

BrowserPolicy *BrowserPolicyLabels
UserDefined map[string]string
}

type BrowserPolicyLabels struct {
Expand Down Expand Up @@ -101,6 +106,14 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
}
}

// extract user defined labels
userDefined := map[string]string{}
for key, val := range labels {
if strings.HasPrefix(key, "m1k1o.neko_rooms.x-") {
userDefined[strings.TrimPrefix(key, "m1k1o.neko_rooms.x-")] = val
}
}

return &RoomLabels{
Name: name,
URL: url,
Expand All @@ -109,6 +122,7 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab
Epr: epr,

BrowserPolicy: browserPolicy,
UserDefined: userDefined,
}, nil
}

Expand All @@ -133,5 +147,16 @@ func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]str
labelsMap["m1k1o.neko_rooms.browser_policy.path"] = labels.BrowserPolicy.Path
}

for key, val := range labels.UserDefined {
// to lowercase
key = strings.ToLower(key)

labelsMap[fmt.Sprintf("m1k1o.neko_rooms.x-%s", key)] = val
}

return labelsMap
}

func CheckLabelKey(name string) bool {
return labelRegex.MatchString(name)
}
13 changes: 8 additions & 5 deletions internal/room/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/csv"
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -62,13 +63,13 @@ func (manager *RoomManagerCtx) Config() types.RoomsConfig {
}
}

func (manager *RoomManagerCtx) List() ([]types.RoomEntry, error) {
containers, err := manager.listContainers()
func (manager *RoomManagerCtx) List(labels map[string]string) ([]types.RoomEntry, error) {
containers, err := manager.listContainers(labels)
if err != nil {
return nil, err
}

result := []types.RoomEntry{}
result := make([]types.RoomEntry, 0, len(containers))
for _, container := range containers {
entry, err := manager.containerToEntry(container)
if err != nil {
Expand Down Expand Up @@ -174,6 +175,7 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
NekoImage: settings.NekoImage,

BrowserPolicy: browserPolicyLabels,
UserDefined: settings.Labels,
})

//
Expand Down Expand Up @@ -652,9 +654,10 @@ func (manager *RoomManagerCtx) GetSettings(id string) (*types.RoomSettings, erro
Name: labels.Name,
NekoImage: labels.NekoImage,
MaxConnections: labels.Epr.Max - labels.Epr.Min + 1,
Labels: labels.UserDefined,
Mounts: mounts,
BrowserPolicy: browserPolicy,
Resources: roomResources,
BrowserPolicy: browserPolicy,
}

if labels.Mux {
Expand All @@ -678,7 +681,7 @@ func (manager *RoomManagerCtx) GetStats(id string) (*types.RoomStats, error) {
}

output, err := manager.containerExec(id, []string{
"wget", "-q", "-O-", "http://127.0.0.1:8080/stats?pwd=" + settings.AdminPass,
"wget", "-q", "-O-", "http://127.0.0.1:8080/stats?pwd=" + url.QueryEscape(settings.AdminPass),
})
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/room/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (manager *RoomManagerCtx) allocatePorts(sum uint16) (EprPorts, error) {
}

func (manager *RoomManagerCtx) getUsedPorts() ([]EprPorts, error) {
containers, err := manager.listContainers()
containers, err := manager.listContainers(nil)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit e7b9d39

Please sign in to comment.