mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
add pull status SSE to API.
This commit is contained in:
+18
@@ -313,6 +313,24 @@ paths:
|
|||||||
description: OK
|
description: OK
|
||||||
'500':
|
'500':
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
|
/api/pull/sse:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- default
|
||||||
|
summary: Get pull status as SSE
|
||||||
|
operationId: pullStatusSSE
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
text/event-stream:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
format: event-stream
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PullLayer'
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
|||||||
@@ -792,6 +792,36 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status as SSE
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStatusSSE: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/api/pull/sse`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
@@ -862,6 +892,16 @@ export const DefaultApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStatus(options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStatus(options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status as SSE
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async pullStatusSSE(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PullLayer>>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.pullStatusSSE(options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Stop existing pull in progress
|
* @summary Stop existing pull in progress
|
||||||
@@ -901,6 +941,15 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa
|
|||||||
pullStatus(options?: any): AxiosPromise<PullStatus> {
|
pullStatus(options?: any): AxiosPromise<PullStatus> {
|
||||||
return localVarFp.pullStatus(options).then((request) => request(axios, basePath));
|
return localVarFp.pullStatus(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status as SSE
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
pullStatusSSE(options?: any): AxiosPromise<Array<PullLayer>> {
|
||||||
|
return localVarFp.pullStatusSSE(options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Stop existing pull in progress
|
* @summary Stop existing pull in progress
|
||||||
@@ -943,6 +992,17 @@ export class DefaultApi extends BaseAPI {
|
|||||||
return DefaultApiFp(this.configuration).pullStatus(options).then((request) => request(this.axios, this.basePath));
|
return DefaultApiFp(this.configuration).pullStatus(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get pull status as SSE
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof DefaultApi
|
||||||
|
*/
|
||||||
|
public pullStatusSSE(options?: AxiosRequestConfig) {
|
||||||
|
return DefaultApiFp(this.configuration).pullStatusSSE(options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Stop existing pull in progress
|
* @summary Stop existing pull in progress
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func (manager *ApiManagerCtx) Mount(r chi.Router) {
|
|||||||
|
|
||||||
r.Route("/pull", func(r chi.Router) {
|
r.Route("/pull", func(r chi.Router) {
|
||||||
r.Get("/", manager.pullStatus)
|
r.Get("/", manager.pullStatus)
|
||||||
|
r.Get("/sse", manager.pullStatusSSE)
|
||||||
r.Post("/", manager.pullStart)
|
r.Post("/", manager.pullStart)
|
||||||
r.Delete("/", manager.pullStop)
|
r.Delete("/", manager.pullStop)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/m1k1o/neko-rooms/internal/types"
|
"github.com/m1k1o/neko-rooms/internal/types"
|
||||||
@@ -32,6 +33,39 @@ func (manager *ApiManagerCtx) pullStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *ApiManagerCtx) pullStatusSSE(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Connection does not support streaming", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sseChan := make(chan string)
|
||||||
|
unsubscribe := manager.pull.Subscribe(sseChan)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-r.Context().Done():
|
||||||
|
manager.logger.Debug().Msg("sse context done")
|
||||||
|
unsubscribe()
|
||||||
|
return
|
||||||
|
case data, ok := <-sseChan:
|
||||||
|
if !ok {
|
||||||
|
manager.logger.Debug().Msg("sse channel closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "data: %s\n\n", data)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (manager *ApiManagerCtx) pullStop(w http.ResponseWriter, r *http.Request) {
|
func (manager *ApiManagerCtx) pullStop(w http.ResponseWriter, r *http.Request) {
|
||||||
err := manager.pull.Stop()
|
err := manager.pull.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@@ -27,6 +28,9 @@ type PullManagerCtx struct {
|
|||||||
cancel func()
|
cancel func()
|
||||||
status types.PullStatus
|
status types.PullStatus
|
||||||
layers map[string]int
|
layers map[string]int
|
||||||
|
|
||||||
|
chansMu sync.Mutex
|
||||||
|
chans []chan<- string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(client *dockerClient.Client, nekoImages []string) *PullManagerCtx {
|
func New(client *dockerClient.Client, nekoImages []string) *PullManagerCtx {
|
||||||
@@ -83,7 +87,7 @@ func (manager *PullManagerCtx) Start(request types.PullStart) error {
|
|||||||
// handle registry auth
|
// handle registry auth
|
||||||
var opts dockerTypes.ImagePullOptions
|
var opts dockerTypes.ImagePullOptions
|
||||||
if request.RegistryUser != "" && request.RegistryPass != "" {
|
if request.RegistryUser != "" && request.RegistryPass != "" {
|
||||||
authConfig := dockerTypes.AuthConfig{
|
authConfig := registry.AuthConfig{
|
||||||
Username: request.RegistryUser,
|
Username: request.RegistryUser,
|
||||||
Password: request.RegistryPass,
|
Password: request.RegistryPass,
|
||||||
}
|
}
|
||||||
@@ -109,6 +113,7 @@ func (manager *PullManagerCtx) Start(request types.PullStart) error {
|
|||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
data := scanner.Bytes()
|
data := scanner.Bytes()
|
||||||
|
manager.sendSSE(string(data))
|
||||||
|
|
||||||
layer := types.PullLayer{}
|
layer := types.PullLayer{}
|
||||||
if err := json.Unmarshal(data, &layer); err != nil {
|
if err := json.Unmarshal(data, &layer); err != nil {
|
||||||
@@ -168,3 +173,49 @@ func (manager *PullManagerCtx) Status() types.PullStatus {
|
|||||||
|
|
||||||
return manager.status
|
return manager.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (manager *PullManagerCtx) sendSSE(status string) {
|
||||||
|
manager.chansMu.Lock()
|
||||||
|
defer manager.chansMu.Unlock()
|
||||||
|
|
||||||
|
for _, ch := range manager.chans {
|
||||||
|
ch <- status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *PullManagerCtx) Subscribe(ch chan<- string) func() {
|
||||||
|
manager.chansMu.Lock()
|
||||||
|
defer manager.chansMu.Unlock()
|
||||||
|
|
||||||
|
// subscribe
|
||||||
|
manager.chans = append(manager.chans, ch)
|
||||||
|
|
||||||
|
// unsubscribe
|
||||||
|
return func() {
|
||||||
|
manager.chansMu.Lock()
|
||||||
|
defer manager.chansMu.Unlock()
|
||||||
|
|
||||||
|
for i, c := range manager.chans {
|
||||||
|
if c == ch {
|
||||||
|
manager.chans = append(manager.chans[:i], manager.chans[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *PullManagerCtx) Shutdown() error {
|
||||||
|
manager.chansMu.Lock()
|
||||||
|
for _, ch := range manager.chans {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
manager.chansMu.Unlock()
|
||||||
|
|
||||||
|
manager.mu.Lock()
|
||||||
|
if manager.cancel != nil {
|
||||||
|
manager.cancel()
|
||||||
|
}
|
||||||
|
manager.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ type PullManager interface {
|
|||||||
Start(request PullStart) error
|
Start(request PullStart) error
|
||||||
Stop() error
|
Stop() error
|
||||||
Status() PullStatus
|
Status() PullStatus
|
||||||
|
Subscribe(ch chan<- string) func()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,9 @@ func (main *MainCtx) Shutdown() {
|
|||||||
|
|
||||||
err = main.proxyManager.Shutdown()
|
err = main.proxyManager.Shutdown()
|
||||||
main.logger.Err(err).Msg("proxy manager shutdown")
|
main.logger.Err(err).Msg("proxy manager shutdown")
|
||||||
|
|
||||||
|
err = main.pullManager.Shutdown()
|
||||||
|
main.logger.Err(err).Msg("pull manager shutdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (main *MainCtx) ServeCommand(cmd *cobra.Command, args []string) {
|
func (main *MainCtx) ServeCommand(cmd *cobra.Command, args []string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user