Compare commits
2 Commits
2.21.3
...
feat/EE-81
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65e4dfa4c0 | ||
|
|
08d0354371 |
@@ -41,6 +41,11 @@ func (m *Monitor) Start() {
|
||||
logFatalf("%s", err)
|
||||
}
|
||||
if !initialized {
|
||||
err = m.DisableInstanceInit()
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] [internal,init] [message: fail to disable initialization monitor: %s]\n", err)
|
||||
}
|
||||
|
||||
logFatalf("[FATAL] [internal,init] No administrator account was created in %f mins. Shutting down the Portainer instance for security reasons", m.timeout.Minutes())
|
||||
}
|
||||
case <-cancellationCtx.Done():
|
||||
@@ -68,3 +73,10 @@ func (m *Monitor) WasInitialized() (bool, error) {
|
||||
}
|
||||
return len(users) > 0, nil
|
||||
}
|
||||
|
||||
func (m *Monitor) DisableInstanceInit() error {
|
||||
state := portainer.State{
|
||||
DisableAdminInit: true,
|
||||
}
|
||||
return m.datastore.State().UpdateState(&state)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func Test_canStopStartedMonitor(t *testing.T) {
|
||||
func Test_start_shouldFatalAfterTimeout_ifNotInitialized(t *testing.T) {
|
||||
timeout := 10 * time.Millisecond
|
||||
|
||||
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}))
|
||||
datastore := i.NewDatastore(i.WithUsers([]portainer.User{}), i.WithState(&portainer.State{}))
|
||||
|
||||
var fataled bool
|
||||
origLogFatalf := logFatalf
|
||||
|
||||
@@ -61,6 +61,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
MaxBatchSize: kingpin.Flag("max-batch-size", "Maximum size of a batch").Int(),
|
||||
MaxBatchDelay: kingpin.Flag("max-batch-delay", "Maximum delay before a batch starts").Duration(),
|
||||
SecretKeyName: kingpin.Flag("secret-key-name", "Secret key name for encryption and will be used as /run/secrets/<secret-key-name>.").Default(defaultSecretKeyName).String(),
|
||||
EnableAdminInit: kingpin.Flag("enable-admin-init", "Enable Portainer admin initialization ability if the previous initialization expires").Bool(),
|
||||
}
|
||||
|
||||
kingpin.Parse()
|
||||
|
||||
@@ -125,6 +125,11 @@ func initDataStore(flags *portainer.CLIFlags, secretKey []byte, fileService port
|
||||
}
|
||||
}
|
||||
|
||||
err = updateStateFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed updating state from flags: %v", err)
|
||||
}
|
||||
|
||||
err = updateSettingsFromFlags(store, flags)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed updating settings from flags: %v", err)
|
||||
@@ -252,6 +257,24 @@ func initStatus(instanceID string) *portainer.Status {
|
||||
}
|
||||
}
|
||||
|
||||
func updateStateFromFlags(dataStore dataservices.DataStore, flags *portainer.CLIFlags) error {
|
||||
state, err := dataStore.State().State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !state.DisableAdminInit {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !*flags.EnableAdminInit {
|
||||
logrus.Fatalln("[FATAL] [internal,init] The Portainer instance initialization is disabled. Restarting the Portainer instance with the optional parameter enable-init")
|
||||
}
|
||||
|
||||
state.DisableAdminInit = !*flags.EnableAdminInit
|
||||
return dataStore.State().UpdateState(state)
|
||||
}
|
||||
|
||||
func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.CLIFlags) error {
|
||||
settings, err := dataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
|
||||
@@ -47,6 +47,7 @@ type (
|
||||
User() UserService
|
||||
Version() VersionService
|
||||
Webhook() WebhookService
|
||||
State() StateService
|
||||
}
|
||||
|
||||
// CustomTemplateService represents a service to manage custom templates
|
||||
@@ -298,6 +299,13 @@ type (
|
||||
DeleteWebhook(ID portainer.WebhookID) error
|
||||
BucketName() string
|
||||
}
|
||||
|
||||
// State represents a service for managing application state
|
||||
StateService interface {
|
||||
State() (*portainer.State, error)
|
||||
UpdateState(state *portainer.State) error
|
||||
BucketName() string
|
||||
}
|
||||
)
|
||||
|
||||
func IsErrObjectNotFound(e error) bool {
|
||||
|
||||
49
api/dataservices/state/state.go
Normal file
49
api/dataservices/state/state.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// BucketName represents the name of the bucket where this service stores data.
|
||||
BucketName = "state"
|
||||
key = "STATE"
|
||||
)
|
||||
|
||||
// Service represents a service for managing environment(endpoint) data.
|
||||
type Service struct {
|
||||
connection portainer.Connection
|
||||
}
|
||||
|
||||
func (service *Service) BucketName() string {
|
||||
return BucketName
|
||||
}
|
||||
|
||||
// NewService creates a new instance of a service.
|
||||
func NewService(connection portainer.Connection) (*Service, error) {
|
||||
err := connection.SetServiceName(BucketName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
connection: connection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// State retrieve the state object.
|
||||
func (service *Service) State() (*portainer.State, error) {
|
||||
var state portainer.State
|
||||
|
||||
err := service.connection.GetObject(BucketName, []byte(key), &state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
// UpdateState persists a State object.
|
||||
func (service *Service) UpdateState(state *portainer.State) error {
|
||||
return service.connection.UpdateObject(BucketName, []byte(key), state)
|
||||
}
|
||||
@@ -27,6 +27,11 @@ func (store *Store) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.checkOrCreateDefaultState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,3 +129,16 @@ func (store *Store) checkOrCreateDefaultData() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) checkOrCreateDefaultState() error {
|
||||
_, err := store.State().State()
|
||||
if store.IsErrObjectNotFound(err) {
|
||||
defaultState := &portainer.State{
|
||||
DisableAdminInit: false,
|
||||
}
|
||||
|
||||
return store.State().UpdateState(defaultState)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/portainer/portainer/api/dataservices/settings"
|
||||
"github.com/portainer/portainer/api/dataservices/ssl"
|
||||
"github.com/portainer/portainer/api/dataservices/stack"
|
||||
"github.com/portainer/portainer/api/dataservices/state"
|
||||
"github.com/portainer/portainer/api/dataservices/tag"
|
||||
"github.com/portainer/portainer/api/dataservices/team"
|
||||
"github.com/portainer/portainer/api/dataservices/teammembership"
|
||||
@@ -68,6 +69,7 @@ type Store struct {
|
||||
UserService *user.Service
|
||||
VersionService *version.Service
|
||||
WebhookService *webhook.Service
|
||||
StateService *state.Service
|
||||
}
|
||||
|
||||
func (store *Store) initServices() error {
|
||||
@@ -227,6 +229,12 @@ func (store *Store) initServices() error {
|
||||
}
|
||||
store.ScheduleService = scheduleService
|
||||
|
||||
stateService, err := state.NewService(store.connection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.StateService = stateService
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -345,6 +353,11 @@ func (store *Store) Webhook() dataservices.WebhookService {
|
||||
return store.WebhookService
|
||||
}
|
||||
|
||||
// State gives access to the State data management layer
|
||||
func (store *Store) State() dataservices.StateService {
|
||||
return store.StateService
|
||||
}
|
||||
|
||||
type storeExport struct {
|
||||
CustomTemplate []portainer.CustomTemplate `json:"customtemplates,omitempty"`
|
||||
EdgeGroup []portainer.EdgeGroup `json:"edgegroups,omitempty"`
|
||||
|
||||
@@ -32,6 +32,7 @@ type testDatastore struct {
|
||||
user dataservices.UserService
|
||||
version dataservices.VersionService
|
||||
webhook dataservices.WebhookService
|
||||
state dataservices.StateService
|
||||
}
|
||||
|
||||
func (d *testDatastore) BackupTo(io.Writer) error { return nil }
|
||||
@@ -74,6 +75,7 @@ func (d *testDatastore) TunnelServer() dataservices.TunnelServerService { re
|
||||
func (d *testDatastore) User() dataservices.UserService { return d.user }
|
||||
func (d *testDatastore) Version() dataservices.VersionService { return d.version }
|
||||
func (d *testDatastore) Webhook() dataservices.WebhookService { return d.webhook }
|
||||
func (d *testDatastore) State() dataservices.StateService { return d.state }
|
||||
|
||||
func (d *testDatastore) IsErrObjectNotFound(e error) bool {
|
||||
return false
|
||||
@@ -267,3 +269,18 @@ func WithEndpoints(endpoints []portainer.Endpoint) datastoreOption {
|
||||
d.endpoint = &stubEndpointService{endpoints: endpoints}
|
||||
}
|
||||
}
|
||||
|
||||
type stubStateService struct {
|
||||
state *portainer.State
|
||||
}
|
||||
|
||||
func (s *stubStateService) BucketName() string { return "state" }
|
||||
func (s *stubStateService) State() (*portainer.State, error) { return s.state, nil }
|
||||
func (s *stubStateService) UpdateState(state *portainer.State) error { return nil }
|
||||
|
||||
// WithState testDatastore option that will instruct testDatastore to return provided state
|
||||
func WithState(s *portainer.State) datastoreOption {
|
||||
return func(d *testDatastore) {
|
||||
d.state = &stubStateService{state: s}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ type (
|
||||
MaxBatchSize *int
|
||||
MaxBatchDelay *time.Duration
|
||||
SecretKeyName *string
|
||||
EnableAdminInit *bool
|
||||
}
|
||||
|
||||
// CustomTemplate represents a custom template
|
||||
@@ -1170,6 +1171,12 @@ type (
|
||||
// WebhookType represents the type of resource a webhook is related to
|
||||
WebhookType int
|
||||
|
||||
// State represents the state of PortainerInstance
|
||||
State struct {
|
||||
// Disable the Portainer instance if the initialization set up expires
|
||||
DisableAdminInit bool `json:"DisableAdminInit" example:"false"`
|
||||
}
|
||||
|
||||
// CLIService represents a service for managing CLI
|
||||
CLIService interface {
|
||||
ParseFlags(version string) (*CLIFlags, error)
|
||||
|
||||
Reference in New Issue
Block a user