mirror of https://github.com/portainer/portainer
refactor(azure): remove Azure ACI endpoint support (#3803)
* feat(templates): remove template management features (#3719) * feat(api): remove template management features * feat(templates): remove template management features * refactor(azure): remove Azure ACI endpoint supportpull/3802/head
parent
665ecf7585
commit
27382c432c
|
@ -53,7 +53,7 @@ func (runner *SnapshotJobRunner) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
if endpoint.Type == portainer.AzureEnvironment || endpoint.Type == portainer.EdgeAgentEnvironment {
|
if endpoint.Type == portainer.EdgeAgentEnvironment {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,7 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers
|
||||||
// a specific endpoint configuration. The nodeName parameter can be used
|
// a specific endpoint configuration. The nodeName parameter can be used
|
||||||
// with an agent enabled endpoint to target a specific node in an agent cluster.
|
// with an agent enabled endpoint to target a specific node in an agent cluster.
|
||||||
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeName string) (*client.Client, error) {
|
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeName string) (*client.Client, error) {
|
||||||
if endpoint.Type == portainer.AzureEnvironment {
|
if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||||
return nil, unsupportedEnvironmentType
|
|
||||||
} else if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
|
||||||
return createAgentClient(endpoint, factory.signatureService, nodeName)
|
return createAgentClient(endpoint, factory.signatureService, nodeName)
|
||||||
} else if endpoint.Type == portainer.EdgeAgentEnvironment {
|
} else if endpoint.Type == portainer.EdgeAgentEnvironment {
|
||||||
return createEdgeClient(endpoint, factory.reverseTunnelService, nodeName)
|
return createEdgeClient(endpoint, factory.reverseTunnelService, nodeName)
|
||||||
|
|
|
@ -39,11 +39,6 @@ const (
|
||||||
ErrEndpointAccessDenied = Error("Access denied to endpoint")
|
ErrEndpointAccessDenied = Error("Access denied to endpoint")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Azure environment errors
|
|
||||||
const (
|
|
||||||
ErrAzureInvalidCredentials = Error("Invalid Azure credentials")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Endpoint group errors.
|
// Endpoint group errors.
|
||||||
const (
|
const (
|
||||||
ErrCannotRemoveDefaultGroup = Error("Cannot remove the default endpoint group")
|
ErrCannotRemoveDefaultGroup = Error("Cannot remove the default endpoint group")
|
||||||
|
|
|
@ -171,6 +171,7 @@ github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2 h1:0PfgGLys9yH
|
||||||
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2/go.mod h1:/wIeGwJOMYc1JplE/OvYMO5korce39HddIfI8VKGyAM=
|
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2/go.mod h1:/wIeGwJOMYc1JplE/OvYMO5korce39HddIfI8VKGyAM=
|
||||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
|
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
|
||||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
|
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
|
||||||
|
github.com/portainer/portainer v0.10.1 h1:I8K345CjGWfUGsVA8c8/gqamwLCC6CIAjxZXSklAFq0=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||||
|
|
|
@ -2,12 +2,9 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,55 +16,6 @@ const (
|
||||||
defaultHTTPTimeout = 5
|
defaultHTTPTimeout = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPClient represents a client to send HTTP requests.
|
|
||||||
type HTTPClient struct {
|
|
||||||
*http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPClient is used to build a new HTTPClient.
|
|
||||||
func NewHTTPClient() *HTTPClient {
|
|
||||||
return &HTTPClient{
|
|
||||||
&http.Client{
|
|
||||||
Timeout: time.Second * time.Duration(defaultHTTPTimeout),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AzureAuthenticationResponse represents an Azure API authentication response.
|
|
||||||
type AzureAuthenticationResponse struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
ExpiresOn string `json:"expires_on"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteAzureAuthenticationRequest is used to execute an authentication request
|
|
||||||
// against the Azure API. It re-uses the same http.Client.
|
|
||||||
func (client *HTTPClient) ExecuteAzureAuthenticationRequest(credentials *portainer.AzureCredentials) (*AzureAuthenticationResponse, error) {
|
|
||||||
loginURL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", credentials.TenantID)
|
|
||||||
params := url.Values{
|
|
||||||
"grant_type": {"client_credentials"},
|
|
||||||
"client_id": {credentials.ApplicationID},
|
|
||||||
"client_secret": {credentials.AuthenticationKey},
|
|
||||||
"resource": {"https://management.azure.com/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := client.PostForm(loginURL, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, portainer.ErrAzureInvalidCredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
var token AzureAuthenticationResponse
|
|
||||||
err = json.NewDecoder(response.Body).Decode(&token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get executes a simple HTTP GET to the specified URL and returns
|
// Get executes a simple HTTP GET to the specified URL and returns
|
||||||
// the content of the response body. Timeout can be specified via the timeout parameter,
|
// the content of the response body. Timeout can be specified via the timeout parameter,
|
||||||
// will default to defaultHTTPTimeout if set to 0.
|
// will default to defaultHTTPTimeout if set to 0.
|
||||||
|
|
|
@ -24,8 +24,6 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
requestBouncer: bouncer,
|
requestBouncer: bouncer,
|
||||||
}
|
}
|
||||||
h.PathPrefix("/{id}/azure").Handler(
|
|
||||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToAzureAPI)))
|
|
||||||
h.PathPrefix("/{id}/docker").Handler(
|
h.PathPrefix("/{id}/docker").Handler(
|
||||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
|
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
|
||||||
h.PathPrefix("/{id}/storidge").Handler(
|
h.PathPrefix("/{id}/storidge").Handler(
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
package endpointproxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
|
||||||
"github.com/portainer/libhttp/request"
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
|
||||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
|
||||||
if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
|
|
||||||
if err == portainer.ErrObjectNotFound {
|
|
||||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
|
||||||
} else if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
|
||||||
if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
var proxy http.Handler
|
|
||||||
proxy = handler.ProxyManager.GetEndpointProxy(endpoint)
|
|
||||||
if proxy == nil {
|
|
||||||
proxy, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create proxy", err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
id := strconv.Itoa(endpointID)
|
|
||||||
http.StripPrefix("/"+id+"/azure", proxy).ServeHTTP(w, r)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -18,21 +18,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type endpointCreatePayload struct {
|
type endpointCreatePayload struct {
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
EndpointType int
|
EndpointType int
|
||||||
PublicURL string
|
PublicURL string
|
||||||
GroupID int
|
GroupID int
|
||||||
TLS bool
|
TLS bool
|
||||||
TLSSkipVerify bool
|
TLSSkipVerify bool
|
||||||
TLSSkipClientVerify bool
|
TLSSkipClientVerify bool
|
||||||
TLSCACertFile []byte
|
TLSCACertFile []byte
|
||||||
TLSCertFile []byte
|
TLSCertFile []byte
|
||||||
TLSKeyFile []byte
|
TLSKeyFile []byte
|
||||||
AzureApplicationID string
|
TagIDs []portainer.TagID
|
||||||
AzureTenantID string
|
|
||||||
AzureAuthenticationKey string
|
|
||||||
TagIDs []portainer.TagID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -44,7 +41,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
|
|
||||||
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
||||||
if err != nil || endpointType == 0 {
|
if err != nil || endpointType == 0 {
|
||||||
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge Agent environment)")
|
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 4 (Edge Agent environment)")
|
||||||
}
|
}
|
||||||
payload.EndpointType = endpointType
|
payload.EndpointType = endpointType
|
||||||
|
|
||||||
|
@ -96,35 +93,14 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch portainer.EndpointType(payload.EndpointType) {
|
endpointURL, err := request.RetrieveMultiPartFormValue(r, "URL", true)
|
||||||
case portainer.AzureEnvironment:
|
if err != nil {
|
||||||
azureApplicationID, err := request.RetrieveMultiPartFormValue(r, "AzureApplicationID", false)
|
return portainer.Error("Invalid endpoint URL")
|
||||||
if err != nil {
|
|
||||||
return portainer.Error("Invalid Azure application ID")
|
|
||||||
}
|
|
||||||
payload.AzureApplicationID = azureApplicationID
|
|
||||||
|
|
||||||
azureTenantID, err := request.RetrieveMultiPartFormValue(r, "AzureTenantID", false)
|
|
||||||
if err != nil {
|
|
||||||
return portainer.Error("Invalid Azure tenant ID")
|
|
||||||
}
|
|
||||||
payload.AzureTenantID = azureTenantID
|
|
||||||
|
|
||||||
azureAuthenticationKey, err := request.RetrieveMultiPartFormValue(r, "AzureAuthenticationKey", false)
|
|
||||||
if err != nil {
|
|
||||||
return portainer.Error("Invalid Azure authentication key")
|
|
||||||
}
|
|
||||||
payload.AzureAuthenticationKey = azureAuthenticationKey
|
|
||||||
default:
|
|
||||||
url, err := request.RetrieveMultiPartFormValue(r, "URL", true)
|
|
||||||
if err != nil {
|
|
||||||
return portainer.Error("Invalid endpoint URL")
|
|
||||||
}
|
|
||||||
payload.URL = url
|
|
||||||
|
|
||||||
publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true)
|
|
||||||
payload.PublicURL = publicURL
|
|
||||||
}
|
}
|
||||||
|
payload.URL = endpointURL
|
||||||
|
|
||||||
|
publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true)
|
||||||
|
payload.PublicURL = publicURL
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -178,9 +154,7 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||||
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
|
if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
|
||||||
return handler.createAzureEndpoint(payload)
|
|
||||||
} else if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
|
|
||||||
return handler.createEdgeAgentEndpoint(payload)
|
return handler.createEdgeAgentEndpoint(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,44 +164,6 @@ func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portain
|
||||||
return handler.createUnsecuredEndpoint(payload)
|
return handler.createUnsecuredEndpoint(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
|
||||||
credentials := portainer.AzureCredentials{
|
|
||||||
ApplicationID: payload.AzureApplicationID,
|
|
||||||
TenantID: payload.AzureTenantID,
|
|
||||||
AuthenticationKey: payload.AzureAuthenticationKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := client.NewHTTPClient()
|
|
||||||
_, err := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate against Azure", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointID := handler.EndpointService.GetNextIdentifier()
|
|
||||||
endpoint := &portainer.Endpoint{
|
|
||||||
ID: portainer.EndpointID(endpointID),
|
|
||||||
Name: payload.Name,
|
|
||||||
URL: "https://management.azure.com",
|
|
||||||
Type: portainer.AzureEnvironment,
|
|
||||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
|
||||||
PublicURL: payload.PublicURL,
|
|
||||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
|
||||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
|
||||||
Extensions: []portainer.EndpointExtension{},
|
|
||||||
AzureCredentials: credentials,
|
|
||||||
TagIDs: payload.TagIDs,
|
|
||||||
Status: portainer.EndpointStatusUp,
|
|
||||||
Snapshots: []portainer.Snapshot{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpoint, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||||
endpointType := portainer.EdgeAgentEnvironment
|
endpointType := portainer.EdgeAgentEnvironment
|
||||||
endpointID := handler.EndpointService.GetNextIdentifier()
|
endpointID := handler.EndpointService.GetNextIdentifier()
|
||||||
|
|
|
@ -23,10 +23,6 @@ func (handler *Handler) endpointSnapshot(w http.ResponseWriter, r *http.Request)
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint.Type == portainer.AzureEnvironment {
|
|
||||||
return &httperror.HandlerError{http.StatusBadRequest, "Snapshots not supported for Azure endpoints", err}
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(endpoint)
|
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(endpoint)
|
||||||
|
|
||||||
latestEndpointReference, err := handler.EndpointService.Endpoint(endpoint.ID)
|
latestEndpointReference, err := handler.EndpointService.Endpoint(endpoint.ID)
|
||||||
|
|
|
@ -17,10 +17,6 @@ func (handler *Handler) endpointSnapshots(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
if endpoint.Type == portainer.AzureEnvironment {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(&endpoint)
|
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(&endpoint)
|
||||||
|
|
||||||
latestEndpointReference, err := handler.EndpointService.Endpoint(endpoint.ID)
|
latestEndpointReference, err := handler.EndpointService.Endpoint(endpoint.ID)
|
||||||
|
|
|
@ -9,24 +9,20 @@ import (
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
"github.com/portainer/libhttp/response"
|
"github.com/portainer/libhttp/response"
|
||||||
"github.com/portainer/portainer/api"
|
"github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/http/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type endpointUpdatePayload struct {
|
type endpointUpdatePayload struct {
|
||||||
Name *string
|
Name *string
|
||||||
URL *string
|
URL *string
|
||||||
PublicURL *string
|
PublicURL *string
|
||||||
GroupID *int
|
GroupID *int
|
||||||
TLS *bool
|
TLS *bool
|
||||||
TLSSkipVerify *bool
|
TLSSkipVerify *bool
|
||||||
TLSSkipClientVerify *bool
|
TLSSkipClientVerify *bool
|
||||||
Status *int
|
Status *int
|
||||||
AzureApplicationID *string
|
TagIDs []portainer.TagID
|
||||||
AzureTenantID *string
|
UserAccessPolicies portainer.UserAccessPolicies
|
||||||
AzureAuthenticationKey *string
|
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||||
TagIDs []portainer.TagID
|
|
||||||
UserAccessPolicies portainer.UserAccessPolicies
|
|
||||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
|
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
|
||||||
|
@ -137,26 +133,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint.Type == portainer.AzureEnvironment {
|
|
||||||
credentials := endpoint.AzureCredentials
|
|
||||||
if payload.AzureApplicationID != nil {
|
|
||||||
credentials.ApplicationID = *payload.AzureApplicationID
|
|
||||||
}
|
|
||||||
if payload.AzureTenantID != nil {
|
|
||||||
credentials.TenantID = *payload.AzureTenantID
|
|
||||||
}
|
|
||||||
if payload.AzureAuthenticationKey != nil {
|
|
||||||
credentials.AuthenticationKey = *payload.AzureAuthenticationKey
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := client.NewHTTPClient()
|
|
||||||
_, authErr := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
|
||||||
if authErr != nil {
|
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate against Azure", authErr}
|
|
||||||
}
|
|
||||||
endpoint.AzureCredentials = credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
if payload.TLS != nil {
|
if payload.TLS != nil {
|
||||||
folder := strconv.Itoa(endpointID)
|
folder := strconv.Itoa(endpointID)
|
||||||
|
|
||||||
|
@ -201,7 +177,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload.URL != nil || payload.TLS != nil || endpoint.Type == portainer.AzureEnvironment {
|
if payload.URL != nil || payload.TLS != nil {
|
||||||
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to register HTTP proxy for the endpoint", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to register HTTP proxy for the endpoint", err}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func hideFields(endpoint *portainer.Endpoint) {
|
func hideFields(endpoint *portainer.Endpoint) {
|
||||||
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
|
||||||
if len(endpoint.Snapshots) > 0 {
|
if len(endpoint.Snapshots) > 0 {
|
||||||
endpoint.Snapshots[0].SnapshotRaw = portainer.SnapshotRaw{}
|
endpoint.Snapshots[0].SnapshotRaw = portainer.SnapshotRaw{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,8 +90,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||||
case strings.Contains(r.URL.Path, "/storidge/"):
|
case strings.Contains(r.URL.Path, "/storidge/"):
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||||
case strings.Contains(r.URL.Path, "/azure/"):
|
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
|
||||||
case strings.Contains(r.URL.Path, "/edge/"):
|
case strings.Contains(r.URL.Path, "/edge/"):
|
||||||
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package factory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/azure"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newAzureProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
|
||||||
remoteURL, err := url.Parse(azureAPIBaseURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := newSingleHostReverseProxyWithHostHeader(remoteURL)
|
|
||||||
proxy.Transport = azure.NewTransport(&endpoint.AzureCredentials)
|
|
||||||
return proxy, nil
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package azure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/portainer/portainer/api"
|
|
||||||
"github.com/portainer/portainer/api/http/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
azureAPIToken struct {
|
|
||||||
value string
|
|
||||||
expirationTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
Transport struct {
|
|
||||||
credentials *portainer.AzureCredentials
|
|
||||||
client *client.HTTPClient
|
|
||||||
token *azureAPIToken
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewTransport returns a pointer to a new instance of Transport that implements the HTTP Transport
|
|
||||||
// interface for proxying requests to the Azure API.
|
|
||||||
func NewTransport(credentials *portainer.AzureCredentials) *Transport {
|
|
||||||
return &Transport{
|
|
||||||
credentials: credentials,
|
|
||||||
client: client.NewHTTPClient(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
|
||||||
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
||||||
err := transport.retrieveAuthenticationToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
request.Header.Set("Authorization", "Bearer "+transport.token.value)
|
|
||||||
return http.DefaultTransport.RoundTrip(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transport *Transport) authenticate() error {
|
|
||||||
token, err := transport.client.ExecuteAzureAuthenticationRequest(transport.credentials)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
expiresOn, err := strconv.ParseInt(token.ExpiresOn, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
transport.token = &azureAPIToken{
|
|
||||||
value: token.AccessToken,
|
|
||||||
expirationTime: time.Unix(expiresOn, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transport *Transport) retrieveAuthenticationToken() error {
|
|
||||||
transport.mutex.Lock()
|
|
||||||
defer transport.mutex.Unlock()
|
|
||||||
|
|
||||||
if transport.token == nil {
|
|
||||||
return transport.authenticate()
|
|
||||||
}
|
|
||||||
|
|
||||||
timeLimit := time.Now().Add(-5 * time.Minute)
|
|
||||||
if timeLimit.After(transport.token.expirationTime) {
|
|
||||||
return transport.authenticate()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"github.com/portainer/portainer/api/docker"
|
"github.com/portainer/portainer/api/docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
const azureAPIBaseURL = "https://management.azure.com"
|
|
||||||
|
|
||||||
var extensionPorts = map[portainer.ExtensionID]string{
|
var extensionPorts = map[portainer.ExtensionID]string{
|
||||||
portainer.RegistryManagementExtension: "7001",
|
portainer.RegistryManagementExtension: "7001",
|
||||||
portainer.OAuthAuthenticationExtension: "7002",
|
portainer.OAuthAuthenticationExtension: "7002",
|
||||||
|
@ -100,11 +98,6 @@ func (factory *ProxyFactory) NewLegacyExtensionProxy(extensionAPIURL string) (ht
|
||||||
|
|
||||||
// NewEndpointProxy returns a new reverse proxy (filesystem based or HTTP) to an endpoint API server
|
// NewEndpointProxy returns a new reverse proxy (filesystem based or HTTP) to an endpoint API server
|
||||||
func (factory *ProxyFactory) NewEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
func (factory *ProxyFactory) NewEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||||
switch endpoint.Type {
|
|
||||||
case portainer.AzureEnvironment:
|
|
||||||
return newAzureProxy(endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return factory.newDockerProxy(endpoint)
|
return factory.newDockerProxy(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ type (
|
||||||
Authorizations map[Authorization]bool
|
Authorizations map[Authorization]bool
|
||||||
|
|
||||||
// AzureCredentials represents the credentials used to connect to an Azure
|
// AzureCredentials represents the credentials used to connect to an Azure
|
||||||
// environment.
|
// environment (deprecated).
|
||||||
AzureCredentials struct {
|
AzureCredentials struct {
|
||||||
ApplicationID string `json:"ApplicationID"`
|
ApplicationID string `json:"ApplicationID"`
|
||||||
TenantID string `json:"TenantID"`
|
TenantID string `json:"TenantID"`
|
||||||
|
@ -140,7 +140,6 @@ type (
|
||||||
PublicURL string `json:"PublicURL"`
|
PublicURL string `json:"PublicURL"`
|
||||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||||
Extensions []EndpointExtension `json:"Extensions"`
|
Extensions []EndpointExtension `json:"Extensions"`
|
||||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
|
||||||
TagIDs []TagID `json:"TagIds"`
|
TagIDs []TagID `json:"TagIds"`
|
||||||
Status EndpointStatus `json:"Status"`
|
Status EndpointStatus `json:"Status"`
|
||||||
Snapshots []Snapshot `json:"Snapshots"`
|
Snapshots []Snapshot `json:"Snapshots"`
|
||||||
|
@ -161,6 +160,9 @@ type (
|
||||||
|
|
||||||
// Deprecated in DBVersion == 22
|
// Deprecated in DBVersion == 22
|
||||||
Tags []string `json:"Tags"`
|
Tags []string `json:"Tags"`
|
||||||
|
|
||||||
|
// Deprecated in DBVersion == 24
|
||||||
|
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndpointAuthorizations represents the authorizations associated to a set of endpoints
|
// EndpointAuthorizations represents the authorizations associated to a set of endpoints
|
||||||
|
@ -1000,7 +1002,7 @@ const (
|
||||||
// APIVersion is the version number of the Portainer API
|
// APIVersion is the version number of the Portainer API
|
||||||
APIVersion = "2.0.0-dev"
|
APIVersion = "2.0.0-dev"
|
||||||
// DBVersion is the version number of the Portainer database
|
// DBVersion is the version number of the Portainer database
|
||||||
DBVersion = 23
|
DBVersion = 24
|
||||||
// AssetsServerURL represents the URL of the Portainer asset server
|
// AssetsServerURL represents the URL of the Portainer asset server
|
||||||
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
|
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
|
||||||
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
|
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved
|
||||||
|
@ -1074,7 +1076,7 @@ const (
|
||||||
DockerEnvironment
|
DockerEnvironment
|
||||||
// AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment
|
// AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment
|
||||||
AgentOnDockerEnvironment
|
AgentOnDockerEnvironment
|
||||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
// AzureEnvironment represents an endpoint connected to an Azure environment (deprecated)
|
||||||
AzureEnvironment
|
AzureEnvironment
|
||||||
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
|
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
|
||||||
EdgeAgentEnvironment
|
EdgeAgentEnvironment
|
||||||
|
|
|
@ -254,7 +254,7 @@ paths:
|
||||||
- name: "EndpointType"
|
- name: "EndpointType"
|
||||||
in: "formData"
|
in: "formData"
|
||||||
type: "integer"
|
type: "integer"
|
||||||
description: "Environment type. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge agent environment)"
|
description: "Environment type. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 4 (Edge agent environment)"
|
||||||
required: true
|
required: true
|
||||||
- name: "URL"
|
- name: "URL"
|
||||||
in: "formData"
|
in: "formData"
|
||||||
|
@ -294,18 +294,6 @@ paths:
|
||||||
in: "formData"
|
in: "formData"
|
||||||
type: "file"
|
type: "file"
|
||||||
description: "TLS client key file"
|
description: "TLS client key file"
|
||||||
- name: "AzureApplicationID"
|
|
||||||
in: "formData"
|
|
||||||
type: "string"
|
|
||||||
description: "Azure application ID. Required if endpoint type is set to 3"
|
|
||||||
- name: "AzureTenantID"
|
|
||||||
in: "formData"
|
|
||||||
type: "string"
|
|
||||||
description: "Azure tenant ID. Required if endpoint type is set to 3"
|
|
||||||
- name: "AzureAuthenticationKey"
|
|
||||||
in: "formData"
|
|
||||||
type: "string"
|
|
||||||
description: "Azure authentication key. Required if endpoint type is set to 3"
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "Success"
|
description: "Success"
|
||||||
|
@ -3221,21 +3209,6 @@ definitions:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "/data/tls/key.pem"
|
example: "/data/tls/key.pem"
|
||||||
description: "Path to the TLS client key file"
|
description: "Path to the TLS client key file"
|
||||||
AzureCredentials:
|
|
||||||
type: "object"
|
|
||||||
properties:
|
|
||||||
ApplicationID:
|
|
||||||
type: "string"
|
|
||||||
example: "eag7cdo9-o09l-9i83-9dO9-f0b23oe78db4"
|
|
||||||
description: "Azure application ID"
|
|
||||||
TenantID:
|
|
||||||
type: "string"
|
|
||||||
example: "34ddc78d-4fel-2358-8cc1-df84c8o839f5"
|
|
||||||
description: "Azure tenant ID"
|
|
||||||
AuthenticationKey:
|
|
||||||
type: "string"
|
|
||||||
example: "cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
|
||||||
description: "Azure authentication key"
|
|
||||||
LDAPSearchSettings:
|
LDAPSearchSettings:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -3507,7 +3480,7 @@ definitions:
|
||||||
Type:
|
Type:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Endpoint environment type. 1 for a Docker environment, 2 for an agent on Docker environment or 3 for an Azure environment."
|
description: "Endpoint environment type. 1 for a Docker environment or 2 for an agent on Docker environment"
|
||||||
URL:
|
URL:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "docker.mydomain.tld:2375"
|
example: "docker.mydomain.tld:2375"
|
||||||
|
@ -3536,8 +3509,6 @@ definitions:
|
||||||
description: "Team identifier"
|
description: "Team identifier"
|
||||||
TLSConfig:
|
TLSConfig:
|
||||||
$ref: "#/definitions/TLSConfiguration"
|
$ref: "#/definitions/TLSConfiguration"
|
||||||
AzureCredentials:
|
|
||||||
$ref: "#/definitions/AzureCredentials"
|
|
||||||
EndpointSubset:
|
EndpointSubset:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -3552,7 +3523,7 @@ definitions:
|
||||||
Type:
|
Type:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Endpoint environment type. 1 for a Docker environment, 2 for an agent on Docker environment, 3 for an Azure environment."
|
description: "Endpoint environment type. 1 for a Docker environment or 2 for an agent on Docker environment"
|
||||||
URL:
|
URL:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "docker.mydomain.tld:2375"
|
example: "docker.mydomain.tld:2375"
|
||||||
|
@ -3732,18 +3703,6 @@ definitions:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: false
|
example: false
|
||||||
description: "Skip client verification when using TLS"
|
description: "Skip client verification when using TLS"
|
||||||
ApplicationID:
|
|
||||||
type: "string"
|
|
||||||
example: "eag7cdo9-o09l-9i83-9dO9-f0b23oe78db4"
|
|
||||||
description: "Azure application ID"
|
|
||||||
TenantID:
|
|
||||||
type: "string"
|
|
||||||
example: "34ddc78d-4fel-2358-8cc1-df84c8o839f5"
|
|
||||||
description: "Azure tenant ID"
|
|
||||||
AuthenticationKey:
|
|
||||||
type: "string"
|
|
||||||
example: "cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
|
||||||
description: "Azure authentication key"
|
|
||||||
UserAccessPolicies:
|
UserAccessPolicies:
|
||||||
$ref: "#/definitions/UserAccessPolicies"
|
$ref: "#/definitions/UserAccessPolicies"
|
||||||
TeamAccessPolicies:
|
TeamAccessPolicies:
|
||||||
|
|
|
@ -2,7 +2,6 @@ import '../assets/css/app.css';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import './agent/_module';
|
import './agent/_module';
|
||||||
import './azure/_module';
|
|
||||||
import './docker/__module';
|
import './docker/__module';
|
||||||
import './edge/__module';
|
import './edge/__module';
|
||||||
import './portainer/__module';
|
import './portainer/__module';
|
||||||
|
@ -28,7 +27,6 @@ angular.module('portainer', [
|
||||||
'luegg.directives',
|
'luegg.directives',
|
||||||
'portainer.app',
|
'portainer.app',
|
||||||
'portainer.agent',
|
'portainer.agent',
|
||||||
'portainer.azure',
|
|
||||||
'portainer.docker',
|
'portainer.docker',
|
||||||
'portainer.edge',
|
'portainer.edge',
|
||||||
'portainer.extensions',
|
'portainer.extensions',
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
angular.module('portainer.azure', ['portainer.app']).config([
|
|
||||||
'$stateRegistryProvider',
|
|
||||||
function ($stateRegistryProvider) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var azure = {
|
|
||||||
name: 'azure',
|
|
||||||
url: '/azure',
|
|
||||||
parent: 'root',
|
|
||||||
abstract: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var containerInstances = {
|
|
||||||
name: 'azure.containerinstances',
|
|
||||||
url: '/containerinstances',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
templateUrl: './views/containerinstances/containerinstances.html',
|
|
||||||
controller: 'AzureContainerInstancesController',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var containerInstanceCreation = {
|
|
||||||
name: 'azure.containerinstances.new',
|
|
||||||
url: '/new/',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
templateUrl: './views/containerinstances/create/createcontainerinstance.html',
|
|
||||||
controller: 'AzureCreateContainerInstanceController',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var dashboard = {
|
|
||||||
name: 'azure.dashboard',
|
|
||||||
url: '/dashboard',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
templateUrl: './views/dashboard/dashboard.html',
|
|
||||||
controller: 'AzureDashboardController',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
$stateRegistryProvider.register(azure);
|
|
||||||
$stateRegistryProvider.register(containerInstances);
|
|
||||||
$stateRegistryProvider.register(containerInstanceCreation);
|
|
||||||
$stateRegistryProvider.register(dashboard);
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,8 +0,0 @@
|
||||||
angular.module('portainer.azure').component('azureEndpointConfig', {
|
|
||||||
bindings: {
|
|
||||||
applicationId: '=',
|
|
||||||
tenantId: '=',
|
|
||||||
authenticationKey: '=',
|
|
||||||
},
|
|
||||||
templateUrl: './azureEndpointConfig.html',
|
|
||||||
});
|
|
|
@ -1,36 +0,0 @@
|
||||||
<div>
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Azure configuration
|
|
||||||
</div>
|
|
||||||
<!-- applicationId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_appid" class="col-sm-3 col-lg-2 control-label text-left">Application ID</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input type="text" class="form-control" name="azure_credential_appid" ng-model="$ctrl.applicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !applicationId-input -->
|
|
||||||
<!-- tenantId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_tenantid" class="col-sm-3 col-lg-2 control-label text-left">Tenant ID</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input type="text" class="form-control" name="azure_credential_tenantid" ng-model="$ctrl.tenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !tenantId-input -->
|
|
||||||
<!-- authenticationkey-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_authkey" class="col-sm-3 col-lg-2 control-label text-left">Authentication key</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="azure_credential_authkey"
|
|
||||||
ng-model="$ctrl.authenticationKey"
|
|
||||||
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !authenticationkey-input -->
|
|
||||||
</div>
|
|
|
@ -1,3 +0,0 @@
|
||||||
angular.module('portainer.azure').component('azureSidebarContent', {
|
|
||||||
templateUrl: './azureSidebarContent.html',
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="azure.dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer-alt fa-fw"></span></a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-list">
|
|
||||||
<a ui-sref="azure.containerinstances" ui-sref-active="active">Container instances <span class="menu-icon fa fa-server fa-fw"></span></a>
|
|
||||||
</li>
|
|
|
@ -1,105 +0,0 @@
|
||||||
<div class="datatable">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-body classes="no-padding">
|
|
||||||
<div class="toolBar">
|
|
||||||
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
|
|
||||||
</div>
|
|
||||||
<div class="actionBar">
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
|
||||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary" ui-sref="azure.containerinstances.new">
|
|
||||||
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add container
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="searchBar">
|
|
||||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="searchInput"
|
|
||||||
ng-model="$ctrl.state.textFilter"
|
|
||||||
ng-change="$ctrl.onTextFilterChange()"
|
|
||||||
placeholder="Search..."
|
|
||||||
auto-focus
|
|
||||||
ng-model-options="{ debounce: 300 }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover table-filters nowrap-cells">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<span class="md-checkbox">
|
|
||||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
|
||||||
<label for="select_all"></label>
|
|
||||||
</span>
|
|
||||||
<a ng-click="$ctrl.changeOrderBy('Name')">
|
|
||||||
Name
|
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
|
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<a ng-click="$ctrl.changeOrderBy('Location')">
|
|
||||||
Location
|
|
||||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Location' && !$ctrl.state.reverseOrder"></i>
|
|
||||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Location' && $ctrl.state.reverseOrder"></i>
|
|
||||||
</a>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Published Ports
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
|
||||||
ng-class="{ active: item.Checked }"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<span class="md-checkbox">
|
|
||||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
|
|
||||||
<label for="select_{{ $index }}"></label>
|
|
||||||
</span>
|
|
||||||
<a ui-sref="azure.containerinstances.container({ id: item.Id })">{{ item.Name | truncate: 50 }}</a>
|
|
||||||
</td>
|
|
||||||
<td>{{ item.Location }}</td>
|
|
||||||
<td>
|
|
||||||
<a ng-if="item.Ports.length > 0" ng-repeat="p in item.Ports" class="image-tag" ng-href="http://{{ item.IPAddress }}:{{ p.port }}" target="_blank">
|
|
||||||
<i class="fa fa-external-link-alt" aria-hidden="true"></i> :{{ p.port }}
|
|
||||||
</a>
|
|
||||||
<span ng-if="item.Ports.length == 0">-</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-if="!$ctrl.dataset">
|
|
||||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
|
||||||
<td colspan="3" class="text-center text-muted">No container available.</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="footer" ng-if="$ctrl.dataset">
|
|
||||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
|
|
||||||
<div class="paginationControls">
|
|
||||||
<form class="form-inline">
|
|
||||||
<span class="limitSelector">
|
|
||||||
<span style="margin-right: 5px;">
|
|
||||||
Items per page
|
|
||||||
</span>
|
|
||||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
|
||||||
<option value="0">All</option>
|
|
||||||
<option value="10">10</option>
|
|
||||||
<option value="25">25</option>
|
|
||||||
<option value="50">50</option>
|
|
||||||
<option value="100">100</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
|
@ -1,13 +0,0 @@
|
||||||
angular.module('portainer.azure').component('containergroupsDatatable', {
|
|
||||||
templateUrl: './containerGroupsDatatable.html',
|
|
||||||
controller: 'GenericDatatableController',
|
|
||||||
bindings: {
|
|
||||||
title: '@',
|
|
||||||
titleIcon: '@',
|
|
||||||
dataset: '<',
|
|
||||||
tableKey: '@',
|
|
||||||
orderBy: '@',
|
|
||||||
reverseOrder: '<',
|
|
||||||
removeAction: '<',
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,66 +0,0 @@
|
||||||
export function ContainerGroupDefaultModel() {
|
|
||||||
this.Location = '';
|
|
||||||
this.OSType = 'Linux';
|
|
||||||
this.Name = '';
|
|
||||||
this.Image = '';
|
|
||||||
this.AllocatePublicIP = true;
|
|
||||||
this.Ports = [
|
|
||||||
{
|
|
||||||
container: 80,
|
|
||||||
host: 80,
|
|
||||||
protocol: 'TCP',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
this.CPU = 1;
|
|
||||||
this.Memory = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ContainerGroupViewModel(data) {
|
|
||||||
this.Id = data.id;
|
|
||||||
this.Name = data.name;
|
|
||||||
this.Location = data.location;
|
|
||||||
this.IPAddress = data.properties.ipAddress.ip;
|
|
||||||
this.Ports = data.properties.ipAddress.ports;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CreateContainerGroupRequest(model) {
|
|
||||||
this.location = model.Location;
|
|
||||||
|
|
||||||
var containerPorts = [];
|
|
||||||
var addressPorts = [];
|
|
||||||
for (var i = 0; i < model.Ports.length; i++) {
|
|
||||||
var binding = model.Ports[i];
|
|
||||||
|
|
||||||
containerPorts.push({
|
|
||||||
port: binding.container,
|
|
||||||
});
|
|
||||||
|
|
||||||
addressPorts.push({
|
|
||||||
port: binding.host,
|
|
||||||
protocol: binding.protocol,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.properties = {
|
|
||||||
osType: model.OSType,
|
|
||||||
containers: [
|
|
||||||
{
|
|
||||||
name: model.Name,
|
|
||||||
properties: {
|
|
||||||
image: model.Image,
|
|
||||||
ports: containerPorts,
|
|
||||||
resources: {
|
|
||||||
requests: {
|
|
||||||
cpu: model.CPU,
|
|
||||||
memoryInGB: model.Memory,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
ipAddress: {
|
|
||||||
type: model.AllocatePublicIP ? 'Public' : 'Private',
|
|
||||||
ports: addressPorts,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
export function LocationViewModel(data) {
|
|
||||||
this.Id = data.id;
|
|
||||||
this.SubscriptionId = data.subscriptionId;
|
|
||||||
this.DisplayName = data.displayName;
|
|
||||||
this.Name = data.name;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import _ from 'lodash-es';
|
|
||||||
|
|
||||||
export function ContainerInstanceProviderViewModel(data) {
|
|
||||||
this.Id = data.id;
|
|
||||||
this.Namespace = data.namespace;
|
|
||||||
|
|
||||||
var containerGroupType = _.find(data.resourceTypes, { resourceType: 'containerGroups' });
|
|
||||||
this.Locations = containerGroupType.locations;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
export function ResourceGroupViewModel(data, subscriptionId) {
|
|
||||||
this.Id = data.id;
|
|
||||||
this.SubscriptionId = subscriptionId;
|
|
||||||
this.Name = data.name;
|
|
||||||
this.Location = data.location;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export function SubscriptionViewModel(data) {
|
|
||||||
this.Id = data.subscriptionId;
|
|
||||||
this.Name = data.displayName;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('Azure', [
|
|
||||||
'$http',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function AzureFactory($http, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.delete = function (id, apiVersion) {
|
|
||||||
var url = API_ENDPOINT_ENDPOINTS + '/' + EndpointProvider.endpointID() + '/azure' + id + '?api-version=' + apiVersion;
|
|
||||||
return $http({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: url,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,45 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('ContainerGroup', [
|
|
||||||
'$resource',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function ContainerGroupFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var resource = {};
|
|
||||||
|
|
||||||
var base = $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions/:subscriptionId/providers/Microsoft.ContainerInstance/containerGroups',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2018-04-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
var withResourceGroup = $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS +
|
|
||||||
'/:endpointId/azure/subscriptions/:subscriptionId/resourceGroups/:resourceGroupName/providers/Microsoft.ContainerInstance/containerGroups/:containerGroupName',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2018-04-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
create: {
|
|
||||||
method: 'PUT',
|
|
||||||
params: {
|
|
||||||
subscriptionId: '@subscriptionId',
|
|
||||||
resourceGroupName: '@resourceGroupName',
|
|
||||||
containerGroupName: '@containerGroupName',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
resource.query = base.query;
|
|
||||||
resource.create = withResourceGroup.create;
|
|
||||||
|
|
||||||
return resource;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,18 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('Location', [
|
|
||||||
'$resource',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function LocationFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions/:subscriptionId/locations',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2016-06-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,18 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('Provider', [
|
|
||||||
'$resource',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function ProviderFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions/:subscriptionId/providers/:providerNamespace',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2018-02-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
get: { method: 'GET', params: { subscriptionId: '@subscriptionId', providerNamespace: '@providerNamespace' } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,18 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('ResourceGroup', [
|
|
||||||
'$resource',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function ResourceGroupFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions/:subscriptionId/resourcegroups',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2018-02-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { method: 'GET', params: { subscriptionId: '@subscriptionId' } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,18 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('Subscription', [
|
|
||||||
'$resource',
|
|
||||||
'API_ENDPOINT_ENDPOINTS',
|
|
||||||
'EndpointProvider',
|
|
||||||
function SubscriptionFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
|
||||||
'use strict';
|
|
||||||
return $resource(
|
|
||||||
API_ENDPOINT_ENDPOINTS + '/:endpointId/azure/subscriptions',
|
|
||||||
{
|
|
||||||
endpointId: EndpointProvider.endpointID,
|
|
||||||
'api-version': '2016-06-01',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { method: 'GET' },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,72 +0,0 @@
|
||||||
angular.module('portainer.azure').factory('AzureService', [
|
|
||||||
'$q',
|
|
||||||
'Azure',
|
|
||||||
'SubscriptionService',
|
|
||||||
'ResourceGroupService',
|
|
||||||
'ContainerGroupService',
|
|
||||||
'ProviderService',
|
|
||||||
function AzureServiceFactory($q, Azure, SubscriptionService, ResourceGroupService, ContainerGroupService, ProviderService) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.deleteContainerGroup = function (id) {
|
|
||||||
return Azure.delete(id, '2018-04-01');
|
|
||||||
};
|
|
||||||
|
|
||||||
service.createContainerGroup = function (model, subscriptionId, resourceGroupName) {
|
|
||||||
return ContainerGroupService.create(model, subscriptionId, resourceGroupName);
|
|
||||||
};
|
|
||||||
|
|
||||||
service.subscriptions = function () {
|
|
||||||
return SubscriptionService.subscriptions();
|
|
||||||
};
|
|
||||||
|
|
||||||
service.containerInstanceProvider = function (subscriptions) {
|
|
||||||
return retrieveResourcesForEachSubscription(subscriptions, ProviderService.containerInstanceProvider);
|
|
||||||
};
|
|
||||||
|
|
||||||
service.resourceGroups = function (subscriptions) {
|
|
||||||
return retrieveResourcesForEachSubscription(subscriptions, ResourceGroupService.resourceGroups);
|
|
||||||
};
|
|
||||||
|
|
||||||
service.containerGroups = function (subscriptions) {
|
|
||||||
return retrieveResourcesForEachSubscription(subscriptions, ContainerGroupService.containerGroups);
|
|
||||||
};
|
|
||||||
|
|
||||||
service.aggregate = function (resourcesBySubcription) {
|
|
||||||
var aggregatedResources = [];
|
|
||||||
Object.keys(resourcesBySubcription).forEach(function (key) {
|
|
||||||
aggregatedResources = aggregatedResources.concat(resourcesBySubcription[key]);
|
|
||||||
});
|
|
||||||
return aggregatedResources;
|
|
||||||
};
|
|
||||||
|
|
||||||
function retrieveResourcesForEachSubscription(subscriptions, resourceQuery) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
var resources = {};
|
|
||||||
|
|
||||||
var resourceQueries = [];
|
|
||||||
for (var i = 0; i < subscriptions.length; i++) {
|
|
||||||
var subscription = subscriptions[i];
|
|
||||||
resourceQueries.push(resourceQuery(subscription.Id));
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.all(resourceQueries)
|
|
||||||
.then(function success(data) {
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
var result = data[i];
|
|
||||||
resources[subscriptions[i].Id] = result;
|
|
||||||
}
|
|
||||||
deferred.resolve(resources);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve resources', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { ContainerGroupViewModel, CreateContainerGroupRequest } from '../models/container_group';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').factory('ContainerGroupService', [
|
|
||||||
'$q',
|
|
||||||
'ContainerGroup',
|
|
||||||
function ContainerGroupServiceFactory($q, ContainerGroup) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.containerGroups = function (subscriptionId) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
ContainerGroup.query({ subscriptionId: subscriptionId })
|
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var containerGroups = data.value.map(function (item) {
|
|
||||||
return new ContainerGroupViewModel(item);
|
|
||||||
});
|
|
||||||
deferred.resolve(containerGroups);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve container groups', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.create = function (model, subscriptionId, resourceGroupName) {
|
|
||||||
var payload = new CreateContainerGroupRequest(model);
|
|
||||||
return ContainerGroup.create(
|
|
||||||
{
|
|
||||||
subscriptionId: subscriptionId,
|
|
||||||
resourceGroupName: resourceGroupName,
|
|
||||||
containerGroupName: model.Name,
|
|
||||||
},
|
|
||||||
payload
|
|
||||||
).$promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { LocationViewModel } from '../models/location';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').factory('LocationService', [
|
|
||||||
'$q',
|
|
||||||
'Location',
|
|
||||||
function LocationServiceFactory($q, Location) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.locations = function (subscriptionId) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
Location.query({ subscriptionId: subscriptionId })
|
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var locations = data.value.map(function (item) {
|
|
||||||
return new LocationViewModel(item);
|
|
||||||
});
|
|
||||||
deferred.resolve(locations);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve locations', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { ContainerInstanceProviderViewModel } from '../models/provider';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').factory('ProviderService', [
|
|
||||||
'$q',
|
|
||||||
'Provider',
|
|
||||||
function ProviderServiceFactory($q, Provider) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.containerInstanceProvider = function (subscriptionId) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
Provider.get({ subscriptionId: subscriptionId, providerNamespace: 'Microsoft.ContainerInstance' })
|
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var provider = new ContainerInstanceProviderViewModel(data);
|
|
||||||
deferred.resolve(provider);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve provider', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { ResourceGroupViewModel } from '../models/resource_group';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').factory('ResourceGroupService', [
|
|
||||||
'$q',
|
|
||||||
'ResourceGroup',
|
|
||||||
function ResourceGroupServiceFactory($q, ResourceGroup) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.resourceGroups = function (subscriptionId) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
ResourceGroup.query({ subscriptionId: subscriptionId })
|
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var resourceGroups = data.value.map(function (item) {
|
|
||||||
return new ResourceGroupViewModel(item, subscriptionId);
|
|
||||||
});
|
|
||||||
deferred.resolve(resourceGroups);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve resource groups', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { SubscriptionViewModel } from '../models/subscription';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').factory('SubscriptionService', [
|
|
||||||
'$q',
|
|
||||||
'Subscription',
|
|
||||||
function SubscriptionServiceFactory($q, Subscription) {
|
|
||||||
'use strict';
|
|
||||||
var service = {};
|
|
||||||
|
|
||||||
service.subscriptions = function () {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
Subscription.query({})
|
|
||||||
.$promise.then(function success(data) {
|
|
||||||
var subscriptions = data.value.map(function (item) {
|
|
||||||
return new SubscriptionViewModel(item);
|
|
||||||
});
|
|
||||||
deferred.resolve(subscriptions);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to retrieve subscriptions', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
return service;
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,44 +0,0 @@
|
||||||
angular.module('portainer.azure').controller('AzureContainerInstancesController', [
|
|
||||||
'$scope',
|
|
||||||
'$state',
|
|
||||||
'AzureService',
|
|
||||||
'Notifications',
|
|
||||||
function ($scope, $state, AzureService, Notifications) {
|
|
||||||
function initView() {
|
|
||||||
AzureService.subscriptions()
|
|
||||||
.then(function success(data) {
|
|
||||||
var subscriptions = data;
|
|
||||||
return AzureService.containerGroups(subscriptions);
|
|
||||||
})
|
|
||||||
.then(function success(data) {
|
|
||||||
$scope.containerGroups = AzureService.aggregate(data);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to load container groups');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.deleteAction = function (selectedItems) {
|
|
||||||
var actionCount = selectedItems.length;
|
|
||||||
angular.forEach(selectedItems, function (item) {
|
|
||||||
AzureService.deleteContainerGroup(item.Id)
|
|
||||||
.then(function success() {
|
|
||||||
Notifications.success('Container group successfully removed', item.Name);
|
|
||||||
var index = $scope.containerGroups.indexOf(item);
|
|
||||||
$scope.containerGroups.splice(index, 1);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to remove container group');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
--actionCount;
|
|
||||||
if (actionCount === 0) {
|
|
||||||
$state.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
initView();
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,21 +0,0 @@
|
||||||
<rd-header>
|
|
||||||
<rd-header-title title-text="Container list">
|
|
||||||
<a data-toggle="tooltip" title="Refresh" ui-sref="azure.containerinstances" ui-sref-opts="{reload: true}">
|
|
||||||
<i class="fa fa-sync" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</rd-header-title>
|
|
||||||
<rd-header-content>Container instances</rd-header-content>
|
|
||||||
</rd-header>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<containergroups-datatable
|
|
||||||
title-text="Containers"
|
|
||||||
title-icon="fa-server"
|
|
||||||
dataset="containerGroups"
|
|
||||||
table-key="containergroups"
|
|
||||||
order-by="Name"
|
|
||||||
remove-action="deleteAction"
|
|
||||||
></containergroups-datatable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,93 +0,0 @@
|
||||||
import { ContainerGroupDefaultModel } from '../../../models/container_group';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').controller('AzureCreateContainerInstanceController', [
|
|
||||||
'$q',
|
|
||||||
'$scope',
|
|
||||||
'$state',
|
|
||||||
'AzureService',
|
|
||||||
'Notifications',
|
|
||||||
function ($q, $scope, $state, AzureService, Notifications) {
|
|
||||||
var allResourceGroups = [];
|
|
||||||
var allProviders = [];
|
|
||||||
|
|
||||||
$scope.state = {
|
|
||||||
actionInProgress: false,
|
|
||||||
selectedSubscription: null,
|
|
||||||
selectedResourceGroup: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.changeSubscription = function () {
|
|
||||||
var selectedSubscription = $scope.state.selectedSubscription;
|
|
||||||
updateResourceGroupsAndLocations(selectedSubscription, allResourceGroups, allProviders);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.addPortBinding = function () {
|
|
||||||
$scope.model.Ports.push({ host: '', container: '', protocol: 'TCP' });
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.removePortBinding = function (index) {
|
|
||||||
$scope.model.Ports.splice(index, 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.create = function () {
|
|
||||||
var model = $scope.model;
|
|
||||||
var subscriptionId = $scope.state.selectedSubscription.Id;
|
|
||||||
var resourceGroupName = $scope.state.selectedResourceGroup.Name;
|
|
||||||
|
|
||||||
$scope.state.actionInProgress = true;
|
|
||||||
AzureService.createContainerGroup(model, subscriptionId, resourceGroupName)
|
|
||||||
.then(function success() {
|
|
||||||
Notifications.success('Container successfully created', model.Name);
|
|
||||||
$state.go('azure.containerinstances');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to create container');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.actionInProgress = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateResourceGroupsAndLocations(subscription, resourceGroups, providers) {
|
|
||||||
$scope.state.selectedResourceGroup = resourceGroups[subscription.Id][0];
|
|
||||||
$scope.resourceGroups = resourceGroups[subscription.Id];
|
|
||||||
|
|
||||||
var currentSubLocations = providers[subscription.Id].Locations;
|
|
||||||
$scope.model.Location = currentSubLocations[0];
|
|
||||||
$scope.locations = currentSubLocations;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initView() {
|
|
||||||
var model = new ContainerGroupDefaultModel();
|
|
||||||
|
|
||||||
AzureService.subscriptions()
|
|
||||||
.then(function success(data) {
|
|
||||||
var subscriptions = data;
|
|
||||||
$scope.state.selectedSubscription = subscriptions[0];
|
|
||||||
$scope.subscriptions = subscriptions;
|
|
||||||
|
|
||||||
return $q.all({
|
|
||||||
resourceGroups: AzureService.resourceGroups(subscriptions),
|
|
||||||
containerInstancesProviders: AzureService.containerInstanceProvider(subscriptions),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(function success(data) {
|
|
||||||
var resourceGroups = data.resourceGroups;
|
|
||||||
allResourceGroups = resourceGroups;
|
|
||||||
|
|
||||||
var containerInstancesProviders = data.containerInstancesProviders;
|
|
||||||
allProviders = containerInstancesProviders;
|
|
||||||
|
|
||||||
$scope.model = model;
|
|
||||||
|
|
||||||
var selectedSubscription = $scope.state.selectedSubscription;
|
|
||||||
updateResourceGroupsAndLocations(selectedSubscription, resourceGroups, containerInstancesProviders);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve Azure resources');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initView();
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,167 +0,0 @@
|
||||||
<rd-header>
|
|
||||||
<rd-header-title title-text="Create container instance"></rd-header-title>
|
|
||||||
<rd-header-content> <a ui-sref="azure.containerinstances">Container instances</a> > Add container </rd-header-content>
|
|
||||||
</rd-header>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-body>
|
|
||||||
<form class="form-horizontal" autocomplete="off">
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Azure settings
|
|
||||||
</div>
|
|
||||||
<!-- subscription-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_subscription" class="col-sm-1 control-label text-left">Subscription</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<select
|
|
||||||
class="form-control"
|
|
||||||
name="azure_subscription"
|
|
||||||
ng-model="state.selectedSubscription"
|
|
||||||
ng-options="subscription.Name for subscription in subscriptions"
|
|
||||||
ng-change="changeSubscription()"
|
|
||||||
></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !subscription-input -->
|
|
||||||
<!-- resourcegroup-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_resourcegroup" class="col-sm-1 control-label text-left">Resource group</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<select
|
|
||||||
class="form-control"
|
|
||||||
name="azure_resourcegroup"
|
|
||||||
ng-model="state.selectedResourceGroup"
|
|
||||||
ng-options="resourceGroup.Name for resourceGroup in resourceGroups"
|
|
||||||
></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !resourcegroup-input -->
|
|
||||||
<!-- location-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_location" class="col-sm-1 control-label text-left">Location</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<select class="form-control" name="azure_location" ng-model="model.Location" ng-options="location for location in locations"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !location-input -->
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Container configuration
|
|
||||||
</div>
|
|
||||||
<!-- name-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="container_name" class="col-sm-1 control-label text-left">Name</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="text" class="form-control" ng-model="model.Name" name="container_name" placeholder="e.g. myContainer" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !name-input -->
|
|
||||||
<!-- image-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="image_name" class="col-sm-1 control-label text-left">Image</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="text" class="form-control" ng-model="model.Image" name="image_name" placeholder="e.g. nginx:alpine" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !image-input -->
|
|
||||||
<!-- os-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="container_os" class="col-sm-1 control-label text-left">OS</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<select class="form-control" ng-model="model.OSType" name="container_os">
|
|
||||||
<option value="Linux">Linux</option>
|
|
||||||
<option value="Windows">Windows</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !os-input -->
|
|
||||||
<!-- port-mapping -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<label class="control-label text-left">Port mapping</label>
|
|
||||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
|
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- port-mapping-input-list -->
|
|
||||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
|
||||||
<div ng-repeat="binding in model.Ports" style="margin-top: 2px;">
|
|
||||||
<!-- host-port -->
|
|
||||||
<div class="input-group col-sm-4 input-group-sm">
|
|
||||||
<span class="input-group-addon">host</span>
|
|
||||||
<input type="text" class="form-control" ng-model="binding.host" placeholder="e.g. 80" />
|
|
||||||
</div>
|
|
||||||
<!-- !host-port -->
|
|
||||||
<span style="margin: 0 10px 0 10px;">
|
|
||||||
<i class="fa fa-long-arrow-alt-right" aria-hidden="true"></i>
|
|
||||||
</span>
|
|
||||||
<!-- container-port -->
|
|
||||||
<div class="input-group col-sm-4 input-group-sm">
|
|
||||||
<span class="input-group-addon">container</span>
|
|
||||||
<input type="text" class="form-control" ng-model="binding.container" placeholder="e.g. 80" />
|
|
||||||
</div>
|
|
||||||
<!-- !container-port -->
|
|
||||||
<!-- protocol-actions -->
|
|
||||||
<div class="input-group col-sm-3 input-group-sm">
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<label class="btn btn-primary" ng-model="binding.protocol" uib-btn-radio="'TCP'">TCP</label>
|
|
||||||
<label class="btn btn-primary" ng-model="binding.protocol" uib-btn-radio="'UDP'">UDP</label>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removePortBinding($index)">
|
|
||||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- !protocol-actions -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !port-mapping-input-list -->
|
|
||||||
</div>
|
|
||||||
<!-- !port-mapping -->
|
|
||||||
<!-- public-ip -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<label for="public_ip" class="control-label text-left">
|
|
||||||
Allocate public IP address
|
|
||||||
</label>
|
|
||||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="public_ip" ng-model="model.AllocatePublicIP" /><i></i> </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- public-ip -->
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Container resources
|
|
||||||
</div>
|
|
||||||
<!-- cpu-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="container_cpu" class="col-sm-1 control-label text-left">CPU</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="number" class="form-control" ng-model="model.CPU" name="container_cpu" placeholder="1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !cpu-input -->
|
|
||||||
<!-- memory-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="container_memory" class="col-sm-1 control-label text-left">Memory</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="number" class="form-control" ng-model="model.Memory" name="container_memory" placeholder="1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !memory-input -->
|
|
||||||
<!-- actions -->
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Actions
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress" ng-click="create()" button-spinner="state.actionInProgress">
|
|
||||||
<span ng-hide="state.actionInProgress">Deploy the container</span>
|
|
||||||
<span ng-show="state.actionInProgress">Deployment in progress...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !actions -->
|
|
||||||
</form>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<rd-header>
|
|
||||||
<rd-header-title title-text="Home"></rd-header-title>
|
|
||||||
<rd-header-content>Dashboard</rd-header-content>
|
|
||||||
</rd-header>
|
|
||||||
|
|
||||||
<div class="row" ng-if="subscriptions">
|
|
||||||
<div class="col-sm-12 col-md-6">
|
|
||||||
<a ui-sref="azure.subscriptions">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-body>
|
|
||||||
<div class="widget-icon blue pull-left">
|
|
||||||
<i class="fa fa-th-list"></i>
|
|
||||||
</div>
|
|
||||||
<div class="title">{{ subscriptions.length }}</div>
|
|
||||||
<div class="comment">Subscriptions</div>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-6" ng-if="resourceGroups">
|
|
||||||
<a ui-sref="azure.resourceGroups">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-body>
|
|
||||||
<div class="widget-icon blue pull-left">
|
|
||||||
<i class="fa fa-th-list"></i>
|
|
||||||
</div>
|
|
||||||
<div class="title">{{ resourceGroups.length }}</div>
|
|
||||||
<div class="comment">Resource groups</div>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,23 +0,0 @@
|
||||||
angular.module('portainer.azure').controller('AzureDashboardController', [
|
|
||||||
'$scope',
|
|
||||||
'AzureService',
|
|
||||||
'Notifications',
|
|
||||||
function ($scope, AzureService, Notifications) {
|
|
||||||
function initView() {
|
|
||||||
AzureService.subscriptions()
|
|
||||||
.then(function success(data) {
|
|
||||||
var subscriptions = data;
|
|
||||||
$scope.subscriptions = subscriptions;
|
|
||||||
return AzureService.resourceGroups(subscriptions);
|
|
||||||
})
|
|
||||||
.then(function success(data) {
|
|
||||||
$scope.resourceGroups = AzureService.aggregate(data);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to load dashboard data');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initView();
|
|
||||||
},
|
|
||||||
]);
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
|
<div class="blocklist-item" ng-click="$ctrl.onSelect($ctrl.model)">
|
||||||
<div class="blocklist-item-box">
|
<div class="blocklist-item-box">
|
||||||
<span ng-class="['blocklist-item-logo', 'endpoint-item', { azure: $ctrl.model.Type === 3 }]">
|
<span class="blocklist-item-logo endpoint-item">
|
||||||
<i ng-if="$ctrl.model.Type !== 4" ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
|
<i ng-if="$ctrl.model.Type !== 4" ng-class="$ctrl.model.Type | endpointtypeicon" class="fa-4x blue-icon" aria-hidden="true"></i>
|
||||||
<img ng-if="$ctrl.model.Type === 4" src="../../../../../assets/images/edge_endpoint.png" />
|
<img ng-if="$ctrl.model.Type === 4" src="../../../../../assets/images/edge_endpoint.png" />
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -128,8 +128,6 @@ angular
|
||||||
return 'Docker';
|
return 'Docker';
|
||||||
} else if (type === 2) {
|
} else if (type === 2) {
|
||||||
return 'Agent';
|
return 'Agent';
|
||||||
} else if (type === 3) {
|
|
||||||
return 'Azure ACI';
|
|
||||||
} else if (type === 4) {
|
} else if (type === 4) {
|
||||||
return 'Edge Agent';
|
return 'Edge Agent';
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,20 +84,6 @@ angular.module('portainer.app').factory('EndpointService', [
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createAzureEndpoint = function (name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
|
||||||
var deferred = $q.defer();
|
|
||||||
|
|
||||||
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
|
||||||
.then(function success(response) {
|
|
||||||
deferred.resolve(response.data);
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
deferred.reject({ msg: 'Unable to connect to Azure', err: err });
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
service.executeJobFromFileUpload = function (image, jobFile, endpointId, nodeName) {
|
service.executeJobFromFileUpload = function (image, jobFile, endpointId, nodeName) {
|
||||||
return FileUploadService.executeEndpointJob(image, jobFile, endpointId, nodeName);
|
return FileUploadService.executeEndpointJob(image, jobFile, endpointId, nodeName);
|
||||||
};
|
};
|
||||||
|
|
|
@ -137,22 +137,6 @@ angular.module('portainer.app').factory('FileUploadService', [
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createAzureEndpoint = function (name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
|
||||||
return Upload.upload({
|
|
||||||
url: 'api/endpoints',
|
|
||||||
data: {
|
|
||||||
Name: name,
|
|
||||||
EndpointType: 3,
|
|
||||||
GroupID: groupId,
|
|
||||||
TagIds: Upload.json(tagIds),
|
|
||||||
AzureApplicationID: applicationId,
|
|
||||||
AzureTenantID: tenantId,
|
|
||||||
AzureAuthenticationKey: authenticationKey,
|
|
||||||
},
|
|
||||||
ignoreLoadingBar: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
service.uploadLDAPTLSFiles = function (TLSCAFile, TLSCertFile, TLSKeyFile) {
|
service.uploadLDAPTLSFiles = function (TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
var queue = [];
|
var queue = [];
|
||||||
|
|
||||||
|
|
|
@ -168,14 +168,6 @@ angular.module('portainer.app').factory('StateManager', [
|
||||||
manager.updateEndpointState = function (endpoint, extensions) {
|
manager.updateEndpointState = function (endpoint, extensions) {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
if (endpoint.Type === 3) {
|
|
||||||
state.endpoint.name = endpoint.Name;
|
|
||||||
state.endpoint.mode = { provider: 'AZURE' };
|
|
||||||
LocalStorage.storeEndpointState(state.endpoint);
|
|
||||||
deferred.resolve();
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
$q.all({
|
$q.all({
|
||||||
version: endpoint.Status === 1 ? SystemService.version() : $q.when(endpoint.Snapshots[0].SnapshotRaw.Version),
|
version: endpoint.Status === 1 ? SystemService.version() : $q.when(endpoint.Snapshots[0].SnapshotRaw.Version),
|
||||||
info: endpoint.Status === 1 ? SystemService.info() : $q.when(endpoint.Snapshots[0].SnapshotRaw.Info),
|
info: endpoint.Status === 1 ? SystemService.info() : $q.when(endpoint.Snapshots[0].SnapshotRaw.Info),
|
||||||
|
|
|
@ -27,9 +27,6 @@ angular
|
||||||
PublicURL: '',
|
PublicURL: '',
|
||||||
GroupId: 1,
|
GroupId: 1,
|
||||||
SecurityFormData: new EndpointSecurityFormData(),
|
SecurityFormData: new EndpointSecurityFormData(),
|
||||||
AzureApplicationId: '',
|
|
||||||
AzureTenantId: '',
|
|
||||||
AzureAuthenticationKey: '',
|
|
||||||
TagIds: [],
|
TagIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,17 +82,6 @@ angular
|
||||||
addEndpoint(name, 4, URL, '', groupId, tagIds, false, false, false, null, null, null);
|
addEndpoint(name, 4, URL, '', groupId, tagIds, false, false, false, null, null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addAzureEndpoint = function () {
|
|
||||||
var name = $scope.formValues.Name;
|
|
||||||
var applicationId = $scope.formValues.AzureApplicationId;
|
|
||||||
var tenantId = $scope.formValues.AzureTenantId;
|
|
||||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
|
||||||
var groupId = $scope.formValues.GroupId;
|
|
||||||
var tagIds = $scope.formValues.TagIds;
|
|
||||||
|
|
||||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.onCreateTag = function onCreateTag(tagName) {
|
$scope.onCreateTag = function onCreateTag(tagName) {
|
||||||
return $async(onCreateTagAsync, tagName);
|
return $async(onCreateTagAsync, tagName);
|
||||||
};
|
};
|
||||||
|
@ -110,21 +96,6 @@ angular
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) {
|
|
||||||
$scope.state.actionInProgress = true;
|
|
||||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds)
|
|
||||||
.then(function success() {
|
|
||||||
Notifications.success('Endpoint created', name);
|
|
||||||
$state.go('portainer.endpoints', {}, { reload: true });
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.actionInProgress = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||||
|
|
|
@ -44,16 +44,6 @@
|
||||||
<p>Directly connect to the Docker API</p>
|
<p>Directly connect to the Docker API</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure" />
|
|
||||||
<label for="azure_endpoint">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Azure
|
|
||||||
</div>
|
|
||||||
<p>Connect to Microsoft Azure ACI</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="state.EnvironmentType === 'docker'">
|
<div ng-if="state.EnvironmentType === 'docker'">
|
||||||
|
@ -97,29 +87,6 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="state.EnvironmentType === 'azure'">
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Information
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<span class="small">
|
|
||||||
<p class="text-muted"> <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental. </p>
|
|
||||||
<p class="text-primary">
|
|
||||||
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
|
|
||||||
</p>
|
|
||||||
<p class="text-muted">
|
|
||||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Have a look at
|
|
||||||
<a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank"
|
|
||||||
>the Azure documentation</a
|
|
||||||
>
|
|
||||||
to retrieve the credentials required below.
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Environment details
|
Environment details
|
||||||
</div>
|
</div>
|
||||||
|
@ -214,76 +181,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !endpoint-public-url-input -->
|
<!-- !endpoint-public-url-input -->
|
||||||
<!-- azure-details -->
|
|
||||||
<div ng-if="state.EnvironmentType === 'azure'">
|
|
||||||
<!-- applicationId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_appid" class="col-sm-3 col-lg-2 control-label text-left">Application ID</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="azure_credential_appid"
|
|
||||||
ng-model="formValues.AzureApplicationId"
|
|
||||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_appid.$invalid">
|
|
||||||
<div class="col-sm-12 small text-warning">
|
|
||||||
<div ng-messages="endpointCreationForm.azure_credential_appid.$error">
|
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !applicationId-input -->
|
|
||||||
<!-- tenantId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_tenantid" class="col-sm-3 col-lg-2 control-label text-left">Tenant ID</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="azure_credential_tenantid"
|
|
||||||
ng-model="formValues.AzureTenantId"
|
|
||||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_tenantid.$invalid">
|
|
||||||
<div class="col-sm-12 small text-warning">
|
|
||||||
<div ng-messages="endpointCreationForm.azure_credential_tenantid.$error">
|
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !tenantId-input -->
|
|
||||||
<!-- authenticationkey-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_authkey" class="col-sm-3 col-lg-2 control-label text-left">Authentication key</label>
|
|
||||||
<div class="col-sm-9 col-lg-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="azure_credential_authkey"
|
|
||||||
ng-model="formValues.AzureAuthenticationKey"
|
|
||||||
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" ng-show="endpointCreationForm.azure_credential_authkey.$invalid">
|
|
||||||
<div class="col-sm-12 small text-warning">
|
|
||||||
<div ng-messages="endpointCreationForm.azure_credential_authkey.$error">
|
|
||||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !authenticationkey-input -->
|
|
||||||
</div>
|
|
||||||
<!-- !azure-details -->
|
|
||||||
<!-- endpoint-security -->
|
<!-- endpoint-security -->
|
||||||
<por-endpoint-security ng-if="state.EnvironmentType === 'docker'" form-data="formValues.SecurityFormData"></por-endpoint-security>
|
<por-endpoint-security ng-if="state.EnvironmentType === 'docker'" form-data="formValues.SecurityFormData"></por-endpoint-security>
|
||||||
<!-- !endpoint-security -->
|
<!-- !endpoint-security -->
|
||||||
|
@ -350,17 +247,6 @@
|
||||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
ng-if="state.EnvironmentType === 'azure'"
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-disabled="state.actionInProgress || !endpointCreationForm.$valid"
|
|
||||||
ng-click="addAzureEndpoint()"
|
|
||||||
button-spinner="state.actionInProgress"
|
|
||||||
>
|
|
||||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
|
||||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !actions -->
|
<!-- !actions -->
|
||||||
|
|
|
@ -118,12 +118,6 @@
|
||||||
<input type="text" class="form-control" id="endpoint_public_url" ng-model="endpoint.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" />
|
<input type="text" class="form-control" id="endpoint_public_url" ng-model="endpoint.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<azure-endpoint-config
|
|
||||||
ng-if="endpoint.Type === 3"
|
|
||||||
application-id="endpoint.AzureCredentials.ApplicationID"
|
|
||||||
tenant-id="endpoint.AzureCredentials.TenantID"
|
|
||||||
authentication-key="endpoint.AzureCredentials.AuthenticationKey"
|
|
||||||
></azure-endpoint-config>
|
|
||||||
<!-- !endpoint-public-url-input -->
|
<!-- !endpoint-public-url-input -->
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Metadata
|
Metadata
|
||||||
|
|
|
@ -89,9 +89,6 @@ angular
|
||||||
TLSCACert: TLSSkipVerify || securityData.TLSCACert === endpoint.TLSConfig.TLSCACert ? null : securityData.TLSCACert,
|
TLSCACert: TLSSkipVerify || securityData.TLSCACert === endpoint.TLSConfig.TLSCACert ? null : securityData.TLSCACert,
|
||||||
TLSCert: TLSSkipClientVerify || securityData.TLSCert === endpoint.TLSConfig.TLSCert ? null : securityData.TLSCert,
|
TLSCert: TLSSkipClientVerify || securityData.TLSCert === endpoint.TLSConfig.TLSCert ? null : securityData.TLSCert,
|
||||||
TLSKey: TLSSkipClientVerify || securityData.TLSKey === endpoint.TLSConfig.TLSKey ? null : securityData.TLSKey,
|
TLSKey: TLSSkipClientVerify || securityData.TLSKey === endpoint.TLSConfig.TLSKey ? null : securityData.TLSKey,
|
||||||
AzureApplicationID: endpoint.AzureCredentials.ApplicationID,
|
|
||||||
AzureTenantID: endpoint.AzureCredentials.TenantID,
|
|
||||||
AzureAuthenticationKey: endpoint.AzureCredentials.AuthenticationKey,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($scope.endpointType !== 'local' && endpoint.Type !== 3) {
|
if ($scope.endpointType !== 'local' && endpoint.Type !== 3) {
|
||||||
|
|
|
@ -26,9 +26,7 @@ angular
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.goToDashboard = function (endpoint) {
|
$scope.goToDashboard = function (endpoint) {
|
||||||
if (endpoint.Type === 3) {
|
if (endpoint.Type === 4) {
|
||||||
return switchToAzureEndpoint(endpoint);
|
|
||||||
} else if (endpoint.Type === 4) {
|
|
||||||
return switchToEdgeEndpoint(endpoint);
|
return switchToEdgeEndpoint(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,19 +87,6 @@ angular
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchToAzureEndpoint(endpoint) {
|
|
||||||
EndpointProvider.setEndpointID(endpoint.Id);
|
|
||||||
EndpointProvider.setEndpointPublicURL(endpoint.PublicURL);
|
|
||||||
EndpointProvider.setOfflineModeFromStatus(endpoint.Status);
|
|
||||||
StateManager.updateEndpointState(endpoint, [])
|
|
||||||
.then(function success() {
|
|
||||||
$state.go('azure.dashboard');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to connect to the Azure endpoint');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchToEdgeEndpoint(endpoint) {
|
function switchToEdgeEndpoint(endpoint) {
|
||||||
if (!endpoint.EdgeID) {
|
if (!endpoint.EdgeID) {
|
||||||
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
|
$state.go('portainer.endpoints.endpoint', { id: endpoint.Id });
|
||||||
|
|
|
@ -55,16 +55,6 @@
|
||||||
<p>Connect to a Portainer agent</p>
|
<p>Connect to a Portainer agent</p>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<input type="radio" id="azure_endpoint" ng-model="formValues.EndpointType" value="azure" />
|
|
||||||
<label for="azure_endpoint">
|
|
||||||
<div class="boxselector_header">
|
|
||||||
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Azure
|
|
||||||
</div>
|
|
||||||
<p>Connect to Microsoft Azure ACI</p>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !endpoint-type -->
|
<!-- !endpoint-type -->
|
||||||
|
@ -168,91 +158,6 @@
|
||||||
<!-- !actions -->
|
<!-- !actions -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !agent-endpoint -->
|
<!-- !agent-endpoint -->
|
||||||
<!-- azure-endpoint -->
|
|
||||||
<div ng-if="formValues.EndpointType === 'azure'">
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Information
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<span class="small">
|
|
||||||
<p class="text-muted"> <i class="fa fa-flask orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This feature is experimental. </p>
|
|
||||||
<p class="text-primary">
|
|
||||||
Connect to Microsoft Azure to manage Azure Container Instances (ACI).
|
|
||||||
</p>
|
|
||||||
<p class="text-muted">
|
|
||||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
|
||||||
Have a look at
|
|
||||||
<a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal" target="_blank"
|
|
||||||
>the Azure documentation</a
|
|
||||||
>
|
|
||||||
to retrieve the credentials required below.
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Environment
|
|
||||||
</div>
|
|
||||||
<!-- name-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="endpoint_name" class="col-sm-4 col-lg-3 control-label text-left">Name</label>
|
|
||||||
<div class="col-sm-8 col-lg-9">
|
|
||||||
<input type="text" class="form-control" id="endpoint_name" ng-model="formValues.Name" placeholder="e.g. azure-01" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !name-input -->
|
|
||||||
<div class="col-sm-12 form-section-title">
|
|
||||||
Azure credentials
|
|
||||||
</div>
|
|
||||||
<!-- applicationId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_appid" class="col-sm-4 col-lg-3 control-label text-left">Application ID</label>
|
|
||||||
<div class="col-sm-8 col-lg-9">
|
|
||||||
<input type="text" class="form-control" id="azure_credential_appid" ng-model="formValues.AzureApplicationId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !applicationId-input -->
|
|
||||||
<!-- tenantId-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_tenantid" class="col-sm-4 col-lg-3 control-label text-left">Tenant ID</label>
|
|
||||||
<div class="col-sm-8 col-lg-9">
|
|
||||||
<input type="text" class="form-control" id="azure_credential_tenantid" ng-model="formValues.AzureTenantId" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !tenantId-input -->
|
|
||||||
<!-- authenticationkey-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="azure_credential_authkey" class="col-sm-4 col-lg-3 control-label text-left">Authentication key</label>
|
|
||||||
<div class="col-sm-8 col-lg-9">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="azure_credential_authkey"
|
|
||||||
ng-model="formValues.AzureAuthenticationKey"
|
|
||||||
placeholder="cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !authenticationkey-input -->
|
|
||||||
<!-- actions -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-disabled="state.actionInProgress || !formValues.Name || !formValues.AzureApplicationId || !formValues.AzureTenantId || !formValues.AzureAuthenticationKey"
|
|
||||||
ng-click="createAzureEndpoint()"
|
|
||||||
button-spinner="state.actionInProgress"
|
|
||||||
>
|
|
||||||
<span ng-hide="state.actionInProgress"><i class="fa fa-bolt" aria-hidden="true"></i> Connect</span>
|
|
||||||
<span ng-show="state.actionInProgress">Connecting...</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !actions -->
|
|
||||||
</div>
|
|
||||||
<!-- !azure-endpoint -->
|
|
||||||
<!-- remote-endpoint -->
|
<!-- remote-endpoint -->
|
||||||
<div ng-if="formValues.EndpointType === 'remote'">
|
<div ng-if="formValues.EndpointType === 'remote'">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
|
|
|
@ -28,9 +28,6 @@ angular.module('portainer.app').controller('InitEndpointController', [
|
||||||
TLSCACert: null,
|
TLSCACert: null,
|
||||||
TLSCert: null,
|
TLSCert: null,
|
||||||
TLSKey: null,
|
TLSKey: null,
|
||||||
AzureApplicationId: '',
|
|
||||||
AzureTenantId: '',
|
|
||||||
AzureAuthenticationKey: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createLocalEndpoint = function () {
|
$scope.createLocalEndpoint = function () {
|
||||||
|
@ -47,15 +44,6 @@ angular.module('portainer.app').controller('InitEndpointController', [
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.createAzureEndpoint = function () {
|
|
||||||
var name = $scope.formValues.Name;
|
|
||||||
var applicationId = $scope.formValues.AzureApplicationId;
|
|
||||||
var tenantId = $scope.formValues.AzureTenantId;
|
|
||||||
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
|
|
||||||
|
|
||||||
createAzureEndpoint(name, applicationId, tenantId, authenticationKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.createAgentEndpoint = function () {
|
$scope.createAgentEndpoint = function () {
|
||||||
var name = $scope.formValues.Name;
|
var name = $scope.formValues.Name;
|
||||||
var URL = $scope.formValues.URL;
|
var URL = $scope.formValues.URL;
|
||||||
|
@ -78,20 +66,6 @@ angular.module('portainer.app').controller('InitEndpointController', [
|
||||||
createRemoteEndpoint(name, 1, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
createRemoteEndpoint(name, 1, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) {
|
|
||||||
$scope.state.actionInProgress = true;
|
|
||||||
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, 1, [])
|
|
||||||
.then(function success() {
|
|
||||||
$state.go('portainer.home');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to connect to the Azure environment');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.actionInProgress = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRemoteEndpoint(name, type, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
function createRemoteEndpoint(name, type, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
$scope.state.actionInProgress = true;
|
$scope.state.actionInProgress = true;
|
||||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
<a ui-sref="portainer.home" ui-sref-active="active">Home <span class="menu-icon fa fa-home fa-fw"></span></a>
|
<a ui-sref="portainer.home" ui-sref-active="active">Home <span class="menu-icon fa fa-home fa-fw"></span></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-title endpoint-name" ng-if="applicationState.endpoint.name"> <span class="fa fa-plug space-right"></span>{{ applicationState.endpoint.name }} </li>
|
<li class="sidebar-title endpoint-name" ng-if="applicationState.endpoint.name"> <span class="fa fa-plug space-right"></span>{{ applicationState.endpoint.name }} </li>
|
||||||
<azure-sidebar-content ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider === 'AZURE'"> </azure-sidebar-content>
|
|
||||||
<docker-sidebar-content
|
<docker-sidebar-content
|
||||||
ng-if="applicationState.endpoint.mode && applicationState.endpoint.mode.provider !== 'AZURE'"
|
|
||||||
endpoint-api-version="applicationState.endpoint.apiVersion"
|
endpoint-api-version="applicationState.endpoint.apiVersion"
|
||||||
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
swarm-management="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'"
|
||||||
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'"
|
standalone-management="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'"
|
||||||
|
|
|
@ -236,10 +236,6 @@ a[ng-click] {
|
||||||
margin: 10px 4px 0 6px;
|
margin: 10px 4px 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blocklist-item-logo.endpoint-item.azure {
|
|
||||||
margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blocklist-item-title {
|
.blocklist-item-title {
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
"baseUrl": "app",
|
"baseUrl": "app",
|
||||||
"paths": {
|
"paths": {
|
||||||
"Agent/*": ["agent/*"],
|
"Agent/*": ["agent/*"],
|
||||||
"Azure/*": ["azure/*"],
|
|
||||||
"Docker/*": ["docker/*"],
|
"Docker/*": ["docker/*"],
|
||||||
"Extensions/*": ["extensions/*"],
|
"Extensions/*": ["extensions/*"],
|
||||||
"Portainer/*": ["portainer/*"]
|
"Portainer/*": ["portainer/*"]
|
||||||
|
|
|
@ -112,7 +112,6 @@ module.exports = {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
Agent: path.resolve(projectRoot, 'app/agent'),
|
Agent: path.resolve(projectRoot, 'app/agent'),
|
||||||
Azure: path.resolve(projectRoot, 'app/azure'),
|
|
||||||
Docker: path.resolve(projectRoot, 'app/docker'),
|
Docker: path.resolve(projectRoot, 'app/docker'),
|
||||||
Extensions: path.resolve(projectRoot, 'app/extensions'),
|
Extensions: path.resolve(projectRoot, 'app/extensions'),
|
||||||
Portainer: path.resolve(projectRoot, 'app/portainer'),
|
Portainer: path.resolve(projectRoot, 'app/portainer'),
|
||||||
|
|
Loading…
Reference in New Issue