Add --single-namespace install flag for restricted permissions (#1721)

* Add --single-namespace install flag for restricted permissions
* Better formatting in install template
* Mark --single-namespace and --proxy-auto-inject as experimental
* Fix wording of --single-namespace check flag
* Small healthcheck refactor

Signed-off-by: Kevin Lingerfelt <kl@buoyant.io>
This commit is contained in:
Kevin Lingerfelt 2018-10-11 10:55:57 -07:00 committed by GitHub
parent 8f4240125e
commit 46c887ca00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1286 additions and 50 deletions

View File

@ -21,6 +21,7 @@ type checkOptions struct {
dataPlaneOnly bool
wait time.Duration
namespace string
singleNamespace bool
}
func newCheckOptions() *checkOptions {
@ -30,6 +31,7 @@ func newCheckOptions() *checkOptions {
dataPlaneOnly: false,
wait: 300 * time.Second,
namespace: "",
singleNamespace: false,
}
}
@ -65,6 +67,7 @@ non-zero exit code.`,
cmd.PersistentFlags().BoolVar(&options.dataPlaneOnly, "proxy", options.dataPlaneOnly, "Only run data-plane checks, to determine if the data plane is healthy")
cmd.PersistentFlags().DurationVar(&options.wait, "wait", options.wait, "Retry and wait for some checks to succeed if they don't pass the first time")
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
cmd.PersistentFlags().BoolVar(&options.singleNamespace, "single-namespace", options.singleNamespace, "When running pre-installation checks (--pre), only check the permissions required to operate the control plane in a single namespace")
return cmd
}
@ -94,6 +97,7 @@ func configureAndRunChecks(options *checkOptions) {
ShouldCheckKubeVersion: true,
ShouldCheckControlPlaneVersion: !(options.preInstallOnly || options.dataPlaneOnly),
ShouldCheckDataPlaneVersion: options.dataPlaneOnly,
SingleNamespace: options.singleNamespace,
})
success := runChecks(os.Stdout, hc)

View File

@ -58,6 +58,7 @@ type installConfig struct {
ProxyResourceRequestCPU string
ProxyResourceRequestMemory string
ProxyBindTimeout string
SingleNamespace bool
}
type installOptions struct {
@ -66,6 +67,7 @@ type installOptions struct {
prometheusReplicas uint
controllerLogLevel string
proxyAutoInject bool
singleNamespace bool
*proxyConfigOptions
}
@ -78,6 +80,7 @@ func newInstallOptions() *installOptions {
prometheusReplicas: 1,
controllerLogLevel: "info",
proxyAutoInject: false,
singleNamespace: false,
proxyConfigOptions: newProxyConfigOptions(),
}
}
@ -104,13 +107,14 @@ func newCmdInstall() *cobra.Command {
cmd.PersistentFlags().UintVar(&options.webReplicas, "web-replicas", options.webReplicas, "Replicas of the web server to deploy")
cmd.PersistentFlags().UintVar(&options.prometheusReplicas, "prometheus-replicas", options.prometheusReplicas, "Replicas of prometheus to deploy")
cmd.PersistentFlags().StringVar(&options.controllerLogLevel, "controller-log-level", options.controllerLogLevel, "Log level for the controller and web components")
cmd.PersistentFlags().BoolVar(&options.proxyAutoInject, "proxy-auto-inject", options.proxyAutoInject, "Enable proxy sidecar auto-injection webhook; default to false")
cmd.PersistentFlags().BoolVar(&options.proxyAutoInject, "proxy-auto-inject", options.proxyAutoInject, "Experimental: Enable proxy sidecar auto-injection webhook (default false)")
cmd.PersistentFlags().BoolVar(&options.singleNamespace, "single-namespace", options.singleNamespace, "Experimental: Configure the control plane to only operate in the installed namespace (default false)")
return cmd
}
func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
if err := validate(options); err != nil {
if err := options.validate(); err != nil {
return nil, err
}
@ -168,6 +172,7 @@ func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
ProxyResourceRequestCPU: options.proxyCpuRequest,
ProxyResourceRequestMemory: options.proxyMemoryRequest,
ProxyBindTimeout: "1m",
SingleNamespace: options.singleNamespace,
}, nil
}
@ -212,9 +217,14 @@ func render(config installConfig, w io.Writer, options *installOptions) error {
return InjectYAML(buf, w, ioutil.Discard, injectOptions)
}
func validate(options *installOptions) error {
func (options *installOptions) validate() error {
if _, err := log.ParseLevel(options.controllerLogLevel); err != nil {
return fmt.Errorf("--controller-log-level must be one of: panic, fatal, error, warn, info, debug")
}
return options.validate()
if options.proxyAutoInject && options.singleNamespace {
return fmt.Errorf("The --proxy-auto-inject and --single-namespace flags cannot both be specified together")
}
return options.proxyConfigOptions.validate()
}

View File

@ -19,7 +19,8 @@ func TestRender(t *testing.T) {
defaultConfig.UUID = "deaab91a-f4ab-448a-b7d1-c832a2fa0a60"
// A configuration that shows that all config setting strings are honored
// by `render()`.
// by `render()`. Note that `SingleNamespace` is tested in a separate
// configuration, since it's incompatible with `ProxyAutoInjectEnabled`.
metaConfig := installConfig{
Namespace: "Namespace",
ControllerImage: "ControllerImage",
@ -64,6 +65,33 @@ func TestRender(t *testing.T) {
ProxyBindTimeout: "1m",
}
singleNamespaceConfig := installConfig{
Namespace: "Namespace",
ControllerImage: "ControllerImage",
WebImage: "WebImage",
PrometheusImage: "PrometheusImage",
GrafanaImage: "GrafanaImage",
ControllerReplicas: 1,
WebReplicas: 2,
PrometheusReplicas: 3,
ImagePullPolicy: "ImagePullPolicy",
UUID: "UUID",
CliVersion: "CliVersion",
ControllerLogLevel: "ControllerLogLevel",
ControllerComponentLabel: "ControllerComponentLabel",
CreatedByAnnotation: "CreatedByAnnotation",
ProxyAPIPort: 123,
EnableTLS: true,
TLSTrustAnchorConfigMapName: "TLSTrustAnchorConfigMapName",
ProxyContainerName: "ProxyContainerName",
TLSTrustAnchorFileName: "TLSTrustAnchorFileName",
TLSCertFileName: "TLSCertFileName",
TLSPrivateKeyFileName: "TLSPrivateKeyFileName",
TLSTrustAnchorVolumeSpecFileName: "TLSTrustAnchorVolumeSpecFileName",
TLSIdentityVolumeSpecFileName: "TLSIdentityVolumeSpecFileName",
SingleNamespace: true,
}
testCases := []struct {
config installConfig
controlPlaneNamespace string
@ -71,6 +99,7 @@ func TestRender(t *testing.T) {
}{
{*defaultConfig, defaultControlPlaneNamespace, "testdata/install_default.golden"},
{metaConfig, metaConfig.Namespace, "testdata/install_output.golden"},
{singleNamespaceConfig, singleNamespaceConfig.Namespace, "testdata/install_single_namespace_output.golden"},
}
for i, tc := range testCases {
@ -93,3 +122,40 @@ func TestRender(t *testing.T) {
})
}
}
func TestValidate(t *testing.T) {
t.Run("Accepts the default options as valid", func(t *testing.T) {
if err := newInstallOptions().validate(); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
})
t.Run("Rejects invalid log level", func(t *testing.T) {
options := newInstallOptions()
options.controllerLogLevel = "super"
expected := "--controller-log-level must be one of: panic, fatal, error, warn, info, debug"
err := options.validate()
if err == nil {
t.Fatalf("Expected error, got nothing")
}
if err.Error() != expected {
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
}
})
t.Run("Rejects single namespace install with auto inject", func(t *testing.T) {
options := newInstallOptions()
options.proxyAutoInject = true
options.singleNamespace = true
expected := "The --proxy-auto-inject and --single-namespace flags cannot both be specified together"
err := options.validate()
if err == nil {
t.Fatalf("Expected error, got nothing")
}
if err.Error() != expected {
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
}
})
}

View File

@ -142,6 +142,7 @@ spec:
- public-api
- -prometheus-url=http://prometheus.linkerd.svc.cluster.local:9090
- -controller-namespace=linkerd
- -single-namespace=false
- -log-level=info
image: gcr.io/linkerd-io/controller:undefined
imagePullPolicy: IfNotPresent
@ -164,6 +165,8 @@ spec:
resources: {}
- args:
- destination
- -controller-namespace=linkerd
- -single-namespace=false
- -enable-tls=false
- -log-level=info
image: gcr.io/linkerd-io/controller:undefined
@ -210,8 +213,9 @@ spec:
resources: {}
- args:
- tap
- -log-level=info
- -controller-namespace=linkerd
- -single-namespace=false
- -log-level=info
image: gcr.io/linkerd-io/controller:undefined
imagePullPolicy: IfNotPresent
livenessProbe:

View File

@ -145,6 +145,7 @@ spec:
- public-api
- -prometheus-url=http://prometheus.Namespace.svc.cluster.local:9090
- -controller-namespace=Namespace
- -single-namespace=false
- -log-level=ControllerLogLevel
image: ControllerImage
imagePullPolicy: ImagePullPolicy
@ -167,6 +168,8 @@ spec:
resources: {}
- args:
- destination
- -controller-namespace=Namespace
- -single-namespace=false
- -enable-tls=true
- -log-level=ControllerLogLevel
image: ControllerImage
@ -213,8 +216,9 @@ spec:
resources: {}
- args:
- tap
- -log-level=ControllerLogLevel
- -controller-namespace=Namespace
- -single-namespace=false
- -log-level=ControllerLogLevel
image: ControllerImage
imagePullPolicy: ImagePullPolicy
livenessProbe:
@ -938,6 +942,7 @@ spec:
- args:
- ca
- -controller-namespace=Namespace
- -single-namespace=false
- -proxy-auto-inject=true
- -log-level=ControllerLogLevel
image: ControllerImage

File diff suppressed because it is too large Load Diff

View File

@ -21,10 +21,13 @@ metadata:
### Controller RBAC ###
---
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-controller
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
rules:
- apiGroups: ["extensions", "apps"]
resources: ["deployments", "replicasets"]
@ -34,13 +37,16 @@ rules:
verbs: ["list", "get", "watch"]
---
kind: ClusterRoleBinding
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-controller
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
name: linkerd-{{.Namespace}}-controller
subjects:
- kind: ServiceAccount
@ -57,23 +63,29 @@ metadata:
### Prometheus RBAC ###
---
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-prometheus
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
kind: ClusterRoleBinding
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-prometheus
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
name: linkerd-{{.Namespace}}-prometheus
subjects:
- kind: ServiceAccount
@ -152,6 +164,7 @@ spec:
- "public-api"
- "-prometheus-url=http://prometheus.{{.Namespace}}.svc.cluster.local:9090"
- "-controller-namespace={{.Namespace}}"
- "-single-namespace={{.SingleNamespace}}"
- "-log-level={{.ControllerLogLevel}}"
livenessProbe:
httpGet:
@ -173,6 +186,8 @@ spec:
imagePullPolicy: {{.ImagePullPolicy}}
args:
- "destination"
- "-controller-namespace={{.Namespace}}"
- "-single-namespace={{.SingleNamespace}}"
- "-enable-tls={{.EnableTLS}}"
- "-log-level={{.ControllerLogLevel}}"
livenessProbe:
@ -217,8 +232,9 @@ spec:
imagePullPolicy: {{.ImagePullPolicy}}
args:
- "tap"
- "-log-level={{.ControllerLogLevel}}"
- "-controller-namespace={{.Namespace}}"
- "-single-namespace={{.SingleNamespace}}"
- "-log-level={{.ControllerLogLevel}}"
livenessProbe:
httpGet:
path: /ping
@ -421,6 +437,10 @@ data:
- job_name: 'linkerd-proxy'
kubernetes_sd_configs:
- role: pod
{{- if .SingleNamespace}}
namespaces:
names: ['{{.Namespace}}']
{{- end}}
relabel_configs:
- source_labels:
- __meta_kubernetes_pod_container_name
@ -596,10 +616,13 @@ metadata:
### CA RBAC ###
---
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-ca
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
rules:
- apiGroups: [""]
resources: ["configmaps"]
@ -624,13 +647,16 @@ rules:
{{- end }}
---
kind: ClusterRoleBinding
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: linkerd-{{.Namespace}}-ca
{{- if .SingleNamespace}}
namespace: {{.Namespace}}
{{- end}}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
name: linkerd-{{.Namespace}}-ca
subjects:
- kind: ServiceAccount
@ -668,6 +694,7 @@ spec:
args:
- "ca"
- "-controller-namespace={{.Namespace}}"
- "-single-namespace={{.SingleNamespace}}"
{{- if and .EnableTLS .ProxyAutoInjectEnabled }}
- "-proxy-auto-inject={{ .ProxyAutoInjectEnabled }}"
{{- end }}

View File

@ -16,6 +16,7 @@ import (
func main() {
metricsAddr := flag.String("metrics-addr", ":9997", "address to serve scrapable metrics on")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
proxyAutoInject := flag.Bool("proxy-auto-inject", false, "if true, watch for the add and update events of mutating webhook configurations")
flags.ConfigureAndParse()
@ -28,11 +29,16 @@ func main() {
log.Fatal(err.Error())
}
restrictToNamespace := ""
if *singleNamespace {
restrictToNamespace = *controllerNamespace
}
var k8sAPI *k8s.API
if *proxyAutoInject {
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS, k8s.MWC)
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS, k8s.MWC)
} else {
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS)
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS)
}
controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI, *proxyAutoInject)

View File

@ -19,6 +19,8 @@ func main() {
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
k8sDNSZone := flag.String("kubernetes-dns-zone", "", "The DNS suffix for the local Kubernetes zone.")
enableTLS := flag.Bool("enable-tls", false, "Enable TLS connections among pods in the service mesh")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
flags.ConfigureAndParse()
stop := make(chan os.Signal, 1)
@ -28,8 +30,13 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
restrictToNamespace := ""
if *singleNamespace {
restrictToNamespace = *controllerNamespace
}
k8sAPI := k8s.NewAPI(
k8sClient,
restrictToNamespace,
k8s.Endpoint,
k8s.Pod,
k8s.RS,

View File

@ -24,6 +24,7 @@ func main() {
metricsAddr := flag.String("metrics-addr", ":9995", "address to serve scrapable metrics on")
tapAddr := flag.String("tap-addr", "127.0.0.1:8088", "address of tap service")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
ignoredNamespaces := flag.String("ignore-namespaces", "kube-system", "comma separated list of namespaces to not list pods from")
flags.ConfigureAndParse()
@ -40,10 +41,14 @@ func main() {
if err != nil {
log.Fatal(err.Error())
}
restrictToNamespace := ""
if *singleNamespace {
restrictToNamespace = *controllerNamespace
}
k8sAPI := k8s.NewAPI(
k8sClient,
restrictToNamespace,
k8s.Deploy,
k8s.NS,
k8s.Pod,
k8s.RC,
k8s.RS,

View File

@ -18,6 +18,7 @@ func main() {
metricsAddr := flag.String("metrics-addr", ":9998", "address to serve scrapable metrics on")
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
tapPort := flag.Uint("tap-port", 4190, "proxy tap port to connect to")
flags.ConfigureAndParse()
@ -28,10 +29,14 @@ func main() {
if err != nil {
log.Fatalf("failed to create Kubernetes client: %s", err)
}
restrictToNamespace := ""
if *singleNamespace {
restrictToNamespace = *controllerNamespace
}
k8sAPI := k8s.NewAPI(
clientSet,
restrictToNamespace,
k8s.Deploy,
k8s.NS,
k8s.Pod,
k8s.RC,
k8s.Svc,

View File

@ -12,6 +12,7 @@ import (
"google.golang.org/grpc/status"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
@ -28,7 +29,6 @@ const (
CM ApiResource = iota
Deploy
Endpoint
NS
Pod
RC
RS
@ -43,7 +43,6 @@ type API struct {
cm coreinformers.ConfigMapInformer
deploy appinformers.DeploymentInformer
endpoint coreinformers.EndpointsInformer
ns coreinformers.NamespaceInformer
pod coreinformers.PodInformer
rc coreinformers.ReplicationControllerInformer
rs appinformers.ReplicaSetInformer
@ -52,16 +51,28 @@ type API struct {
syncChecks []cache.InformerSynced
sharedInformers informers.SharedInformerFactory
namespace string
}
// NewAPI takes a Kubernetes client and returns an initialized API
func NewAPI(k8sClient kubernetes.Interface, resources ...ApiResource) *API {
sharedInformers := informers.NewSharedInformerFactory(k8sClient, 10*time.Minute)
func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiResource) *API {
var sharedInformers informers.SharedInformerFactory
if namespace == "" {
sharedInformers = informers.NewSharedInformerFactory(k8sClient, 10*time.Minute)
} else {
sharedInformers = informers.NewFilteredSharedInformerFactory(
k8sClient,
10*time.Minute,
namespace,
nil,
)
}
api := &API{
Client: k8sClient,
syncChecks: make([]cache.InformerSynced, 0),
sharedInformers: sharedInformers,
namespace: namespace,
}
for _, resource := range resources {
@ -75,9 +86,6 @@ func NewAPI(k8sClient kubernetes.Interface, resources ...ApiResource) *API {
case Endpoint:
api.endpoint = sharedInformers.Core().V1().Endpoints()
api.syncChecks = append(api.syncChecks, api.endpoint.Informer().HasSynced)
case NS:
api.ns = sharedInformers.Core().V1().Namespaces()
api.syncChecks = append(api.syncChecks, api.ns.Informer().HasSynced)
case Pod:
api.pod = sharedInformers.Core().V1().Pods()
api.syncChecks = append(api.syncChecks, api.pod.Informer().HasSynced)
@ -119,13 +127,6 @@ func (api *API) Sync(readyCh chan<- struct{}) {
}
}
func (api *API) NS() coreinformers.NamespaceInformer {
if api.ns == nil {
panic("NS informer not configured")
}
return api.ns
}
func (api *API) Deploy() appinformers.DeploymentInformer {
if api.deploy == nil {
panic("Deploy informer not configured")
@ -286,20 +287,32 @@ func (api *API) GetPodsFor(obj runtime.Object, includeFailed bool) ([]*apiv1.Pod
return allPods, nil
}
// getNamespaces returns the namespace matching the specified name. If no name
// is given, it returns all namespaces, unless the API was configured to only
// work with a single namespace, in which case it returns that namespace. Note
// that namespace reads are not cached.
func (api *API) getNamespaces(name string) ([]runtime.Object, error) {
var err error
var namespaces []*apiv1.Namespace
namespaces := make([]*apiv1.Namespace, 0)
if name == "" {
namespaces, err = api.NS().Lister().List(labels.Everything())
} else {
var namespace *apiv1.Namespace
namespace, err = api.NS().Lister().Get(name)
namespaces = []*apiv1.Namespace{namespace}
if name == "" && api.namespace != "" {
name = api.namespace
}
if err != nil {
return nil, err
if name == "" {
namespaceList, err := api.Client.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, item := range namespaceList.Items {
ns := item // must create separate var in order to get unique pointers
namespaces = append(namespaces, &ns)
}
} else {
namespace, err := api.Client.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
namespaces = []*apiv1.Namespace{namespace}
}
objects := []runtime.Object{}

View File

@ -10,7 +10,9 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
)
func newAPI(resourceConfigs []string, extraConfigs ...string) (*API, []runtime.Object, error) {
@ -132,6 +134,19 @@ metadata:
namespace: not-my-ns`,
},
},
getObjectsExpected{
err: nil,
namespace: "",
resType: k8s.Namespace,
name: "",
k8sResResults: []string{`
apiVersion: v1
kind: Namespace
metadata:
name: my-ns`,
},
k8sResMisc: []string{},
},
}
for _, exp := range expectations {
@ -155,6 +170,37 @@ metadata:
}
})
t.Run("In single-namespace mode", func(t *testing.T) {
t.Run("Returns only the configured namespace", func(t *testing.T) {
ns1 := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "namespace1",
},
}
ns2 := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "namespace2",
},
}
clientSet := fake.NewSimpleClientset(ns1, ns2)
api := NewAPI(clientSet, "namespace1")
namespaces, err := api.GetObjects("", k8s.Namespace, "")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if len(namespaces) != 1 {
t.Fatalf("expected 1 namespace, got %d", len(namespaces))
}
if namespaces[0].(*apiv1.Namespace).Name != "namespace1" {
t.Fatalf("expected namespace1, got %v", namespaces[0])
}
})
})
t.Run("If objects are pods", func(t *testing.T) {
t.Run("Return running or pending pods", func(t *testing.T) {
expectations := []getObjectsExpected{

View File

@ -25,10 +25,10 @@ func NewFakeAPI(configs ...string) (*API, error) {
clientSet := fake.NewSimpleClientset(objs...)
return NewAPI(
clientSet,
"",
CM,
Deploy,
Endpoint,
NS,
Pod,
RC,
RS,

View File

@ -94,6 +94,7 @@ type HealthCheckOptions struct {
ShouldCheckKubeVersion bool
ShouldCheckControlPlaneVersion bool
ShouldCheckDataPlaneVersion bool
SingleNamespace bool
}
type HealthChecker struct {
@ -197,21 +198,28 @@ func (hc *HealthChecker) addLinkerdPreInstallChecks() {
},
})
roleType := "ClusterRole"
roleBindingType := "ClusterRoleBinding"
if hc.SingleNamespace {
roleType = "Role"
roleBindingType = "RoleBinding"
}
hc.checkers = append(hc.checkers, &checker{
category: LinkerdPreInstallCategory,
description: "can create ClusterRoles",
description: fmt.Sprintf("can create %ss", roleType),
fatal: true,
check: func() error {
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", "ClusterRole")
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", roleType)
},
})
hc.checkers = append(hc.checkers, &checker{
category: LinkerdPreInstallCategory,
description: "can create ClusterRoleBindings",
description: fmt.Sprintf("can create %ss", roleBindingType),
fatal: true,
check: func() error {
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", "ClusterRoleBinding")
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", roleBindingType)
},
})