mirror of https://github.com/portainer/portainer
feat(registry): Add ProGet registry type EE-703 (#5196)
* intermediate commit * feat(registry): backport ProGet registry to CE (#954) * backport EE changes * label updates and remove auth-toggle Co-authored-by: Dennis Buduev <dennis.buduev@portainer.io>pull/5266/head
parent
8b80eb1731
commit
90a472c08b
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/http/proxy"
|
"github.com/portainer/portainer/api/http/proxy"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
)
|
)
|
||||||
|
@ -26,11 +26,20 @@ type Handler struct {
|
||||||
|
|
||||||
// NewHandler creates a handler to manage registry operations.
|
// NewHandler creates a handler to manage registry operations.
|
||||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
h := &Handler{
|
h := newHandler(bouncer)
|
||||||
|
h.initRouter(bouncer)
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
|
return &Handler{
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
requestBouncer: bouncer,
|
requestBouncer: bouncer,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) initRouter(bouncer accessGuard) {
|
||||||
h.Handle("/registries",
|
h.Handle("/registries",
|
||||||
bouncer.AdminAccess(httperror.LoggerHandler(h.registryCreate))).Methods(http.MethodPost)
|
bouncer.AdminAccess(httperror.LoggerHandler(h.registryCreate))).Methods(http.MethodPost)
|
||||||
h.Handle("/registries",
|
h.Handle("/registries",
|
||||||
|
@ -45,5 +54,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
bouncer.AdminAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
|
bouncer.AdminAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
|
||||||
h.PathPrefix("/registries/proxies/gitlab").Handler(
|
h.PathPrefix("/registries/proxies/gitlab").Handler(
|
||||||
bouncer.AdminAccess(httperror.LoggerHandler(h.proxyRequestsToGitlabAPIWithoutRegistry)))
|
bouncer.AdminAccess(httperror.LoggerHandler(h.proxyRequestsToGitlabAPIWithoutRegistry)))
|
||||||
return h
|
}
|
||||||
|
|
||||||
|
type accessGuard interface {
|
||||||
|
AdminAccess(h http.Handler) http.Handler
|
||||||
|
RestrictedAccess(h http.Handler) http.Handler
|
||||||
|
AuthenticatedAccess(h http.Handler) http.Handler
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package registries
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
|
@ -14,10 +15,12 @@ import (
|
||||||
type registryCreatePayload struct {
|
type registryCreatePayload struct {
|
||||||
// Name that will be used to identify this registry
|
// Name that will be used to identify this registry
|
||||||
Name string `example:"my-registry" validate:"required"`
|
Name string `example:"my-registry" validate:"required"`
|
||||||
// Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container registry), 3 (custom registry) or 4 (Gitlab registry)
|
// Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container registry), 3 (custom registry), 4 (Gitlab registry) or 5 (ProGet registry)
|
||||||
Type portainer.RegistryType `example:"1" validate:"required" enums:"1,2,3,4"`
|
Type portainer.RegistryType `example:"1" validate:"required" enums:"1,2,3,4,5"`
|
||||||
// URL or IP address of the Docker registry
|
// URL or IP address of the Docker registry
|
||||||
URL string `example:"registry.mydomain.tld:2375" validate:"required"`
|
URL string `example:"registry.mydomain.tld:2375/feed" validate:"required"`
|
||||||
|
// BaseURL required for ProGet registry
|
||||||
|
BaseURL string `example:"registry.mydomain.tld:2375"`
|
||||||
// Is authentication against this registry enabled
|
// Is authentication against this registry enabled
|
||||||
Authentication bool `example:"false" validate:"required"`
|
Authentication bool `example:"false" validate:"required"`
|
||||||
// Username used to authenticate against this registry. Required when Authentication is true
|
// Username used to authenticate against this registry. Required when Authentication is true
|
||||||
|
@ -30,7 +33,7 @@ type registryCreatePayload struct {
|
||||||
Quay portainer.QuayRegistryData
|
Quay portainer.QuayRegistryData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *registryCreatePayload) Validate(r *http.Request) error {
|
func (payload *registryCreatePayload) Validate(_ *http.Request) error {
|
||||||
if govalidator.IsNull(payload.Name) {
|
if govalidator.IsNull(payload.Name) {
|
||||||
return errors.New("Invalid registry name")
|
return errors.New("Invalid registry name")
|
||||||
}
|
}
|
||||||
|
@ -40,9 +43,17 @@ func (payload *registryCreatePayload) Validate(r *http.Request) error {
|
||||||
if payload.Authentication && (govalidator.IsNull(payload.Username) || govalidator.IsNull(payload.Password)) {
|
if payload.Authentication && (govalidator.IsNull(payload.Username) || govalidator.IsNull(payload.Password)) {
|
||||||
return errors.New("Invalid credentials. Username and password must be specified when authentication is enabled")
|
return errors.New("Invalid credentials. Username and password must be specified when authentication is enabled")
|
||||||
}
|
}
|
||||||
if payload.Type != portainer.QuayRegistry && payload.Type != portainer.AzureRegistry && payload.Type != portainer.CustomRegistry && payload.Type != portainer.GitlabRegistry {
|
|
||||||
return errors.New("Invalid registry type. Valid values are: 1 (Quay.io), 2 (Azure container registry), 3 (custom registry) or 4 (Gitlab registry)")
|
switch payload.Type {
|
||||||
|
case portainer.QuayRegistry, portainer.AzureRegistry, portainer.CustomRegistry, portainer.GitlabRegistry, portainer.ProGetRegistry:
|
||||||
|
default:
|
||||||
|
return errors.New("invalid registry type. Valid values are: 1 (Quay.io), 2 (Azure container registry), 3 (custom registry), 4 (Gitlab registry) or 5 (ProGet registry)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.Type == portainer.ProGetRegistry && payload.BaseURL == "" {
|
||||||
|
return fmt.Errorf("BaseURL is required for registry type %d (ProGet)", portainer.ProGetRegistry)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +81,7 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
|
||||||
Type: portainer.RegistryType(payload.Type),
|
Type: portainer.RegistryType(payload.Type),
|
||||||
Name: payload.Name,
|
Name: payload.Name,
|
||||||
URL: payload.URL,
|
URL: payload.URL,
|
||||||
|
BaseURL: payload.BaseURL,
|
||||||
Authentication: payload.Authentication,
|
Authentication: payload.Authentication,
|
||||||
Username: payload.Username,
|
Username: payload.Username,
|
||||||
Password: payload.Password,
|
Password: payload.Password,
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package registries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_registryCreatePayload_Validate(t *testing.T) {
|
||||||
|
basePayload := registryCreatePayload{Name: "Test registry", URL: "http://example.com"}
|
||||||
|
t.Run("Can't create a ProGet registry if BaseURL is empty", func(t *testing.T) {
|
||||||
|
payload := basePayload
|
||||||
|
payload.Type = portainer.ProGetRegistry
|
||||||
|
err := payload.Validate(nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Can create a GitLab registry if BaseURL is empty", func(t *testing.T) {
|
||||||
|
payload := basePayload
|
||||||
|
payload.Type = portainer.GitlabRegistry
|
||||||
|
err := payload.Validate(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Can create a ProGet registry if BaseURL is not empty", func(t *testing.T) {
|
||||||
|
payload := basePayload
|
||||||
|
payload.Type = portainer.ProGetRegistry
|
||||||
|
payload.BaseURL = "http://example.com"
|
||||||
|
err := payload.Validate(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testRegistryService struct {
|
||||||
|
portainer.RegistryService
|
||||||
|
createRegistry func(r *portainer.Registry) error
|
||||||
|
updateRegistry func(ID portainer.RegistryID, r *portainer.Registry) error
|
||||||
|
getRegistry func(ID portainer.RegistryID) (*portainer.Registry, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDataStore struct {
|
||||||
|
portainer.DataStore
|
||||||
|
registry *testRegistryService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testDataStore) Registry() portainer.RegistryService {
|
||||||
|
return t.registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testRegistryService) CreateRegistry(r *portainer.Registry) error {
|
||||||
|
return t.createRegistry(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testRegistryService) UpdateRegistry(ID portainer.RegistryID, r *portainer.Registry) error {
|
||||||
|
return t.updateRegistry(ID, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testRegistryService) Registry(ID portainer.RegistryID) (*portainer.Registry, error) {
|
||||||
|
return t.getRegistry(ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testRegistryService) Registries() ([]portainer.Registry, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler_registryCreate(t *testing.T) {
|
||||||
|
payload := registryCreatePayload{
|
||||||
|
Name: "Test registry",
|
||||||
|
Type: portainer.ProGetRegistry,
|
||||||
|
URL: "http://example.com",
|
||||||
|
BaseURL: "http://example.com",
|
||||||
|
Authentication: false,
|
||||||
|
Username: "username",
|
||||||
|
Password: "password",
|
||||||
|
Gitlab: portainer.GitlabRegistryData{},
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payloadBytes))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
registry := portainer.Registry{}
|
||||||
|
handler := Handler{}
|
||||||
|
handler.DataStore = testDataStore{
|
||||||
|
registry: &testRegistryService{
|
||||||
|
createRegistry: func(r *portainer.Registry) error {
|
||||||
|
registry = *r
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
handlerError := handler.registryCreate(w, r)
|
||||||
|
assert.Nil(t, handlerError)
|
||||||
|
assert.Equal(t, payload.Name, registry.Name)
|
||||||
|
assert.Equal(t, payload.Type, registry.Type)
|
||||||
|
assert.Equal(t, payload.URL, registry.URL)
|
||||||
|
assert.Equal(t, payload.BaseURL, registry.BaseURL)
|
||||||
|
assert.Equal(t, payload.Authentication, registry.Authentication)
|
||||||
|
assert.Equal(t, payload.Username, registry.Username)
|
||||||
|
assert.Equal(t, payload.Password, registry.Password)
|
||||||
|
}
|
|
@ -12,18 +12,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type registryUpdatePayload struct {
|
type registryUpdatePayload struct {
|
||||||
// Name that will be used to identify this registry
|
Name *string `json:",omitempty" example:"my-registry" validate:"required"`
|
||||||
Name *string `validate:"required" example:"my-registry"`
|
URL *string `json:",omitempty" example:"registry.mydomain.tld:2375/feed" validate:"required"`
|
||||||
// URL or IP address of the Docker registry
|
BaseURL *string `json:",omitempty" example:"registry.mydomain.tld:2375"`
|
||||||
URL *string `validate:"required" example:"registry.mydomain.tld:2375"`
|
Authentication *bool `json:",omitempty" example:"false" validate:"required"`
|
||||||
// Is authentication against this registry enabled
|
Username *string `json:",omitempty" example:"registry_user"`
|
||||||
Authentication *bool `example:"false" validate:"required"`
|
Password *string `json:",omitempty" example:"registry_password"`
|
||||||
// Username used to authenticate against this registry. Required when Authentication is true
|
UserAccessPolicies portainer.UserAccessPolicies `json:",omitempty"`
|
||||||
Username *string `example:"registry_user"`
|
TeamAccessPolicies portainer.TeamAccessPolicies `json:",omitempty"`
|
||||||
// Password used to authenticate against this registry. required when Authentication is true
|
|
||||||
Password *string `example:"registry_password"`
|
|
||||||
UserAccessPolicies portainer.UserAccessPolicies
|
|
||||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
|
||||||
Quay *portainer.QuayRegistryData
|
Quay *portainer.QuayRegistryData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +80,10 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
registry.URL = *payload.URL
|
registry.URL = *payload.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if registry.Type == portainer.ProGetRegistry && payload.BaseURL != nil {
|
||||||
|
registry.BaseURL = *payload.BaseURL
|
||||||
|
}
|
||||||
|
|
||||||
if payload.Authentication != nil {
|
if payload.Authentication != nil {
|
||||||
if *payload.Authentication {
|
if *payload.Authentication {
|
||||||
registry.Authentication = true
|
registry.Authentication = true
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package registries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ps(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func pb(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestBouncer struct{}
|
||||||
|
|
||||||
|
func (t TestBouncer) AdminAccess(h http.Handler) http.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TestBouncer) RestrictedAccess(h http.Handler) http.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TestBouncer) AuthenticatedAccess(h http.Handler) http.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandler_registryUpdate(t *testing.T) {
|
||||||
|
payload := registryUpdatePayload{
|
||||||
|
Name: ps("Updated test registry"),
|
||||||
|
URL: ps("http://example.org/feed"),
|
||||||
|
BaseURL: ps("http://example.org"),
|
||||||
|
Authentication: pb(true),
|
||||||
|
Username: ps("username"),
|
||||||
|
Password: ps("password"),
|
||||||
|
}
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
registry := portainer.Registry{Type: portainer.ProGetRegistry, ID: 5}
|
||||||
|
r := httptest.NewRequest(http.MethodPut, "/registries/5", bytes.NewReader(payloadBytes))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
updatedRegistry := portainer.Registry{}
|
||||||
|
handler := newHandler(nil)
|
||||||
|
handler.initRouter(TestBouncer{})
|
||||||
|
handler.DataStore = testDataStore{
|
||||||
|
registry: &testRegistryService{
|
||||||
|
getRegistry: func(_ portainer.RegistryID) (*portainer.Registry, error) {
|
||||||
|
return ®istry, nil
|
||||||
|
},
|
||||||
|
updateRegistry: func(ID portainer.RegistryID, r *portainer.Registry) error {
|
||||||
|
assert.Equal(t, ID, r.ID)
|
||||||
|
updatedRegistry = *r
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Router.ServeHTTP(w, r)
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
// Registry type should remain intact
|
||||||
|
assert.Equal(t, registry.Type, updatedRegistry.Type)
|
||||||
|
|
||||||
|
assert.Equal(t, *payload.Name, updatedRegistry.Name)
|
||||||
|
assert.Equal(t, *payload.URL, updatedRegistry.URL)
|
||||||
|
assert.Equal(t, *payload.BaseURL, updatedRegistry.BaseURL)
|
||||||
|
assert.Equal(t, *payload.Authentication, updatedRegistry.Authentication)
|
||||||
|
assert.Equal(t, *payload.Username, updatedRegistry.Username)
|
||||||
|
assert.Equal(t, *payload.Password, updatedRegistry.Password)
|
||||||
|
|
||||||
|
}
|
|
@ -511,12 +511,14 @@ type (
|
||||||
Registry struct {
|
Registry struct {
|
||||||
// Registry Identifier
|
// Registry Identifier
|
||||||
ID RegistryID `json:"Id" example:"1"`
|
ID RegistryID `json:"Id" example:"1"`
|
||||||
// Registry Type (1 - Quay, 2 - Azure, 3 - Custom, 4 - Gitlab)
|
// Registry Type (1 - Quay, 2 - Azure, 3 - Custom, 4 - Gitlab, 5 - ProGet)
|
||||||
Type RegistryType `json:"Type" enums:"1,2,3,4"`
|
Type RegistryType `json:"Type" enums:"1,2,3,4,5"`
|
||||||
// Registry Name
|
// Registry Name
|
||||||
Name string `json:"Name" example:"my-registry"`
|
Name string `json:"Name" example:"my-registry"`
|
||||||
// URL or IP address of the Docker registry
|
// URL or IP address of the Docker registry
|
||||||
URL string `json:"URL" example:"registry.mydomain.tld:2375"`
|
URL string `json:"URL" example:"registry.mydomain.tld:2375"`
|
||||||
|
// Base URL, introduced for ProGet registry
|
||||||
|
BaseURL string `json:"BaseURL" example:"registry.mydomain.tld:2375"`
|
||||||
// Is authentication against this registry enabled
|
// Is authentication against this registry enabled
|
||||||
Authentication bool `json:"Authentication" example:"true"`
|
Authentication bool `json:"Authentication" example:"true"`
|
||||||
// Username used to authenticate against this registry
|
// Username used to authenticate against this registry
|
||||||
|
@ -1489,6 +1491,8 @@ const (
|
||||||
CustomRegistry
|
CustomRegistry
|
||||||
// GitlabRegistry represents a gitlab registry
|
// GitlabRegistry represents a gitlab registry
|
||||||
GitlabRegistry
|
GitlabRegistry
|
||||||
|
// ProGetRegistry represents a proget registry
|
||||||
|
ProGetRegistry
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1187,17 +1187,22 @@ definitions:
|
||||||
TeamAccessPolicies:
|
TeamAccessPolicies:
|
||||||
$ref: '#/definitions/portainer.TeamAccessPolicies'
|
$ref: '#/definitions/portainer.TeamAccessPolicies'
|
||||||
Type:
|
Type:
|
||||||
description: Registry Type (1 - Quay, 2 - Azure, 3 - Custom, 4 - Gitlab)
|
description: Registry Type (1 - Quay, 2 - Azure, 3 - Custom, 4 - Gitlab, 5 - ProGet)
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
- 4
|
- 4
|
||||||
|
- 5
|
||||||
type: integer
|
type: integer
|
||||||
URL:
|
URL:
|
||||||
description: URL or IP address of the Docker registry
|
description: URL or IP address of the Docker registry
|
||||||
example: registry.mydomain.tld:2375
|
example: registry.mydomain.tld:2375
|
||||||
type: string
|
type: string
|
||||||
|
BaseURL:
|
||||||
|
description: Base URL or IP address of the ProGet registry
|
||||||
|
example: registry.mydomain.tld:2375
|
||||||
|
type: string
|
||||||
UserAccessPolicies:
|
UserAccessPolicies:
|
||||||
$ref: '#/definitions/portainer.UserAccessPolicies'
|
$ref: '#/definitions/portainer.UserAccessPolicies'
|
||||||
Username:
|
Username:
|
||||||
|
@ -1827,18 +1832,23 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
description: 'Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container
|
description: 'Registry Type. Valid values are: 1 (Quay.io), 2 (Azure container
|
||||||
registry), 3 (custom registry) or 4 (Gitlab registry)'
|
registry), 3 (custom registry), 4 (Gitlab registry) or 5 (ProGet registry)'
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- 1
|
||||||
- 2
|
- 2
|
||||||
- 3
|
- 3
|
||||||
- 4
|
- 4
|
||||||
|
- 5
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
url:
|
url:
|
||||||
description: URL or IP address of the Docker registry
|
description: URL or IP address of the Docker registry
|
||||||
example: registry.mydomain.tld:2375
|
example: registry.mydomain.tld:2375
|
||||||
type: string
|
type: string
|
||||||
|
baseUrl:
|
||||||
|
description: Base URL or IP address of the ProGet registry
|
||||||
|
example: registry.mydomain.tld:2375
|
||||||
|
type: string
|
||||||
username:
|
username:
|
||||||
description: Username used to authenticate against this registry. Required
|
description: Username used to authenticate against this registry. Required
|
||||||
when Authentication is true
|
when Authentication is true
|
||||||
|
@ -1871,6 +1881,10 @@ definitions:
|
||||||
description: URL or IP address of the Docker registry
|
description: URL or IP address of the Docker registry
|
||||||
example: registry.mydomain.tld:2375
|
example: registry.mydomain.tld:2375
|
||||||
type: string
|
type: string
|
||||||
|
baseUrl:
|
||||||
|
description: Base URL or IP address of the ProGet registry
|
||||||
|
example: registry.mydomain.tld:2375
|
||||||
|
type: string
|
||||||
userAccessPolicies:
|
userAccessPolicies:
|
||||||
$ref: '#/definitions/portainer.UserAccessPolicies'
|
$ref: '#/definitions/portainer.UserAccessPolicies'
|
||||||
username:
|
username:
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<form class="form-horizontal" name="registryFormProGet" ng-submit="$ctrl.formAction()">
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
ProGet registry details
|
||||||
|
</div>
|
||||||
|
<!-- name-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="registry_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="proget-registry" required auto-focus />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="registryFormProGet.registry_name.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="registryFormProGet.registry_name.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i>This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !name-input -->
|
||||||
|
<!-- url-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="registry_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
|
Registry URL
|
||||||
|
<portainer-tooltip position="bottom" message="The URL of the ProGet registry including the Feed name"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="proget.example.com/example-registry" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="registryFormProGet.registry_url.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="registryFormProGet.registry_url.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- url-input -->
|
||||||
|
<!-- base-url-input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="registry_base_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||||
|
Base URL
|
||||||
|
<portainer-tooltip position="bottom" message="The base URL of the ProGet registry"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<input type="text" class="form-control" id="registry_base_url" name="registry_base_url" ng-model="$ctrl.model.BaseURL" placeholder="proget.example.com" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="registryFormProGet.registry_base_url.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="registryFormProGet.registry_base_url.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !base-url-input -->
|
||||||
|
<div>
|
||||||
|
<!-- credentials-user -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="registryFormProGet.registry_username.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="registryFormProGet.registry_username.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !credentials-user -->
|
||||||
|
<!-- credentials-password -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="registry_password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-show="registryFormProGet.registry_password.$invalid">
|
||||||
|
<div class="col-sm-12 small text-warning">
|
||||||
|
<div ng-messages="registryFormProGet.registry_password.$error">
|
||||||
|
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !credentials-password -->
|
||||||
|
</div>
|
||||||
|
<!-- actions -->
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Actions
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="$ctrl.actionInProgress || !registryFormProGet.$valid" button-spinner="$ctrl.actionInProgress">
|
||||||
|
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
|
||||||
|
<span ng-show="$ctrl.actionInProgress">In progress...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !actions -->
|
||||||
|
</form>
|
|
@ -0,0 +1,9 @@
|
||||||
|
angular.module('portainer.app').component('registryFormProget', {
|
||||||
|
templateUrl: './registry-form-proget.html',
|
||||||
|
bindings: {
|
||||||
|
model: '=',
|
||||||
|
formAction: '<',
|
||||||
|
formActionLabel: '@',
|
||||||
|
actionInProgress: '<',
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,11 +1,12 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
import { RegistryTypes } from './registryTypes';
|
||||||
|
|
||||||
export function RegistryViewModel(data) {
|
export function RegistryViewModel(data) {
|
||||||
this.Id = data.Id;
|
this.Id = data.Id;
|
||||||
this.Type = data.Type;
|
this.Type = data.Type;
|
||||||
this.Name = data.Name;
|
this.Name = data.Name;
|
||||||
this.URL = data.URL;
|
this.URL = data.URL;
|
||||||
|
this.BaseURL = data.BaseURL;
|
||||||
this.Authentication = data.Authentication;
|
this.Authentication = data.Authentication;
|
||||||
this.Username = data.Username;
|
this.Username = data.Username;
|
||||||
this.Password = data.Password;
|
this.Password = data.Password;
|
||||||
|
@ -33,7 +34,7 @@ export function RegistryManagementConfigurationDefaultModel(registry) {
|
||||||
this.TLS = true;
|
this.TLS = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registry.Type === RegistryTypes.CUSTOM && registry.Authentication) {
|
if ((registry.Type === RegistryTypes.CUSTOM || registry.Type === RegistryTypes.PROGET) && registry.Authentication) {
|
||||||
this.Authentication = true;
|
this.Authentication = true;
|
||||||
this.Username = registry.Username;
|
this.Username = registry.Username;
|
||||||
}
|
}
|
||||||
|
@ -71,4 +72,8 @@ export function RegistryCreateRequest(model) {
|
||||||
organisationName: model.Quay.organisationName,
|
organisationName: model.Quay.organisationName,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (model.Type === RegistryTypes.PROGET) {
|
||||||
|
this.BaseURL = _.replace(model.BaseURL, /^https?\:\/\//i, '');
|
||||||
|
this.BaseURL = _.replace(this.BaseURL, /\/$/, '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,5 @@ export const RegistryTypes = Object.freeze({
|
||||||
AZURE: 2,
|
AZURE: 2,
|
||||||
CUSTOM: 3,
|
CUSTOM: 3,
|
||||||
GITLAB: 4,
|
GITLAB: 4,
|
||||||
|
PROGET: 5,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
import { RegistryTypes } from '@/portainer/models/registryTypes';
|
||||||
import { RegistryDefaultModel } from '../../../models/registry';
|
import { RegistryDefaultModel } from '@/portainer/models/registry';
|
||||||
|
|
||||||
angular.module('portainer.app').controller('CreateRegistryController', [
|
angular.module('portainer.app').controller('CreateRegistryController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
@ -11,6 +11,7 @@ angular.module('portainer.app').controller('CreateRegistryController', [
|
||||||
$scope.selectQuayRegistry = selectQuayRegistry;
|
$scope.selectQuayRegistry = selectQuayRegistry;
|
||||||
$scope.selectAzureRegistry = selectAzureRegistry;
|
$scope.selectAzureRegistry = selectAzureRegistry;
|
||||||
$scope.selectCustomRegistry = selectCustomRegistry;
|
$scope.selectCustomRegistry = selectCustomRegistry;
|
||||||
|
$scope.selectProGetRegistry = selectProGetRegistry;
|
||||||
$scope.selectGitlabRegistry = selectGitlabRegistry;
|
$scope.selectGitlabRegistry = selectGitlabRegistry;
|
||||||
$scope.create = createRegistry;
|
$scope.create = createRegistry;
|
||||||
$scope.useDefaultGitlabConfiguration = useDefaultGitlabConfiguration;
|
$scope.useDefaultGitlabConfiguration = useDefaultGitlabConfiguration;
|
||||||
|
@ -65,6 +66,13 @@ angular.module('portainer.app').controller('CreateRegistryController', [
|
||||||
$scope.model.Authentication = false;
|
$scope.model.Authentication = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectProGetRegistry() {
|
||||||
|
$scope.model.Name = '';
|
||||||
|
$scope.model.URL = '';
|
||||||
|
$scope.model.BaseURL = '';
|
||||||
|
$scope.model.Authentication = true;
|
||||||
|
}
|
||||||
|
|
||||||
function retrieveGitlabRegistries() {
|
function retrieveGitlabRegistries() {
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
RegistryGitlabService.projects($scope.model.Gitlab.InstanceURL, $scope.model.Token)
|
RegistryGitlabService.projects($scope.model.Gitlab.InstanceURL, $scope.model.Token)
|
||||||
|
|
|
@ -26,6 +26,16 @@
|
||||||
<p>Quay container registry</p>
|
<p>Quay container registry</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="registry_proget" ng-model="model.Type" ng-value="RegistryTypes.PROGET" />
|
||||||
|
<label for="registry_proget" ng-click="selectProGetRegistry()">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-database" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
ProGet
|
||||||
|
</div>
|
||||||
|
<p>ProGet container registry</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="registry_azure" ng-model="model.Type" ng-value="RegistryTypes.AZURE" />
|
<input type="radio" id="registry_azure" ng-model="model.Type" ng-value="RegistryTypes.AZURE" />
|
||||||
<label for="registry_azure" ng-click="selectAzureRegistry()">
|
<label for="registry_azure" ng-click="selectAzureRegistry()">
|
||||||
|
@ -83,6 +93,14 @@
|
||||||
action-in-progress="state.actionInProgress"
|
action-in-progress="state.actionInProgress"
|
||||||
></registry-form-custom>
|
></registry-form-custom>
|
||||||
|
|
||||||
|
<registry-form-proget
|
||||||
|
ng-if="model.Type === RegistryTypes.PROGET"
|
||||||
|
model="model"
|
||||||
|
form-action="create"
|
||||||
|
form-action-label="Add registry"
|
||||||
|
action-in-progress="state.actionInProgress"
|
||||||
|
></registry-form-proget>
|
||||||
|
|
||||||
<registry-form-gitlab
|
<registry-form-gitlab
|
||||||
ng-if="model.Type === RegistryTypes.GITLAB"
|
ng-if="model.Type === RegistryTypes.GITLAB"
|
||||||
model="model"
|
model="model"
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !registry-url-input -->
|
<!-- !registry-url-input -->
|
||||||
<!-- authentication-checkbox -->
|
<!-- authentication-checkbox -->
|
||||||
<div class="form-group">
|
<div class="form-group" ng-if="registry.Type !== RegistryTypes.PROGET">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label for="registry_auth" class="control-label text-left">
|
<label for="registry_auth" class="control-label text-left">
|
||||||
Authentication
|
Authentication
|
||||||
|
|
Loading…
Reference in New Issue