diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f01c9df7e..059a043a6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -37,6 +37,7 @@ Any other info e.g. Why do you consider this to be a bug? What did you expect to **Technical details:** * Portainer version: +* Portainer Docker image tag (latest/arm/windows...): * Target Docker version (the host/cluster you manage): * Target Swarm version (if applicable): * Platform (windows/linux): diff --git a/.gitignore b/.gitignore index 42c695629..244386e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bower_components dist portainer-checksum.txt api/cmd/portainer/portainer* +.tmp diff --git a/api/cli/cli.go b/api/cli/cli.go index ccd9c1f9a..10f476c9b 100644 --- a/api/cli/cli.go +++ b/api/cli/cli.go @@ -25,14 +25,14 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(), Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(), Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')), - Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(":9000").Short('p').String(), - Assets: kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String(), - Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default("/data").Short('d').String(), - Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default("https://raw.githubusercontent.com/portainer/templates/master/templates.json").Short('t').String(), - TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default("false").Bool(), - TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String(), - TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String(), - TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default("/certs/key.pem").String(), + Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(), + Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(), + Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(), + Templates: kingpin.Flag("templates", "URL to the templates (apps) definitions").Default(defaultTemplatesURL).Short('t').String(), + TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(), + TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(), + TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(), + TLSKey: kingpin.Flag("tlskey", "Path to the TLS key").Default(defaultTLSKeyPath).String(), } kingpin.Parse() diff --git a/api/cli/defaults.go b/api/cli/defaults.go new file mode 100644 index 000000000..adf8affbf --- /dev/null +++ b/api/cli/defaults.go @@ -0,0 +1,14 @@ +// +build !windows + +package cli + +const ( + defaultBindAddress = ":9000" + defaultDataDirectory = "/data" + defaultAssetsDirectory = "." + defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" + defaultTLSVerify = "false" + defaultTLSCACertPath = "/certs/ca.pem" + defaultTLSCertPath = "/certs/cert.pem" + defaultTLSKeyPath = "/certs/key.pem" +) diff --git a/api/cli/defaults_windows.go b/api/cli/defaults_windows.go new file mode 100644 index 000000000..3a4106c74 --- /dev/null +++ b/api/cli/defaults_windows.go @@ -0,0 +1,12 @@ +package cli + +const ( + defaultBindAddress = ":9000" + defaultDataDirectory = "C:\\data" + defaultAssetsDirectory = "." + defaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" + defaultTLSVerify = "false" + defaultTLSCACertPath = "C:\\certs\\ca.pem" + defaultTLSCertPath = "C:\\certs\\cert.pem" + defaultTLSKeyPath = "C:\\certs\\key.pem" +) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index c11f9b34f..e01c304a9 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -29,6 +29,11 @@ func main() { Logo: *flags.Logo, } + fileService, err := file.NewService(*flags.Data, "") + if err != nil { + log.Fatal(err) + } + var store = bolt.NewStore(*flags.Data) err = store.Open() if err != nil { @@ -41,11 +46,6 @@ func main() { log.Fatal(err) } - fileService, err := file.NewService(*flags.Data) - if err != nil { - log.Fatal(err) - } - var cryptoService portainer.CryptoService = &crypto.Service{} // Initialize the active endpoint from the CLI only if there is no diff --git a/api/file/file.go b/api/file/file.go index cd76acec9..c9ad71a4a 100644 --- a/api/file/file.go +++ b/api/file/file.go @@ -1,13 +1,12 @@ package file import ( - "strconv" - "github.com/portainer/portainer" "io" "os" "path" + "strconv" ) const ( @@ -21,18 +20,26 @@ const ( TLSKeyFile = "key.pem" ) -// Service represents a service for managing files. +// Service represents a service for managing files and directories. type Service struct { + dataStorePath string fileStorePath string } -// NewService initializes a new service. -func NewService(fileStorePath string) (*Service, error) { +// NewService initializes a new service. It creates a data directory and a directory to store files +// inside this directory if they don't exist. +func NewService(dataStorePath, fileStorePath string) (*Service, error) { service := &Service{ - fileStorePath: fileStorePath, + dataStorePath: dataStorePath, + fileStorePath: path.Join(dataStorePath, fileStorePath), } - err := service.createFolderInStoreIfNotExist(TLSStorePath) + err := createDirectoryIfNotExist(dataStorePath, 0755) + if err != nil { + return nil, err + } + + err = service.createDirectoryInStoreIfNotExist(TLSStorePath) if err != nil { return nil, err } @@ -44,7 +51,7 @@ func NewService(fileStorePath string) (*Service, error) { func (service *Service) StoreTLSFile(endpointID portainer.EndpointID, fileType portainer.TLSFileType, r io.Reader) error { ID := strconv.Itoa(int(endpointID)) endpointStorePath := path.Join(TLSStorePath, ID) - err := service.createFolderInStoreIfNotExist(endpointStorePath) + err := service.createDirectoryInStoreIfNotExist(endpointStorePath) if err != nil { return err } @@ -97,12 +104,20 @@ func (service *Service) DeleteTLSFiles(endpointID portainer.EndpointID) error { return nil } -// createFolderInStoreIfNotExist creates a new folder in the file store if it doesn't exists on the file system. -func (service *Service) createFolderInStoreIfNotExist(name string) error { +// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system. +func (service *Service) createDirectoryInStoreIfNotExist(name string) error { path := path.Join(service.fileStorePath, name) + return createDirectoryIfNotExist(path, 0700) +} + +// createDirectoryIfNotExist creates a directory if it doesn't exists on the file system. +func createDirectoryIfNotExist(path string, mode uint32) error { _, err := os.Stat(path) if os.IsNotExist(err) { - os.Mkdir(path, 0600) + err = os.Mkdir(path, os.FileMode(mode)) + if err != nil { + return err + } } else if err != nil { return err } diff --git a/api/http/file_handler.go b/api/http/file_handler.go new file mode 100644 index 000000000..95ebc022c --- /dev/null +++ b/api/http/file_handler.go @@ -0,0 +1,20 @@ +package http + +import "net/http" + +// FileHandler represents an HTTP API handler for managing static files. +type FileHandler struct { + http.Handler +} + +func newFileHandler(assetPath string) *FileHandler { + h := &FileHandler{ + Handler: http.FileServer(http.Dir(assetPath)), + } + return h +} + +func (fileHandler *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "max-age=31536000") + fileHandler.Handler.ServeHTTP(w, r) +} diff --git a/api/http/handler.go b/api/http/handler.go index ca4b15ede..5c88a2805 100644 --- a/api/http/handler.go +++ b/api/http/handler.go @@ -19,7 +19,7 @@ type Handler struct { DockerHandler *DockerHandler WebSocketHandler *WebSocketHandler UploadHandler *UploadHandler - FileHandler http.Handler + FileHandler *FileHandler } const ( diff --git a/api/http/server.go b/api/http/server.go index cd376883b..32975944b 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -63,7 +63,7 @@ func (server *Server) Start() error { endpointHandler.server = server var uploadHandler = NewUploadHandler(middleWareService) uploadHandler.FileService = server.FileService - var fileHandler = http.FileServer(http.Dir(server.AssetsPath)) + var fileHandler = newFileHandler(server.AssetsPath) server.Handler = &Handler{ AuthHandler: authHandler, diff --git a/api/portainer.go b/api/portainer.go index a2e99e0de..c03679e84 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -118,7 +118,7 @@ type ( const ( // APIVersion is the version number of portainer API. - APIVersion = "1.11.0" + APIVersion = "1.11.1" ) const ( diff --git a/app/app.js b/app/app.js index a070100ed..2c2a58589 100644 --- a/app/app.js +++ b/app/app.js @@ -547,11 +547,11 @@ angular.module('portainer', [ // This is your docker url that the api will use to make requests // You need to set this to the api endpoint without the port i.e. http://192.168.1.9 .constant('DOCKER_PORT', '') // Docker port, leave as an empty string if no port is required. If you have a port, prefix it with a ':' i.e. :4243 - .constant('DOCKER_ENDPOINT', '/api/docker') - .constant('CONFIG_ENDPOINT', '/api/settings') - .constant('AUTH_ENDPOINT', '/api/auth') - .constant('USERS_ENDPOINT', '/api/users') - .constant('ENDPOINTS_ENDPOINT', '/api/endpoints') - .constant('TEMPLATES_ENDPOINT', '/api/templates') + .constant('DOCKER_ENDPOINT', 'api/docker') + .constant('CONFIG_ENDPOINT', 'api/settings') + .constant('AUTH_ENDPOINT', 'api/auth') + .constant('USERS_ENDPOINT', 'api/users') + .constant('ENDPOINTS_ENDPOINT', 'api/endpoints') + .constant('TEMPLATES_ENDPOINT', 'api/templates') .constant('PAGINATION_MAX_ITEMS', 10) - .constant('UI_VERSION', 'v1.11.0'); + .constant('UI_VERSION', 'v1.11.1'); diff --git a/app/components/container/container.html b/app/components/container/container.html index 7a58a335a..a204903d4 100644 --- a/app/components/container/container.html +++ b/app/components/container/container.html @@ -174,6 +174,23 @@ +
Name | +{{ container.HostConfig.RestartPolicy.Name }} | +
MaximumRetryCount | ++ {{ container.HostConfig.RestartPolicy.MaximumRetryCount }} + | +