Compare commits

...

15 Commits

Author SHA1 Message Date
Anthony Lapenna
ecf52616e2 feat(edge): update deployment instructions 2019-06-17 11:37:09 +12:00
Anthony Lapenna
69bc599b5b feat(edge): refactor key creation 2019-06-17 09:18:17 +12:00
Anthony Lapenna
e58b019ffa feat(edge): wip edge 2019-06-12 12:06:59 +12:00
Anthony Lapenna
1fc4e7bddb Merge branch 'develop' into edge 2019-06-05 09:24:22 +12:00
Anthony Lapenna
2cabfd574c Merge branch 'develop' into edge
# Conflicts:
#	api/portainer.go
2019-05-27 11:29:35 +12:00
Anthony Lapenna
3b946d84ac feat(endpoint-creation): update Edge agent deployment instructions 2019-05-24 18:01:50 +12:00
Anthony Lapenna
28abe55179 refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment 2019-05-24 12:02:46 +12:00
Anthony Lapenna
e31365c6a5 refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment 2019-05-24 11:46:18 +12:00
Anthony Lapenna
bedb4fc7f4 style(endpoint-creation): refactor IoT agent to Edge agent 2019-05-24 11:26:59 +12:00
Anthony Lapenna
8f05ba77b4 Merge branch 'develop' into edge 2019-05-24 11:11:57 +12:00
Anthony Lapenna
d03e22e26e feat(intel): add -v /:/host and --name portainer_agent_iot to agent command 2019-04-24 20:15:05 +12:00
Anthony Lapenna
ec667a19a0 feat(intel): add -e CAP_HOST_MANAGEMENT=1 to agent command 2019-04-24 20:05:28 +12:00
Anthony Lapenna
8afe1ac37b feat(intel): display agent features when connected to IoT endpoint 2019-04-24 19:35:26 +12:00
Anthony Lapenna
9dc3188cc0 feat(intel): fix webconsole and agent deployment command 2019-04-24 19:31:22 +12:00
Anthony Lapenna
9cf014adab feat(intel): POC Intel 2019-04-24 14:59:15 +12:00
20 changed files with 393 additions and 129 deletions

43
api/chisel/server.go Normal file
View File

@@ -0,0 +1,43 @@
package chisel
import (
chserver "github.com/jpillora/chisel/server"
)
type Server struct {
address string
port string
fingerprint string
}
func NewServer(address string, port string) *Server {
return &Server{
address: address,
port: port,
}
}
// Start starts the reverse tunnel server
func (server *Server) Start() error {
// TODO: keyseed management (persistence)
// + auth management
// Consider multiple users for auth?
config := &chserver.Config{
Reverse: true,
KeySeed: "keyseedexample",
Auth: "agent@randomstring",
}
chiselServer, err := chserver.NewServer(config)
if err != nil {
return err
}
server.fingerprint = chiselServer.GetFingerprint()
return chiselServer.Start(server.address, server.port)
}
func (server *Server) GetFingerprint() string {
return server.fingerprint
}

View File

@@ -33,6 +33,8 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
flags := &portainer.CLIFlags{
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),

View File

@@ -3,21 +3,23 @@
package cli
const (
defaultBindAddress = ":9000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultNoAuth = "false"
defaultNoAnalytics = "false"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultSSL = "false"
defaultSSLCertPath = "/certs/portainer.crt"
defaultSSLKeyPath = "/certs/portainer.key"
defaultSyncInterval = "60s"
defaultSnapshot = "true"
defaultSnapshotInterval = "5m"
defaultTemplateFile = "/templates.json"
defaultBindAddress = ":9000"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "/data"
defaultAssetsDirectory = "./"
defaultNoAuth = "false"
defaultNoAnalytics = "false"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "/certs/ca.pem"
defaultTLSCertPath = "/certs/cert.pem"
defaultTLSKeyPath = "/certs/key.pem"
defaultSSL = "false"
defaultSSLCertPath = "/certs/portainer.crt"
defaultSSLKeyPath = "/certs/portainer.key"
defaultSyncInterval = "60s"
defaultSnapshot = "true"
defaultSnapshotInterval = "5m"
defaultTemplateFile = "/templates.json"
)

View File

@@ -1,21 +1,23 @@
package cli
const (
defaultBindAddress = ":9000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultNoAuth = "false"
defaultNoAnalytics = "false"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultSSL = "false"
defaultSSLCertPath = "C:\\certs\\portainer.crt"
defaultSSLKeyPath = "C:\\certs\\portainer.key"
defaultSyncInterval = "60s"
defaultSnapshot = "true"
defaultSnapshotInterval = "5m"
defaultTemplateFile = "/templates.json"
defaultBindAddress = ":9000"
defaultTunnelServerAddress = "0.0.0.0"
defaultTunnelServerPort = "8000"
defaultDataDirectory = "C:\\data"
defaultAssetsDirectory = "./"
defaultNoAuth = "false"
defaultNoAnalytics = "false"
defaultTLS = "false"
defaultTLSSkipVerify = "false"
defaultTLSCACertPath = "C:\\certs\\ca.pem"
defaultTLSCertPath = "C:\\certs\\cert.pem"
defaultTLSKeyPath = "C:\\certs\\key.pem"
defaultSSL = "false"
defaultSSLCertPath = "C:\\certs\\portainer.crt"
defaultSSLKeyPath = "C:\\certs\\portainer.key"
defaultSyncInterval = "60s"
defaultSnapshot = "true"
defaultSnapshotInterval = "5m"
defaultTemplateFile = "/templates.json"
)

View File

@@ -2,12 +2,14 @@ package main
import (
"encoding/json"
"log"
"os"
"strings"
"time"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/cron"
"github.com/portainer/portainer/api/crypto"
@@ -20,8 +22,6 @@ import (
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/ldap"
"github.com/portainer/portainer/api/libcompose"
"log"
)
func initCLI() *portainer.CLIFlags {
@@ -658,44 +658,51 @@ func main() {
go terminateIfNoAdminCreated(store.UserService)
}
var tunnelServer portainer.TunnelServer = chisel.NewServer(*flags.TunnelAddr, *flags.TunnelPort)
err = tunnelServer.Start()
if err != nil {
log.Fatal(err)
}
var server portainer.Server = &http.Server{
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
AuthDisabled: *flags.NoAuth,
EndpointManagement: endpointManagement,
RoleService: store.RoleService,
UserService: store.UserService,
TeamService: store.TeamService,
TeamMembershipService: store.TeamMembershipService,
EndpointService: store.EndpointService,
EndpointGroupService: store.EndpointGroupService,
ExtensionService: store.ExtensionService,
ResourceControlService: store.ResourceControlService,
SettingsService: store.SettingsService,
RegistryService: store.RegistryService,
DockerHubService: store.DockerHubService,
StackService: store.StackService,
ScheduleService: store.ScheduleService,
TagService: store.TagService,
TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
ExtensionManager: extensionManager,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
GitService: gitService,
SignatureService: digitalSignatureService,
JobScheduler: jobScheduler,
Snapshotter: snapshotter,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
JobService: jobService,
TunnelServerFingerprint: tunnelServer.GetFingerprint(),
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
AuthDisabled: *flags.NoAuth,
EndpointManagement: endpointManagement,
RoleService: store.RoleService,
UserService: store.UserService,
TeamService: store.TeamService,
TeamMembershipService: store.TeamMembershipService,
EndpointService: store.EndpointService,
EndpointGroupService: store.EndpointGroupService,
ExtensionService: store.ExtensionService,
ResourceControlService: store.ResourceControlService,
SettingsService: store.SettingsService,
RegistryService: store.RegistryService,
DockerHubService: store.DockerHubService,
StackService: store.StackService,
ScheduleService: store.ScheduleService,
TagService: store.TagService,
TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
ExtensionManager: extensionManager,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
GitService: gitService,
SignatureService: digitalSignatureService,
JobScheduler: jobScheduler,
Snapshotter: snapshotter,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
JobService: jobService,
}
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)

View File

@@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
if endpoint.Status == portainer.EndpointStatusDown {
if endpoint.Type != 4 && endpoint.Status == portainer.EndpointStatusDown {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
}

View File

@@ -1,10 +1,13 @@
package endpoints
import (
"encoding/base64"
"log"
"math/rand"
"net/http"
"runtime"
"strconv"
"strings"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@@ -41,7 +44,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
if err != nil || endpointType == 0 {
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 3 (Azure environment)")
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)")
}
payload.EndpointType = endpointType
@@ -149,6 +152,8 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
return handler.createAzureEndpoint(payload)
} else if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
return handler.createEdgeAgentEndpoint(payload)
}
if payload.TLS {
@@ -195,6 +200,63 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
return endpoint, nil
}
// TODO: relocate in a service
// must be unique (e.g. not used / referenced)
func randomInt(min, max int) int {
// should be randomize at service creation time?
// if not seeded, will always get same port order
// might not be a problem and maybe not required
//rand.Seed(time.Now().UnixNano())
return min + rand.Intn(max-min)
}
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
endpointType := portainer.EdgeAgentEnvironment
endpointID := handler.EndpointService.GetNextIdentifier()
// get random port
// Dynamic ports (also called private ports) are 49152 to 65535.
// TODO: register this port somewhere
portnumber := randomInt(49152, 65535)
keyInformation := []string{
strings.TrimPrefix(payload.URL, "tcp://"),
"8000",
handler.TunnelServerFingerprint,
strconv.Itoa(portnumber),
"agent:randomstring",
}
key := strings.Join(keyInformation, "|")
encodedKey := base64.RawStdEncoding.EncodeToString([]byte(key))
endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID),
Name: payload.Name,
URL: "tcp://localhost:" + strconv.Itoa(portnumber),
Type: endpointType,
GroupID: portainer.EndpointGroupID(payload.GroupID),
TLSConfig: portainer.TLSConfiguration{
TLS: false,
},
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{},
Tags: payload.Tags,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
EdgeKey: string(encodedKey),
}
err := handler.EndpointService.CreateEndpoint(endpoint)
if err != nil {
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
}
return endpoint, nil
}
func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
endpointType := portainer.DockerEnvironment

View File

@@ -32,6 +32,8 @@ type Handler struct {
ProxyManager *proxy.Manager
Snapshotter portainer.Snapshotter
JobService portainer.JobService
// TODO: figure out a way to manage this (service?)
TunnelServerFingerprint string
}
// NewHandler creates a handler to manage endpoint operations.

View File

@@ -68,7 +68,7 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
r.Header.Del("Origin")
if params.nodeName != "" || params.endpoint.Type == portainer.AgentOnDockerEnvironment {
if params.nodeName != "" || (params.endpoint.Type == portainer.AgentOnDockerEnvironment || params.endpoint.Type == portainer.EdgeAgentEnvironment) {
return handler.proxyWebsocketRequest(w, r, params)
}

View File

@@ -3,10 +3,10 @@
package proxy
import (
"github.com/Microsoft/go-winio"
"net"
"net/http"
"github.com/Microsoft/go-winio"
portainer "github.com/portainer/portainer/api"
)

View File

@@ -39,44 +39,45 @@ import (
// Server implements the portainer.Server interface
type Server struct {
BindAddress string
AssetsPath string
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
ExtensionManager portainer.ExtensionManager
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
JobScheduler portainer.JobScheduler
Snapshotter portainer.Snapshotter
RoleService portainer.RoleService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
ExtensionService portainer.ExtensionService
RegistryService portainer.RegistryService
ResourceControlService portainer.ResourceControlService
ScheduleService portainer.ScheduleService
SettingsService portainer.SettingsService
StackService portainer.StackService
SwarmStackManager portainer.SwarmStackManager
TagService portainer.TagService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
TemplateService portainer.TemplateService
UserService portainer.UserService
WebhookService portainer.WebhookService
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
JobService portainer.JobService
BindAddress string
AssetsPath string
TunnelServerFingerprint string
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
ExtensionManager portainer.ExtensionManager
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
JobScheduler portainer.JobScheduler
Snapshotter portainer.Snapshotter
RoleService portainer.RoleService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
ExtensionService portainer.ExtensionService
RegistryService portainer.RegistryService
ResourceControlService portainer.ResourceControlService
ScheduleService portainer.ScheduleService
SettingsService portainer.SettingsService
StackService portainer.StackService
SwarmStackManager portainer.SwarmStackManager
TagService portainer.TagService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
TemplateService portainer.TemplateService
UserService portainer.UserService
WebhookService portainer.WebhookService
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
JobService portainer.JobService
}
// Start starts the HTTP server
@@ -132,6 +133,7 @@ func (server *Server) Start() error {
endpointHandler.ProxyManager = proxyManager
endpointHandler.Snapshotter = server.Snapshotter
endpointHandler.JobService = server.JobService
endpointHandler.TunnelServerFingerprint = server.TunnelServerFingerprint
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService

View File

@@ -10,6 +10,8 @@ type (
// CLIFlags represents the available flags on the CLI
CLIFlags struct {
Addr *string
TunnelAddr *string
TunnelPort *string
AdminPassword *string
AdminPasswordFile *string
Assets *string
@@ -250,7 +252,7 @@ type (
Snapshots []Snapshot `json:"Snapshots"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
EdgeKey string
// Deprecated fields
// Deprecated in DBVersion == 4
TLS bool `json:"TLS,omitempty"`
@@ -594,6 +596,13 @@ type (
Start() error
}
// Tunnel server defines the interface for the reverse tunneling server used
// with Edge agents.
TunnelServer interface {
Start() error
GetFingerprint() string
}
// UserService represents a service for managing user data
UserService interface {
User(ID UserID) (*User, error)
@@ -951,6 +960,8 @@ const (
AgentOnDockerEnvironment
// AzureEnvironment represents an endpoint connected to an Azure environment
AzureEnvironment
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
EdgeAgentEnvironment
)
const (

View File

@@ -13,7 +13,7 @@ angular.module('portainer.docker')
agentProxy: false
};
if (type === 2) {
if (type === 2 || type === 4) {
mode.agentProxy = true;
}

View File

@@ -74,7 +74,8 @@
{{ item.Type | endpointtypename }}
</span>
</td>
<td>{{ item.URL | stripprotocol }}</td>
<td ng-if="item.Type !== 4">{{ item.URL | stripprotocol }}</td>
<td ng-if="item.Type === 4">-</td>
<td>{{ item.GroupName }}</td>
<td>
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">

View File

@@ -124,6 +124,8 @@ angular.module('portainer.app')
return 'Agent';
} else if (type === 3) {
return 'Azure ACI';
} else if (type === 4) {
return 'Edge Agent';
}
return '';
};
@@ -133,6 +135,8 @@ angular.module('portainer.app')
return function (type) {
if (type === 3) {
return 'fab fa-microsoft';
} else if (type === 4) {
return 'fa fa-cloud';
}
return 'fab fa-docker';
};

View File

@@ -56,6 +56,15 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
};
$scope.addEdgeAgentEndpoint = function() {
var name = $scope.formValues.Name;
var groupId = $scope.formValues.GroupId;
var tags = $scope.formValues.Tags;
var URL = window.location.hostname;
addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null);
};
$scope.addAzureEndpoint = function() {
var name = $scope.formValues.Name;
var applicationId = $scope.formValues.AzureApplicationId;
@@ -85,9 +94,13 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
$scope.state.actionInProgress = true;
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success() {
.then(function success(data) {
Notifications.success('Endpoint created', name);
$state.go('portainer.endpoints', {}, {reload: true});
if (type === 4) {
$state.go('portainer.endpoints.endpoint', { id: data.Id });
} else {
$state.go('portainer.endpoints', {}, {reload: true});
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create endpoint');

View File

@@ -36,6 +36,16 @@
<p>Portainer agent</p>
</label>
</div>
<div>
<input type="radio" id="edge_agent_endpoint" ng-model="state.EnvironmentType" value="edge_agent">
<label for="edge_agent_endpoint">
<div class="boxselector_header">
<i class="fa fa-cloud" aria-hidden="true" style="margin-right: 2px;"></i>
Edge Agent
</div>
<p>Portainer Edge agent</p>
</label>
</div>
<div>
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
<label for="azure_endpoint">
@@ -77,6 +87,16 @@
</span>
</div>
</div>
<div ng-if="state.EnvironmentType === 'edge_agent'">
<div class="col-sm-12 form-section-title" >
Information
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
Allows you to create an endpoint that can be registered with an Edge agent. All the required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
</span>
</div>
</div>
<div ng-if="state.EnvironmentType === 'azure'">
<div class="col-sm-12 form-section-title" >
Information
@@ -238,6 +258,10 @@
<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>
<button ng-if="state.EnvironmentType === 'edge_agent'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addEdgeAgentEndpoint()" 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>
<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>

View File

@@ -5,6 +5,80 @@
</rd-header-content>
</rd-header>
<div class="row">
<information-panel ng-if="endpoint.Type === 4" title-text="Deploy an agent">
<span class="small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Deploy the Edge agent on your remote Docker environment using the following command
</p>
<div style="margin-top: 10px;">
<uib-tabset active="state.deploymentTab">
<uib-tab index="0" heading="Standalone">
<code style=display:block;white-space:pre-wrap>
docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
-v /:/host \
--restart always \
-e EDGE=1 \
-e CAP_HOST_MANAGEMENT=1 \
-p 80:80 \
--name portainer_edge_agent \
portainer/pagent:edge
</code>
</uib-tab>
<uib-tab index="1" heading="Swarm">
<code style=display:block;white-space:pre-wrap>
docker network create \
--driver overlay \
--attachable \
portainer_agent_network;
docker service create \
--name portainer_edge_agent \
--network portainer_agent_network \
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \
-e EDGE=1 \
-e CAP_HOST_MANAGEMENT=1 \
--mode global \
--publish mode=host,target=80,published=80 \
--constraint 'node.platform.os == linux' \
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \
--mount type=bind,src=//,dst=/host \
portainer/pagent:edge
</code>
</uib-tab>
</uib-tabset>
<div style="margin-top: 10px;">
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentDeploymentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
<span id="copyNotificationDeploymentCommand" style="margin-left: 7px; display: none; color: #23ae89;">
<i class="fa fa-check" aria-hidden="true" ></i> copied
</span>
</div>
</div>
<div class="col-sm-12 form-section-title" style="margin-top: 25px;">
Join token
</div>
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Use the following join token to associate the Edge agent with this endpoint
</p>
<div style="margin-top: 10px;">
<code style=display:block;white-space:pre-wrap>
{{ endpoint.EdgeKey }}
</code>
<div style="margin-top: 10px;">
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentKey()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy token</span>
<span id="copyNotificationEdgeKey" style="margin-left: 7px; display: none; color: #23ae89;">
<i class="fa fa-check" aria-hidden="true" ></i> copied
</span>
</div>
</div>
</span>
</information-panel>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
@@ -22,7 +96,7 @@
</div>
<!-- !name-input -->
<!-- endpoint-url-input -->
<div class="form-group">
<div class="form-group" ng-if="endpoint.Type !== 4">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Endpoint URL
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
@@ -70,7 +144,7 @@
</div>
<!-- !tags -->
<!-- endpoint-security -->
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3">
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3 && endpoint.Type !== 4">
<div class="col-sm-12 form-section-title">
Security
</div>

View File

@@ -1,8 +1,8 @@
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
angular.module('portainer.app')
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
if (!$scope.applicationState.application.endpointManagement) {
$state.go('portainer.endpoints');
@@ -10,13 +10,28 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
$scope.state = {
uploadInProgress: false,
actionInProgress: false
actionInProgress: false,
deploymentTab: 0
};
$scope.formValues = {
SecurityFormData: new EndpointSecurityFormData()
};
$scope.copyEdgeAgentDeploymentCommand = function() {
if ($scope.state.deploymentTab === 0) {
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --name portainer_agent_iot portainer/pagent:edge');
} else {
clipboard.copyText('docker network create --driver overlay --attachable portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --mode global --publish mode=host,target=80,published=80 --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host portainer/pagent:edge');
}
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
};
$scope.copyEdgeAgentKey = function() {
clipboard.copyText($scope.endpoint.EdgeKey);
$('#copyNotificationEdgeKey').show().fadeOut(2500);
};
$scope.updateEndpoint = function() {
var endpoint = $scope.endpoint;
var securityData = $scope.formValues.SecurityFormData;

View File

@@ -48,7 +48,7 @@ module.exports = function(grunt) {
]);
});
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
function(p, a) {
grunt.task.run([
'config:prod',
@@ -146,7 +146,7 @@ gruntfile_cfg.copy = {
files: [
{
dest: '<%= root %>/',
src: 'templates.json',
src: 'templates.json',
cwd: ''
}
]
@@ -185,7 +185,7 @@ function shell_buildBinaryOnDevOps(p, a) {
function shell_run() {
return [
'docker rm -f portainer',
'docker run -d -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
'docker run -d -p 9999:9999 -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
].join(';');
}