feat: auto-discover container images from registries
- Add ListImages() to registry interface, implement for Gitea - Add owner field to registry config (needed for Gitea packages API) - GET /api/registries/:id/images endpoint - "Browse Images" button on Projects and Quick Deploy pages - Image dropdown with registry grouping and search - i18n support (EN/RU) for all new UI strings
This commit is contained in:
@@ -17,6 +17,7 @@ type registryRequest struct {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
Owner string `json:"owner"`
|
||||
}
|
||||
|
||||
// listRegistries handles GET /api/registries.
|
||||
@@ -34,6 +35,7 @@ func (s *Server) listRegistries(w http.ResponseWriter, r *http.Request) {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
HasToken bool `json:"has_token"`
|
||||
Owner string `json:"owner"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
@@ -46,6 +48,7 @@ func (s *Server) listRegistries(w http.ResponseWriter, r *http.Request) {
|
||||
URL: reg.URL,
|
||||
Type: reg.Type,
|
||||
HasToken: reg.Token != "",
|
||||
Owner: reg.Owner,
|
||||
CreatedAt: reg.CreatedAt,
|
||||
UpdatedAt: reg.UpdatedAt,
|
||||
}
|
||||
@@ -84,6 +87,7 @@ func (s *Server) createRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
URL: req.URL,
|
||||
Type: req.Type,
|
||||
Token: encToken,
|
||||
Owner: req.Owner,
|
||||
})
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to create registry: "+err.Error())
|
||||
@@ -125,6 +129,8 @@ func (s *Server) updateRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
if req.Type != "" {
|
||||
updated.Type = req.Type
|
||||
}
|
||||
// Owner can be set to empty string intentionally, so always update it.
|
||||
updated.Owner = req.Owner
|
||||
|
||||
// Only re-encrypt if a new token is provided.
|
||||
if req.Token != "" {
|
||||
@@ -265,3 +271,49 @@ func (s *Server) listRegistryTags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
respondJSON(w, http.StatusOK, tags)
|
||||
}
|
||||
|
||||
// listRegistryImages handles GET /api/registries/{id}/images.
|
||||
// Returns all container images available in the registry for the configured owner.
|
||||
func (s *Server) listRegistryImages(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
reg, err := s.store.GetRegistryByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
respondNotFound(w, "registry")
|
||||
return
|
||||
}
|
||||
respondError(w, http.StatusInternalServerError, "failed to get registry: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if reg.Owner == "" {
|
||||
respondError(w, http.StatusBadRequest, "registry has no owner configured; set the owner in registry settings")
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt the token.
|
||||
token := reg.Token
|
||||
if token != "" {
|
||||
decrypted, err := crypto.Decrypt(s.encKey, token)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to decrypt registry token")
|
||||
return
|
||||
}
|
||||
token = decrypted
|
||||
}
|
||||
|
||||
client, err := registry.NewClient(reg.Type, reg.URL, token)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "unsupported registry type: "+reg.Type)
|
||||
return
|
||||
}
|
||||
|
||||
images, err := client.ListImages(r.Context(), reg.Owner)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadGateway, "failed to list images: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
respondJSON(w, http.StatusOK, images)
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ func (s *Server) Router() chi.Router {
|
||||
r.Delete("/", s.deleteRegistry)
|
||||
r.Post("/test", s.testRegistry)
|
||||
r.Get("/tags/*", s.listRegistryTags)
|
||||
r.Get("/images", s.listRegistryImages)
|
||||
})
|
||||
|
||||
// Settings endpoints.
|
||||
|
||||
Reference in New Issue
Block a user