Skip to content
This repository was archived by the owner on Apr 17, 2025. It is now read-only.

Commit 2dd6590

Browse files
committed
Add API to list resources under namespace tree
This change adds a new command and configuration to register an API endpoint which will allow users to query for resources belonging to descendants of any namespace. This means that finding all resources under a parent namespace can be done in one request, instead finding and iterating over all descendants. The new endpoints are: * /apis/resources.hnc.x-k8s.io/v1alpha1 - discovery endpoint * /apis/resources.hnc.x-k8s.io/v1alpha1/{resource} - global resource query, equivalent to the same global request for the original resource * /apis/resources.hnc.x-k8s.io/v1alpha1/namespaces/{namespace}/{resource} - returns resources in all namespaces under {namespace}, including {namespace}. A new subcommand for the kubectl-hns plugin is also added: kubectl hns get -n <parent ns> <resource>
1 parent fa104e8 commit 2dd6590

File tree

569 files changed

+67724
-33
lines changed

Some content is hidden

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

569 files changed

+67724
-33
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ COPY internal/ internal/
1414

1515
# Build
1616
RUN CGO_ENABLED=0 GO111MODULE=on go build -a -o manager ./cmd/manager/main.go
17+
RUN CGO_ENABLED=0 GO111MODULE=on go build -a -o apiextension ./cmd/apiextension/main.go
1718

1819
# Copied from kubebuilder scaffold to run as nonroot at
1920
# https://github.com/kubernetes-sigs/kubebuilder/blob/7af89cb00c224c57ece37dc14ea37caf1eb769db/pkg/scaffold/v2/dockerfile.go#L60
@@ -22,5 +23,6 @@ RUN CGO_ENABLED=0 GO111MODULE=on go build -a -o manager ./cmd/manager/main.go
2223
FROM gcr.io/distroless/static:nonroot
2324
WORKDIR /
2425
COPY --from=builder /workspace/manager .
26+
COPY --from=builder /workspace/apiextension .
2527
USER nonroot:nonroot
2628
ENTRYPOINT ["/manager"]

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ test-only: build-setup-envtest
113113
# Builds all binaries (manager and kubectl) and manifests
114114
build: generate fmt vet staticcheck manifests
115115
go build -o bin/manager ./cmd/manager/main.go
116+
go build -o bin/apiextension ./cmd/apiextension/main.go
116117
GOOS=linux GOARCH=amd64 go build \
117118
-o bin/kubectl/kubectl-hns_linux_amd64 \
118119
-ldflags="-X sigs.k8s.io/hierarchical-namespaces/internal/version.Version=${HNC_IMG_TAG}" \

cmd/apiextension/main.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"flag"
8+
"fmt"
9+
"net/http"
10+
"os"
11+
"time"
12+
13+
"github.com/gorilla/mux"
14+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
15+
"k8s.io/client-go/discovery"
16+
"k8s.io/client-go/dynamic"
17+
"k8s.io/client-go/dynamic/dynamicinformer"
18+
"k8s.io/client-go/informers"
19+
"k8s.io/client-go/kubernetes"
20+
corecache "k8s.io/client-go/listers/core/v1"
21+
"k8s.io/client-go/rest"
22+
"k8s.io/client-go/tools/cache"
23+
"k8s.io/client-go/tools/clientcmd"
24+
apiregv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
25+
"path/filepath"
26+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
27+
"sigs.k8s.io/hierarchical-namespaces/internal/apiextension/apiresources"
28+
"sigs.k8s.io/hierarchical-namespaces/internal/apiextension/handlers"
29+
)
30+
31+
var (
32+
setupLog = zap.New().WithName("setup-apiext")
33+
listenAddress string
34+
certPath string
35+
keyPath string
36+
debug bool
37+
)
38+
39+
func main() {
40+
parseFlags()
41+
cfg, err := getConfig()
42+
if err != nil {
43+
setupLog.Error(err, "unable to get cluster config")
44+
os.Exit(1)
45+
}
46+
server(context.Background(), cfg)
47+
}
48+
49+
func parseFlags() {
50+
setupLog.Info("Parsing flags")
51+
flag.StringVar(&listenAddress, "address", ":7443", "The address to listen on.")
52+
flag.StringVar(&certPath, "cert", "", "Path to the server cert.")
53+
flag.StringVar(&keyPath, "key", "", "Path to the server key.")
54+
flag.BoolVar(&debug, "debug", false, "Enable debug logging.")
55+
flag.Parse()
56+
}
57+
58+
func getConfig() (*rest.Config, error) {
59+
cfg, err := rest.InClusterConfig()
60+
if err == nil {
61+
return cfg, nil
62+
}
63+
kubeconfig := os.Getenv("KUBECONFIG")
64+
if kubeconfig == "" {
65+
home, err := os.UserHomeDir()
66+
if err != nil {
67+
return nil, fmt.Errorf("could not get kubeconfig: %w", err)
68+
}
69+
kubeconfig = filepath.Join(home, ".kube", "config")
70+
}
71+
cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
72+
if err != nil {
73+
return nil, err
74+
}
75+
return cfg, nil
76+
}
77+
78+
func server(ctx context.Context, cfg *rest.Config) {
79+
discovery, err := discovery.NewDiscoveryClientForConfig(cfg)
80+
if err != nil {
81+
setupLog.Error(err, "could not start watcher")
82+
os.Exit(1)
83+
}
84+
dynamicClient, err := dynamic.NewForConfig(cfg)
85+
if err != nil {
86+
setupLog.Error(err, "could not get dynammic client for config")
87+
os.Exit(1)
88+
}
89+
dynamicFactory := getDynamicInformerFactory(dynamicClient)
90+
crdInformer, apiServiceInformer := setUpAPIInformers(dynamicFactory, ctx.Done())
91+
apis := apiresources.WatchAPIResources(ctx, discovery, crdInformer, apiServiceInformer)
92+
factory, err := getInformerFactory(cfg)
93+
if err != nil {
94+
setupLog.Error(err, "could not get informer factory")
95+
os.Exit(1)
96+
}
97+
namespaceCache, configMapCache, err := setUpInformers(factory, ctx.Done())
98+
if err != nil {
99+
setupLog.Error(err, "failed to set up informers")
100+
os.Exit(1)
101+
}
102+
mux := mux.NewRouter()
103+
mux.HandleFunc("/apis/resources.hnc.x-k8s.io/v1alpha1", handlers.DiscoveryHandler(apis))
104+
mux.HandleFunc("/apis/resources.hnc.x-k8s.io/v1alpha1/{resource}", handlers.Forwarder(dynamicClient, apis))
105+
mux.HandleFunc("/apis/resources.hnc.x-k8s.io/v1alpha1/namespaces/{namespace}/{resource}", handlers.NamespaceHandler(apis, namespaceCache, dynamicClient))
106+
mux.Use(handlers.AuthenticateMiddleware(configMapCache))
107+
108+
clientCA, err := getClientCA(configMapCache)
109+
if err != nil {
110+
setupLog.Error(err, "could not get client CA from configmap")
111+
os.Exit(1)
112+
}
113+
caCertPool := x509.NewCertPool()
114+
caCertPool.AppendCertsFromPEM([]byte(clientCA))
115+
tlsConfig := &tls.Config{
116+
ClientCAs: caCertPool,
117+
ClientAuth: tls.RequireAndVerifyClientCert,
118+
}
119+
server := http.Server{
120+
Addr: listenAddress,
121+
Handler: mux,
122+
TLSConfig: tlsConfig,
123+
}
124+
setupLog.Info(fmt.Sprintf("starting server on %s", listenAddress))
125+
err = server.ListenAndServeTLS(certPath, keyPath)
126+
if err != nil {
127+
setupLog.Error(err, "could not start server")
128+
os.Exit(1)
129+
}
130+
}
131+
132+
func getClientCA(configMapCache corecache.ConfigMapNamespaceLister) (string, error) {
133+
config, err := configMapCache.Get(handlers.ExtensionConfigMap)
134+
if err != nil {
135+
return "", err
136+
}
137+
clientCA, ok := config.Data[handlers.ClientCAKey]
138+
if !ok {
139+
return "", fmt.Errorf("invalid extension config")
140+
}
141+
return string(clientCA), nil
142+
}
143+
144+
func getInformerFactory(cfg *rest.Config) (informers.SharedInformerFactory, error) {
145+
clientset, err := kubernetes.NewForConfig(cfg)
146+
if err != nil {
147+
return nil, err
148+
}
149+
return informers.NewSharedInformerFactory(clientset, 0), nil
150+
}
151+
152+
func getDynamicInformerFactory(dynamicClient dynamic.Interface) dynamicinformer.DynamicSharedInformerFactory {
153+
return dynamicinformer.NewDynamicSharedInformerFactory(dynamicClient, time.Minute)
154+
}
155+
156+
func setUpInformers(factory informers.SharedInformerFactory, stop <-chan struct{}) (corecache.NamespaceLister, corecache.ConfigMapNamespaceLister, error) {
157+
namespaceInformer := factory.Core().V1().Namespaces()
158+
configMapInformer := factory.Core().V1().ConfigMaps()
159+
go factory.Start(stop)
160+
if !cache.WaitForCacheSync(stop, namespaceInformer.Informer().HasSynced, configMapInformer.Informer().HasSynced) {
161+
return nil, nil, fmt.Errorf("cached failed to sync")
162+
}
163+
return namespaceInformer.Lister(), configMapInformer.Lister().ConfigMaps(handlers.KubeSystemNamespace), nil
164+
}
165+
166+
func setUpAPIInformers(factory dynamicinformer.DynamicSharedInformerFactory, stop <-chan struct{}) (cache.SharedIndexInformer, cache.SharedIndexInformer) {
167+
crdGVR := apiextv1.SchemeGroupVersion.WithResource("customresourcedefinitions")
168+
crdInformer := factory.ForResource(crdGVR).Informer()
169+
apiServiceGVR := apiregv1.SchemeGroupVersion.WithResource("apiservices")
170+
apiServiceInformer := factory.ForResource(apiServiceGVR).Informer()
171+
go factory.Start(stop)
172+
return crdInformer, apiServiceInformer
173+
}

config/apiextension/apiextension.yaml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
apiVersion: rbac.authorization.k8s.io/v1
3+
kind: ClusterRole
4+
metadata:
5+
name: resourcelist
6+
rules:
7+
- apiGroups:
8+
- "*"
9+
resources:
10+
- "*"
11+
verbs:
12+
- get
13+
- list
14+
- watch
15+
---
16+
apiVersion: rbac.authorization.k8s.io/v1
17+
kind: ClusterRoleBinding
18+
metadata:
19+
name: resourcelist
20+
roleRef:
21+
apiGroup: rbac.authorization.k8s.io
22+
kind: ClusterRole
23+
name: resourcelist
24+
subjects:
25+
- kind: ServiceAccount
26+
name: default
27+
namespace: hnc-system
28+
---
29+
apiVersion: apps/v1
30+
kind: Deployment
31+
metadata:
32+
labels:
33+
app: resourcelist
34+
name: resourcelist
35+
spec:
36+
replicas: 1
37+
selector:
38+
matchLabels:
39+
app: resourcelist
40+
template:
41+
metadata:
42+
labels:
43+
app: resourcelist
44+
spec:
45+
securityContext:
46+
fsGroup: 2000
47+
runAsNonRoot: true
48+
runAsUser: 1000
49+
containers:
50+
- image: controller:latest # this is usually overridden by kustomize
51+
name: resourcelist
52+
command:
53+
- /apiextension
54+
args:
55+
- "--cert=/certs/tls.crt"
56+
- "--key=/certs/tls.key"
57+
imagePullPolicy: IfNotPresent
58+
volumeMounts:
59+
- name: certs
60+
mountPath: /certs
61+
securityContext:
62+
allowPrivilegeEscalation: false
63+
readOnlyRootFilesystem: true
64+
runAsNonRoot: true
65+
seccompProfile:
66+
type: RuntimeDefault
67+
capabilities:
68+
drop: ["ALL"]
69+
ports:
70+
- containerPort: 7443
71+
name: server
72+
protocol: TCP
73+
volumes:
74+
- secret:
75+
defaultMode: 420
76+
secretName: hnc-resourcelist
77+
name: certs
78+
---
79+
apiVersion: v1
80+
kind: Service
81+
metadata:
82+
labels:
83+
app: resourcelist
84+
name: resourcelist
85+
spec:
86+
ports:
87+
- port: 7443
88+
protocol: TCP
89+
targetPort: 7443
90+
selector:
91+
app: resourcelist
92+
---
93+
apiVersion: cert-manager.io/v1
94+
kind: Certificate
95+
metadata:
96+
name: resourcelist
97+
spec:
98+
dnsNames:
99+
- hnc-resourcelist.hnc-system.svc
100+
- hnc-resourcelist.hnc-system.svc.cluster.local
101+
issuerRef:
102+
kind: Issuer
103+
name: selfsigned-issuer
104+
secretName: hnc-resourcelist
105+
---
106+
apiVersion: apiregistration.k8s.io/v1
107+
kind: APIService
108+
metadata:
109+
name: v1alpha1.resources.hnc.x-k8s.io
110+
annotations:
111+
cert-manager.io/inject-ca-from: hnc-system/hnc-resourcelist
112+
spec:
113+
group: resources.hnc.x-k8s.io
114+
version: v1alpha1
115+
groupPriorityMinimum: 10
116+
versionPriority: 10
117+
service:
118+
namespace: hnc-system
119+
name: resourcelist
120+
port: 7443
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
resources:
2+
- apiextension.yaml

config/variants/default-cc/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ bases:
1414
- ../../manager
1515
- ../../rbac
1616
- ../../webhook
17+
- ../../apiextension
1718

1819
patches:
1920
- patch: |-

config/variants/default-cm/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ bases:
1414
- ../../manager
1515
- ../../rbac
1616
- ../../webhook
17+
- ../../apiextension
1718

1819
patchesStrategicMerge:
1920
- webhookcainjection_patch.yaml

config/variants/hrq/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ bases:
1414
- ../../manager
1515
- ../../rbac
1616
- ../../webhook
17+
- ../../apiextension
1718

1819
patchesStrategicMerge:
1920
- webhook_patch.yaml

go.mod

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,12 @@ require (
9393
golang.org/x/mod v0.7.0 // indirect
9494
golang.org/x/net v0.3.0 // indirect
9595
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
96-
golang.org/x/sync v0.1.0 // indirect
96+
golang.org/x/sync v0.1.0
9797
golang.org/x/sys v0.3.0 // indirect
9898
golang.org/x/term v0.3.0 // indirect
9999
golang.org/x/text v0.5.0
100100
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
101101
golang.org/x/tools v0.4.1-0.20221208213631-3f74d914ae6d // indirect
102-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
103102
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
104103
google.golang.org/api v0.44.0 // indirect
105104
google.golang.org/appengine v1.6.7 // indirect
@@ -121,6 +120,15 @@ require (
121120
)
122121

123122
require (
123+
github.com/gorilla/mux v1.8.0
124+
k8s.io/apiserver v0.23.2
125+
k8s.io/kube-aggregator v0.23.2
126+
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe
127+
)
128+
129+
require (
130+
github.com/blang/semver v3.5.1+incompatible // indirect
131+
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
132+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
124133
github.com/spf13/afero v1.6.0 // indirect
125-
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20230208013708-22718275bffe // indirect
126134
)

0 commit comments

Comments
 (0)