Skip to content

Commit ad2cac1

Browse files
committed
Make Cluster Roles Optional
This commit makes Cluster Roles optional within this PostgreSQL Operator. This is specifically accomplished by introducing the concept of namespace operating modes, which represent various modes of operation of the Postgres Operator that dictate which namespace capabilities are enabled (if any at all). The specific namespace mode assigned to an Operator is determined based on the specific Cluster Roles assigned to the postgres-operator Service Account. Therefore, users can choose to install the Cluster Roles required for a desired operating mode, and the Operator will automatically detect those roles and enable or disable the proper namespace capabilities. This includes allowing users to avoid the installation of any Cluster Roles altogether, fully disabling namespace capabilities. When the PostgreSQL Operator and all of its various components (i.e. the apisever, scheduler, etc.) are run, the Kubernetes environment is inspected to determine what cluster roles are currently assigned to the pgo-operator Service Account (i.e. the Service Account running the Pod the PostgreSQL Operator is running within). Based on the Cluster Roles identified, one of the following namespace operating modes will be enabled for the installation: - "dynamic": Enables full dynamic namespace capabilities, in which the Operator can create, delete and update any namespaces within the Kubernetes cluster, while then also having the ability to create the roles, role bindings and service accounts within those namespaces as required for the Operator to create PG clusters. Additionally, while in this mode the Operator can listen for namespace events (e.g. namespace additions, updates and deletions), and then create or remove controllers for various namespaces as those namespaces are added or removed from the Kubernetes cluster and/or Operator install. - "readOnly": In this mode the Operator is still able to listen for namespace events within the Kubernetetes cluster, and then create and run and/or remove controllers as namespaces are added, updated and deleted. However, while in this mode the Operator is unable to create, delete or update namespaces itself, nor can it create the RBAC it requires in any of those namespaces to create PG clusters. Therefore, while in a "readonly" mode namespaces must be pre-configured with the proper RBAC, since the Operator cannot create the RBAC itself. - "disabled": Disables namespace capabilities within the Operator altogether. While in this mode the Operator will simply attempt to work with the target namespaces specified during installation. If no target namespaces are specified, then the Operator will be configured to work within the namespace in which it is deployed. As with "readonly", while in this mode namespaces must be pre-configured with the proper RBAC, since the Operator cannot create the RBAC itself. In support of this change, both the Ansible and Bash installers have been updated to provide the user with a way of easily installing the RBAC corresponding to each namespace operating mode. The specific variables used to install the proper Cluster Roles and therefore enable the desired namespace operating mode are as follows: - Bash: PGO_NAMESPACE_MODE - Ansible: namespace_mode When using the above variables with either installer, the valid options available are: - "dynamic" - "readonly" - "disabled" When any of the values are selected during installation, the proper Cluster roles will be installed to enable the corresponding namespace operating mode as described above. The default value for either installer is "dynamic", which maintains the current default behavior when installing the Operator. And finally this commit also updates various controller logic. All namespace informers now only listen for namespaces with the proper "crunchydata" vendor label, as well as those labeled for the current Operator install. Additionally, all namespace controllers how have a resync interval of 10 minutes. Also, all controller manager functions are now thread safe, while the Scheduler and Operator now share the create the same type of namespace controller. Otherwise various other controller cleanup and/or reogranization is included.
1 parent bc812e9 commit ad2cac1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1317
-592
lines changed

apiserver/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ var (
4848
// ErrStandbyNotAllowed contains the error message returned when an API call is not
4949
// permitted because it involves a cluster that is in standby mode
5050
ErrStandbyNotAllowed = errors.New("Action not permitted because standby mode is enabled")
51+
52+
// ErrMethodNotAllowed represents the error that is thrown when a feature is disabled within the
53+
// current Operator install
54+
ErrMethodNotAllowed = errors.New("This method has is not allowed in the current PostgreSQL " +
55+
"Operator installation")
5156
)
5257

5358
// ReplicaPodStatus stores the name of the node a replica pod is assigned to, as well

apiserver/namespaceservice/namespaceimpl.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ func CreateNamespace(clientset *kubernetes.Clientset, createdBy string, request
9090
//iterate thru all the args (namespace names)
9191
for _, namespace := range request.Args {
9292

93-
err := ns.CreateNamespace(clientset, apiserver.InstallationName, apiserver.PgoNamespace, createdBy, namespace)
94-
if err != nil {
93+
if err := ns.CreateNamespaceAndRBAC(clientset, apiserver.InstallationName,
94+
apiserver.PgoNamespace, createdBy, namespace); err != nil {
9595
resp.Status.Code = msgs.Error
9696
resp.Status.Msg = err.Error()
9797
return resp
@@ -140,8 +140,8 @@ func UpdateNamespace(clientset *kubernetes.Clientset, updatedBy string, request
140140
//iterate thru all the args (namespace names)
141141
for _, namespace := range request.Args {
142142

143-
err := ns.UpdateNamespace(clientset, apiserver.InstallationName, apiserver.PgoNamespace, updatedBy, namespace)
144-
if err != nil {
143+
if err := ns.UpdateNamespaceAndRBAC(clientset, apiserver.InstallationName,
144+
apiserver.PgoNamespace, updatedBy, namespace); err != nil {
145145
resp.Status.Code = msgs.Error
146146
resp.Status.Msg = err.Error()
147147
return resp

apiserver/namespaceservice/namespaceservice.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ limitations under the License.
1717

1818
import (
1919
"encoding/json"
20+
"fmt"
21+
"net/http"
22+
2023
"github.com/crunchydata/postgres-operator/apiserver"
2124
msgs "github.com/crunchydata/postgres-operator/apiservermsgs"
25+
"github.com/crunchydata/postgres-operator/ns"
2226
log "github.com/sirupsen/logrus"
23-
"net/http"
2427
)
2528

2629
// ShowNamespaceHandler ...
@@ -54,6 +57,14 @@ func ShowNamespaceHandler(w http.ResponseWriter, r *http.Request) {
5457

5558
log.Debugf("ShowNamespaceHandler called [%v]", request)
5659

60+
// return 405 Method Not Allowed if all namespace functionality is disabled
61+
if apiserver.NamespaceOperatingMode() == ns.NamespaceOperatingModeDisabled {
62+
w.Header().Set("Allow", "")
63+
http.Error(w, fmt.Errorf("Unable to show namespaces: %w",
64+
apiserver.ErrMethodNotAllowed).Error(), http.StatusMethodNotAllowed)
65+
return
66+
}
67+
5768
username, err := apiserver.Authn(apiserver.SHOW_NAMESPACE_PERM, w, r)
5869
if err != nil {
5970
return
@@ -100,6 +111,14 @@ func CreateNamespaceHandler(w http.ResponseWriter, r *http.Request) {
100111
var request msgs.CreateNamespaceRequest
101112
_ = json.NewDecoder(r.Body).Decode(&request)
102113

114+
// return 405 Method Not Allowed if dynamic namespace functionality is not enabled
115+
if apiserver.NamespaceOperatingMode() != ns.NamespaceOperatingModeDynamic {
116+
w.Header().Set("Allow", "")
117+
http.Error(w, fmt.Errorf("Unable to create namespaces: %w",
118+
apiserver.ErrMethodNotAllowed).Error(), http.StatusMethodNotAllowed)
119+
return
120+
}
121+
103122
username, err := apiserver.Authn(apiserver.CREATE_NAMESPACE_PERM, w, r)
104123
if err != nil {
105124
return
@@ -144,6 +163,14 @@ func DeleteNamespaceHandler(w http.ResponseWriter, r *http.Request) {
144163

145164
log.Debugf("DeleteNamespaceHandler parameters [%v]", request)
146165

166+
// return 405 Method Not Allowed if dynamic namespace functionality is not enabled
167+
if apiserver.NamespaceOperatingMode() != ns.NamespaceOperatingModeDynamic {
168+
w.Header().Set("Allow", "")
169+
http.Error(w, fmt.Errorf("Unable to delete namespaces: %w",
170+
apiserver.ErrMethodNotAllowed).Error(), http.StatusMethodNotAllowed)
171+
return
172+
}
173+
147174
username, err := apiserver.Authn(apiserver.DELETE_NAMESPACE_PERM, w, r)
148175
if err != nil {
149176
return
@@ -194,6 +221,14 @@ func UpdateNamespaceHandler(w http.ResponseWriter, r *http.Request) {
194221
var request msgs.UpdateNamespaceRequest
195222
_ = json.NewDecoder(r.Body).Decode(&request)
196223

224+
// return 405 Method Not Allowed if dynamic namespace functionality is not enabled
225+
if apiserver.NamespaceOperatingMode() != ns.NamespaceOperatingModeDynamic {
226+
w.Header().Set("Allow", "")
227+
http.Error(w, fmt.Errorf("Unable to update namespaces: %w",
228+
apiserver.ErrMethodNotAllowed).Error(), http.StatusMethodNotAllowed)
229+
return
230+
}
231+
197232
username, err := apiserver.Authn(apiserver.UPDATE_NAMESPACE_PERM, w, r)
198233
if err != nil {
199234
return

apiserver/root.go

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ var BasicAuth bool
6666
var PgoNamespace string
6767
var InstallationName string
6868

69+
// namespaceList is the list of namespaces identified at install time
70+
var namespaceList []string
71+
6972
var CRUNCHY_DEBUG bool
7073

7174
// TreeTrunk is for debugging only in this context
@@ -83,6 +86,11 @@ type CredentialDetail struct {
8386

8487
var Pgo config.PgoConfig
8588

89+
// NamespaceOperatingMode defines the namespace operating mode for the cluster,
90+
// e.g. "dynamic", "readonly" or "disabled". See type NamespaceOperatingMode
91+
// for detailed explanations of each mode available.
92+
var namespaceOperatingMode ns.NamespaceOperatingMode
93+
8694
func Initialize() {
8795

8896
PgoNamespace = os.Getenv("PGO_OPERATOR_NAMESPACE")
@@ -92,9 +100,6 @@ func Initialize() {
92100
}
93101
log.Info("Pgo Namespace is [" + PgoNamespace + "]")
94102

95-
//namespaceList := util.GetNamespaces()
96-
//log.Debugf("watching the following namespaces: [%v]", namespaceList)
97-
98103
InstallationName = os.Getenv("PGO_INSTALLATION_NAME")
99104
if InstallationName == "" {
100105
log.Error("PGO_INSTALLATION_NAME environment variable is missng")
@@ -127,9 +132,19 @@ func Initialize() {
127132

128133
initConfig()
129134

130-
validateWithKube()
135+
if err := setNamespaceOperatingMode(); err != nil {
136+
log.Error(err)
137+
os.Exit(2)
138+
}
139+
140+
namespaceList, err = ns.GetNamespaceList(Clientset, NamespaceOperatingMode(),
141+
InstallationName, PgoNamespace)
142+
if err != nil {
143+
log.Error(err)
144+
os.Exit(2)
145+
}
131146

132-
//validateUserCredentials()
147+
log.Infof("Namespace operating mode is '%s'", NamespaceOperatingMode())
133148
}
134149

135150
// ConnectToKube ...
@@ -279,12 +294,7 @@ func GetNamespace(clientset *kubernetes.Clientset, username, requestedNS string)
279294
return requestedNS, errors.New(errMsg)
280295
}
281296

282-
if ns.WatchingNamespace(clientset, requestedNS, InstallationName) {
283-
return requestedNS, nil
284-
}
285-
286-
log.Debugf("GetNamespace did not find the requested namespace %s", requestedNS)
287-
return requestedNS, errors.New("requested Namespace was not found to be in the list of Namespaces being watched.")
297+
return requestedNS, nil
288298
}
289299

290300
// Authn performs HTTP Basic Authentication against a user if "BasicAuth" is set
@@ -380,37 +390,43 @@ func ValidateNodeLabel(nodeLabel string) error {
380390
return nil
381391
}
382392

383-
func validateWithKube() {
384-
log.Debug("validateWithKube called")
385-
386-
err := ns.ValidateNamespaces(Clientset, InstallationName, PgoNamespace)
387-
if err != nil {
388-
log.Error(err)
389-
os.Exit(2)
390-
}
391-
392-
}
393-
394-
//returns installation access and user access
395-
//installation access means a namespace belongs to this Operator installation
396-
//user access means this user has access to a namespace
393+
// UserIsPermittedInNamespace returns installation access and user access.
394+
// Installation access means a namespace belongs to this Operator installation.
395+
// User access means this user has access to a namespace.
397396
func UserIsPermittedInNamespace(username, requestedNS string) (bool, bool) {
398397

399398
iAccess := false
400399
uAccess := false
401400

402-
ns, found, err := kubeapi.GetNamespace(Clientset, requestedNS)
403-
if !found {
404-
log.Error(err)
405-
log.Errorf("could not find namespace %s ", requestedNS)
406-
return iAccess, uAccess
407-
}
408-
409-
if ns.ObjectMeta.Labels[config.LABEL_VENDOR] == config.LABEL_CRUNCHY &&
401+
// If the namespace operating mode isn't "disabled", then query the live Kube cluster
402+
// for the namespace and then check its labels to determine if its part of the
403+
// current Operator install. Otherwise verify that it was a namespace specified
404+
// during installation of the Operator.
405+
if namespaceOperatingMode != ns.NamespaceOperatingModeDisabled {
406+
ns, found, err := kubeapi.GetNamespace(Clientset, requestedNS)
407+
if !found {
408+
log.Error(err)
409+
log.Errorf("could not find namespace %s ", requestedNS)
410+
return iAccess, uAccess
411+
}
410412

411-
ns.ObjectMeta.Labels[config.LABEL_PGO_INSTALLATION_NAME] == InstallationName {
412-
iAccess = true
413+
if ns.ObjectMeta.Labels[config.LABEL_VENDOR] == config.LABEL_CRUNCHY &&
414+
ns.ObjectMeta.Labels[config.LABEL_PGO_INSTALLATION_NAME] == InstallationName {
415+
iAccess = true
416+
}
417+
} else {
418+
var exists bool
419+
for _, namespace := range namespaceList {
420+
if requestedNS == namespace {
421+
exists = true
422+
iAccess = true
423+
}
424+
}
413425

426+
if !exists {
427+
log.Errorf("namespace %s is not included in this installation", requestedNS)
428+
return iAccess, uAccess
429+
}
414430
}
415431

416432
//get the pgouser Secret for this username
@@ -528,3 +544,22 @@ func generateTLSCert(certPath, keyPath string) error {
528544
return err
529545

530546
}
547+
548+
// setNamespaceOperatingMode set the namespace operating mode for the Operator by calling the
549+
// proper utility function to determine which mode is applicable based on the current
550+
// permissions assigned to the Operator Service Account.
551+
func setNamespaceOperatingMode() error {
552+
nsOpMode, err := ns.GetNamespaceOperatingMode(Clientset)
553+
if err != nil {
554+
return err
555+
}
556+
namespaceOperatingMode = nsOpMode
557+
558+
return nil
559+
}
560+
561+
// NamespaceOperatingMode returns the namespace operating mode for the current Operator
562+
// installation, which is stored in the "namespaceOperatingMode" variable
563+
func NamespaceOperatingMode() ns.NamespaceOperatingMode {
564+
return namespaceOperatingMode
565+
}

conf/postgres-operator/pgo-target-role.json

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,6 @@
5050
"*"
5151
]
5252
},
53-
{
54-
"apiGroups": [
55-
""
56-
],
57-
"resources": [
58-
"storageclasses"
59-
],
60-
"verbs": [
61-
"get",
62-
"list"
63-
]
64-
},
6553
{
6654
"apiGroups": [
6755
"batch"

config/labels.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ const LABEL_CRUNCHY = "crunchydata"
169169
const LABEL_PGO_CREATED_BY = "pgo-created-by"
170170
const LABEL_PGO_UPDATED_BY = "pgo-updated-by"
171171

172-
const LABEL_PGO_DEFAULT_SC = "pgo-default-sc"
173172
const LABEL_FAILOVER_STARTED = "failover-started"
174173

175174
const GLOBAL_CUSTOM_CONFIGMAP = "pgo-custom-pg-config"

config/pgoconfig.go

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -542,14 +542,6 @@ func (c *PgoConfig) GetConfig(clientset *kubernetes.Clientset, namespace string)
542542
return err
543543
}
544544

545-
//determine the default storage class if necessary
546-
if cMap == nil {
547-
err = c.SetDefaultStorageClass(clientset)
548-
if err != nil {
549-
return err
550-
}
551-
}
552-
553545
c.CheckEnv()
554546

555547
//load up all the templates
@@ -789,49 +781,6 @@ func (c *PgoConfig) DefaultTemplate(path string) (string, error) {
789781
return value, nil
790782
}
791783

792-
func (c *PgoConfig) SetDefaultStorageClass(clientset *kubernetes.Clientset) error {
793-
794-
selector := LABEL_PGO_DEFAULT_SC + "=true"
795-
scList, err := kubeapi.GetStorageClasses(clientset, selector)
796-
if err != nil {
797-
return err
798-
}
799-
800-
if len(scList.Items) == 0 {
801-
//no pgo default sc was found, so we will use 1st sc we find
802-
scList, err = kubeapi.GetAllStorageClasses(clientset)
803-
if err != nil {
804-
return err
805-
}
806-
if len(scList.Items) == 0 {
807-
return errors.New("no storage classes were found on this Kube system")
808-
}
809-
//configure with the 1st SC on the system
810-
} else {
811-
//configure with the default pgo sc
812-
}
813-
814-
log.Infof("setting pgo-default-sc to %s", scList.Items[0].Name)
815-
816-
//add the storage class into the config
817-
c.Storage[LABEL_PGO_DEFAULT_SC] = StorageStruct{
818-
AccessMode: "ReadWriteOnce",
819-
Size: "1G",
820-
StorageType: "dynamic",
821-
StorageClass: scList.Items[0].Name,
822-
SupplementalGroups: "",
823-
MatchLabels: "",
824-
}
825-
826-
//set the default storage configs to this new one
827-
c.PrimaryStorage = LABEL_PGO_DEFAULT_SC
828-
c.BackupStorage = LABEL_PGO_DEFAULT_SC
829-
c.ReplicaStorage = LABEL_PGO_DEFAULT_SC
830-
c.BackrestStorage = LABEL_PGO_DEFAULT_SC
831-
832-
return nil
833-
}
834-
835784
// CheckEnv is mostly used for the OLM deployment use case
836785
// when someone wants to deploy with OLM, use the baked-in
837786
// configuration, but use a different set of images, by

controller/controllerutil.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,35 @@ limitations under the License.
1616
*/
1717

1818
import (
19+
"errors"
20+
1921
crv1 "github.com/crunchydata/postgres-operator/apis/crunchydata.com/v1"
2022
"github.com/crunchydata/postgres-operator/config"
2123
"github.com/crunchydata/postgres-operator/kubeapi"
2224
log "github.com/sirupsen/logrus"
2325
"k8s.io/client-go/rest"
2426
)
2527

28+
// ErrControllerGroupExists is the error that is thrown when a controller group for a specific
29+
// namespace already exists
30+
var ErrControllerGroupExists = errors.New("A controller group for the namespace specified already" +
31+
"exists")
32+
2633
// WorkerRunner is an interface for controllers the have worker queues that need to be run
2734
type WorkerRunner interface {
2835
RunWorker()
2936
}
3037

3138
// ManagerInterface defines the interface for a controller manager
3239
type ManagerInterface interface {
33-
AddControllerGroup(namespace string) error
34-
AddAndRunControllerGroup(namespace string)
40+
AddGroup(namespace string) error
41+
AddAndRunGroup(namespace string) error
42+
RemoveAll()
43+
RemoveGroup(namespace string)
3544
RunAll()
3645
RunGroup(namespace string)
3746
StopAll()
3847
StopGroup(namespace string)
39-
RemoveAll()
40-
RemoveGroup(namespace string)
4148
}
4249

4350
// InitializeReplicaCreation initializes the creation of replicas for a cluster. For a regular

0 commit comments

Comments
 (0)