Skip to content

Commit

Permalink
Migrate namespaces retrieval from Kubeops to Kubeapps APIs (#5239)
Browse files Browse the repository at this point in the history
* Migrate namespaces retrieval from Kubeops to Kubeapps APIs

Signed-off-by: Rafa Castelblanque <rcastelblanq@vmware.com>

* Added missing copyright headers

Signed-off-by: Rafa Castelblanque <rcastelblanq@vmware.com>

* Leave the two CLI params as deprecated

Signed-off-by: Rafa Castelblanque <rcastelblanq@vmware.com>

* Updated README.md

Signed-off-by: Rafa Castelblanque <rcastelblanq@vmware.com>

Signed-off-by: Rafa Castelblanque <rcastelblanq@vmware.com>
  • Loading branch information
castelblanque committed Aug 31, 2022
1 parent 2403fcf commit 83b277a
Show file tree
Hide file tree
Showing 22 changed files with 783 additions and 611 deletions.
4 changes: 2 additions & 2 deletions chart/kubeapps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,6 @@ Once you have installed Kubeapps follow the [Getting Started Guide](https://gith
| `kubeops.image.digest` | Kubeops image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` |
| `kubeops.image.pullPolicy` | Kubeops image pull policy | `IfNotPresent` |
| `kubeops.image.pullSecrets` | Kubeops image pull secrets | `[]` |
| `kubeops.namespaceHeaderName` | Additional header name for trusted namespaces | `""` |
| `kubeops.namespaceHeaderPattern` | Additional header pattern for trusted namespaces | `""` |
| `kubeops.qps` | Kubeops QPS (queries per second) rate | `""` |
| `kubeops.burst` | Kubeops burst rate | `""` |
| `kubeops.extraFlags` | Additional command line flags for Kubeops | `[]` |
Expand Down Expand Up @@ -549,6 +547,8 @@ Once you have installed Kubeapps follow the [Getting Started Guide](https://gith
| `kubeappsapis.pluginConfig.kappController.packages.v1alpha1.defaultAllowDowngrades` | Default policy for allowing applications to be downgraded to previous versions | `false` |
| `kubeappsapis.pluginConfig.flux.packages.v1alpha1.defaultUpgradePolicy` | Default upgrade policy generating version constraints | `none` |
| `kubeappsapis.pluginConfig.flux.packages.v1alpha1.userManagedSecrets` | Default policy for handling repository secrets, either managed by the user or by kubeapps-apis | `false` |
| `kubeappsapis.pluginConfig.resources.packages.v1alpha1.trustedNamespaces.headerName` | Optional header name for trusted namespaces | `""` |
| `kubeappsapis.pluginConfig.resources.packages.v1alpha1.trustedNamespaces.headerPattern` | Optional header pattern for trusted namespaces | `""` |
| `kubeappsapis.image.registry` | Kubeapps-APIs image registry | `docker.io` |
| `kubeappsapis.image.repository` | Kubeapps-APIs image repository | `kubeapps/kubeapps-apis` |
| `kubeappsapis.image.tag` | Kubeapps-APIs image tag (immutable tags are recommended) | `latest` |
Expand Down
42 changes: 42 additions & 0 deletions chart/kubeapps/templates/kubeappsapis/rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{{- if .Values.rbac.create -}}
apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }}
kind: ClusterRole
metadata:
name: {{ printf "kubeapps:%s:kubeappsapis-ns-discovery" .Release.Namespace | quote }}
labels: {{- include "common.labels.standard" . | nindent 4 }}
app.kubernetes.io/component: kubeappsapis
{{- if .Values.commonLabels }}
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" . ) | nindent 4 }}
{{- end }}
{{- if .Values.commonAnnotations }}
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- list
---
apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }}
kind: ClusterRoleBinding
metadata:
name: {{ printf "kubeapps:%s:kubeappsapis-ns-discovery" .Release.Namespace | quote }}
labels: {{- include "common.labels.standard" . | nindent 4 }}
app.kubernetes.io/component: kubeappsapis
{{- if .Values.commonLabels }}
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" . ) | nindent 4 }}
{{- end }}
{{- if .Values.commonAnnotations }}
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ printf "kubeapps:%s:kubeappsapis-ns-discovery" .Release.Namespace | quote }}
subjects:
- kind: ServiceAccount
name: {{ template "kubeapps.kubeappsapis.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end -}}
26 changes: 16 additions & 10 deletions chart/kubeapps/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1156,16 +1156,6 @@ kubeops:
## - myRegistryKeySecretName
##
pullSecrets: []
## @param kubeops.namespaceHeaderName Additional header name for trusted namespaces
## e.g:
## namespaceHeaderName: X-Consumer-Groups
##
namespaceHeaderName: ""
## @param kubeops.namespaceHeaderPattern Additional header pattern for trusted namespaces
## e.g:
## namespaceHeaderPattern: namespace:^([\w-]+):\w+$
##
namespaceHeaderPattern: ""
## @param kubeops.qps Kubeops QPS (queries per second) rate
##
qps: ""
Expand Down Expand Up @@ -1843,6 +1833,22 @@ kubeappsapis:
defaultUpgradePolicy: none
## @param kubeappsapis.pluginConfig.flux.packages.v1alpha1.userManagedSecrets Default policy for handling repository secrets, either managed by the user or by kubeapps-apis
userManagedSecrets: false
resources:
packages:
v1alpha1:
## Trusted namespaces parameters
##
trustedNamespaces:
## @param kubeappsapis.pluginConfig.resources.packages.v1alpha1.trustedNamespaces.headerName Optional header name for trusted namespaces
## e.g:
## headerName: X-Consumer-Groups
##
headerName: ""
## @param kubeappsapis.pluginConfig.resources.packages.v1alpha1.trustedNamespaces.headerPattern Optional header pattern for trusted namespaces
## e.g:
## headerPattern: namespace:^([\w-]+):\w+$
##
headerPattern: ""
## Bitnami Kubeapps-APIs image
## ref: https://hub.docker.com/r/bitnami/kubeapps-apis/tags/
## @param kubeappsapis.image.registry Kubeapps-APIs image registry
Expand Down
61 changes: 61 additions & 0 deletions cmd/kubeapps-apis/plugins/resources/v1alpha1/common/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2022 the Kubeapps contributors.
// SPDX-License-Identifier: Apache-2.0

package common

import (
"encoding/json"
"fmt"
"os"
)

type ResourcesPluginConfig struct {
TrustedNamespaces TrustedNamespaces
}

type TrustedNamespaces struct {
HeaderName string
HeaderPattern string
}

func NewDefaultPluginConfig() *ResourcesPluginConfig {
// If no config is provided, we default to the existing values for backwards compatibility.
return &ResourcesPluginConfig{}
}

// ParsePluginConfig parses the input plugin configuration json file and returns the configuration options.
func ParsePluginConfig(pluginConfigPath string) (*ResourcesPluginConfig, error) {

// Resources plugin config defines the following struct and json config
type resourcesConfig struct {
Resources struct {
Packages struct {
V1alpha1 struct {
TrustedNamespaces struct {
HeaderName string `json:"headerName"`
HeaderPattern string `json:"headerPattern"`
} `json:"trustedNamespaces"`
} `json:"v1alpha1"`
} `json:"packages"`
} `json:"resources"`
}
var config resourcesConfig

// #nosec G304
pluginConfig, err := os.ReadFile(pluginConfigPath)
if err != nil {
return nil, fmt.Errorf("unable to open plugin config at %q: %w", pluginConfigPath, err)
}
err = json.Unmarshal(pluginConfig, &config)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal pluginconfig: %q error: %w", string(pluginConfig), err)
}

// return configured value
return &ResourcesPluginConfig{
TrustedNamespaces: TrustedNamespaces{
HeaderName: config.Resources.Packages.V1alpha1.TrustedNamespaces.HeaderName,
HeaderPattern: config.Resources.Packages.V1alpha1.TrustedNamespaces.HeaderPattern,
},
}, nil
}
97 changes: 97 additions & 0 deletions cmd/kubeapps-apis/plugins/resources/v1alpha1/common/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2022 the Kubeapps contributors.
// SPDX-License-Identifier: Apache-2.0
package common

import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/vmware-tanzu/kubeapps/cmd/kubeapps-apis/plugins/pkg/pkgutils"
log "k8s.io/klog/v2"
"os"
"runtime"
"sigs.k8s.io/yaml"
"strings"
"testing"
)

func TestParsePluginConfig(t *testing.T) {
testCases := []struct {
name string
pluginYAMLConf []byte
expectedConfig *ResourcesPluginConfig
expectedError string
}{
{
name: "non existing plugin-config file",
pluginYAMLConf: nil,
expectedConfig: &ResourcesPluginConfig{},
expectedError: "",
},
{
name: "invalid plugin config",
pluginYAMLConf: []byte(`
resources:
packages:
v1alpha1:
trustedNamespaces:
headerName: true
`),
expectedConfig: nil,
expectedError: "json: cannot unmarshal",
},
{
name: "non-default, valid plugin config",
pluginYAMLConf: []byte(`
resources:
packages:
v1alpha1:
trustedNamespaces:
headerName: "X-Consumer-Groups"
headerPattern: "^namespace:([\\w-]+)$"
`),
expectedConfig: &ResourcesPluginConfig{
TrustedNamespaces: TrustedNamespaces{
HeaderName: "X-Consumer-Groups",
HeaderPattern: "^namespace:([\\w-]+)$",
},
},
expectedError: "",
},
}
opts := cmpopts.IgnoreUnexported(pkgutils.VersionsInSummary{})
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// TODO(agamez): env vars and file paths should be handled properly for Windows operating system
if runtime.GOOS == "windows" {
t.Skip("Skipping in a Windows OS")
}
filename := ""
if tc.pluginYAMLConf != nil {
pluginJSONConf, err := yaml.YAMLToJSON(tc.pluginYAMLConf)
if err != nil {
log.Fatalf("%s", err)
}
f, err := os.CreateTemp(".", "plugin_json_conf")
if err != nil {
log.Fatalf("%s", err)
}
defer os.Remove(f.Name()) // clean up
if _, err := f.Write(pluginJSONConf); err != nil {
log.Fatalf("%s", err)
}
if err := f.Close(); err != nil {
log.Fatalf("%s", err)
}
filename = f.Name()
}
pluginConfig, err := ParsePluginConfig(filename)
if err != nil && !strings.Contains(err.Error(), tc.expectedError) {
t.Errorf("err got %q, want to find %q", err.Error(), tc.expectedError)
} else if pluginConfig != nil {
if got, want := pluginConfig, tc.expectedConfig; !cmp.Equal(want, got, opts) {
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(want, got, opts))
}
}
})
}
}
5 changes: 4 additions & 1 deletion cmd/kubeapps-apis/plugins/resources/v1alpha1/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ func init() {

// RegisterWithGRPCServer enables a plugin to register with a gRPC server
// returning the server implementation.
//
//nolint:deadcode
func RegisterWithGRPCServer(opts pluginsv1alpha1.GRPCPluginRegistrationOptions) (interface{}, error) {
svr, err := NewServer(opts.ConfigGetter, opts.ClientQPS, opts.ClientBurst)
svr, err := NewServer(opts.ConfigGetter, opts.ClientQPS, opts.ClientBurst, opts.PluginConfigPath)
if err != nil {
return nil, err
}
Expand All @@ -39,12 +40,14 @@ func RegisterWithGRPCServer(opts pluginsv1alpha1.GRPCPluginRegistrationOptions)

// RegisterHTTPHandlerFromEndpoint enables a plugin to register an http
// handler to translate to the gRPC request.
//
//nolint:deadcode
func RegisterHTTPHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error {
return v1alpha1.RegisterResourcesServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)
}

// GetPluginDetail returns a core.plugins.Plugin describing itself.
//
//nolint:deadcode
func GetPluginDetail() *pluginsgrpcv1alpha1.Plugin {
return &pluginDetail
Expand Down
19 changes: 8 additions & 11 deletions cmd/kubeapps-apis/plugins/resources/v1alpha1/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,25 @@ func (s *Server) CreateNamespace(ctx context.Context, r *v1alpha1.CreateNamespac
return &v1alpha1.CreateNamespaceResponse{}, nil
}

// GetNamespaceNames returns the list of namespace names for a cluster if the
// user has the required RBAC.
//
// Note that we can't yet use this from the dashboard to replace the similar endpoint
// in kubeops until we update to ensure a configured service account can also be
// passed in (resources plugin config) and used if the user does not have RBAC.
// GetNamespaceNames returns the list of namespace names from either the cluster or the incoming trusted namespaces.
// In any case, only if the user has the required RBAC.
func (s *Server) GetNamespaceNames(ctx context.Context, r *v1alpha1.GetNamespaceNamesRequest) (*v1alpha1.GetNamespaceNamesResponse, error) {
cluster := r.GetCluster()
log.InfoS("+resources GetNamespaceNames ", "cluster", cluster)

typedClient, _, err := s.clientGetter(ctx, cluster)
// Check if there are trusted namespaces in the request
trustedNamespaces, err := getTrustedNamespacesFromHeader(ctx, s.pluginConfig.TrustedNamespaces.HeaderName, s.pluginConfig.TrustedNamespaces.HeaderPattern)
if err != nil {
return nil, status.Errorf(codes.Internal, "unable to get the k8s client: '%v'", err)
return nil, statuserror.FromK8sError("get", "Namespaces", "", err)
}

namespaceList, err := typedClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
namespaceList, err := s.GetAccessibleNamespaces(ctx, cluster, trustedNamespaces)
if err != nil {
return nil, statuserror.FromK8sError("list", "Namespaces", "", err)
}

namespaces := make([]string, len(namespaceList.Items))
for i, ns := range namespaceList.Items {
namespaces := make([]string, len(namespaceList))
for i, ns := range namespaceList {
namespaces[i] = ns.Name
}

Expand Down

0 comments on commit 83b277a

Please sign in to comment.