Compare commits

..

2 Commits

Author SHA1 Message Date
Stéphane Busso
40092be5eb Fix wget 2020-12-23 11:12:42 +13:00
Stéphane Busso
d87ccd75b2 Add static compose build for linux 2020-12-21 20:39:09 +13:00
100 changed files with 490 additions and 1455 deletions

View File

@@ -1,10 +1,6 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug/need-confirmation, kind/bug
assignees: ''
---
<!--
@@ -13,7 +9,7 @@ Thanks for reporting a bug for Portainer !
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/.
Before opening a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
@@ -44,7 +40,6 @@ You can see how [here](https://documentation.portainer.io/archive/1.23.2/faq/#ho
- Portainer version:
- Docker version (managed by Portainer):
- Kubernetes version (managed by Portainer):
- Platform (windows/linux):
- Command used to start Portainer (`docker run -p 9000:9000 portainer/portainer`):
- Browser:

View File

@@ -1,20 +1,17 @@
---
name: Question
about: Ask us a question about Portainer usage or deployment
title: ''
labels: ''
assignees: ''
---
<!--
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
-->
**Question**:
How can I deploy Portainer on... ?
---
name: Question
about: Ask us a question about Portainer usage or deployment
---
<!--
You can find more information about Portainer support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/
Also, be sure to check our FAQ and documentation first: https://portainer.readthedocs.io
-->
**Question**:
How can I deploy Portainer on... ?

View File

@@ -1,34 +1,31 @@
---
name: Feature request
about: Suggest a feature/enhancement that should be added in Portainer
title: ''
labels: ''
assignees: ''
---
<!--
Thanks for opening a feature request for Portainer !
Do you need help or have a question? Come chat with us on Slack http://portainer.slack.com/
Before opening a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.
Also, be sure to check our FAQ and documentation first: https://documentation.portainer.io/
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
---
name: Feature request
about: Suggest a feature/enhancement that should be added in Portainer
---
<!--
Thanks for opening a feature request for Portainer !
Do you need help or have a question? Come chat with us on Slack http://portainer.io/slack/
Before opening a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.
Also, be sure to check our FAQ and documentation first: https://portainer.readthedocs.io
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.github/stale.yml vendored
View File

@@ -15,7 +15,6 @@ issues:
- kind/question
- kind/style
- kind/workaround
- kind/refactor
- bug/need-confirmation
- bug/confirmed
- status/discuss

View File

@@ -74,23 +74,3 @@ Our contribution process is described below. Some of the steps can be visualized
The feature request process is similar to the bug report process but has an extra functional validation before the technical validation as well as a documentation validation before the testing phase.
![portainer_featurerequest_workflow](https://user-images.githubusercontent.com/5485061/45727229-5ad39f00-bbf5-11e8-9550-16ba66c50615.png)
## Build Portainer locally
Ensure you have Docker, Node.js, yarn, and Golang installed in the correct versions.
Install dependencies with yarn:
```sh
$ yarn
```
Then build and run the project:
```sh
$ yarn start
```
Portainer can now be accessed at <http://localhost:9000>.
Find more detailed steps at <https://documentation.portainer.io/contributing/instructions/>.

View File

@@ -30,13 +30,12 @@ Unlike the public demo, the playground sessions are deleted after 4 hours. Apart
- [Deploy Portainer](https://www.portainer.io/installation/)
- [Documentation](https://documentation.portainer.io)
- [Building Portainer](https://documentation.portainer.io/contributing/instructions/)
## Getting help
For FORMAL Support, please purchase a support subscription from here: https://www.portainer.io/products/portainer-business
For FORMAL Support, please purchase a support subscription from here: https://www.portainer.io/products-services/portainer-business-support/
For community support: You can find more information about Portainer's community support framework policy here: https://www.portainer.io/products/community-edition/customer-success
For community support: You can find more information about Portainer's community support framework policy here: https://www.portainer.io/2019/04/portainer-support-policy/
- Issues: https://github.com/portainer/portainer/issues
- FAQ: https://documentation.portainer.io

View File

@@ -6,7 +6,7 @@ import (
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
@@ -17,8 +17,6 @@ import (
"github.com/portainer/portainer/api/git"
"github.com/portainer/portainer/api/http"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/api/http/proxy"
kubeproxy "github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/internal/snapshot"
"github.com/portainer/portainer/api/jwt"
"github.com/portainer/portainer/api/kubernetes"
@@ -73,12 +71,7 @@ func initDataStore(dataStorePath string, fileService portainer.FileService) port
return store
}
func initComposeStackManager(assetsPath string, dataStorePath string, reverseTunnelService portainer.ReverseTunnelService, proxyManager *proxy.Manager) portainer.ComposeStackManager {
composeWrapper := exec.NewComposeWrapper(assetsPath, proxyManager)
if composeWrapper != nil {
return composeWrapper
}
func initComposeStackManager(dataStorePath string, reverseTunnelService portainer.ReverseTunnelService) portainer.ComposeStackManager {
return libcompose.NewComposeStackManager(dataStorePath, reverseTunnelService)
}
@@ -96,10 +89,6 @@ func initJWTService(dataStore portainer.DataStore) (portainer.JWTService, error)
return nil, err
}
if settings.UserSessionTimeout == "" {
settings.UserSessionTimeout = portainer.DefaultUserSessionTimeout
dataStore.Settings().UpdateSettings(settings)
}
jwtService, err := jwt.NewService(settings.UserSessionTimeout)
if err != nil {
return nil, err
@@ -391,10 +380,8 @@ func main() {
if err != nil {
log.Fatal(err)
}
kubernetesTokenCacheManager := kubeproxy.NewTokenCacheManager()
proxyManager := proxy.NewManager(dataStore, digitalSignatureService, reverseTunnelService, dockerClientFactory, kubernetesClientFactory, kubernetesTokenCacheManager)
composeStackManager := initComposeStackManager(*flags.Assets, *flags.Data, reverseTunnelService, proxyManager)
composeStackManager := initComposeStackManager(*flags.Data, reverseTunnelService)
kubernetesDeployer := initKubernetesDeployer(*flags.Assets)
@@ -461,29 +448,27 @@ func main() {
}
var server portainer.Server = &http.Server{
ReverseTunnelService: reverseTunnelService,
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
DataStore: dataStore,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
KubernetesDeployer: kubernetesDeployer,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
OAuthService: oauthService,
GitService: gitService,
ProxyManager: proxyManager,
KubernetesTokenCacheManager: kubernetesTokenCacheManager,
SignatureService: digitalSignatureService,
SnapshotService: snapshotService,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: dockerClientFactory,
KubernetesClientFactory: kubernetesClientFactory,
ReverseTunnelService: reverseTunnelService,
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
DataStore: dataStore,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
KubernetesDeployer: kubernetesDeployer,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
OAuthService: oauthService,
GitService: gitService,
SignatureService: digitalSignatureService,
SnapshotService: snapshotService,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: dockerClientFactory,
KubernetesClientFactory: kubernetesClientFactory,
}
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)

View File

@@ -1,132 +0,0 @@
package exec
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy"
)
// ComposeWrapper is a wrapper for docker-compose binary
type ComposeWrapper struct {
binaryPath string
proxyManager *proxy.Manager
}
// NewComposeWrapper returns a docker-compose wrapper if corresponding binary present, otherwise nil
func NewComposeWrapper(binaryPath string, proxyManager *proxy.Manager) *ComposeWrapper {
if !IsBinaryPresent(programPath(binaryPath, "docker-compose")) {
return nil
}
return &ComposeWrapper{
binaryPath: binaryPath,
proxyManager: proxyManager,
}
}
// ComposeSyntaxMaxVersion returns the maximum supported version of the docker compose syntax
func (w *ComposeWrapper) ComposeSyntaxMaxVersion() string {
return portainer.ComposeSyntaxMaxVersion
}
// Up builds, (re)creates and starts containers in the background. Wraps `docker-compose up -d` command
func (w *ComposeWrapper) Up(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
_, err := w.command([]string{"up", "-d"}, stack, endpoint)
return err
}
// Down stops and removes containers, networks, images, and volumes. Wraps `docker-compose down --remove-orphans` command
func (w *ComposeWrapper) Down(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
_, err := w.command([]string{"down", "--remove-orphans"}, stack, endpoint)
return err
}
func (w *ComposeWrapper) command(command []string, stack *portainer.Stack, endpoint *portainer.Endpoint) ([]byte, error) {
if endpoint == nil {
return nil, errors.New("cannot call a compose command on an empty endpoint")
}
program := programPath(w.binaryPath, "docker-compose")
options := setComposeFile(stack)
options = addProjectNameOption(options, stack)
options, err := addEnvFileOption(options, stack)
if err != nil {
return nil, err
}
if !(endpoint.URL == "" || strings.HasPrefix(endpoint.URL, "unix://") || strings.HasPrefix(endpoint.URL, "npipe://")) {
proxy, err := w.proxyManager.CreateComposeProxyServer(endpoint)
if err != nil {
return nil, err
}
defer proxy.Close()
options = append(options, "-H", fmt.Sprintf("http://127.0.0.1:%d", proxy.Port))
}
args := append(options, command...)
var stderr bytes.Buffer
cmd := exec.Command(program, args...)
cmd.Stderr = &stderr
out, err := cmd.Output()
if err != nil {
return out, errors.New(stderr.String())
}
return out, nil
}
func setComposeFile(stack *portainer.Stack) []string {
options := make([]string, 0)
if stack == nil || stack.EntryPoint == "" {
return options
}
composeFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
options = append(options, "-f", composeFilePath)
return options
}
func addProjectNameOption(options []string, stack *portainer.Stack) []string {
if stack == nil || stack.Name == "" {
return options
}
options = append(options, "-p", stack.Name)
return options
}
func addEnvFileOption(options []string, stack *portainer.Stack) ([]string, error) {
if stack == nil || stack.Env == nil || len(stack.Env) == 0 {
return options, nil
}
envFilePath := path.Join(stack.ProjectPath, "stack.env")
envfile, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return options, err
}
for _, v := range stack.Env {
envfile.WriteString(fmt.Sprintf("%s=%s\n", v.Name, v.Value))
}
envfile.Close()
options = append(options, "--env-file", envFilePath)
return options, nil
}

View File

@@ -1,75 +0,0 @@
// +build integration
package exec
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
portainer "github.com/portainer/portainer/api"
)
const composeFile = `version: "3.9"
services:
busybox:
image: "alpine:latest"
container_name: "compose_wrapper_test"`
const composedContainerName = "compose_wrapper_test"
func setup(t *testing.T) (*portainer.Stack, *portainer.Endpoint) {
dir := t.TempDir()
composeFileName := "compose_wrapper_test.yml"
f, _ := os.Create(filepath.Join(dir, composeFileName))
f.WriteString(composeFile)
stack := &portainer.Stack{
ProjectPath: dir,
EntryPoint: composeFileName,
Name: "project-name",
}
endpoint := &portainer.Endpoint{}
return stack, endpoint
}
func Test_UpAndDown(t *testing.T) {
stack, endpoint := setup(t)
w := NewComposeWrapper("", nil)
err := w.Up(stack, endpoint)
if err != nil {
t.Fatalf("Error calling docker-compose up: %s", err)
}
if containerExists(composedContainerName) == false {
t.Fatal("container should exist")
}
err = w.Down(stack, endpoint)
if err != nil {
t.Fatalf("Error calling docker-compose down: %s", err)
}
if containerExists(composedContainerName) {
t.Fatal("container should be removed")
}
}
func containerExists(contaierName string) bool {
cmd := exec.Command(osProgram("docker"), "ps", "-a", "-f", fmt.Sprintf("name=%s", contaierName))
out, err := cmd.Output()
if err != nil {
log.Fatalf("failed to list containers: %s", err)
}
return strings.Contains(string(out), contaierName)
}

View File

@@ -1,143 +0,0 @@
package exec
import (
"io/ioutil"
"os"
"path"
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/assert"
)
func Test_setComposeFile(t *testing.T) {
tests := []struct {
name string
stack *portainer.Stack
expected []string
}{
{
name: "should return empty result if stack is missing",
stack: nil,
expected: []string{},
},
{
name: "should return empty result if stack don't have entrypoint",
stack: &portainer.Stack{},
expected: []string{},
},
{
name: "should allow file name and dir",
stack: &portainer.Stack{
ProjectPath: "dir",
EntryPoint: "file",
},
expected: []string{"-f", path.Join("dir", "file")},
},
{
name: "should allow file name only",
stack: &portainer.Stack{
EntryPoint: "file",
},
expected: []string{"-f", "file"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := setComposeFile(tt.stack)
assert.ElementsMatch(t, tt.expected, result)
})
}
}
func Test_addProjectNameOption(t *testing.T) {
tests := []struct {
name string
stack *portainer.Stack
expected []string
}{
{
name: "should not add project option if stack is missing",
stack: nil,
expected: []string{},
},
{
name: "should not add project option if stack doesn't have name",
stack: &portainer.Stack{},
expected: []string{},
},
{
name: "should add project name option if stack has a name",
stack: &portainer.Stack{
Name: "project-name",
},
expected: []string{"-p", "project-name"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options := []string{"-a", "b"}
result := addProjectNameOption(options, tt.stack)
assert.ElementsMatch(t, append(options, tt.expected...), result)
})
}
}
func Test_addEnvFileOption(t *testing.T) {
dir := t.TempDir()
tests := []struct {
name string
stack *portainer.Stack
expected []string
expectedContent string
}{
{
name: "should not add env file option if stack is missing",
stack: nil,
expected: []string{},
},
{
name: "should not add env file option if stack doesn't have env variables",
stack: &portainer.Stack{},
expected: []string{},
},
{
name: "should not add env file option if stack's env variables are empty",
stack: &portainer.Stack{
ProjectPath: dir,
Env: []portainer.Pair{},
},
expected: []string{},
},
{
name: "should add env file option if stack has env variables",
stack: &portainer.Stack{
ProjectPath: dir,
Env: []portainer.Pair{
{Name: "var1", Value: "value1"},
{Name: "var2", Value: "value2"},
},
},
expected: []string{"--env-file", path.Join(dir, "stack.env")},
expectedContent: "var1=value1\nvar2=value2\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options := []string{"-a", "b"}
result, _ := addEnvFileOption(options, tt.stack)
assert.ElementsMatch(t, append(options, tt.expected...), result)
if tt.expectedContent != "" {
f, _ := os.Open(path.Join(dir, "stack.env"))
content, _ := ioutil.ReadAll(f)
assert.Equal(t, tt.expectedContent, string(content))
}
})
}
}

View File

@@ -134,8 +134,6 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, dataPa
if !endpoint.TLSConfig.TLSSkipVerify {
args = append(args, "--tlsverify", "--tlscacert", endpoint.TLSConfig.TLSCACertPath)
} else {
args = append(args, "--tlscacert", "''")
}
if endpoint.TLSConfig.TLSCertPath != "" && endpoint.TLSConfig.TLSKeyPath != "" {

View File

@@ -1,24 +0,0 @@
package exec
import (
"os/exec"
"path/filepath"
"runtime"
)
func osProgram(program string) string {
if runtime.GOOS == "windows" {
program += ".exe"
}
return program
}
func programPath(rootPath, program string) string {
return filepath.Join(rootPath, osProgram(program))
}
// IsBinaryPresent returns true if corresponding program exists on PATH
func IsBinaryPresent(program string) bool {
_, err := exec.LookPath(program)
return err == nil
}

View File

@@ -1,16 +0,0 @@
package exec
import (
"testing"
)
func Test_isBinaryPresent(t *testing.T) {
if !IsBinaryPresent("docker") {
t.Error("expect docker binary to exist on the path")
}
if IsBinaryPresent("executable-with-this-name-should-not-exist") {
t.Error("expect binary with a random name to be missing on the path")
}
}

View File

@@ -28,7 +28,6 @@ require (
github.com/portainer/libcompose v0.5.3
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45

View File

@@ -262,15 +262,12 @@ github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc=
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
@@ -395,8 +392,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -6,7 +6,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
@@ -30,7 +30,6 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
}
hideFields(endpoint)
endpoint.ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
return response.JSON(w, endpoint)
}

View File

@@ -5,11 +5,12 @@ import (
"strconv"
"strings"
"github.com/portainer/portainer/api"
"github.com/portainer/libhttp/request"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
)
@@ -88,7 +89,6 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
for idx := range paginatedEndpoints {
hideFields(&paginatedEndpoints[idx])
paginatedEndpoints[idx].ComposeSyntaxMaxVersion = handler.ComposeStackManager.ComposeSyntaxMaxVersion()
}
w.Header().Set("X-Total-Count", strconv.Itoa(filteredEndpointCount))

View File

@@ -27,7 +27,6 @@ type Handler struct {
ProxyManager *proxy.Manager
ReverseTunnelService portainer.ReverseTunnelService
SnapshotService portainer.SnapshotService
ComposeStackManager portainer.ComposeStackManager
}
// NewHandler creates a handler to manage endpoint operations.

View File

@@ -7,7 +7,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
@@ -71,7 +71,7 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
registry.Username = *payload.Username
}
if payload.Password != nil && *payload.Password != "" {
if payload.Password != nil {
registry.Password = *payload.Password
}

View File

@@ -7,12 +7,11 @@ import (
"regexp"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
)
@@ -61,14 +60,13 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@@ -91,8 +89,6 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@@ -150,14 +146,13 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@@ -190,8 +185,6 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@@ -249,14 +242,13 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerComposeStack,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@@ -279,8 +271,6 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@@ -357,6 +347,7 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
!isAdminOrEndpointAdmin {
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
if err != nil {
return err

View File

@@ -6,12 +6,11 @@ import (
"path"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/filesystem"
"github.com/portainer/portainer/api/http/security"
)
@@ -56,15 +55,14 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@@ -87,8 +85,6 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@@ -149,15 +145,14 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@@ -190,8 +185,6 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}
@@ -256,15 +249,14 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
stackID := handler.DataStore.Stack().GetNextIdentifier()
stack := &portainer.Stack{
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
CreationDate: time.Now().Unix(),
ID: portainer.StackID(stackID),
Name: payload.Name,
Type: portainer.DockerSwarmStack,
SwarmID: payload.SwarmID,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@@ -287,8 +279,6 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
}
stack.CreatedBy = config.user.Username
err = handler.DataStore.Stack().CreateStack(stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the stack inside the database", err}

View File

@@ -7,7 +7,7 @@ import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
@@ -78,17 +78,6 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
}
func (handler *Handler) userIsAdmin(userID portainer.UserID) (bool, error) {
user, err := handler.DataStore.User().User(userID)
if err != nil {
return false, err
}
isAdmin := user.Role == portainer.AdministratorRole
return isAdmin, nil
}
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
isAdmin := user.Role == portainer.AdministratorRole

View File

@@ -183,20 +183,9 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
}
func (handler *Handler) decorateStackResponse(w http.ResponseWriter, stack *portainer.Stack, userID portainer.UserID) *httperror.HandlerError {
var resourceControl *portainer.ResourceControl
resourceControl := authorization.NewPrivateResourceControl(stack.Name, portainer.StackResourceControl, userID)
isAdmin, err := handler.userIsAdmin(userID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
}
if isAdmin {
resourceControl = authorization.NewAdministratorsOnlyResourceControl(stack.Name, portainer.StackResourceControl)
} else {
resourceControl = authorization.NewPrivateResourceControl(stack.Name, portainer.StackResourceControl, userID)
}
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
err := handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist resource control inside the database", err}
}

View File

@@ -155,6 +155,5 @@ func (handler *Handler) deleteStack(stack *portainer.Stack, endpoint *portainer.
if stack.Type == portainer.DockerSwarmStack {
return handler.SwarmStackManager.Remove(stack, endpoint)
}
return handler.ComposeStackManager.Down(stack, endpoint)
}

View File

@@ -4,13 +4,13 @@ import (
"errors"
"net/http"
portainer "github.com/portainer/portainer/api"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)

View File

@@ -4,14 +4,15 @@ import (
"errors"
"net/http"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
// POST request on /api/stacks/:id/stop

View File

@@ -4,13 +4,12 @@ import (
"errors"
"net/http"
"strconv"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
@@ -136,9 +135,6 @@ func (handler *Handler) updateComposeStack(r *http.Request, stack *portainer.Sta
return configErr
}
stack.UpdateDate = time.Now().Unix()
stack.UpdatedBy = config.user.Username
err = handler.deployComposeStack(config)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
@@ -167,9 +163,6 @@ func (handler *Handler) updateSwarmStack(r *http.Request, stack *portainer.Stack
return configErr
}
stack.UpdateDate = time.Now().Unix()
stack.UpdatedBy = config.user.Username
err = handler.deploySwarmStack(config)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}

View File

@@ -1,88 +0,0 @@
package factory
import (
"fmt"
"log"
"net"
"net/http"
"net/url"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/http/proxy/factory/dockercompose"
)
// ProxyServer provide an extedned proxy with a local server to forward requests
type ProxyServer struct {
server *http.Server
Port int
}
func (factory *ProxyFactory) NewDockerComposeAgentProxy(endpoint *portainer.Endpoint) (*ProxyServer, error) {
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
return &ProxyServer{
Port: factory.reverseTunnelService.GetTunnelDetails(endpoint.ID).Port,
}, nil
}
endpointURL, err := url.Parse(endpoint.URL)
if err != nil {
return nil, err
}
endpointURL.Scheme = "http"
httpTransport := &http.Transport{}
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
if err != nil {
return nil, err
}
httpTransport.TLSClientConfig = config
endpointURL.Scheme = "https"
}
proxy := newSingleHostReverseProxyWithHostHeader(endpointURL)
proxy.Transport = dockercompose.NewAgentTransport(factory.signatureService, httpTransport)
proxyServer := &ProxyServer{
&http.Server{
Handler: proxy,
},
0,
}
return proxyServer, proxyServer.start()
}
func (proxy *ProxyServer) start() error {
listener, err := net.Listen("tcp", ":0")
if err != nil {
return err
}
proxy.Port = listener.Addr().(*net.TCPAddr).Port
go func() {
proxyHost := fmt.Sprintf("127.0.0.1:%d", proxy.Port)
log.Printf("Starting Proxy server on %s...\n", proxyHost)
err := proxy.server.Serve(listener)
log.Printf("Exiting Proxy server %s\n", proxyHost)
if err != http.ErrServerClosed {
log.Printf("Proxy server %s exited with an error: %s\n", proxyHost, err)
}
}()
return nil
}
// Close shuts down the server
func (proxy *ProxyServer) Close() {
if proxy.server != nil {
proxy.server.Close()
}
}

View File

@@ -1,40 +0,0 @@
package dockercompose
import (
"net/http"
portainer "github.com/portainer/portainer/api"
)
type (
// AgentTransport is an http.Transport wrapper that adds custom http headers to communicate to an Agent
AgentTransport struct {
httpTransport *http.Transport
signatureService portainer.DigitalSignatureService
endpointIdentifier portainer.EndpointID
}
)
// NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer agent
func NewAgentTransport(signatureService portainer.DigitalSignatureService, httpTransport *http.Transport) *AgentTransport {
transport := &AgentTransport{
httpTransport: httpTransport,
signatureService: signatureService,
}
return transport
}
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *AgentTransport) RoundTrip(request *http.Request) (*http.Response, error) {
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
if err != nil {
return nil, err
}
request.Header.Set(portainer.PortainerAgentPublicKeyHeader, transport.signatureService.EncodedPublicKey())
request.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
return transport.httpTransport.RoundTrip(request)
}

View File

@@ -3,7 +3,6 @@ package kubernetes
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"github.com/portainer/portainer/api/http/security"
@@ -14,16 +13,14 @@ import (
type (
localTransport struct {
httpTransport *http.Transport
tokenManager *tokenManager
endpointIdentifier portainer.EndpointID
httpTransport *http.Transport
tokenManager *tokenManager
}
agentTransport struct {
httpTransport *http.Transport
tokenManager *tokenManager
signatureService portainer.DigitalSignatureService
endpointIdentifier portainer.EndpointID
httpTransport *http.Transport
tokenManager *tokenManager
signatureService portainer.DigitalSignatureService
}
edgeTransport struct {
@@ -53,11 +50,21 @@ func NewLocalTransport(tokenManager *tokenManager) (*localTransport, error) {
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *localTransport) RoundTrip(request *http.Request) (*http.Response, error) {
token, err := getRoundTripToken(request, transport.tokenManager, transport.endpointIdentifier)
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
}
var token string
if tokenData.Role == portainer.AdministratorRole {
token = transport.tokenManager.getAdminServiceAccountToken()
} else {
token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID))
if err != nil {
return nil, err
}
}
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return transport.httpTransport.RoundTrip(request)
@@ -78,11 +85,21 @@ func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsCo
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *agentTransport) RoundTrip(request *http.Request) (*http.Response, error) {
token, err := getRoundTripToken(request, transport.tokenManager, transport.endpointIdentifier)
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
}
var token string
if tokenData.Role == portainer.AdministratorRole {
token = transport.tokenManager.getAdminServiceAccountToken()
} else {
token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID))
if err != nil {
return nil, err
}
}
request.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token)
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
@@ -110,11 +127,21 @@ func NewEdgeTransport(reverseTunnelService portainer.ReverseTunnelService, endpo
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *edgeTransport) RoundTrip(request *http.Request) (*http.Response, error) {
token, err := getRoundTripToken(request, transport.tokenManager, transport.endpointIdentifier)
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
}
var token string
if tokenData.Role == portainer.AdministratorRole {
token = transport.tokenManager.getAdminServiceAccountToken()
} else {
token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID))
if err != nil {
return nil, err
}
}
request.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token)
response, err := transport.httpTransport.RoundTrip(request)
@@ -127,27 +154,3 @@ func (transport *edgeTransport) RoundTrip(request *http.Request) (*http.Response
return response, err
}
func getRoundTripToken(
request *http.Request,
tokenManager *tokenManager,
endpointIdentifier portainer.EndpointID,
) (string, error) {
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return "", err
}
var token string
if tokenData.Role == portainer.AdministratorRole {
token = tokenManager.getAdminServiceAccountToken()
} else {
token, err = tokenManager.getUserServiceAccountToken(int(tokenData.ID))
if err != nil {
log.Printf("Failed retrieving service account token: %v", err)
return "", err
}
}
return token, nil
}

View File

@@ -1,7 +1,6 @@
package proxy
import (
"fmt"
"net/http"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
@@ -22,7 +21,6 @@ type (
proxyFactory *factory.ProxyFactory
endpointProxies cmap.ConcurrentMap
legacyExtensionProxies cmap.ConcurrentMap
k8sClientFactory *cli.ClientFactory
}
)
@@ -31,7 +29,6 @@ func NewManager(dataStore portainer.DataStore, signatureService portainer.Digita
return &Manager{
endpointProxies: cmap.New(),
legacyExtensionProxies: cmap.New(),
k8sClientFactory: kubernetesClientFactory,
proxyFactory: factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager),
}
}
@@ -44,19 +41,13 @@ func (manager *Manager) CreateAndRegisterEndpointProxy(endpoint *portainer.Endpo
return nil, err
}
manager.endpointProxies.Set(fmt.Sprint(endpoint.ID), proxy)
manager.endpointProxies.Set(string(endpoint.ID), proxy)
return proxy, nil
}
// CreateComposeProxyServer creates a new HTTP reverse proxy based on endpoint properties and and adds it to the registered proxies.
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
func (manager *Manager) CreateComposeProxyServer(endpoint *portainer.Endpoint) (*factory.ProxyServer, error) {
return manager.proxyFactory.NewDockerComposeAgentProxy(endpoint)
}
// GetEndpointProxy returns the proxy associated to a key
func (manager *Manager) GetEndpointProxy(endpoint *portainer.Endpoint) http.Handler {
proxy, ok := manager.endpointProxies.Get(fmt.Sprint(endpoint.ID))
proxy, ok := manager.endpointProxies.Get(string(endpoint.ID))
if !ok {
return nil
}
@@ -65,11 +56,8 @@ func (manager *Manager) GetEndpointProxy(endpoint *portainer.Endpoint) http.Hand
}
// DeleteEndpointProxy deletes the proxy associated to a key
// and cleans the k8s endpoint client cache. DeleteEndpointProxy
// is currently only called for edge connection clean up.
func (manager *Manager) DeleteEndpointProxy(endpoint *portainer.Endpoint) {
manager.endpointProxies.Remove(fmt.Sprint(endpoint.ID))
manager.k8sClientFactory.RemoveKubeClient(endpoint)
manager.endpointProxies.Remove(string(endpoint.ID))
}
// CreateLegacyExtensionProxy creates a new HTTP reverse proxy for a legacy extension and adds it to the registered proxies

View File

@@ -39,41 +39,39 @@ import (
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/kubernetes/cli"
)
// Server implements the portainer.Server interface
type Server struct {
BindAddress string
AssetsPath string
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
SnapshotService portainer.SnapshotService
FileService portainer.FileService
DataStore portainer.DataStore
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
OAuthService portainer.OAuthService
SwarmStackManager portainer.SwarmStackManager
ProxyManager *proxy.Manager
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
KubernetesClientFactory *cli.ClientFactory
KubernetesDeployer portainer.KubernetesDeployer
BindAddress string
AssetsPath string
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
SnapshotService portainer.SnapshotService
FileService portainer.FileService
DataStore portainer.DataStore
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
OAuthService portainer.OAuthService
SwarmStackManager portainer.SwarmStackManager
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
KubernetesClientFactory *cli.ClientFactory
KubernetesDeployer portainer.KubernetesDeployer
}
// Start starts the HTTP server
func (server *Server) Start() error {
kubernetesTokenCacheManager := server.KubernetesTokenCacheManager
kubernetesTokenCacheManager := kubernetes.NewTokenCacheManager()
proxyManager := proxy.NewManager(server.DataStore, server.SignatureService, server.ReverseTunnelService, server.DockerClientFactory, server.KubernetesClientFactory, kubernetesTokenCacheManager)
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService)
@@ -84,7 +82,7 @@ func (server *Server) Start() error {
authHandler.CryptoService = server.CryptoService
authHandler.JWTService = server.JWTService
authHandler.LDAPService = server.LDAPService
authHandler.ProxyManager = server.ProxyManager
authHandler.ProxyManager = proxyManager
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
authHandler.OAuthService = server.OAuthService
@@ -118,10 +116,10 @@ func (server *Server) Start() error {
var endpointHandler = endpoints.NewHandler(requestBouncer)
endpointHandler.DataStore = server.DataStore
endpointHandler.FileService = server.FileService
endpointHandler.ProxyManager = server.ProxyManager
endpointHandler.ProxyManager = proxyManager
endpointHandler.SnapshotService = server.SnapshotService
endpointHandler.ProxyManager = proxyManager
endpointHandler.ReverseTunnelService = server.ReverseTunnelService
endpointHandler.ComposeStackManager = server.ComposeStackManager
var endpointEdgeHandler = endpointedge.NewHandler(requestBouncer)
endpointEdgeHandler.DataStore = server.DataStore
@@ -133,7 +131,7 @@ func (server *Server) Start() error {
var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer)
endpointProxyHandler.DataStore = server.DataStore
endpointProxyHandler.ProxyManager = server.ProxyManager
endpointProxyHandler.ProxyManager = proxyManager
endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
@@ -143,7 +141,7 @@ func (server *Server) Start() error {
var registryHandler = registries.NewHandler(requestBouncer)
registryHandler.DataStore = server.DataStore
registryHandler.FileService = server.FileService
registryHandler.ProxyManager = server.ProxyManager
registryHandler.ProxyManager = proxyManager
var resourceControlHandler = resourcecontrols.NewHandler(requestBouncer)
resourceControlHandler.DataStore = server.DataStore

View File

@@ -6,21 +6,6 @@ import (
"github.com/portainer/portainer/api"
)
// NewAdministratorsOnlyResourceControl will create a new administrators only resource control associated to the resource specified by the
// identifier and type parameters.
func NewAdministratorsOnlyResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: true,
Public: false,
System: false,
}
}
// NewPrivateResourceControl will create a new private resource control associated to the resource specified by the
// identifier and type parameters. It automatically assigns it to the user specified by the userID parameter.
func NewPrivateResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userID portainer.UserID) *portainer.ResourceControl {

View File

@@ -40,11 +40,6 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers
}
}
// Remove the cached kube client so a new one can be created
func (factory *ClientFactory) RemoveKubeClient(endpoint *portainer.Endpoint) {
factory.endpointClients.Remove(strconv.Itoa(int(endpoint.ID)))
}
// GetKubeClient checks if an existing client is already registered for the endpoint and returns it if one is found.
// If no client is registered, it will create a new client, register it, and returns it.
func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) {

View File

@@ -13,12 +13,11 @@ import (
"github.com/portainer/libcompose/lookup"
"github.com/portainer/libcompose/project"
"github.com/portainer/libcompose/project/options"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api"
)
const (
dockerClientVersion = "1.24"
composeSyntaxMaxVersion = "2"
dockerClientVersion = "1.24"
)
// ComposeStackManager represents a service for managing compose stacks.
@@ -59,11 +58,6 @@ func (manager *ComposeStackManager) createClient(endpoint *portainer.Endpoint) (
return client.NewDefaultFactory(clientOpts)
}
// ComposeSyntaxMaxVersion returns the maximum supported version of the docker compose syntax
func (manager *ComposeStackManager) ComposeSyntaxMaxVersion() string {
return composeSyntaxMaxVersion
}
// Up will deploy a compose stack (equivalent of docker-compose up)
func (manager *ComposeStackManager) Up(stack *portainer.Stack, endpoint *portainer.Endpoint) error {

View File

@@ -190,25 +190,24 @@ type (
// Endpoint represents a Docker endpoint with all the info required
// to connect to it
Endpoint struct {
ID EndpointID `json:"Id"`
Name string `json:"Name"`
Type EndpointType `json:"Type"`
URL string `json:"URL"`
GroupID EndpointGroupID `json:"GroupId"`
PublicURL string `json:"PublicURL"`
TLSConfig TLSConfiguration `json:"TLSConfig"`
Extensions []EndpointExtension `json:"Extensions"`
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
TagIDs []TagID `json:"TagIds"`
Status EndpointStatus `json:"Status"`
Snapshots []DockerSnapshot `json:"Snapshots"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
EdgeID string `json:"EdgeID,omitempty"`
EdgeKey string `json:"EdgeKey"`
EdgeCheckinInterval int `json:"EdgeCheckinInterval"`
Kubernetes KubernetesData `json:"Kubernetes"`
ComposeSyntaxMaxVersion string `json:"ComposeSyntaxMaxVersion"`
ID EndpointID `json:"Id"`
Name string `json:"Name"`
Type EndpointType `json:"Type"`
URL string `json:"URL"`
GroupID EndpointGroupID `json:"GroupId"`
PublicURL string `json:"PublicURL"`
TLSConfig TLSConfiguration `json:"TLSConfig"`
Extensions []EndpointExtension `json:"Extensions"`
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
TagIDs []TagID `json:"TagIds"`
Status EndpointStatus `json:"Status"`
Snapshots []DockerSnapshot `json:"Snapshots"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
EdgeID string `json:"EdgeID,omitempty"`
EdgeKey string `json:"EdgeKey"`
EdgeCheckinInterval int `json:"EdgeCheckinInterval"`
Kubernetes KubernetesData `json:"Kubernetes"`
// Deprecated fields
// Deprecated in DBVersion == 4
@@ -555,10 +554,6 @@ type (
Env []Pair `json:"Env"`
ResourceControl *ResourceControl `json:"ResourceControl"`
Status StackStatus `json:"Status"`
CreationDate int64
CreatedBy string
UpdateDate int64
UpdatedBy string
ProjectPath string
}
@@ -779,7 +774,6 @@ type (
// ComposeStackManager represents a service to manage Compose stacks
ComposeStackManager interface {
ComposeSyntaxMaxVersion() string
Up(stack *Stack, endpoint *Endpoint) error
Down(stack *Stack, endpoint *Endpoint) error
}
@@ -1125,11 +1119,9 @@ type (
const (
// APIVersion is the version number of the Portainer API
APIVersion = "2.0.1"
APIVersion = "2.0.0"
// DBVersion is the version number of the Portainer database
DBVersion = 25
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
ComposeSyntaxMaxVersion = "3.9"
// AssetsServerURL represents the URL of the Portainer asset server
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved

View File

@@ -927,27 +927,6 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active {
z-index: 2;
}
.striketext:before,
.striketext:after {
background-color: #777777;
content: '';
display: inline-block;
height: 1px;
position: relative;
vertical-align: middle;
width: 50%;
}
.striketext:before {
right: 0.5em;
margin-left: -50%;
}
.striketext:after {
left: 0.5em;
margin-right: -50%;
}
/*bootbox override*/
.modal-open {
padding-right: 0 !important;

View File

@@ -4,8 +4,100 @@
<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 class="settings">
<datatable-columns-visibility columns="$ctrl.columnVisibility.columns" on-change="($ctrl.onColumnVisibilityChange)"></datatable-columns-visibility>
<span
class="setting"
ng-class="{ 'setting-active': $ctrl.columnVisibility.state.open }"
uib-dropdown
dropdown-append-to-body
auto-close="disabled"
is-open="$ctrl.columnVisibility.state.open"
>
<span uib-dropdown-toggle><i class="fa fa-columns space-right" aria-hidden="true"></i>Columns</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader">
Show / Hide Columns
</div>
<div class="menuContent">
<div class="md-checkbox">
<input
id="col_vis_state"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.state.display"
/>
<label for="col_vis_state" ng-bind="$ctrl.columnVisibility.columns.state.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_actions"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.actions.display"
/>
<label for="col_vis_actions" ng-bind="$ctrl.columnVisibility.columns.actions.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_stack"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.stack.display"
/>
<label for="col_vis_stack" ng-bind="$ctrl.columnVisibility.columns.stack.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_image"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.image.display"
/>
<label for="col_vis_image" ng-bind="$ctrl.columnVisibility.columns.image.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_created"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.created.display"
/>
<label for="col_vis_created" ng-bind="$ctrl.columnVisibility.columns.created.label"></label>
</div>
<div class="md-checkbox" ng-if="$ctrl.showHostColumn">
<input
id="col_vis_host"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.host.display"
/>
<label for="col_vis_host" ng-bind="$ctrl.columnVisibility.columns.host.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_ports"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.ports.display"
/>
<label for="col_vis_ports" ng-bind="$ctrl.columnVisibility.columns.ports.label"></label>
</div>
<div class="md-checkbox">
<input
id="col_vis_ownership"
ng-change="$ctrl.onColumnVisibilityChange($ctrl.columnVisibility)"
type="checkbox"
ng-model="$ctrl.columnVisibility.columns.ownership.display"
/>
<label for="col_vis_ownership" ng-bind="$ctrl.columnVisibility.columns.ownership.label"></label>
</div>
</div>
<div>
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.columnVisibility.state.open = false;">Close</a>
</div>
</div>
</div>
</span>
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>

View File

@@ -36,6 +36,9 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
};
this.columnVisibility = {
state: {
open: false,
},
columns: {
state: {
label: 'State',
@@ -72,11 +75,9 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
},
};
this.onColumnVisibilityChange = onColumnVisibilityChange.bind(this);
function onColumnVisibilityChange(columns) {
this.columnVisibility.columns = columns;
DatatableService.setColumnVisibilitySettings(this.tableKey, this.columnVisibility);
}
this.onColumnVisibilityChange = function (columnVisibility) {
DatatableService.setColumnVisibilitySettings(this.tableKey, columnVisibility);
};
this.onSelectionChanged = function () {
this.updateSelectionState();
@@ -198,6 +199,7 @@ angular.module('portainer.docker').controller('ContainersDatatableController', [
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
this.columnVisibility.state.open = false;
}
};
},

View File

@@ -36,13 +36,10 @@
<!-- don't use registry -->
<div ng-if="!$ctrl.model.UseRegistry">
<div class="form-group">
<span class="small">
<p class="text-muted" style="margin-left: 15px;">
<i class="fa fa-exclamation-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
When using advanced mode, image and repository <b>must be</b> publicly available.
</p>
</span>
<label for="image_name" ng-class="$ctrl.labelClass" class="control-label text-left">Image </label>
<label for="image_name" ng-class="$ctrl.labelClass" class="control-label text-left"
>Image
<portainer-tooltip position="bottom" message="Image and repository should be publicly available."></portainer-tooltip>
</label>
<div ng-class="$ctrl.inputClass">
<input type="text" class="form-control" ng-model="$ctrl.model.Image" name="image_name" placeholder="e.g. registry:port/myImage:myTag" required />
</div>

View File

@@ -4,6 +4,7 @@ export function ImageViewModel(data) {
this.Repository = data.Repository;
this.Created = data.Created;
this.Checked = false;
this.RepoTags = data.RepoTags;
if (!this.RepoTags && data.RepoDigests) {
this.RepoTags = [];
@@ -20,7 +21,6 @@ export function ImageViewModel(data) {
if (data.Portainer && data.Portainer.Agent && data.Portainer.Agent.NodeName) {
this.NodeName = data.Portainer.Agent.NodeName;
}
this.Labels = data.Labels;
}
export function ImageBuildModel(data) {

View File

@@ -16,5 +16,4 @@ export function ImageDetailsViewModel(data) {
this.ExposedPorts = data.ContainerConfig.ExposedPorts ? Object.keys(data.ContainerConfig.ExposedPorts) : [];
this.Volumes = data.ContainerConfig.Volumes ? Object.keys(data.ContainerConfig.Volumes) : [];
this.Env = data.ContainerConfig.Env ? data.ContainerConfig.Env : [];
this.Labels = data.ContainerConfig.Labels;
}

View File

@@ -128,17 +128,6 @@
<td>Build</td>
<td>Docker {{ image.DockerVersion }} on {{ image.Os }}, {{ image.Architecture }}</td>
</tr>
<tr ng-if="!(image.Labels | emptyobject)">
<td>Labels</td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="(k, v) in image.Labels">
<td>{{ k }}</td>
<td>{{ v }}</td>
</tr>
</table>
</td>
</tr>
<tr ng-if="image.Author">
<td>Author</td>
<td>{{ image.Author }}</td>

View File

@@ -196,8 +196,7 @@
<div class="form-group" ng-hide="config.Driver === 'macvlan' && formValues.Macvlan.Scope === 'local'">
<div class="col-sm-12">
<label for="ownership" class="control-label text-left">
Isolated network
<portainer-tooltip position="bottom" message="An isolated network has no inbound or outbound communications."></portainer-tooltip>
Restrict external access to the network
</label>
<label name="ownership" class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="config.Internal" />

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html
><html lang="en" ng-app="<%= name %>" ng-strict-di>
><html lang="en" ng-app="<%= name %>">
<head>
<meta charset="utf-8" />
<title>Portainer</title>

View File

@@ -71,7 +71,7 @@
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th style="width: 55px;">
<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>
@@ -142,9 +142,8 @@
</td>
</tr>
<tr dir-paginate-end ng-show="item.Expanded" ng-repeat="app in item.Applications" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }">
<td></td>
<td colspan="4">
<a ui-sref="kubernetes.applications.application({ name: app.Name, namespace: app.ResourcePool })">{{ app.Name }}</a>
<td colspan="5">
<a ui-sref="kubernetes.applications.application({ name: app.Name, namespace: app.ResourcePool })" style="margin-left: 25px;">{{ app.Name }}</a>
<span style="margin-left: 5px;" class="label label-primary image-tag" ng-if="!$ctrl.isSystemNamespace(app.ResourcePool) && $ctrl.isExternalApplication(app)"
>external</span
>

View File

@@ -118,7 +118,7 @@
<span style="margin-left: 5px;" class="label label-info image-tag" ng-if="$ctrl.isSystemNamespace(item)">system</span>
</td>
<td> <i class="fa {{ item.Quota ? 'fa-toggle-on' : 'fa-toggle-off' }}" aria-hidden="true" style="margin-right: 2px;"></i> {{ item.Quota ? 'Yes' : 'No' }} </td>
<td>{{ item.Namespace.CreationDate | getisodate }} {{ item.Namespace.ResourcePoolOwner ? 'by ' + item.Namespace.ResourcePoolOwner : '' }}</td>
<td>{{ item.CreationDate | getisodate }} {{ item.Namespace.ResourcePoolOwner ? 'by ' + item.Namespace.ResourcePoolOwner : '' }}</td>
<td ng-if="$ctrl.isAdmin">
<a ng-if="$ctrl.canManageAccess(item)" ui-sref="kubernetes.resourcePools.resourcePool.access({id: item.Namespace.Name})">
<i class="fa fa-users" aria-hidden="true"></i> Manage access

View File

@@ -39,8 +39,7 @@ class KubernetesConfigMapConverter {
const res = new KubernetesConfigMapCreatePayload();
res.metadata.name = data.Name;
res.metadata.namespace = data.Namespace;
const configurationOwner = _.truncate(data.ConfigurationOwner, { length: 63, omission: '' });
res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = configurationOwner;
res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = data.ConfigurationOwner;
res.data = data.Data;
return res;
}

View File

@@ -1,4 +1,5 @@
import * as JsonPatch from 'fast-json-patch';
import { KubernetesDaemonSet } from 'Kubernetes/models/daemon-set/models';
import { KubernetesDaemonSetCreatePayload } from 'Kubernetes/models/daemon-set/payloads';
import {

View File

@@ -1,4 +1,3 @@
import _ from 'lodash-es';
import { KubernetesNamespace } from 'Kubernetes/models/namespace/models';
import { KubernetesNamespaceCreatePayload } from 'Kubernetes/models/namespace/payloads';
import { KubernetesPortainerResourcePoolNameLabel, KubernetesPortainerResourcePoolOwnerLabel } from 'Kubernetes/models/resource-pool/models';
@@ -21,8 +20,7 @@ class KubernetesNamespaceConverter {
res.metadata.name = namespace.Name;
res.metadata.labels[KubernetesPortainerResourcePoolNameLabel] = namespace.ResourcePoolName;
if (namespace.ResourcePoolOwner) {
const resourcePoolOwner = _.truncate(namespace.ResourcePoolOwner, { length: 63, omission: '' });
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = resourcePoolOwner;
res.metadata.labels[KubernetesPortainerResourcePoolOwnerLabel] = namespace.ResourcePoolOwner;
}
return res;
}

View File

@@ -9,8 +9,7 @@ class KubernetesSecretConverter {
const res = new KubernetesSecretCreatePayload();
res.metadata.name = secret.Name;
res.metadata.namespace = secret.Namespace;
const configurationOwner = _.truncate(secret.configurationOwner, { length: 63, omission: '' });
res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = configurationOwner;
res.metadata.labels[KubernetesPortainerConfigurationOwnerLabel] = secret.ConfigurationOwner;
res.stringData = secret.Data;
return res;
}

View File

@@ -69,7 +69,7 @@ class KubernetesServiceConverter {
payload.metadata.namespace = service.Namespace;
payload.metadata.labels[KubernetesPortainerApplicationStackNameLabel] = service.StackName;
payload.metadata.labels[KubernetesPortainerApplicationNameLabel] = service.ApplicationName;
payload.metadata.labels[KubernetesPortainerApplicationOwnerLabel] = service.Application;
payload.metadata.labels[KubernetesPortainerApplicationOwnerLabel] = service.ApplicationOwner;
payload.spec.ports = service.Ports;
payload.spec.selector.app = service.ApplicationName;
if (service.Headless) {

View File

@@ -175,10 +175,7 @@ class KubernetesApplicationHelper {
item.OverridenKeys = _.map(keys, (k) => {
const fvKey = new KubernetesApplicationConfigurationFormValueOverridenKey();
fvKey.Key = k.Key;
if (!k.Count) {
// !k.Count indicates k.Key is new added to the configuration and has not been loaded to the application yet
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.NONE;
} else if (index < k.EnvCount) {
if (index < k.EnvCount) {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT;
} else {
fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM;

View File

@@ -10,11 +10,7 @@ class KubernetesCommonHelper {
}
static ownerToLabel(owner) {
let label = _.replace(owner, /[^-A-Za-z0-9_.]/g, '.');
label = _.truncate(label, { length: 63, omission: '' });
label = _.replace(label, /^[-_.]*/g, '');
label = _.replace(label, /[-_.]*$/g, '');
return label;
return _.replace(owner, /[^-A-Za-z0-9_.]/g, '.');
}
}
export default KubernetesCommonHelper;

View File

@@ -35,7 +35,6 @@ export class KubernetesApplicationFormValues {
}
export const KubernetesApplicationConfigurationFormValueOverridenKeyTypes = Object.freeze({
NONE: 0,
ENVIRONMENT: 1,
FILESYSTEM: 2,
});

View File

@@ -28,19 +28,8 @@ class KubernetesConfigMapService {
this.KubernetesConfigMaps(namespace).get(params).$promise,
this.KubernetesConfigMaps(namespace).getYaml(params).$promise,
]);
if (_.get(rawPromise, 'reason.status') == 404 && _.get(yamlPromise, 'reason.status') == 404) {
return KubernetesConfigMapConverter.defaultConfigMap(namespace, name);
}
// Saving binary data to 'data' field in configMap Object is not allowed by kubernetes and getYaml() may get
// an error. We should keep binary data to 'binaryData' field instead of 'data'. Before that, we
// use response from get() and ignore 500 error as a workaround.
if (rawPromise.value) {
return KubernetesConfigMapConverter.apiToConfigMap(rawPromise.value, yamlPromise.value);
}
throw new PortainerError('Unable to retrieve config map ', name);
const configMap = KubernetesConfigMapConverter.apiToConfigMap(rawPromise.value, yamlPromise.value);
return configMap;
} catch (err) {
if (err.status === 404) {
return KubernetesConfigMapConverter.defaultConfigMap(namespace, name);

View File

@@ -1,11 +0,0 @@
<information-panel title-text="Advanced deployment">
<span class="small">
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
As an administrator user, you have access to the advanced deployment feature allowing you to deploy any Kubernetes manifest inside your cluster.
</p>
<p>
<button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.deploy"> <i class="fa fa-file-code space-right" aria-hidden="true"></i>Advanced deployment </button>
</p>
</span>
</information-panel>

View File

@@ -5,7 +5,19 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady">
<div ng-if="ctrl.state.isAdmin" ng-include="'app/kubernetes/templates/advancedDeploymentPanel.html'"></div>
<information-panel title-text="Advanced deployment" ng-if="ctrl.state.isAdmin">
<span class="small">
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
As an administrator user, you have access to the advanced deployment feature allowing you to deploy any Kubernetes manifest inside your cluster.
</p>
<p>
<button type="button" class="btn btn-sm btn-primary" ui-sref="kubernetes.deploy">
<i class="fa fa-file-code space-right" aria-hidden="true"></i>Advanced deployment
</button>
</p>
</span>
</information-panel>
<div class="row">
<div class="col-sm-12">

View File

@@ -1,5 +1,3 @@
require('../../templates/advancedDeploymentPanel.html');
import angular from 'angular';
import * as _ from 'lodash-es';
import KubernetesStackHelper from 'Kubernetes/helpers/stackHelper';

View File

@@ -92,8 +92,8 @@
</div>
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded()">
<div class="col-sm-12 small text-danger">
<i class="fa fa-exclamation-circle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<div class="col-sm-12 small text-warning">
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This resource pool has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
resource pool.
</div>
@@ -634,8 +634,8 @@
</div>
<div class="form-group" ng-if="ctrl.state.resourcePoolHasQuota && ctrl.resourceQuotaCapacityExceeded()">
<div class="col-sm-12 small text-danger">
<i class="fa fa-exclamation-circle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This resource pool has exhausted its resource capacity and you will not be able to deploy the application. Contact your administrator to expand the capacity of the
resource pool.
</div>
@@ -813,8 +813,8 @@
</div>
<div class="form-group" ng-if="ctrl.resourceReservationsOverflow()">
<div class="col-sm-12 small text-danger">
<i class="fa fa-exclamation-circle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application would exceed available resources. Please review resource reservations or the instance count.
</div>
</div>
@@ -930,8 +930,8 @@
</table>
<div class="form-group" ng-if="ctrl.autoScalerOverflow()" style="margin-bottom: 10px;">
<div class="col-sm-12 small text-danger">
<i class="fa fa-exclamation-circle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<div class="col-sm-12 small text-muted">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
This application would exceed available resources. Please review resource reservations or the maximum instance count of the auto-scaling policy.
</div>
</div>
@@ -1353,7 +1353,7 @@
class="btn btn-primary"
ng-model="publishedPort.Protocol"
uib-btn-radio="'TCP'"
ng-change="ctrl.onChangePortProtocol($index)"
ng-change="ctrl.onChangePortMappingContainerPort()"
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'TCP')"
>TCP</label
>
@@ -1361,7 +1361,7 @@
class="btn btn-primary"
ng-model="publishedPort.Protocol"
uib-btn-radio="'UDP'"
ng-change="ctrl.onChangePortProtocol($index)"
ng-change="ctrl.onChangePortMappingContainerPort()"
ng-disabled="ctrl.isProtocolOptionDisabled($index, 'UDP')"
>UDP</label
>

View File

@@ -303,9 +303,6 @@ class KubernetesCreateApplicationController {
const ingresses = this.filteredIngresses;
p.IngressName = ingresses && ingresses.length ? ingresses[0].Name : undefined;
p.IngressHost = ingresses && ingresses.length ? ingresses[0].Host : undefined;
if (this.formValues.PublishedPorts.length) {
p.Protocol = this.formValues.PublishedPorts[0].Protocol;
}
this.formValues.PublishedPorts.push(p);
}
@@ -338,7 +335,6 @@ class KubernetesCreateApplicationController {
this.onChangePortMappingNodePort();
this.onChangePortMappingIngressRoute();
this.onChangePortMappingLoadBalancer();
this.onChangePortProtocol();
}
onChangePortMappingContainerPort() {
@@ -407,16 +403,6 @@ class KubernetesCreateApplicationController {
state.hasDuplicates = false;
}
}
onChangePortProtocol(index) {
this.onChangePortMappingContainerPort();
if (this.formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER) {
const newPorts = _.filter(this.formValues.PublishedPorts, { IsNew: true });
_.forEach(newPorts, (port) => {
port.Protocol = index ? this.formValues.PublishedPorts[index].Protocol : newPorts[0].Protocol;
});
}
}
/* #endregion */
/* #region STATE VALIDATION FUNCTIONS */
@@ -575,10 +561,6 @@ class KubernetesCreateApplicationController {
return this.state.isEdit && !this.formValues.Placements[index].IsNew;
}
isNewAndNotFirst(index) {
return !this.state.isEdit && index !== 0;
}
showPlacementPolicySection() {
const placements = _.filter(this.formValues.Placements, { NeedsDeletion: false });
return placements.length !== 0;
@@ -618,17 +600,8 @@ class KubernetesCreateApplicationController {
return this.state.isEdit && this.formValues.PublishedPorts.length > 0 && ports.length > 0;
}
isEditLBWithPorts() {
return this.formValues.PublishingType === KubernetesApplicationPublishingTypes.LOAD_BALANCER && _.filter(this.formValues.PublishedPorts, { IsNew: false }).length;
}
isProtocolOptionDisabled(index, protocol) {
return (
this.disableLoadBalancerEdit() ||
(this.isEditAndNotNewPublishedPort(index) && this.formValues.PublishedPorts[index].Protocol !== protocol) ||
(this.isEditLBWithPorts() && this.formValues.PublishedPorts[index].Protocol !== protocol) ||
(this.isNewAndNotFirst(index) && this.formValues.PublishedPorts[index].Protocol !== protocol)
);
return this.disableLoadBalancerEdit() || (this.isEditAndNotNewPublishedPort(index) && this.formValues.PublishedPorts[index].Protocol !== protocol);
}
/* #endregion */

View File

@@ -7,8 +7,6 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady">
<div ng-if="ctrl.state.isAdmin" ng-include="'app/kubernetes/templates/advancedDeploymentPanel.html'"></div>
<div class="row">
<div class="col-sm-12">
<rd-widget>
@@ -135,13 +133,7 @@
</uib-tab>
<uib-tab index="1" classes="btn-sm" select="ctrl.selectTab(1)">
<uib-tab-heading>
<i class="fas fa-compress-arrows-alt space-right" aria-hidden="true"></i> Placement
<div ng-if="ctrl.state.placementWarning">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
warning
</div>
</uib-tab-heading>
<uib-tab-heading> <i class="fas fa-compress-arrows-alt space-right" aria-hidden="true"></i> Placement </uib-tab-heading>
<div class="small text-muted" style="padding: 20px;">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
The placement component helps you understand whether or not this application can be deployed on a specific node.

View File

@@ -1,5 +1,3 @@
require('../../../templates/advancedDeploymentPanel.html');
import angular from 'angular';
import * as _ from 'lodash-es';
import * as JsonPatch from 'fast-json-patch';
@@ -103,7 +101,6 @@ class KubernetesApplicationController {
Notifications,
LocalStorage,
ModalService,
Authentication,
KubernetesApplicationService,
KubernetesEventService,
KubernetesStackService,
@@ -117,7 +114,6 @@ class KubernetesApplicationController {
this.Notifications = Notifications;
this.LocalStorage = LocalStorage;
this.ModalService = ModalService;
this.Authentication = Authentication;
this.KubernetesApplicationService = KubernetesApplicationService;
this.KubernetesEventService = KubernetesEventService;
@@ -310,7 +306,6 @@ class KubernetesApplicationController {
});
this.placements = computePlacements(nodes, this.application);
this.state.placementWarning = _.find(this.placements, { AcceptsApplication: true }) ? false : true;
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to retrieve application details');
} finally {
@@ -336,10 +331,8 @@ class KubernetesApplicationController {
name: this.$transition$.params().name,
},
eventWarningCount: 0,
placementWarning: false,
expandedNote: false,
useIngress: false,
isAdmin: this.Authentication.isAdmin(),
};
this.state.activeTab = this.LocalStorage.getActiveTab('application');

View File

@@ -5,8 +5,6 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady">
<div ng-if="ctrl.state.isAdmin" ng-include="'app/kubernetes/templates/advancedDeploymentPanel.html'"></div>
<div class="row">
<div class="col-sm-12">
<kubernetes-configurations-datatable

View File

@@ -1,15 +1,12 @@
require('../../templates/advancedDeploymentPanel.html');
import angular from 'angular';
import KubernetesConfigurationHelper from 'Kubernetes/helpers/configurationHelper';
class KubernetesConfigurationsController {
/* @ngInject */
constructor($async, $state, Notifications, Authentication, KubernetesConfigurationService, KubernetesApplicationService, ModalService) {
constructor($async, $state, Notifications, KubernetesConfigurationService, KubernetesApplicationService, ModalService) {
this.$async = $async;
this.$state = $state;
this.Notifications = Notifications;
this.Authentication = Authentication;
this.KubernetesConfigurationService = KubernetesConfigurationService;
this.KubernetesApplicationService = KubernetesApplicationService;
this.ModalService = ModalService;
@@ -96,7 +93,6 @@ class KubernetesConfigurationsController {
configurationsLoading: true,
applicationsLoading: true,
viewReady: false,
isAdmin: this.Authentication.isAdmin(),
};
await this.getApplications();

View File

@@ -106,16 +106,6 @@
</div>
<!-- !type options -->
<div class="col-sm-12 form-section-title" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET">
Information
</div>
<div class="form-group" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET">
<div class="col-sm-12 small text-muted">
Creating a sensitive configuration will create a Kubernetes Secret of type <code>Opaque</code>. You can find more information about this in the
<a href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" target="_blank">official documentation</a>.
</div>
</div>
<kubernetes-configuration-data ng-if="ctrl.formValues" form-values="ctrl.formValues" is-valid="ctrl.state.isDataValid"></kubernetes-configuration-data>
<!-- actions -->

View File

@@ -33,7 +33,7 @@
<div class="form-group">
<span class="col-sm-12 text-muted small">
Configuring ingress controllers will allow users to expose application they deploy over a HTTP route.<br />
Adding ingress controllers will allow users to expose application they deploy over a HTTP route.<br />
<p style="margin-top: 2px;">
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Ingress classes must be manually specified for each controller you want to use in the cluster. Make sure that each controller is running inside your cluster.
@@ -44,7 +44,7 @@
<div class="col-sm-12">
<label class="control-label text-left">Ingress controller</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="ctrl.addIngressClass()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> configure ingress controller
<i class="fa fa-plus-circle" aria-hidden="true"></i> add ingress controller
</span>
</div>

View File

@@ -58,19 +58,19 @@
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th style="width: 20px;">
<th style="width: 10%;">
<a ng-click="$ctrl.expandAll()" ng-if="$ctrl.hasExpandableItems()">
<i ng-class="{ 'fas fa-angle-down': $ctrl.state.expandAll, 'fas fa-angle-right': !$ctrl.state.expandAll }" aria-hidden="true"></i>
</a>
</th>
<th style="width: 60%;">
<th style="width: 55%;">
<a ng-click="$ctrl.changeOrderBy('Name')">
Storage
<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 style="width: 38%;">
<th style="width: 35%;">
<a ng-click="$ctrl.changeOrderBy('Size')">
Usage
<i class="fa fa-sort-numeric-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Size' && !$ctrl.state.reverseOrder"></i>
@@ -96,9 +96,8 @@
<td>{{ item.Size }}</td>
</tr>
<tr dir-paginate-end ng-show="item.Expanded" ng-repeat="vol in item.Volumes" ng-style="{ background: item.Highlighted ? '#d5e8f3' : '#f5f5f5' }">
<td></td>
<td>
<a ui-sref="kubernetes.volumes.volume({ name: vol.PersistentVolumeClaim.Name, namespace: vol.PersistentVolumeClaim.Namespace })">
<td colspan="2">
<a ui-sref="kubernetes.volumes.volume({ name: vol.PersistentVolumeClaim.Name, namespace: vol.PersistentVolumeClaim.Namespace })" style="margin-left: 25px;">
{{ vol.PersistentVolumeClaim.Name }}
</a>
</td>

View File

@@ -37,17 +37,6 @@
<td>Storage</td>
<td>{{ ctrl.volume.PersistentVolumeClaim.StorageClass.Name }}</td>
</tr>
<tr>
<td>Shared Access Policy</td>
<td
>{{ ctrl.state.volumeSharedAccessPolicy }}
<portainer-tooltip
position="bottom"
ng-if="ctrl.state.volumeSharedAccessPolicyTooltip"
message="{{ ctrl.state.volumeSharedAccessPolicyTooltip }}"
></portainer-tooltip
></td>
</tr>
<tr>
<td>Provisioner</td>
<td>{{ ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner ? ctrl.volume.PersistentVolumeClaim.StorageClass.Provisioner : '-' }}</td>

View File

@@ -2,7 +2,6 @@ import angular from 'angular';
import _ from 'lodash-es';
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
import filesizeParser from 'filesize-parser';
class KubernetesVolumeController {
@@ -180,8 +179,6 @@ class KubernetesVolumeController {
volumeSize: 0,
volumeSizeUnit: 'GB',
volumeSizeError: false,
volumeSharedAccessPolicy: '',
volumeSharedAccessPolicyTooltip: '',
};
this.state.activeTab = this.LocalStorage.getActiveTab('volume');
@@ -189,16 +186,6 @@ class KubernetesVolumeController {
try {
await this.getVolume();
await this.getEvents();
if (this.volume.PersistentVolumeClaim.StorageClass !== undefined) {
this.state.volumeSharedAccessPolicy = this.volume.PersistentVolumeClaim.StorageClass.AccessModes[this.volume.PersistentVolumeClaim.StorageClass.AccessModes.length - 1];
let policies = KubernetesStorageClassAccessPolicies();
policies.forEach((policy) => {
if (policy.Name == this.state.volumeSharedAccessPolicy) {
this.state.volumeSharedAccessPolicyTooltip = policy.Description;
}
});
}
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to load view data');
} finally {

View File

@@ -5,8 +5,6 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady">
<div ng-if="ctrl.state.isAdmin" ng-include="'app/kubernetes/templates/advancedDeploymentPanel.html'"></div>
<div class="row">
<div class="col-sm-12">
<rd-widget>

View File

@@ -1,5 +1,3 @@
require('../../templates/advancedDeploymentPanel.html');
import * as _ from 'lodash-es';
import filesizeParser from 'filesize-parser';
import angular from 'angular';
@@ -41,22 +39,10 @@ function computeSize(volumes) {
class KubernetesVolumesController {
/* @ngInject */
constructor(
$async,
$state,
Notifications,
Authentication,
ModalService,
LocalStorage,
EndpointProvider,
KubernetesStorageService,
KubernetesVolumeService,
KubernetesApplicationService
) {
constructor($async, $state, Notifications, ModalService, LocalStorage, EndpointProvider, KubernetesStorageService, KubernetesVolumeService, KubernetesApplicationService) {
this.$async = $async;
this.$state = $state;
this.Notifications = Notifications;
this.Authentication = Authentication;
this.ModalService = ModalService;
this.LocalStorage = LocalStorage;
this.EndpointProvider = EndpointProvider;
@@ -131,7 +117,6 @@ class KubernetesVolumesController {
currentName: this.$state.$current.name,
endpointId: this.EndpointProvider.endpointID(),
activeTab: this.LocalStorage.getActiveTab('volumes'),
isAdmin: this.Authentication.isAdmin(),
};
await this.getVolumes();

View File

@@ -1,7 +0,0 @@
export default class DatatableColumnsVisibilityController {
constructor() {
this.state = {
isOpen: false,
};
}
}

View File

@@ -1,19 +0,0 @@
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.isOpen }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.state.isOpen">
<span uib-dropdown-toggle><i class="fa fa-columns space-right" aria-hidden="true"></i>Columns</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
<div class="tableMenu">
<div class="menuHeader">
Show / Hide Columns
</div>
<div class="menuContent">
<div class="md-checkbox" ng-repeat="(key, value) in $ctrl.columns">
<input id="col_vis_{{::key}}" ng-change="$ctrl.onChange($ctrl.columns)" type="checkbox" ng-model="value.display" />
<label for="col_vis_{{::key}}">{{ value.label }}</label>
</div>
</div>
<div>
<a type="button" class="btn btn-default btn-sm" ng-click="$ctrl.state.isOpen = false;">Close</a>
</div>
</div>
</div>
</span>

View File

@@ -1,12 +0,0 @@
import angular from 'angular';
import controller from './datatable-columns-visibility.controller';
angular.module('portainer.app').component('datatableColumnsVisibility', {
templateUrl: './datatable-columns-visibility.html',
controller,
bindings: {
columns: '<',
onChange: '<',
},
});

View File

@@ -33,7 +33,6 @@ angular.module('portainer.app').controller('GenericDatatableController', [
refreshRate: '30',
},
};
this.resetSelectionState = function () {
this.state.selectAll = false;
this.state.selectedItems = [];
@@ -159,11 +158,6 @@ angular.module('portainer.app').controller('GenericDatatableController', [
this.settings.open = false;
}
this.onSettingsRepeaterChange();
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
}
};
/**

View File

@@ -4,7 +4,6 @@
<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 class="settings">
<datatable-columns-visibility columns="$ctrl.columnVisibility.columns" on-change="($ctrl.onColumnVisibilityChange)"></datatable-columns-visibility>
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
<div class="dropdown-menu dropdown-menu-right" uib-dropdown-menu>
@@ -118,20 +117,6 @@
</a>
</th>
<th>Control</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.CreationDate')">
Created
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.CreationDate' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th ng-if="$ctrl.columnVisibility.columns.updated.display">
<a ng-click="$ctrl.changeOrderBy('ResourceControl.UpdateDate')">
Updated
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ResourceControl.UpdateDate' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('ResourceControl.Ownership')">
Ownership
@@ -169,14 +154,6 @@
</span>
<span ng-if="!item.External">Total</span>
</td>
<td>
<span ng-if="item.CreationDate">{{ item.CreationDate | getisodatefromtimestamp }} {{ item.CreatedBy ? 'by ' + item.CreatedBy : '' }}</span>
<span ng-if="!item.CreationDate"> - </span>
</td>
<td ng-if="$ctrl.columnVisibility.columns.updated.display">
<span ng-if="item.UpdateDate">{{ item.UpdateDate | getisodatefromtimestamp }} {{ item.UpdatedBy ? 'by ' + item.UpdatedBy : '' }}</span>
<span ng-if="!item.UpdateDate"> - </span>
</td>
<td>
<span>
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
@@ -185,10 +162,10 @@
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="6" class="text-center text-muted">Loading...</td>
<td colspan="4" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="6" class="text-center text-muted">No stack available.</td>
<td colspan="4" class="text-center text-muted">No stack available.</td>
</tr>
</tbody>
</table>

View File

@@ -15,24 +15,6 @@ angular.module('portainer.app').controller('StacksDatatableController', [
},
};
this.columnVisibility = {
state: {
open: false,
},
columns: {
updated: {
label: 'Updated',
display: false,
},
},
};
this.onColumnVisibilityChange = onColumnVisibilityChange.bind(this);
function onColumnVisibilityChange(columns) {
this.columnVisibility.columns = columns;
DatatableService.setColumnVisibilitySettings(this.tableKey, this.columnVisibility);
}
/**
* Do not allow external items
*/
@@ -89,11 +71,6 @@ angular.module('portainer.app').controller('StacksDatatableController', [
this.settings.open = false;
}
this.onSettingsRepeaterChange();
var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
if (storedColumnVisibility !== null) {
this.columnVisibility = storedColumnVisibility;
}
};
},
]);

View File

@@ -1,26 +1,36 @@
import _ from 'lodash-es';
import { ExternalStackViewModel } from '@/portainer/models/stack';
angular.module('portainer.app').factory('StackHelper', [
function StackHelperFactory() {
'use strict';
var helper = {};
helper.getExternalStacksFromContainers = function (containers) {
return getExternalStacksFromLabel(containers, 'com.docker.compose.project', 2);
helper.getExternalStackNamesFromContainers = function (containers) {
var stackNames = [];
for (var i = 0; i < containers.length; i++) {
var container = containers[i];
if (!container.Labels || !container.Labels['com.docker.compose.project']) continue;
var stackName = container.Labels['com.docker.compose.project'];
stackNames.push(stackName);
}
return _.uniq(stackNames);
};
helper.getExternalStacksFromServices = function (services) {
return getExternalStacksFromLabel(services, 'com.docker.stack.namespace', 1);
};
helper.getExternalStackNamesFromServices = function (services) {
var stackNames = [];
function getExternalStacksFromLabel(items, label, type) {
return _.uniqBy(
items.filter((item) => item.Labels && item.Labels[label]).map((item) => new ExternalStackViewModel(item.Labels[label], type, item.Created)),
'Name'
);
}
for (var i = 0; i < services.length; i++) {
var service = services[i];
if (!service.Labels || !service.Labels['com.docker.stack.namespace']) continue;
var stackName = service.Labels['com.docker.stack.namespace'];
stackNames.push(stackName);
}
return _.uniq(stackNames);
};
return helper;
},

View File

@@ -13,16 +13,11 @@ export function StackViewModel(data) {
}
this.External = false;
this.Status = data.Status;
this.CreationDate = data.CreationDate;
this.CreatedBy = data.CreatedBy;
this.UpdateDate = data.UpdateDate;
this.UpdatedBy = data.UpdatedBy;
}
export function ExternalStackViewModel(name, type, creationDate) {
export function ExternalStackViewModel(name, type) {
this.Name = name;
this.Type = type;
this.External = true;
this.Checked = false;
this.CreationDate = creationDate;
}

View File

@@ -1,5 +1,5 @@
import _ from 'lodash-es';
import { StackViewModel } from '../../models/stack';
import { ExternalStackViewModel, StackViewModel } from '../../models/stack';
angular.module('portainer.app').factory('StackService', [
'$q',
@@ -121,8 +121,13 @@ angular.module('portainer.app').factory('StackService', [
var deferred = $q.defer();
ServiceService.services()
.then(function success(services) {
deferred.resolve(StackHelper.getExternalStacksFromServices(services));
.then(function success(data) {
var services = data;
var stackNames = StackHelper.getExternalStackNamesFromServices(services);
var stacks = stackNames.map(function (name) {
return new ExternalStackViewModel(name, 1);
});
deferred.resolve(stacks);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve external stacks', err: err });
@@ -135,8 +140,13 @@ angular.module('portainer.app').factory('StackService', [
var deferred = $q.defer();
ContainerService.containers(1)
.then(function success(containers) {
deferred.resolve(StackHelper.getExternalStacksFromContainers(containers));
.then(function success(data) {
var containers = data;
var stackNames = StackHelper.getExternalStackNamesFromContainers(containers);
var stacks = stackNames.map(function (name) {
return new ExternalStackViewModel(name, 2);
});
deferred.resolve(stacks);
})
.catch(function error(err) {
deferred.reject({ msg: 'Unable to retrieve external stacks', err: err });

View File

@@ -179,7 +179,7 @@ angular.module('portainer.app').factory('StateManager', [
}
var applicationState = LocalStorage.getApplicationState();
if (applicationState && applicationState.validity) {
if (applicationState) {
var now = moment().unix();
var cacheValidity = now - applicationState.validity;
if (cacheValidity > APPLICATION_CACHE_VALIDITY) {

View File

@@ -8,62 +8,41 @@
<img ng-if="ctrl.logo" ng-src="{{ ctrl.logo }}" class="simple-box-logo" />
</div>
<!-- !login box logo -->
<!-- login panel -->
<div class="panel panel-default" ng-if="!ctrl.state.loginInProgress">
<div class="panel-body">
<!-- login form -->
<form class="simple-box-form form-horizontal">
<div class="form-group">
<div class="col-sm-12" style="display: flex; justify-content: center;" ng-if="ctrl.state.showOAuthLogin">
<a ng-href="{{ ctrl.OAuthLoginURI }}">
<div class="btn btn-primary pull-left" style="font-size: 120%; margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Microsoft'">
<i class="fab fa-microsoft" aria-hidden="true"></i> Login with Microsoft
</div>
<div class="btn btn-primary pull-left" style="font-size: 120%; margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Google'">
<i class="fab fa-google" aria-hidden="true"></i> Login with Google
</div>
<div class="btn btn-primary pull-left" style="font-size: 120%; margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Github'">
<i class="fab fa-github" aria-hidden="true"></i> Login with Github
</div>
<div class="btn btn-primary pull-left" style="font-size: 120%; margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'OAuth'">
<i class="fa fa-sign-in-alt" aria-hidden="true"></i> Login with OAuth
</div>
</a>
</div>
</div>
<!-- divider -->
<div class="form-group" ng-if="ctrl.state.showOAuthLogin">
<div class="col-sm-12" style="display: flex; align-items: center; justify-content: center;">
<div class="striketext small text-muted" style="display: flex; align-items: center; justify-content: center; width: 90%;">or</div>
</div>
</div>
<!-- use internal auth button -->
<div class="form-group" ng-if="ctrl.state.showOAuthLogin && !ctrl.state.showStandardLogin">
<div class="col-sm-12" style="display: flex; justify-content: center;">
<div class="btn btn-sm btn-primary pull-left" style="margin-left: 2px;" ng-click="ctrl.toggleStandardLogin()">
Use internal authentication
</div>
</div>
</div>
<!-- !username input -->
<div class="input-group" ng-if="ctrl.state.showStandardLogin">
<!-- username input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></span>
<input id="username" type="text" class="form-control" name="username" ng-model="ctrl.formValues.Username" auto-focus />
</div>
<!-- !username input -->
<!-- password input -->
<div class="input-group" ng-if="ctrl.state.showStandardLogin">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
<input id="password" type="password" class="form-control" name="password" ng-model="ctrl.formValues.Password" />
</div>
<!-- !password input -->
<!-- login button -->
<div class="form-group">
<div class="col-sm-12">
<a ng-href="{{ ctrl.OAuthLoginURI }}" ng-if="ctrl.AuthenticationMethod === 3">
<div class="btn btn-primary btn-sm pull-left" style="margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Microsoft'">
<i class="fab fa-microsoft" aria-hidden="true"></i> Login with Microsoft
</div>
<div class="btn btn-primary btn-sm pull-left" style="margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Google'">
<i class="fab fa-google" aria-hidden="true"></i> Login with Google
</div>
<div class="btn btn-primary btn-sm pull-left" style="margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'Github'">
<i class="fab fa-github" aria-hidden="true"></i> Login with Github
</div>
<div class="btn btn-primary btn-sm pull-left" style="margin-left: 2px;" ng-if="ctrl.state.OAuthProvider === 'OAuth'">
<i class="fa fa-sign-in-alt" aria-hidden="true"></i> Login with OAuth
</div>
</a>
<div class="form-group" ng-if="ctrl.state.showStandardLogin">
<!-- login button -->
<div class="col-sm-12" style="display: flex; align-items: center; justify-content: center;">
<button
type="submit"
class="btn btn-primary btn-sm pull-right"
@@ -74,19 +53,19 @@
<span ng-hide="ctrl.state.loginInProgress"><i class="fa fa-sign-in-alt" aria-hidden="true"></i> Login</span>
<span ng-show="ctrl.state.loginInProgress">Login in progress...</span>
</button>
<span class="pull-right" style="margin: 5px;" ng-if="ctrl.state.AuthenticationError">
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<span class="small text-danger">{{ ctrl.state.AuthenticationError }}</span>
</span>
</div>
</div>
<!-- !login button -->
</form>
<!-- !login form -->
<!-- error message -->
<div class="pull-right" ng-if="ctrl.state.AuthenticationError">
<i class="fa fa-exclamation-triangle red-icon" aria-hidden="true" style="margin-right: 2px;"></i>
<span class="small text-danger">{{ ctrl.state.AuthenticationError }}</span>
</div>
</div>
</div>
<!-- !login panel -->
<div class="panel panel-default" ng-show="ctrl.state.loginInProgress">
<div class="panel-body">

View File

@@ -40,8 +40,6 @@ class AuthenticationController {
Password: '',
};
this.state = {
showOAuthLogin: false,
showStandardLogin: false,
AuthenticationError: '',
loginInProgress: true,
OAuthProvider: '',
@@ -224,17 +222,12 @@ class AuthenticationController {
}
}
toggleStandardLogin() {
this.state.showStandardLogin = !this.state.showStandardLogin;
}
async onInit() {
try {
const settings = await this.SettingsService.publicSettings();
this.state.showOAuthLogin = settings.AuthenticationMethod === 3;
this.state.showStandardLogin = !this.state.showOAuthLogin;
this.state.OAuthLoginURI = settings.OAuthLoginURI;
this.AuthenticationMethod = settings.AuthenticationMethod;
this.state.OAuthProvider = this.determineOauthProvider(settings.OAuthLoginURI);
this.state.OAuthLoginURI = settings.OAuthLoginURI;
const code = this.URLHelper.getParameter('code');
const state = this.URLHelper.getParameter('state');

View File

@@ -14,8 +14,7 @@ angular
FormValidator,
ResourceControlService,
FormHelper,
CustomTemplateService,
EndpointProvider
CustomTemplateService
) {
$scope.formValues = {
Name: '',
@@ -167,7 +166,6 @@ angular
async function initView() {
var endpointMode = $scope.applicationState.endpoint.mode;
const endpointId = +$state.params.endpointId;
$scope.state.StackType = 2;
if (endpointMode.provider === 'DOCKER_SWARM_MODE' && endpointMode.role === 'MANAGER') {
$scope.state.StackType = 1;
@@ -179,13 +177,6 @@ angular
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve Custom Templates');
}
try {
const endpoint = EndpointProvider.currentEndpoint();
$scope.composeSyntaxMaxVersion = endpoint.ComposeSyntaxMaxVersion;
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve the ComposeSyntaxMaxVersion');
}
}
initView();

View File

@@ -20,15 +20,10 @@
<span class="col-sm-12 text-muted small" ng-if="state.StackType === 1">
This stack will be deployed using the equivalent of the <code>docker stack deploy</code> command.
</span>
<div class="col-sm-12 text-muted small" ng-if="state.StackType === 2 && composeSyntaxMaxVersion == 2">
<div style="margin-bottom: 7px;">
This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment.
</div>
<span class="col-sm-12 text-muted small" ng-if="state.StackType === 2">
This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment. <br /><br />
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
Note: Due to a limitation of libcompose, the name of the stack will be standardized to remove all special characters and uppercase letters.
</div>
<span class="col-sm-12 text-muted small" ng-if="state.StackType === 2 && composeSyntaxMaxVersion > 2">
This stack will be deployed using <code>docker-compose</code>.
</span>
</div>
<!-- build-method -->

View File

@@ -39,6 +39,14 @@
<div class="form-group">
{{ stackName }}
<a
ng-if="!state.externalStack && stackFileContent"
class="btn btn-primary btn-xs"
ui-sref="docker.templates.custom.new({fileContent: stackFileContent, type: stack.Type})"
>
Create template from stack
</a>
<button
authorization="PortainerStackUpdate"
ng-if="!state.externalStack && stack.Status === 2"
@@ -65,15 +73,6 @@
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>
Delete this stack
</button>
<button
ng-if="!state.externalStack && stackFileContent"
class="btn btn-primary btn-xs"
ui-sref="docker.templates.custom.new({fileContent: stackFileContent, type: stack.Type})"
>
<i class="fa fa-plus space-right" aria-hidden="true"></i>
Create template from stack
</button>
</div>
</div>
<!-- !stack-details -->
@@ -90,16 +89,10 @@
</uib-tab>
<!-- !tab-info -->
<!-- tab-file -->
<uib-tab index="1" select="showEditor()" ng-if="!state.externalStack">
<uib-tab index="1" select="showEditor()">
<uib-tab-heading> <i class="fa fa-pencil-alt space-right" aria-hidden="true"></i> Editor </uib-tab-heading>
<form class="form-horizontal" ng-if="state.showEditorTab" style="margin-top: 10px;">
<div class="form-group">
<span class="col-sm-12 text-muted small" style="margin-bottom: 7px;" ng-if="stackType == 2 && composeSyntaxMaxVersion == 2">
This stack will be deployed using the equivalent of <code>docker-compose</code>. Only Compose file format version <b>2</b> is supported at the moment.
</span>
<span class="col-sm-12 text-muted small" style="margin-bottom: 7px;" ng-if="stackType == 2 && composeSyntaxMaxVersion > 2">
This stack will be deployed using <code>docker-compose</code>.
</span>
<span class="col-sm-12 text-muted small">
You can get more information about Compose file format in the <a href="https://docs.docker.com/compose/compose-file/" target="_blank">official documentation</a>.
</span>

View File

@@ -359,7 +359,7 @@ angular.module('portainer.app').controller('StackController', [
});
}
async function initView() {
function initView() {
var stackName = $transition$.params().name;
$scope.stackName = stackName;
var external = $transition$.params().external;
@@ -372,15 +372,6 @@ angular.module('portainer.app').controller('StackController', [
var stackId = $transition$.params().id;
loadStack(stackId);
}
try {
const endpoint = EndpointProvider.currentEndpoint();
$scope.composeSyntaxMaxVersion = endpoint.ComposeSyntaxMaxVersion;
} catch (err) {
Notifications.error('Failure', err, 'Unable to retrieve the ComposeSyntaxMaxVersion');
}
$scope.stackType = $transition$.params().type;
}
initView();

View File

@@ -3,13 +3,11 @@ param (
)
$ErrorActionPreference = "Stop";
$ProgressPreference = "SilentlyContinue";
New-Item -Path "docker-binary" -ItemType Directory | Out-Null
$download_folder = "docker-binary"
Invoke-WebRequest -O "$($download_folder)/docker-binaries.zip" "https://dockermsft.azureedge.net/dockercontainer/docker-$($docker_version).zip"
Invoke-WebRequest -O "$($download_folder)/docker-binaries.zip" "https://download.docker.com/win/static/stable/x86_64/docker-$($docker_version).zip"
Expand-Archive -Path "$($download_folder)/docker-binaries.zip" -DestinationPath "$($download_folder)"
Move-Item -Path "$($download_folder)/docker/docker.exe" -Destination "dist"
Move-Item -Path "$($download_folder)/docker/*.dll" -Destination "dist"

View File

@@ -3,6 +3,5 @@ param (
)
$ErrorActionPreference = "Stop";
$ProgressPreference = "SilentlyContinue";
Invoke-WebRequest -O "dist/docker-compose.exe" "https://github.com/docker/compose/releases/download/$($docker_compose_version)/docker-compose-Windows-x86_64.exe"

View File

@@ -4,12 +4,12 @@ PLATFORM=$1
ARCH=$2
DOCKER_COMPOSE_VERSION=$3
if [ "${PLATFORM}" == 'linux' ] && [ "${ARCH}" == 'amd64' ]; then
if [ "${PLATFORM}" == 'win' ]; then
wget -O "dist/docker-compose.exe" "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-Windows-x86_64.exe"
elif [ "${PLATFORM}" == 'linux' ] && [ "${ARCH}" == 'amd64' ]; then
wget -O "dist/docker-compose" "https://github.com/portainer/docker-compose-linux-amd64-static-binary/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose"
chmod +x "dist/docker-compose"
elif [ "${PLATFORM}" == 'mac' ]; then
wget -O "dist/docker-compose" "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-Darwin-x86_64"
chmod +x "dist/docker-compose"
fi
exit 0

View File

@@ -3,6 +3,5 @@ param (
)
$ErrorActionPreference = "Stop";
$ProgressPreference = "SilentlyContinue";
Invoke-WebRequest -O "dist/kompose.exe" "https://github.com/kubernetes/kompose/releases/download/$($kompose_version)/kompose-windows-amd64.exe"

View File

@@ -3,6 +3,5 @@ param (
)
$ErrorActionPreference = "Stop";
$ProgressPreference = "SilentlyContinue";
Invoke-WebRequest -O "dist/kubectl.exe" "https://storage.googleapis.com/kubernetes-release/release/$($kubectl_version)/bin/windows/amd64/kubectl.exe"

View File

@@ -1,24 +1,12 @@
FROM mcr.microsoft.com/windows/servercore:ltsc2019 as core
ENV GIT_VERSION 2.30.0
ENV GIT_PATCH_VERSION 2
RUN powershell -Command $ErrorActionPreference = 'Stop' ; \
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; \
Invoke-WebRequest $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}.{1}-busybox-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION) -OutFile 'mingit.zip' -UseBasicParsing ; \
Expand-Archive mingit.zip -DestinationPath c:\mingit
FROM mcr.microsoft.com/windows/nanoserver:1809-amd64
USER ContainerAdministrator
COPY --from=core /windows/system32/netapi32.dll /windows/system32/netapi32.dll
COPY --from=core /mingit /mingit
COPY dist /
RUN setx /M path "C:\mingit\cmd;%path%"
WORKDIR /
EXPOSE 9000

View File

@@ -17,10 +17,9 @@ module.exports = function (grunt) {
root: 'dist',
distdir: 'dist/public',
binaries: {
dockerLinuxVersion: '19.03.13',
dockerWindowsVersion: '19-03-12',
dockerLinuxComposeVersion: '1.28.2',
dockerWindowsComposeVersion: '1.28.0',
dockerLinuxVersion: '18.09.3',
dockerWindowsVersion: '17.09.0-ce',
dockerComposeVersion: '1.27.4',
komposeVersion: 'v1.22.0',
kubectlVersion: 'v1.18.0',
},
@@ -177,7 +176,7 @@ function shell_run_container() {
'docker rm -f portainer',
'docker run -d -p 8000:8000 -p 9000:9000 -v $(pwd)/dist:/app -v ' +
portainer_data +
':/data -v /var/run/docker.sock:/var/run/docker.sock:z -v /tmp:/tmp --name portainer portainer/base /app/portainer',
':/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer',
].join(';');
}
@@ -212,27 +211,26 @@ function shell_download_docker_binary(p, a) {
function shell_download_docker_compose_binary(p, a) {
var ps = { windows: 'win', darwin: 'mac' };
var as = { arm: 'armhf', arm64: 'aarch64' };
var ip = ps[p] || p;
var ia = as[a] || a;
var linuxBinaryVersion = '<%= binaries.dockerLinuxComposeVersion %>';
var windowsBinaryVersion = '<%= binaries.dockerWindowsComposeVersion %>';
var as = { amd64: 'x86_64', arm: 'armhf', arm64: 'aarch64' };
var ip = ps[p] === undefined ? p : ps[p];
var ia = as[a] === undefined ? a : as[a];
var binaryVersion = '<%= binaries.dockerComposeVersion %>';
if (ip === 'linux' || ip === 'mac') {
if (p === 'linux' || p === 'mac') {
return [
'if [ -f dist/docker-compose ]; then',
'if [ -f dist/docker ]; then',
'echo "Docker Compose binary exists";',
'else',
'build/download_docker_compose_binary.sh ' + ip + ' ' + ia + ' ' + linuxBinaryVersion + ';',
'build/download_docker_compose_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';',
'fi',
].join(' ');
} else if (ip === 'win') {
} else {
return [
'powershell -Command "& {if (Test-Path -Path "dist/docker-compose.exe") {',
'Write-Host "Skipping download, Docker Compose binary exists"',
'return',
'} else {',
'& ".\\build\\download_docker_compose_binary.ps1" -docker_compose_version ' + windowsBinaryVersion + '',
'& ".\\build\\download_docker_compose_binary.ps1" -docker_compose_version ' + binaryVersion + '',
'}}"',
].join(' ');
}

View File

@@ -21,8 +21,7 @@
"build:server": "grunt clean:server && grunt build:server",
"build:client": "grunt clean:client && grunt build:client",
"clean": "grunt clean:all",
"start": "grunt start",
"start:clean": "grunt clean:all && grunt start",
"start": "grunt clean:all && grunt start",
"start:localserver": "grunt start:localserver",
"start:server": "grunt clean:server && grunt start:server",
"start:client": "grunt clean:client && grunt start:client",
@@ -83,7 +82,6 @@
"lodash-es": "^4.17.15",
"moment": "^2.21.0",
"ng-file-upload": "~12.2.13",
"source-map-loader": "^1.1.2",
"spinkit": "^2.0.1",
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
"strip-ansi": "^6.0.0",

View File

@@ -19,11 +19,6 @@ module.exports = {
},
module: {
rules: [
{
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,

View File

@@ -4,7 +4,6 @@ const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig, {
mode: 'development',
devtool: 'eval-source-map',
module: {
rules: [
{

View File

@@ -3,7 +3,6 @@ const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig, {
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{

View File

@@ -925,11 +925,6 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
"@types/json-schema@^7.0.6":
version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1155,11 +1150,6 @@ LiveScript@1.0.1:
dependencies:
prelude-ls ">= 0.6.0"
abab@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -1235,11 +1225,6 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.0.tgz#5c894537098785926d71e696114a53ce768ed773"
integrity sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw==
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^4.7.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@@ -1268,16 +1253,6 @@ ajv@^6.12.3:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -1352,7 +1327,6 @@ angular-moment-picker@^0.10.2:
dependencies:
angular-mocks "1.6.1"
angular-sanitize "1.6.1"
lodash-es "^4.17.15"
angular-resource@1.8.0:
version "1.8.0"
@@ -5774,13 +5748,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -6956,15 +6923,6 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4
emojis-list "^3.0.0"
json5 "^1.0.1"
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^2.1.2"
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -9563,7 +9521,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -9599,15 +9557,6 @@ schema-utils@^2.6.5:
ajv "^6.12.2"
ajv-keywords "^3.4.1"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
seek-bzip@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
@@ -9954,18 +9903,6 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
source-map-loader@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-1.1.2.tgz#5b782bf08496d3a7f355e1780df0e25190a80991"
integrity sha512-bjf6eSENOYBX4JZDfl9vVLNsGAQ6Uz90fLmOazcmMcyDYOBFsGxPNn83jXezWLY9bJsVAo1ObztxPcV8HAbjVA==
dependencies:
abab "^2.0.5"
iconv-lite "^0.6.2"
loader-utils "^2.0.0"
schema-utils "^3.0.0"
source-map "^0.6.1"
whatwg-mimetype "^2.3.0"
source-map-resolve@^0.5.0:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -11238,11 +11175,6 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-mimetype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whet.extend@~0.9.9:
version "0.9.9"
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"