diff --git a/.github/workflows/ci-helm.yml b/.github/workflows/ci-helm.yml index 93a9a844707a..a6c3f2ac5a93 100644 --- a/.github/workflows/ci-helm.yml +++ b/.github/workflows/ci-helm.yml @@ -23,24 +23,31 @@ jobs: name: Lint and test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - name: Checkout + uses: actions/checkout@v4.1.1 with: fetch-depth: 0 - - uses: azure/setup-helm@v4.2.0 + - name: Set up Helm + uses: azure/setup-helm@v4.2.0 with: version: v3.12.1 - - uses: helm/chart-testing-action@v2.6.1 - - id: list-changed + - name: Set up Chart Testing CLI + uses: helm/chart-testing-action@v2.6.1 + - name: Change Detection + id: list-changed run: | changed=$(ct list-changed --config=.ct.yml --target-branch ${{ github.event.repository.default_branch }}) if [[ -n "$changed" ]]; then echo "changed=true" >> "$GITHUB_OUTPUT" fi - - if: steps.list-changed.outputs.changed == 'true' - run: ct lint --config=.ct.yml --target-branch ${{ github.event.repository.default_branch }} - - if: steps.list-changed.outputs.changed == 'true' + - name: Lint Chart + if: steps.list-changed.outputs.changed == 'true' + run: ct lint --config=.ct.yml --target-branch ${{ github.event.repository.default_branch }} --check-version-increment=false + - name: Set up Kind + if: steps.list-changed.outputs.changed == 'true' uses: helm/kind-action@v1.10.0 with: node_image: kindest/node:v1.30.2 - - if: steps.list-changed.outputs.changed == 'true' + - name: Install Chart + if: steps.list-changed.outputs.changed == 'true' run: ct install --target-branch ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddeeb01cd024..c3b01c23e6ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,62 @@ jobs: fetch-depth: 0 - run: make image + publish: + name: Build and push API container + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Docket meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/kubernetes/dashboard-api + docker.io/kubernetesui/dashboard-api + tags: | + type=sha + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}},value=${{ needs.prepare.outputs.new_release_version }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.2.0 + - name: set up Docker Buildx + uses: docker/setup-buildx-action@v3.7.1 + with: + config: .github/buildkitd.toml + - name: Login to Docker + uses: docker/login-action@v3.3.0 + with: + username: ${{ secrets.DOCKER_RELEASE_USER }} + password: ${{ secrets.DOCKER_RELEASE_PASS }} + - name: Login to GHCR + uses: docker/login-action@v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + id: push + uses: docker/build-push-action@v6 + with: + context: modules + file: modules/api/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VERSION=${{ steps.meta.outputs.version || 'latest' }} + unit-tests: name: Unit tests with coverage runs-on: ubuntu-latest diff --git a/charts/kubernetes-dashboard/templates/NOTES.txt b/charts/kubernetes-dashboard/templates/NOTES.txt index 718f9d48ab8e..4c74f9cdac45 100644 --- a/charts/kubernetes-dashboard/templates/NOTES.txt +++ b/charts/kubernetes-dashboard/templates/NOTES.txt @@ -5,7 +5,7 @@ Congratulations! You have just installed Kubernetes Dashboard in your cluster. {{ if not (.Values.nginx.enabled) }} To access Dashboard run: - kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443 + kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-kong-proxy 8443:443 NOTE: In case port-forward command does not work, make sure that kong service name is correct. Check the services in Kubernetes Dashboard namespace using: @@ -17,7 +17,7 @@ Dashboard will be available at: {{ if and (has "localhost" .Values.app.ingress.hosts) (eq .Values.app.ingress.ingressClassName "internal-nginx") (.Values.nginx.enabled) }} To access Dashboard run: - kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-nginx-controller 8443:443 + kubectl -n {{ .Release.Namespace }} port-forward svc/{{ .Release.Name }}-nginx-controller 8443:443 NOTE: In case port-forward command does not work, make sure that nginx service name is correct. Check the services in Kubernetes Dashboard namespace using: diff --git a/docs/common/arguments.md b/docs/common/arguments.md index 357d912d9e01..3b4241604fbc 100644 --- a/docs/common/arguments.md +++ b/docs/common/arguments.md @@ -3,65 +3,81 @@ Dashboard containers accept multiple arguments that can be used to customize them. ## API module arguments + | Argument name | Default value | Description | |------------------------------|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| insecure-port | 9000 | The port to listen to for incoming HTTP requests. | -| port | 9001 | The secure port to listen to for incoming HTTPS requests. | +| disable-csrf-protection | false | Allows disabling CSRF protection. | +| act-as-proxy | false | Forces dashboard to work in full proxy mode, meaning that any internal in-cluster client calls are disabled. | +| openapi-enabled | false | Enables OpenAPI v2 endpoint user '/apidocs.json'. Used to autogenerate OpenAPI/GraphQL schema. | +| profiler | false | Enables pprof handler. By default it will be exposed on localhost:8070 under '/debug/pprof'. | +| prometheus-enabled | false | Enables prometheus metrics handler. By default it will be exposed on localhost:8080 under '/metrics'. | +| apiserver-skip-tls-verify | false | Enable if connection with remote Kubernetes API should skip TLS verify. | +| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | +| cache-enabled | true | Whether the client cache should be enabled or not. | +| cluster-context-enabled | false | Whether multi-cluster cache context support should be enabled or not. | +| cache-size | 1000 | Max number of cache entries to hold at once. | +| cache-ttl | 10m | Time to live of each cache entry. | +| cache-refresh-debounce | 5s | Minimal time between cache refreshes in background. | +| insecure-port | 8000 | The port to listen to for incoming HTTP requests. | +| port | 8001 | The secure port to listen to for incoming HTTPS requests. | +| metric-client-check-period | 30 | Time in seconds that defines how often configured metric client health check should be run. | | insecure-bind-address | 127.0.0.1 | The IP address on which to serve the `--insecure-port` (set to 127.0.0.1 for loopback only). | | bind-address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | +| token-exchange-endpoint | - | Endpoint used when `--cluster-context-enabled=true` to exchange auth token for the unique context identifier. | | default-cert-dir | /certs | Directory path containing `--tls-cert-file` and `--tls-key-file` files. Used also when auto-generating certificates flag is set. Relative to the container, not the host. | | tls-cert-file | - | File containing the default x509 Certificate for HTTPS. | | tls-key-file | - | File containing the default x509 private key matching --tls-cert-file. | -| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | | apiserver-host | - | The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and local discovery is attempted. | -| sidecar-host | - | The address of the Sidecar Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8000. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | | metrics-provider | sidecar | Select provider type for metrics. 'none' will not check metrics. | -| metric-client-check-period | 30 | Time in seconds that defines how often configured metric client health check should be run. | +| sidecar-host | - | The address of the Sidecar Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8000. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and service proxy will be used. | | kubeconfig | - | Path to kubeconfig file with control plane location information. | | namespace | kubernetes-dashboard | Namespace to use when accessing Dashboard specific resources, i.e. metrics scraper service. | | metrics-scraper-service-name | kubernetes-dashboard-metrics-scraper | Name of the dashboard metrics scraper service. | -| disable-csrf-protection | false | Allows disabling CSRF protection. | | csrf-key | - | Base64 encoded random 256 bytes key. Can be loaded from 'CSRF_KEY' environment variable. | -| act-as-proxy | false | Forces dashboard to work in full proxy mode, meaning that any in-cluster calls are disabled. | -| v | 1 | Number for the log level verbosity (default 1) | | +| v | 1 | Number for the log level verbosity (default 1) | | ## Auth module arguments -| Argument name | Default value | Description | -|---------------|---------------|------------------------------------------------------------------------------------------| -| port | 8000 | The secure port to listen to for incoming HTTPS requests. | -| address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | -| kubeconfig | - | Path to `kubeconfig` file. | -| csrf-key | - | Base64 encoded random 256 bytes key. Can be loaded from 'CSRF_KEY' environment variable. | -| v | 1 | Number for the log level verbosity (default 1) | |# Metrics scraper module arguments + +| Argument name | Default value | Description | +|---------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| apiserver-skip-tls-verify | false | Enable if connection with remote Kubernetes API should skip TLS verify. | +| port | 8000 | The secure port to listen to for incoming HTTPS requests. | +| address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | +| kubeconfig | - | Path to `kubeconfig` file. | +| apiserver-host | - | The address of the Kubernetes Apiserver to connect to in the format of protocol://address:port, e.g., http://localhost:8080. If not specified, the assumption is that the binary runs inside a Kubernetes cluster and local discovery is attempted. | +| csrf-key | - | Base64 encoded random 256 bytes key. Can be loaded from 'CSRF_KEY' environment variable. | +| v | 1 | Number for the log level verbosity (default 1) | |# Metrics scraper module arguments ## Metrics scraper module arguments -| Argument name | Default value | Description | -|-------------------|-----------------|------------------------------------------------------------------------------| -| kubeconfig | - | Path to `kubeconfig` file. | -| db-file | /tmp/metrics.db | What file to use as a SQLite3 database. | -| metric-resolution | 1m | The resolution at which dashboard-metrics-scraper will poll metrics. | -| metric-duration | 15m | The duration after which metrics are purged from the database. | -| logtostderr | true | Log to standard error. | -| namespace | - | The namespace to use for all metric calls. When provided, skip node metrics. | -| v | 1 | Number for the log level verbosity (default 1) | | + +| Argument name | Default value | Description | +|-------------------|-----------------|---------------------------------------------------------------------------| +| metric-resolution | 1m | The resolution at which dashboard-metrics-scraper will poll metrics. | +| metric-duration | 15m | The duration after which metrics are purged from the database. | +| kubeconfig | - | Path to `kubeconfig` file. | +| db-file | /tmp/metrics.db | What file to use as a SQLite3 database. | +| namespaces | - | Namespaces to use for all metric calls. When provided, skip node metrics. | +| v | 1 | Number for the log level verbosity (default 1) | | ## Web module arguments + | Argument name | Default value | Description | |----------------------------|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | | insecure-port | 8000 | The port to listen to for incoming HTTP requests. | | port | 8001 | The secure port to listen to for incoming HTTPS requests. | | insecure-bind-address | 127.0.0.1 | The IP address on which to serve the `--insecure-port` (set to 127.0.0.1 for loopback only). | | bind-address | 0.0.0.0 | The IP address on which to serve the `--port` (set to 0.0.0.0 for all interfaces). | +| namespace | kube-system | Namespace to use when creating Dashboard specific resources, i.e. settings config map. | | default-cert-dir | /certs | Directory path containing `--tls-cert-file` and `--tls-key-file` files. Used also when auto-generating certificates flag is set. Relative to the container, not the host. | | tls-cert-file | - | File containing the default x509 Certificate for HTTPS. | | tls-key-file | - | File containing the default x509 private key matching --tls-cert-file. | -| auto-generate-certificates | false | When set to true, Dashboard will automatically generate certificates used to serve HTTPS. | -| locale-config | ./locale_conf.json | File containing the configuration of locales. | -| namespace | kube-system | Namespace to use when creating Dashboard specific resources, i.e. settings config map. | | settings-config-map-name | kubernetes-dashboard-settings | Name of a config map, that stores settings. | | system-banner | - | When non-empty displays message to Dashboard users. Accepts simple HTML tags. | | system-banner-severity | INFO | Severity of system banner. Should be one of `INFO\|WARNING\|ERROR`. | +| locale-config | ./locale_conf.json | File containing the configuration of locales. | | kubeconfig | - | Path to `kubeconfig` file. | | v | 1 | Number for the log level verbosity (default 1) | | + ---- _Copyright 2019 [The Kubernetes Dashboard Authors](https://github.com/kubernetes/dashboard/graphs/contributors)_ diff --git a/docs/design/README.md b/docs/design/README.md index a60613075b5f..86d8ed403877 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -2,7 +2,6 @@ This guide is for anyone interested in contributing design work themselves or contributing in a way that is impacted by design. ## Resources: -* Get in touch with Dan Romlein (@danielromlein) for general Dashboard UX questions or suggestions of tasks that need design work. * Follow the [Getting started guide](https://github.com/kubernetes/dashboard/wiki/Getting-started) to get the most recent version of Dashboard up and running. * Dashboard is based on Google’s [Material](https://material.io/guidelines/) design system. Refer to their spec for guidance. diff --git a/docs/design/cache.md b/docs/design/cache.md new file mode 100644 index 000000000000..ea83453b3db7 --- /dev/null +++ b/docs/design/cache.md @@ -0,0 +1,104 @@ +# Table of Contents +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Terminology](#terminology) +- [Proposal](#proposal) +- [Design](#design) +- [Implementation](#implementation) + +## Motivation +The Kubernetes Dashboard has been around for a long time now, and one of its pain points have always been the performance and responsiveness when running in clusters with a large number of resources. Given that, we have been thinking about implementing a proper API caching solution to enhance overall responsiveness and user experience. As clusters grow in size and complexity, users often face latency issues when interacting with the Dashboard, which can lead to inefficiencies in managing and troubleshooting applications. By implementing a proper caching solution, we can significantly reduce the time it takes to retrieve resource data, decrease peak memory usage and optimize overall resource consumption, thereby minimizing delays and improving the fluidity of the user interface. + +### Goals +The primary goals of implementing the API caching solution are to: +- **Reduce Latency**: Minimize the time required to retrieve data from the Dashboard API during consecutive requests, enabling faster access to information. +- **Enhance User Experience**: Provide a smoother, more responsive interface for users managing complex clusters. +- **Optimize Resource Utilization**: Decrease the pressure on the Kubernetes API server by caching frequently accessed data, thus improving overall cluster performance. +- **Support Scalability**: Ensure the solution can accommodate clusters of varying sizes and complexities without degrading performance. Cache should be running in multi-cluster as well as in the single-cluster setup. +- **Configurability**: Provide opt-out and other configuration options. + +### Non-Goals +This proposal does not aim to: + +- **Replace Existing API Functionality**: The caching solution will transparently work with, not replace, the existing API endpoints and interactions. +- **Introduce Complexity for Users**: The implementation should remain transparent to users, avoiding any additional steps or configurations. +- **Cover All Resource Types Equally**: While the caching solution will enhance responsiveness, it may initially focus on the most frequently accessed resource types rather than attempting to cache every possible resource. + +### Terminology +- **API Caching**: The process of storing responses from API requests temporarily to reduce the need for repeated fetching of the same data. +- **Resource**: An entity within Kubernetes, such as pods, services, deployments, etc., that users manage through the Dashboard. +- **Latency**: The time delay between a user action and the corresponding response from the system. + +## Proposal +The proposed solution involves implementing a caching layer within the Kubernetes Dashboard that stores a configurable number of API responses for a configurable duration. This caching layer will hook into Kubernetes client interfaces and serve cached data when available, falling back to the API server only when necessary. The solution will leverage techniques such as time and cost-based expiration and cache invalidation strategies to ensure data freshness while balancing performance. + +In general, it will resemble the "cache-and-network" type of caching due to the nature of Dashboard auth layer. Since Dashboard does not require any permissions on its own, it has to rely on the user permissions and the only time when it can act as a user is the time from receiving a request to sending a response. Such an architecture requires an on-the-fly client creation as well as background cache updates. + +To ensure that cached data will not be served to unauthorized entities, every time before API returns data from the cache, it will first create a Self Subject Access Review request to the API server to validate user permissions. + +It is especially important in a multi-cluster scenarios where Dashboard API is used to access multiple clusters. To avoid the situation where path stored in cache could be served from the wrong cluster context, multi-cluster cache context needs to have a way to exchange user authorization token for a unique context ID and it has to be a part of the cache key. + +Cache key should consist of the below fields: +- **Kind**: resource kind +- **Namespace**: optional namespace name +- **List Options**: `v1.ListOptions` should also be part of the key to ensure that filtered API requests are stored under a separate cache key +- **Context ID**: optional context (cluster) identifier, used only in multi-context caching, controlled by dedicated argument + +SHA should be created based on the above key structure and used as an internal cache key. + +## Design +These sequence diagrams show simplified way of how cache works. + +### Standard Caching +![Cache Sequence Diagram](../images/cache-sequence-diagram.png) + +1. User requests to see a cached resource view +2. API checks if unique key generated based on the request has a corresponding value in the cache + 1. In case value is not cached, return the data directly from K8S API and cache it. + 2. In case value is cached, create a Self Subject Access Review to check user permissions, return cached data and in background request latest data from K8S API to update cache. + +### Multi-Context Caching +![Multi-Context Cache Sequence Diagram](../images/multi-context-cache-sequence-diagram.png) + +The flow is very similar to the standard caching with the difference being that provided user authorization token has to be able to be exchanged for the unique context ID using configured `token-exchange-endpoint`. It is then used to create unique cache key. + +## Implementation +Cache is implemented with the help of [Theine](Yiling-J/theine-go) package. It provides in-memory cache that has good performance, supports generics and keeps its API simple. + +Cache is a global variable initialized during application startup. It maps internal key SHAs to the resource lists. + +It can be configured via the following arguments: + +- `cache-enabled` - Enables the cache. Enabled by default. +- `cache-size` - Maximum number of items in the cache. Set to 1000 by default. +- `cache-ttl` - Cache entry time-to-live. Set to 10 minutes by default. +- `cache-refresh-debounce` - Minimal time that has to pass between consecutive cache refreshes in the background. Set to 5 seconds by default. +- `cluster-context-enabled` - Enables multi-context cache. Disabled by default. Requires `token-exchange-endpoint` to be set if enabled. +- `token-exchange-endpoint` - Endpoint used when multi-context cache is enabled. It exchanges tokens for a context identifiers. It has to be HTTP(s) `GET` that returns raw string with context identifier and accepts `Authorization: Bearer ` header. + +Cache package provides following interface: + +- `Get` - fetches item from the cache. +- `Set` - stores item in the cache. +- `DefferedLoad` - updates cache in the background. Used after cache is read to refresh items. +- `SyncedLoad` - initializes the cache ensuring that there will be no concurrent calls to the Kubernetes API for the same resources. + +In order to minimize the amount of code, we have created custom interfaces similar to the `client-go` interfaces where we could override only a single `List` method and still use their generic `client.Interface`. This way our internal implementation and usage of kubernetes client did not have to change at all and we were able to inject cached client globally. + +The initial implementation supports caching of the following resources: +1. Core + 1. Pod + 2. Node + 3. ConfigMap + 4. Secret + 5. Namespace + 6. PVC + 7. PV +2. Extensions + 1. Custom Resource Definitions + +Whole cache implementation lives under [modules/common/client/cache](../../modules/common/client/cache). +- **Generic ResourceLister**: [resourcelister.go](../../modules/common/client/cache/client/common/resourcelister.go) +- **Core Client**: [core.go](../../modules/common/client/cache/client/core/core.go) +- **Extensions Client**: [extensions.go](../../modules/common/client/cache/client/extensions/extensions.go) diff --git a/docs/images/cache-sequence-diagram-transparent.png b/docs/images/cache-sequence-diagram-transparent.png new file mode 100644 index 000000000000..1765b86b298f Binary files /dev/null and b/docs/images/cache-sequence-diagram-transparent.png differ diff --git a/docs/images/cache-sequence-diagram.png b/docs/images/cache-sequence-diagram.png new file mode 100644 index 000000000000..6c29fe55d2cc Binary files /dev/null and b/docs/images/cache-sequence-diagram.png differ diff --git a/docs/images/multi-context-cache-sequence-diagram-transparent.png b/docs/images/multi-context-cache-sequence-diagram-transparent.png new file mode 100644 index 000000000000..d8a2244036c7 Binary files /dev/null and b/docs/images/multi-context-cache-sequence-diagram-transparent.png differ diff --git a/docs/images/multi-context-cache-sequence-diagram.png b/docs/images/multi-context-cache-sequence-diagram.png new file mode 100644 index 000000000000..3481cd33c6bb Binary files /dev/null and b/docs/images/multi-context-cache-sequence-diagram.png differ diff --git a/modules/api/go.mod b/modules/api/go.mod index 675bd6481bf9..161dad33f687 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -28,6 +28,7 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Yiling-J/theine-go v0.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect @@ -40,6 +41,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.0 // indirect github.com/go-errors/errors v1.4.2 // indirect @@ -94,6 +96,7 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.28.0 // indirect diff --git a/modules/api/go.sum b/modules/api/go.sum index bf6f753c3c04..e0449decda5b 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Yiling-J/theine-go v0.5.0 h1:ZIyQDTMRBWEqEopViwmScgSUKBNatTNcIfniN/hXMbo= +github.com/Yiling-J/theine-go v0.5.0/go.mod h1:mdch1vjgGWd7s3rWKvY+MF5InRLfRv/CWVI9RVNQ8wY= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -50,6 +52,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -241,6 +245,10 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/modules/api/pkg/args/args.go b/modules/api/pkg/args/args.go index 7c8652b9d34b..41b02808b20a 100644 --- a/modules/api/pkg/args/args.go +++ b/modules/api/pkg/args/args.go @@ -23,6 +23,8 @@ import ( "github.com/spf13/pflag" "k8s.io/klog/v2" + // Load client args + _ "k8s.io/dashboard/client/args" "k8s.io/dashboard/csrf" "k8s.io/dashboard/helpers" ) @@ -37,26 +39,41 @@ const ( LogLevelTrace = klog.Level(5) ) +const ( + defaultInsecurePort = 8000 + defaultPort = 8001 + defaultProfilerPort = 8070 + defaultPrometheusPort = 8080 + + defaultProfilerPath = "/debug/pprof/" + defaultPrometheusPath = "/metrics" +) + var ( - argInsecurePort = pflag.Int("insecure-port", 8000, "port to listen to for incoming HTTP requests") - argPort = pflag.Int("port", 8001, "secure port to listen to for incoming HTTPS requests") - argInsecureBindAddress = pflag.IP("insecure-bind-address", net.IPv4(127, 0, 0, 1), "IP address on which to serve the --insecure-port, set to 127.0.0.1 for all interfaces") - argBindAddress = pflag.IP("bind-address", net.IPv4(0, 0, 0, 0), "IP address on which to serve the --port, set to 0.0.0.0 for all interfaces") + argDisableCSRFProtection = pflag.Bool("disable-csrf-protection", false, "allows disabling CSRF protection") + argIsProxyEnabled = pflag.Bool("act-as-proxy", false, "forces dashboard to work in full proxy mode, meaning that any in-cluster calls are disabled") + argOpenAPIEnabled = pflag.Bool("openapi-enabled", false, "enables OpenAPI v2 endpoint under '/apidocs.json'") + argProfiler = pflag.Bool("profiler", false, "Enable pprof handler. By default it will be exposed on localhost:8070 under '/debug/pprof'") + argPrometheusEnabled = pflag.Bool("prometheus-enabled", false, "Enable prometheus metrics handler. By default it will be exposed on localhost:8080 under '/metrics'") + argApiServerSkipTLSVerify = pflag.Bool("apiserver-skip-tls-verify", false, "enable if connection with remote Kubernetes API server should skip TLS verify") + argAutoGenerateCertificates = pflag.Bool("auto-generate-certificates", false, "enables automatic certificates generation used to serve HTTPS") + + argInsecurePort = pflag.Int("insecure-port", defaultInsecurePort, "port to listen to for incoming HTTP requests") + argPort = pflag.Int("port", defaultPort, "secure port to listen to for incoming HTTPS requests") + argMetricClientCheckPeriod = pflag.Int("metric-client-check-period", 30, "time interval between separate metric client health checks in seconds") + + argInsecureBindAddress = pflag.IP("insecure-bind-address", net.IPv4(127, 0, 0, 1), "IP address on which to serve the --insecure-port, set to 127.0.0.1 for all interfaces") + argBindAddress = pflag.IP("bind-address", net.IPv4(0, 0, 0, 0), "IP address on which to serve the --port, set to 0.0.0.0 for all interfaces") + argDefaultCertDir = pflag.String("default-cert-dir", "/certs", "directory path containing files from --tls-cert-file and --tls-key-file, used also when auto-generating certificates flag is set") argCertFile = pflag.String("tls-cert-file", "", "file containing the default x509 certificate for HTTPS") argKeyFile = pflag.String("tls-key-file", "", "file containing the default x509 private key matching --tls-cert-file") argApiServerHost = pflag.String("apiserver-host", "", "address of the Kubernetes API server to connect to in the format of protocol://address:port, leave it empty if the binary runs inside cluster for local discovery attempt") - argApiServerSkipTLSVerify = pflag.Bool("apiserver-skip-tls-verify", false, "enable if connection with remote Kubernetes API server should skip TLS verify") argMetricsProvider = pflag.String("metrics-provider", "sidecar", "select provider type for metrics, 'none' will not check metrics") argSidecarHost = pflag.String("sidecar-host", "", "address of the Sidecar API server to connect to in the format of protocol://address:port, leave it empty if the binary runs inside cluster for service proxy usage") argKubeConfigFile = pflag.String("kubeconfig", "", "path to kubeconfig file with control plane location information") - argMetricClientCheckPeriod = pflag.Int("metric-client-check-period", 30, "time interval between separate metric client health checks in seconds") - argAutoGenerateCertificates = pflag.Bool("auto-generate-certificates", false, "enables automatic certificates generation used to serve HTTPS") argNamespace = pflag.String("namespace", helpers.GetEnv("POD_NAMESPACE", "kubernetes-dashboard"), "Namespace to use when accessing Dashboard specific resources, i.e. metrics scraper service") argMetricsScraperServiceName = pflag.String("metrics-scraper-service-name", "kubernetes-dashboard-metrics-scraper", "name of the dashboard metrics scraper service") - argDisableCSRFProtection = pflag.Bool("disable-csrf-protection", false, "allows disabling CSRF protection") - argIsProxyEnabled = pflag.Bool("act-as-proxy", false, "forces dashboard to work in full proxy mode, meaning that any in-cluster calls are disabled") - argOpenAPIEnabled = pflag.Bool("openapi-enabled", false, "enables OpenAPI v2 endpoint under '/apidocs.json'") ) func init() { @@ -73,6 +90,14 @@ func init() { if IsCSRFProtectionEnabled() { csrf.Ensure() } + + if *argProfiler { + initProfiler() + } + + if *argPrometheusEnabled { + initPrometheus() + } } func Address() string { diff --git a/modules/api/pkg/args/pprof.go b/modules/api/pkg/args/pprof.go new file mode 100644 index 000000000000..dc33d2c16ebe --- /dev/null +++ b/modules/api/pkg/args/pprof.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package args + +import ( + "fmt" + "net/http" + "net/http/pprof" + + "k8s.io/klog/v2" +) + +func initProfiler() { + klog.V(LogLevelInfo).Info("Initializing profiler") + + mux := http.NewServeMux() + mux.HandleFunc(defaultProfilerPath, pprof.Index) + go func() { + if err := http.ListenAndServe(fmt.Sprintf(":%d", defaultProfilerPort), mux); err != nil { + klog.Fatal(err) + } + }() +} diff --git a/modules/api/pkg/args/prometheus.go b/modules/api/pkg/args/prometheus.go new file mode 100644 index 000000000000..7bb4bec87e15 --- /dev/null +++ b/modules/api/pkg/args/prometheus.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package args + +import ( + "fmt" + "net/http" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "k8s.io/klog/v2" +) + +func initPrometheus() { + klog.V(LogLevelInfo).Info("Initializing prometheus metrics") + + mux := http.NewServeMux() + mux.Handle(defaultPrometheusPath, promhttp.Handler()) + go func() { + if err := http.ListenAndServe(fmt.Sprintf(":%d", defaultPrometheusPort), mux); err != nil { + klog.Fatal(err) + } + }() +} diff --git a/modules/api/pkg/handler/apihandler.go b/modules/api/pkg/handler/apihandler.go index c4807a747d12..f863babaffd7 100644 --- a/modules/api/pkg/handler/apihandler.go +++ b/modules/api/pkg/handler/apihandler.go @@ -16,13 +16,13 @@ package handler import ( "io" - "log" "net/http" "strconv" "strings" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/networkpolicy" "k8s.io/dashboard/client" @@ -74,10 +74,10 @@ import ( const ( // RequestLogString is a template for request log message. - RequestLogString = "[%s] Incoming %s %s %s request from %s: %s" + RequestLogString = "Incoming %s %s %s request from %s: %s" // ResponseLogString is a template for response log message. - ResponseLogString = "[%s] Outcoming response to %s with %d status code" + ResponseLogString = "Outgoing response to %s with %d status code" ) // APIHandler is a representation of API handler. Structure contains clientapi and clientapi configuration. @@ -2119,7 +2119,7 @@ func (apiHandler *APIHandler) handleGetPodEvents(request *restful.Request, respo return } - log.Println("Getting events related to a pod in namespace") + klog.V(4).Info("Getting events related to a pod in namespace") namespace := request.PathParameter("namespace") name := request.PathParameter("pod") dataSelect := parser.ParseDataSelectPathParameter(request) @@ -3214,7 +3214,7 @@ func (apiHandler *APIHandler) handleGetCustomResourceObjectDetail(request *restf } func (apiHandler *APIHandler) handleGetCustomResourceObjectEvents(request *restful.Request, response *restful.Response) { - log.Println("Getting events related to a custom resource object in namespace") + klog.V(4).Info("Getting events related to a custom resource object in namespace") k8sClient, err := client.Client(request.Request) if err != nil { diff --git a/modules/api/pkg/handler/filter.go b/modules/api/pkg/handler/filter.go index da010d37d3aa..5d6def5ce394 100644 --- a/modules/api/pkg/handler/filter.go +++ b/modules/api/pkg/handler/filter.go @@ -77,14 +77,23 @@ func formatRequestLog(request *restful.Request) string { content = "{ content hidden }" } - return fmt.Sprintf(RequestLogString, time.Now().Format(time.RFC3339), request.Request.Proto, - request.Request.Method, uri, getRemoteAddr(request.Request), content) + return fmt.Sprintf( + RequestLogString, + request.Request.Proto, + request.Request.Method, + uri, + getRemoteAddr(request.Request), + content, + ) } // formatResponseLog formats response log string. func formatResponseLog(response *restful.Response, request *restful.Request) string { - return fmt.Sprintf(ResponseLogString, time.Now().Format(time.RFC3339), - getRemoteAddr(request.Request), response.StatusCode()) + return fmt.Sprintf( + ResponseLogString, + getRemoteAddr(request.Request), + response.StatusCode(), + ) } // checkSensitiveUrl checks if a string matches against a sensitive URL diff --git a/modules/api/pkg/handler/terminal.go b/modules/api/pkg/handler/terminal.go index ea3b550027c6..c3784897ac42 100755 --- a/modules/api/pkg/handler/terminal.go +++ b/modules/api/pkg/handler/terminal.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "sync" "time" @@ -33,6 +32,9 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" + "k8s.io/klog/v2" + + "k8s.io/dashboard/api/pkg/args" ) const END_OF_TRANSMISSION = "\u0004" @@ -164,7 +166,7 @@ func (sm *SessionMap) Close(sessionId string, status uint32, reason string) { ses := sm.Sessions[sessionId] err := ses.sockJSSession.Close(status, reason) if err != nil { - log.Println(err) + klog.Error(err) } close(ses.sizeChan) delete(sm.Sessions, sessionId) @@ -182,22 +184,22 @@ func handleTerminalSession(session sockjs.Session) { ) if buf, err = session.Recv(); err != nil { - log.Printf("handleTerminalSession: can't Recv: %v", err) + klog.Errorf("handleTerminalSession: can't Recv: %v", err) return } if err = json.Unmarshal([]byte(buf), &msg); err != nil { - log.Printf("handleTerminalSession: can't UnMarshal (%v): %s", err, buf) + klog.Errorf("handleTerminalSession: can't UnMarshal (%v): %s", err, buf) return } if msg.Op != "bind" { - log.Printf("handleTerminalSession: expected 'bind' message, got: %s", buf) + klog.V(args.LogLevelVerbose).Infof("handleTerminalSession: expected 'bind' message, got: %s", buf) return } if terminalSession = terminalSessions.Get(msg.SessionID); terminalSession.id == "" { - log.Printf("handleTerminalSession: can't find session '%s'", msg.SessionID) + klog.V(args.LogLevelVerbose).Infof("handleTerminalSession: can't find session '%s'", msg.SessionID) return } diff --git a/modules/api/pkg/integration/metric/sidecar/client.go b/modules/api/pkg/integration/metric/sidecar/client.go index 41a193e802ac..60e27b7541f3 100644 --- a/modules/api/pkg/integration/metric/sidecar/client.go +++ b/modules/api/pkg/integration/metric/sidecar/client.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "strings" "k8s.io/apimachinery/pkg/types" @@ -43,7 +42,7 @@ type sidecarClient struct { // HealthCheck implements integration app interface. See Integration interface for more information. func (self sidecarClient) HealthCheck() error { if self.client == nil { - return errors.New("Sidecar not configured") + return errors.New("sidecar not configured") } return self.client.HealthCheck() @@ -204,7 +203,7 @@ func (self sidecarClient) allInOneDownload(selector sidecarSelector, metricName } if len(result) != len(rawResults.Items) { - log.Printf(`received %d resources from sidecar instead of %d`, len(rawResults.Items), len(result)) + klog.V(args.LogLevelVerbose).Infof(`received %d resources from sidecar instead of %d`, len(rawResults.Items), len(result)) } // rawResult.Items have indefinite order. diff --git a/modules/api/pkg/integration/metric/sidecar/client_test.go b/modules/api/pkg/integration/metric/sidecar/client_test.go index 1805aeb26be8..355cc15e0b00 100644 --- a/modules/api/pkg/integration/metric/sidecar/client_test.go +++ b/modules/api/pkg/integration/metric/sidecar/client_test.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "reflect" "regexp" "strings" @@ -97,7 +96,6 @@ func (self FakeSidecar) ID() integrationapi.IntegrationID { func (self FakeRequest) DoRaw(ctx context.Context) ([]byte, error) { _NumRequests.increment() - log.Println("Performing req...") path := self.Path time.Sleep(50 * time.Millisecond) // simulate response delay of 0.05 seconds if strings.Contains(path, "/pod-list/") { @@ -120,7 +118,6 @@ func (self FakeRequest) DoRaw(ctx context.Context) ([]byte, error) { items.Items = append(items.Items, metricapi.SidecarMetric{MetricPoints: self.PodData[pod+"/"+namespace], UIDs: []string{pod}}) } x, err := json.Marshal(items) - log.Println("Got you:", string(x)) return x, err } else if strings.Contains(path, "/nodes/") { @@ -135,7 +132,6 @@ func (self FakeRequest) DoRaw(ctx context.Context) ([]byte, error) { items.Items = append(items.Items, metricapi.SidecarMetric{MetricPoints: self.NodeData[requestedNode], UIDs: []string{requestedNode}}) x, err := json.Marshal(items) - log.Println("Got you:", string(x)) return x, err } else { return nil, fmt.Errorf("Invalid request url %s", path) @@ -294,7 +290,6 @@ func TestDownloadMetric(t *testing.T) { }, } for _, testCase := range testCases { - log.Println("-----------\n\n\n", testCase.Info, int(_NumRequests.get())) hClient := sidecarClient{fakeSidecarClient} promises := hClient.DownloadMetric(testCase.Selectors, "", &metricapi.CachedResources{}) diff --git a/modules/api/pkg/integration/metric/sidecar/selector.go b/modules/api/pkg/integration/metric/sidecar/selector.go index d4ef132a8b9f..d6083222275f 100644 --- a/modules/api/pkg/integration/metric/sidecar/selector.go +++ b/modules/api/pkg/integration/metric/sidecar/selector.go @@ -17,9 +17,9 @@ package sidecar import ( "fmt" - "github.com/emicklei/go-restful/v3/log" v1 "k8s.io/api/core/v1" apimachinery "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/helpers" @@ -39,7 +39,7 @@ func getSidecarSelectors(selectors []metricapi.ResourceSelector, for i, selector := range selectors { sidecarSelector, err := getSidecarSelector(selector, cachedResources) if err != nil { - log.Printf("There was an error during transformation to sidecar selector: %s", err.Error()) + klog.Errorf("There was an error during transformation to sidecar selector: %s", err.Error()) continue } diff --git a/modules/api/pkg/resource/clusterrole/list.go b/modules/api/pkg/resource/clusterrole/list.go index 8e9044d74c76..e417d4098c4f 100644 --- a/modules/api/pkg/resource/clusterrole/list.go +++ b/modules/api/pkg/resource/clusterrole/list.go @@ -15,10 +15,9 @@ package clusterrole import ( - "log" - rbac "k8s.io/api/rbac/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -40,7 +39,7 @@ type ClusterRole struct { } func GetClusterRoleList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*ClusterRoleList, error) { - log.Println("Getting list of RBAC roles") + klog.V(4).Info("Getting list of RBAC roles") channels := &common.ResourceChannels{ ClusterRoleList: common.GetClusterRoleListChannel(client, 1), } diff --git a/modules/api/pkg/resource/clusterrolebinding/list.go b/modules/api/pkg/resource/clusterrolebinding/list.go index 789332afe67f..0edef11f4ca5 100644 --- a/modules/api/pkg/resource/clusterrolebinding/list.go +++ b/modules/api/pkg/resource/clusterrolebinding/list.go @@ -15,10 +15,9 @@ package clusterrolebinding import ( - "log" - rbac "k8s.io/api/rbac/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -44,7 +43,7 @@ type ClusterRoleBinding struct { // GetClusterRoleBindingList returns a list of all ClusterRoleBindings in the cluster. func GetClusterRoleBindingList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*ClusterRoleBindingList, error) { - log.Print("Getting list of all clusterRoleBindings in the cluster") + klog.V(4).Infof("Getting list of all clusterRoleBindings in the cluster") channels := &common.ResourceChannels{ ClusterRoleBindingList: common.GetClusterRoleBindingListChannel(client, 1), } diff --git a/modules/api/pkg/resource/configmap/detail.go b/modules/api/pkg/resource/configmap/detail.go index 813be1c8d5e4..eaedeff7d3d5 100644 --- a/modules/api/pkg/resource/configmap/detail.go +++ b/modules/api/pkg/resource/configmap/detail.go @@ -16,11 +16,11 @@ package configmap import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // ConfigMapDetail API resource provides mechanisms to inject containers with configuration data while keeping @@ -36,7 +36,7 @@ type ConfigMapDetail struct { // GetConfigMapDetail returns detailed information about a config map func GetConfigMapDetail(client kubernetes.Interface, namespace, name string) (*ConfigMapDetail, error) { - log.Printf("Getting details of %s config map in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s config map in %s namespace", name, namespace) rawConfigMap, err := client.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) diff --git a/modules/api/pkg/resource/configmap/list.go b/modules/api/pkg/resource/configmap/list.go index 8bc8fdf648bc..fdedae8648a5 100644 --- a/modules/api/pkg/resource/configmap/list.go +++ b/modules/api/pkg/resource/configmap/list.go @@ -15,11 +15,10 @@ package configmap import ( - "log" - v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -47,7 +46,7 @@ type ConfigMap struct { // GetConfigMapList returns a list of all ConfigMaps in the cluster. func GetConfigMapList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*ConfigMapList, error) { - log.Printf("Getting list config maps in the namespace %s", nsQuery.ToRequestParam()) + klog.V(4).Infof("Getting list config maps in the namespace %s", nsQuery.ToRequestParam()) channels := &common.ResourceChannels{ ConfigMapList: common.GetConfigMapListChannel(client, nsQuery, 1), } diff --git a/modules/api/pkg/resource/cronjob/list.go b/modules/api/pkg/resource/cronjob/list.go index 16e23de5d390..f55865ab745d 100644 --- a/modules/api/pkg/resource/cronjob/list.go +++ b/modules/api/pkg/resource/cronjob/list.go @@ -15,11 +15,10 @@ package cronjob import ( - "log" - batch "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -55,7 +54,7 @@ type CronJob struct { // GetCronJobList returns a list of all CronJobs in the cluster. func GetCronJobList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*CronJobList, error) { - log.Print("Getting list of all cron jobs in the cluster") + klog.V(4).Infof("Getting list of all cron jobs in the cluster") channels := &common.ResourceChannels{ CronJobList: common.GetCronJobListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/customresourcedefinition/common.go b/modules/api/pkg/resource/customresourcedefinition/common.go index abc291aa50e9..f73e4a7bc166 100644 --- a/modules/api/pkg/resource/customresourcedefinition/common.go +++ b/modules/api/pkg/resource/customresourcedefinition/common.go @@ -48,20 +48,6 @@ func GetExtensionsAPIVersion(client apiextensionsclientset.Interface) (string, e return "", errors.NewNotFound("supported version for extensions api not found") } -func GetExtensionsAPIRestClient(client apiextensionsclientset.Interface) (rest.Interface, error) { - version, err := GetExtensionsAPIVersion(client) - if err != nil { - return nil, err - } - - switch version { - case v1: - return client.ApiextensionsV1().RESTClient(), nil - } - - return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api version: %s", version)) -} - func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*types.CustomResourceDefinitionList, error) { version, err := GetExtensionsAPIVersion(client) if err != nil { diff --git a/modules/api/pkg/resource/daemonset/detail.go b/modules/api/pkg/resource/daemonset/detail.go index 319b441fbf02..fb951330e2ed 100644 --- a/modules/api/pkg/resource/daemonset/detail.go +++ b/modules/api/pkg/resource/daemonset/detail.go @@ -16,10 +16,11 @@ package daemonset import ( "context" - "log" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" ) @@ -39,7 +40,7 @@ type DaemonSetDetail struct { func GetDaemonSetDetail(client k8sClient.Interface, metricClient metricapi.MetricClient, namespace, name string) (*DaemonSetDetail, error) { - log.Printf("Getting details of %s daemon set in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s daemon set in %s namespace", name, namespace) daemonSet, err := client.AppsV1().DaemonSets(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { return nil, err diff --git a/modules/api/pkg/resource/daemonset/pods.go b/modules/api/pkg/resource/daemonset/pods.go index b3b02dfd925f..e0b0ae7a0ae1 100644 --- a/modules/api/pkg/resource/daemonset/pods.go +++ b/modules/api/pkg/resource/daemonset/pods.go @@ -16,11 +16,12 @@ package daemonset import ( "context" - "log" api "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -32,7 +33,7 @@ import ( // GetDaemonSetPods return list of pods targeting daemon set. func GetDaemonSetPods(client k8sClient.Interface, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery, daemonSetName, namespace string) (*pod.PodList, error) { - log.Printf("Getting replication controller %s pods in namespace %s", daemonSetName, namespace) + klog.V(4).Infof("Getting replication controller %s pods in namespace %s", daemonSetName, namespace) pods, err := getRawDaemonSetPods(client, daemonSetName, namespace) if err != nil { diff --git a/modules/api/pkg/resource/dataselect/dataselect.go b/modules/api/pkg/resource/dataselect/dataselect.go index d998eef2281c..61327682641c 100644 --- a/modules/api/pkg/resource/dataselect/dataselect.go +++ b/modules/api/pkg/resource/dataselect/dataselect.go @@ -19,6 +19,7 @@ import ( "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/errors" ) @@ -195,7 +196,7 @@ func (self *DataSelector) GetCumulativeMetrics(metricClient metricapi.MetricClie metricNames := self.DataSelectQuery.MetricQuery.MetricNames if metricNames == nil { - klog.V(1).Info("Metrics names not provided. Skipping.") + klog.V(args.LogLevelVerbose).Info("Metrics names not provided. Skipping.") return self } diff --git a/modules/api/pkg/resource/deployment/deploy.go b/modules/api/pkg/resource/deployment/deploy.go index 38780c40f598..03294a7d278e 100644 --- a/modules/api/pkg/resource/deployment/deploy.go +++ b/modules/api/pkg/resource/deployment/deploy.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "io" - "log" "strings" apps "k8s.io/api/apps/v1" @@ -35,7 +34,9 @@ import ( "k8s.io/client-go/dynamic" client "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" derrors "k8s.io/dashboard/errors" ) @@ -161,7 +162,7 @@ type Protocols struct { // client. App deployment consists of a deployment and an optional service. Both of them // share common labels. func DeployApp(spec *AppDeploymentSpec, client client.Interface) error { - log.Printf("Deploying %s application into %s namespace", spec.Name, spec.Namespace) + klog.V(args.LogLevelVerbose).Infof("Deploying %s application into %s namespace", spec.Name, spec.Namespace) annotations := map[string]string{} if spec.Description != nil { @@ -304,7 +305,7 @@ func getLabelsMap(labels []Label) map[string]string { // DeployAppFromFile deploys an app based on the given yaml or json file. func DeployAppFromFile(cfg *rest.Config, spec *AppDeploymentFromFileSpec) (bool, error) { reader := strings.NewReader(spec.Content) - log.Printf("Namespace for deploy from file: %s\n", spec.Namespace) + klog.V(args.LogLevelVerbose).Infof("Namespace for deploy from file: %s\n", spec.Namespace) d := yaml.NewYAMLOrJSONDecoder(reader, 4096) for { data := &unstructured.Unstructured{} diff --git a/modules/api/pkg/resource/deployment/detail.go b/modules/api/pkg/resource/deployment/detail.go index 39671b8515c1..8046fb3d29c7 100644 --- a/modules/api/pkg/resource/deployment/detail.go +++ b/modules/api/pkg/resource/deployment/detail.go @@ -16,12 +16,13 @@ package deployment import ( "context" - "log" apps "k8s.io/api/apps/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/errors" ) @@ -82,7 +83,7 @@ type DeploymentDetail struct { // GetDeploymentDetail returns model object of deployment and error, if any. func GetDeploymentDetail(client client.Interface, namespace string, deploymentName string) (*DeploymentDetail, error) { - log.Printf("Getting details of %s deployment in %s namespace", deploymentName, namespace) + klog.V(4).Infof("Getting details of %s deployment in %s namespace", deploymentName, namespace) deployment, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/deployment/list.go b/modules/api/pkg/resource/deployment/list.go index 34947d48993d..a9b9b5b861ff 100644 --- a/modules/api/pkg/resource/deployment/list.go +++ b/modules/api/pkg/resource/deployment/list.go @@ -15,11 +15,10 @@ package deployment import ( - "log" - apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -64,7 +63,7 @@ type Deployment struct { // GetDeploymentList returns a list of all Deployments in the cluster. func GetDeploymentList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery, metricClient metricapi.MetricClient) (*DeploymentList, error) { - log.Print("Getting list of all deployments in the cluster") + klog.V(4).Infof("Getting list of all deployments in the cluster") channels := &common.ResourceChannels{ DeploymentList: common.GetDeploymentListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/endpoint/endpoint.go b/modules/api/pkg/resource/endpoint/endpoint.go index 1050f25eb7e2..ed1a007aa853 100644 --- a/modules/api/pkg/resource/endpoint/endpoint.go +++ b/modules/api/pkg/resource/endpoint/endpoint.go @@ -15,14 +15,14 @@ package endpoint import ( - "log" - v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/types" ) @@ -57,7 +57,7 @@ func GetServiceEndpoints(client k8sClient.Interface, namespace, name string) (*E } endpointList = toEndpointList(serviceEndpoints) - log.Printf("Found %d endpoints related to %s service in %s namespace", len(endpointList.Endpoints), name, namespace) + klog.V(args.LogLevelVerbose).Infof("Found %d endpoints related to %s service in %s namespace", len(endpointList.Endpoints), name, namespace) return endpointList, nil } diff --git a/modules/api/pkg/resource/event/list.go b/modules/api/pkg/resource/event/list.go index 8406aab14afd..a4e654b93a2f 100644 --- a/modules/api/pkg/resource/event/list.go +++ b/modules/api/pkg/resource/event/list.go @@ -15,9 +15,9 @@ package event import ( - "log" - k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" "k8s.io/dashboard/errors" @@ -25,7 +25,7 @@ import ( func GetEventList(client k8sClient.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*common.EventList, error) { - log.Printf("Getting list of events in namespace: %s", nsQuery.ToRequestParam()) + klog.V(4).Infof("Getting list of events in namespace: %s", nsQuery.ToRequestParam()) channels := &common.ResourceChannels{ EventList: common.GetEventListChannel(client, nsQuery, 2), diff --git a/modules/api/pkg/resource/horizontalpodautoscaler/detail.go b/modules/api/pkg/resource/horizontalpodautoscaler/detail.go index 2648d42293ef..08c2a2e5aa1e 100644 --- a/modules/api/pkg/resource/horizontalpodautoscaler/detail.go +++ b/modules/api/pkg/resource/horizontalpodautoscaler/detail.go @@ -16,11 +16,11 @@ package horizontalpodautoscaler import ( "context" - "log" autoscaling "k8s.io/api/autoscaling/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // HorizontalPodAutoscalerDetail provides the presentation layer view of Kubernetes Horizontal Pod Autoscaler resource. @@ -35,7 +35,7 @@ type HorizontalPodAutoscalerDetail struct { // GetHorizontalPodAutoscalerDetail returns detailed information about a horizontal pod autoscaler func GetHorizontalPodAutoscalerDetail(client client.Interface, namespace string, name string) (*HorizontalPodAutoscalerDetail, error) { - log.Printf("Getting details of %s horizontal pod autoscaler", name) + klog.V(4).Infof("Getting details of %s horizontal pod autoscaler", name) rawHorizontalPodAutoscaler, err := client.AutoscalingV1().HorizontalPodAutoscalers(namespace).Get(context.TODO(), name, v1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/ingress/detail.go b/modules/api/pkg/resource/ingress/detail.go index 1c8191e114a2..b094ca30af29 100644 --- a/modules/api/pkg/resource/ingress/detail.go +++ b/modules/api/pkg/resource/ingress/detail.go @@ -16,11 +16,11 @@ package ingress import ( "context" - "log" v1 "k8s.io/api/networking/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // IngressDetail API resource provides mechanisms to inject containers with configuration data while keeping @@ -41,7 +41,7 @@ type IngressDetail struct { // GetIngressDetail returns detailed information about an ingress func GetIngressDetail(client client.Interface, namespace, name string) (*IngressDetail, error) { - log.Printf("Getting details of %s ingress in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s ingress in %s namespace", name, namespace) rawIngress, err := client.NetworkingV1().Ingresses(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) diff --git a/modules/api/pkg/resource/ingressclass/detail.go b/modules/api/pkg/resource/ingressclass/detail.go index 278aae0441e8..912d20239ab7 100644 --- a/modules/api/pkg/resource/ingressclass/detail.go +++ b/modules/api/pkg/resource/ingressclass/detail.go @@ -16,11 +16,11 @@ package ingressclass import ( "context" - "log" networkingv1 "k8s.io/api/networking/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // IngressClassDetail provides the presentation layer view of Ingress Class resource. @@ -32,7 +32,7 @@ type IngressClassDetail struct { // GetIngressClass returns Storage Class resource. func GetIngressClass(client kubernetes.Interface, name string) (*IngressClassDetail, error) { - log.Printf("Getting details of %s ingress class", name) + klog.V(4).Infof("Getting details of %s ingress class", name) ic, err := client.NetworkingV1().IngressClasses().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/ingressclass/list.go b/modules/api/pkg/resource/ingressclass/list.go index 0ecf95c19fe5..a7cda490694c 100644 --- a/modules/api/pkg/resource/ingressclass/list.go +++ b/modules/api/pkg/resource/ingressclass/list.go @@ -15,10 +15,9 @@ package ingressclass import ( - "log" - networkingv1 "k8s.io/api/networking/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -45,7 +44,7 @@ type IngressClass struct { // GetIngressClassList returns a list of all Ingress class objects in the cluster. func GetIngressClassList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) ( *IngressClassList, error) { - log.Print("Getting list of ingress classes in the cluster") + klog.V(4).Infof("Getting list of ingress classes in the cluster") channels := &common.ResourceChannels{ IngressClassList: common.GetIngressClassListChannel(client, 1), diff --git a/modules/api/pkg/resource/job/list.go b/modules/api/pkg/resource/job/list.go index 1210c1eca054..d1f6c18dbc09 100644 --- a/modules/api/pkg/resource/job/list.go +++ b/modules/api/pkg/resource/job/list.go @@ -15,11 +15,10 @@ package job import ( - "log" - batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -89,7 +88,7 @@ type Job struct { // GetJobList returns a list of all Jobs in the cluster. func GetJobList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery, metricClient metricapi.MetricClient) (*JobList, error) { - log.Print("Getting list of all jobs in the cluster") + klog.V(4).Infof("Getting list of all jobs in the cluster") channels := &common.ResourceChannels{ JobList: common.GetJobListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/job/pods.go b/modules/api/pkg/resource/job/pods.go index 9598350f45c0..aa3a5990c4c8 100644 --- a/modules/api/pkg/resource/job/pods.go +++ b/modules/api/pkg/resource/job/pods.go @@ -16,7 +16,6 @@ package job import ( "context" - "log" batch "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" @@ -24,6 +23,8 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -35,7 +36,7 @@ import ( // GetJobPods return list of pods targeting job. func GetJobPods(client k8sClient.Interface, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery, namespace string, jobName string) (*pod.PodList, error) { - log.Printf("Getting replication controller %s pods in namespace %s", jobName, namespace) + klog.V(4).Infof("Getting replication controller %s pods in namespace %s", jobName, namespace) pods, err := getRawJobPods(client, jobName, namespace) if err != nil { diff --git a/modules/api/pkg/resource/namespace/common.go b/modules/api/pkg/resource/namespace/common.go index 82868d507d9b..a4f73144cbf4 100644 --- a/modules/api/pkg/resource/namespace/common.go +++ b/modules/api/pkg/resource/namespace/common.go @@ -16,11 +16,13 @@ package namespace import ( "context" - "log" api "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/dataselect" ) @@ -32,7 +34,7 @@ type NamespaceSpec struct { // CreateNamespace creates namespace based on given specification. func CreateNamespace(spec *NamespaceSpec, client kubernetes.Interface) error { - log.Printf("Creating namespace %s", spec.Name) + klog.V(args.LogLevelVerbose).Infof("Creating namespace %s", spec.Name) namespace := &api.Namespace{ ObjectMeta: metaV1.ObjectMeta{ diff --git a/modules/api/pkg/resource/namespace/detail.go b/modules/api/pkg/resource/namespace/detail.go index c15d6045314b..3ff40ab63f39 100644 --- a/modules/api/pkg/resource/namespace/detail.go +++ b/modules/api/pkg/resource/namespace/detail.go @@ -16,11 +16,11 @@ package namespace import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/limitrange" rq "k8s.io/dashboard/api/pkg/resource/resourcequota" @@ -47,7 +47,7 @@ type NamespaceDetail struct { // GetNamespaceDetail gets namespace details. func GetNamespaceDetail(client k8sClient.Interface, name string) (*NamespaceDetail, error) { - log.Printf("Getting details of %s namespace\n", name) + klog.V(4).Infof("Getting details of %s namespace\n", name) namespace, err := client.CoreV1().Namespaces().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/namespace/list.go b/modules/api/pkg/resource/namespace/list.go index d43a5c49fbb8..6d8081e8f908 100644 --- a/modules/api/pkg/resource/namespace/list.go +++ b/modules/api/pkg/resource/namespace/list.go @@ -16,10 +16,10 @@ package namespace import ( "context" - "log" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -64,7 +64,7 @@ func GetNamespaceListFromChannels(channels *common.ResourceChannels, dsQuery *da // GetNamespaceList returns a list of all namespaces in the cluster. func GetNamespaceList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*NamespaceList, error) { - log.Println("Getting list of namespaces") + klog.V(4).Info("Getting list of namespaces") namespaces, err := client.CoreV1().Namespaces().List(context.TODO(), helpers.ListEverything) nonCriticalErrors, criticalError := errors.ExtractErrors(err) diff --git a/modules/api/pkg/resource/networkpolicy/detail.go b/modules/api/pkg/resource/networkpolicy/detail.go index eed56420f68b..9396cb750d8a 100644 --- a/modules/api/pkg/resource/networkpolicy/detail.go +++ b/modules/api/pkg/resource/networkpolicy/detail.go @@ -16,11 +16,11 @@ package networkpolicy import ( "context" - "log" v1 "k8s.io/api/networking/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // NetworkPolicyDetail contains detailed information about a network policy. @@ -35,7 +35,7 @@ type NetworkPolicyDetail struct { // GetNetworkPolicyDetail returns detailed information about a network policy. func GetNetworkPolicyDetail(client client.Interface, namespace, name string) (*NetworkPolicyDetail, error) { - log.Printf("Getting details of %s network policy in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s network policy in %s namespace", name, namespace) raw, err := client.NetworkingV1().NetworkPolicies(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/node/detail.go b/modules/api/pkg/resource/node/detail.go index b33e67d193fa..05eb9d67b455 100644 --- a/modules/api/pkg/resource/node/detail.go +++ b/modules/api/pkg/resource/node/detail.go @@ -16,13 +16,13 @@ package node import ( "context" - "log" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -127,7 +127,7 @@ type NodeDetail struct { // GetNodeDetail gets node details. func GetNodeDetail(client k8sClient.Interface, metricClient metricapi.MetricClient, name string, dsQuery *dataselect.DataSelectQuery) (*NodeDetail, error) { - log.Printf("Getting details of %s node", name) + klog.V(4).Infof("Getting details of %s node", name) node, err := client.CoreV1().Nodes().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/node/drain.go b/modules/api/pkg/resource/node/drain.go index 52094aea17a4..78cc9faca48c 100644 --- a/modules/api/pkg/resource/node/drain.go +++ b/modules/api/pkg/resource/node/drain.go @@ -17,13 +17,15 @@ package node import ( "context" "fmt" - "log" "os" "time" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/kubectl/pkg/drain" + + "k8s.io/dashboard/api/pkg/args" ) // NodeDrainSpec is a specification to control the behavior of drainer. @@ -89,7 +91,7 @@ func newHelper(ctx context.Context, client k8sClient.Interface, spec *NodeDrainS // DrainNode drains the Node. func DrainNode(client k8sClient.Interface, name string, spec *NodeDrainSpec) error { - log.Printf("Draining %s node", name) + klog.V(args.LogLevelVerbose).Infof("Draining %s node", name) node, err := client.CoreV1().Nodes().Get(context.Background(), name, metaV1.GetOptions{}) if err != nil { @@ -106,7 +108,7 @@ func DrainNode(client k8sClient.Interface, name string, spec *NodeDrainSpec) err return fmt.Errorf("error draining node: %w", err) } - log.Printf("Successfully drained %s node", name) + klog.V(args.LogLevelVerbose).Infof("Successfully drained %s node", name) return nil } diff --git a/modules/api/pkg/resource/node/list.go b/modules/api/pkg/resource/node/list.go index 52699c5917cd..d601ad5f4a86 100644 --- a/modules/api/pkg/resource/node/list.go +++ b/modules/api/pkg/resource/node/list.go @@ -16,10 +16,10 @@ package node import ( "context" - "log" v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -75,7 +75,7 @@ func toNodeList(client client.Interface, nodes []v1.Node, nonCriticalErrors []er for _, node := range nodes { pods, err := getNodePods(client, node) if err != nil { - log.Printf("Couldn't get pods of %s node: %s\n", node.Name, err) + klog.Errorf("Couldn't get pods of %s node: %s\n", node.Name, err) } nodeList.Nodes = append(nodeList.Nodes, toNode(node, pods)) @@ -93,7 +93,7 @@ func toNodeList(client client.Interface, nodes []v1.Node, nonCriticalErrors []er func toNode(node v1.Node, pods *v1.PodList) Node { allocatedResources, err := getNodeAllocatedResources(node, pods) if err != nil { - log.Printf("Couldn't get allocated resources of %s node: %s\n", node.Name, err) + klog.Errorf("Couldn't get allocated resources of %s node: %s\n", node.Name, err) } return Node{ diff --git a/modules/api/pkg/resource/persistentvolume/common.go b/modules/api/pkg/resource/persistentvolume/common.go index 549307a2e799..1901f05ddeeb 100644 --- a/modules/api/pkg/resource/persistentvolume/common.go +++ b/modules/api/pkg/resource/persistentvolume/common.go @@ -16,12 +16,14 @@ package persistentvolume import ( "context" - "log" "strings" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" "k8s.io/dashboard/errors" @@ -57,7 +59,7 @@ func GetStorageClassPersistentVolumes(client client.Interface, storageClassName } } - log.Printf("Found %d persistentvolumes related to %s storageclass", + klog.V(args.LogLevelVerbose).Infof("Found %d persistentvolumes related to %s storageclass", len(storagePersistentVolumes), storageClassName) return toPersistentVolumeList(storagePersistentVolumes, nonCriticalErrors, dsQuery), nil diff --git a/modules/api/pkg/resource/persistentvolume/detail.go b/modules/api/pkg/resource/persistentvolume/detail.go index 8e990a8e9ce9..d158e858a806 100644 --- a/modules/api/pkg/resource/persistentvolume/detail.go +++ b/modules/api/pkg/resource/persistentvolume/detail.go @@ -16,11 +16,11 @@ package persistentvolume import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // PersistentVolumeDetail provides the presentation layer view of Kubernetes Persistent Volume resource. @@ -34,7 +34,7 @@ type PersistentVolumeDetail struct { // GetPersistentVolumeDetail returns detailed information about a persistent volume func GetPersistentVolumeDetail(client client.Interface, name string) (*PersistentVolumeDetail, error) { - log.Printf("Getting details of %s persistent volume", name) + klog.V(4).Infof("Getting details of %s persistent volume", name) rawPersistentVolume, err := client.CoreV1().PersistentVolumes().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/persistentvolume/list.go b/modules/api/pkg/resource/persistentvolume/list.go index 904f8b1362db..7ee55ed04701 100644 --- a/modules/api/pkg/resource/persistentvolume/list.go +++ b/modules/api/pkg/resource/persistentvolume/list.go @@ -15,10 +15,9 @@ package persistentvolume import ( - "log" - v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -51,7 +50,7 @@ type PersistentVolume struct { // GetPersistentVolumeList returns a list of all Persistent Volumes in the cluster. func GetPersistentVolumeList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) (*PersistentVolumeList, error) { - log.Print("Getting list persistent volumes") + klog.V(4).Infof("Getting list persistent volumes") channels := &common.ResourceChannels{ PersistentVolumeList: common.GetPersistentVolumeListChannel(client, 1), } diff --git a/modules/api/pkg/resource/persistentvolumeclaim/common.go b/modules/api/pkg/resource/persistentvolumeclaim/common.go index f68fabf0b127..53e298327c70 100644 --- a/modules/api/pkg/resource/persistentvolumeclaim/common.go +++ b/modules/api/pkg/resource/persistentvolumeclaim/common.go @@ -16,13 +16,14 @@ package persistentvolumeclaim import ( "context" - "log" "strings" api "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" "k8s.io/dashboard/errors" @@ -75,14 +76,14 @@ func GetPodPersistentVolumeClaims(client client.Interface, namespace string, pod } } - log.Printf("Found %d persistentvolumeclaims related to %s pod", + klog.V(args.LogLevelExtended).Infof("Found %d persistentvolumeclaims related to %s pod", len(podPersistentVolumeClaims), podName) return toPersistentVolumeClaimList(podPersistentVolumeClaims, nonCriticalErrors, dsQuery), nil } - log.Printf("No persistentvolumeclaims found related to %s pod", podName) + klog.V(args.LogLevelExtended).Infof("No persistentvolumeclaims found related to %s pod", podName) // No ClaimNames found in Pod details, return empty response. return &PersistentVolumeClaimList{}, nil diff --git a/modules/api/pkg/resource/persistentvolumeclaim/detail.go b/modules/api/pkg/resource/persistentvolumeclaim/detail.go index 89e5fedb8448..c0b2dae47810 100644 --- a/modules/api/pkg/resource/persistentvolumeclaim/detail.go +++ b/modules/api/pkg/resource/persistentvolumeclaim/detail.go @@ -16,11 +16,11 @@ package persistentvolumeclaim import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // PersistentVolumeClaimDetail provides the presentation layer view of Kubernetes Persistent Volume Claim resource. @@ -31,7 +31,7 @@ type PersistentVolumeClaimDetail struct { // GetPersistentVolumeClaimDetail returns detailed information about a persistent volume claim func GetPersistentVolumeClaimDetail(client kubernetes.Interface, namespace string, name string) (*PersistentVolumeClaimDetail, error) { - log.Printf("Getting details of %s persistent volume claim", name) + klog.V(4).Infof("Getting details of %s persistent volume claim", name) pvc, err := client.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/persistentvolumeclaim/list.go b/modules/api/pkg/resource/persistentvolumeclaim/list.go index 0decca260033..dc2298389b4c 100644 --- a/modules/api/pkg/resource/persistentvolumeclaim/list.go +++ b/modules/api/pkg/resource/persistentvolumeclaim/list.go @@ -15,10 +15,9 @@ package persistentvolumeclaim import ( - "log" - v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -52,7 +51,7 @@ type PersistentVolumeClaim struct { func GetPersistentVolumeClaimList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*PersistentVolumeClaimList, error) { - log.Print("Getting list persistent volumes claims") + klog.V(4).Infof("Getting list persistent volumes claims") channels := &common.ResourceChannels{ PersistentVolumeClaimList: common.GetPersistentVolumeClaimListChannel(client, nsQuery, 1), } diff --git a/modules/api/pkg/resource/pod/detail.go b/modules/api/pkg/resource/pod/detail.go index 263d95bbc66f..b6fa150ca352 100644 --- a/modules/api/pkg/resource/pod/detail.go +++ b/modules/api/pkg/resource/pod/detail.go @@ -18,11 +18,11 @@ import ( "context" "encoding/base64" "fmt" - "log" "math" "strconv" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" v1 "k8s.io/api/core/v1" res "k8s.io/apimachinery/pkg/api/resource" @@ -141,7 +141,7 @@ type VolumeMount struct { // GetPodDetail returns the details of a named Pod from a particular namespace. func GetPodDetail(client kubernetes.Interface, metricClient metricapi.MetricClient, namespace, name string) ( *PodDetail, error) { - log.Printf("Getting details of %s pod in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s pod in %s namespace", name, namespace) channels := &common.ResourceChannels{ ConfigMapList: common.GetConfigMapListChannel(client, common.NewSameNamespaceQuery(namespace), 1), @@ -403,18 +403,18 @@ func evalValueFrom(src *v1.EnvVarSource, container *v1.Container, pod *v1.Pod, case src.FieldRef != nil: gv, err := schema.ParseGroupVersion(src.FieldRef.APIVersion) if err != nil { - log.Println(err) + klog.Error(err) return "" } gvk := gv.WithKind("Pod") internalFieldPath, _, err := runtime.NewScheme().ConvertFieldLabel(gvk, src.FieldRef.FieldPath, "") if err != nil { - log.Println(err) + klog.Error(err) return "" } valueFrom, err := ExtractFieldPathAsString(pod, internalFieldPath) if err != nil { - log.Println(err) + klog.Error(err) return "" } return valueFrom diff --git a/modules/api/pkg/resource/pod/list.go b/modules/api/pkg/resource/pod/list.go index dddf6882d2a2..10d19b7ca333 100644 --- a/modules/api/pkg/resource/pod/list.go +++ b/modules/api/pkg/resource/pod/list.go @@ -15,11 +15,11 @@ package pod import ( - "log" - v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -104,7 +104,7 @@ var EmptyPodList = &PodList{ // GetPodList returns a list of all Pods in the cluster. func GetPodList(client k8sClient.Interface, metricClient metricapi.MetricClient, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*PodList, error) { - log.Print("Getting list of all pods in the cluster") + klog.V(4).Infof("Getting list of all pods in the cluster") channels := &common.ResourceChannels{ PodList: common.GetPodListChannelWithOptions(client, nsQuery, metaV1.ListOptions{}, 1), @@ -152,7 +152,7 @@ func ToPodList(pods []v1.Pod, events []v1.Event, nonCriticalErrors []error, dsQu metrics, err := getMetricsPerPod(pods, metricClient, dsQuery) if err != nil { - log.Printf("Skipping metrics because of error: %s\n", err) + klog.ErrorS(err, "skipping metrics") } for _, pod := range pods { @@ -163,7 +163,7 @@ func ToPodList(pods []v1.Pod, events []v1.Event, nonCriticalErrors []error, dsQu cumulativeMetrics, err := cumulativeMetricsPromises.GetMetrics() if err != nil { - log.Printf("Skipping metrics because of error: %s\n", err) + klog.ErrorS(err, "skipping metrics") cumulativeMetrics = make([]metricapi.Metric, 0) } @@ -174,7 +174,7 @@ func ToPodList(pods []v1.Pod, events []v1.Event, nonCriticalErrors []error, dsQu func toPod(pod *v1.Pod, metrics *MetricsByPod, warnings []common.Event) Pod { allocatedResources, err := getPodAllocatedResources(pod) if err != nil { - log.Printf("Couldn't get allocated resources of %s pod: %s\n", pod.Name, err) + klog.ErrorS(err, "couldn't get allocated resources", "pod", pod.Name) } podDetail := Pod{ diff --git a/modules/api/pkg/resource/pod/metrics.go b/modules/api/pkg/resource/pod/metrics.go index f05eb62a8f8e..2eb1fff052e1 100644 --- a/modules/api/pkg/resource/pod/metrics.go +++ b/modules/api/pkg/resource/pod/metrics.go @@ -15,11 +15,11 @@ package pod import ( - "log" - v1 "k8s.io/api/core/v1" apimachinery "k8s.io/apimachinery/pkg/types" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/dataselect" "k8s.io/dashboard/errors" @@ -47,7 +47,7 @@ type PodMetrics struct { func getMetricsPerPod(pods []v1.Pod, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery) ( *MetricsByPod, error) { - log.Println("Getting pod metrics") + klog.V(args.LogLevelDebug).Info("Getting pod metrics") result := &MetricsByPod{MetricsMap: make(map[apimachinery.UID]PodMetrics)} @@ -60,7 +60,7 @@ func getMetricsPerPod(pods []v1.Pod, metricClient metricapi.MetricClient, dsQuer for _, m := range metrics { uid, err := getPodUIDFromMetric(m) if err != nil { - log.Printf("Skipping metric because of error: %s", err.Error()) + continue } podMetrics := PodMetrics{} diff --git a/modules/api/pkg/resource/replicaset/detail.go b/modules/api/pkg/resource/replicaset/detail.go index 70cd7e36da21..87497fc2e29c 100644 --- a/modules/api/pkg/resource/replicaset/detail.go +++ b/modules/api/pkg/resource/replicaset/detail.go @@ -16,11 +16,12 @@ package replicaset import ( "context" - "log" apps "k8s.io/api/apps/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" hpa "k8s.io/dashboard/api/pkg/resource/horizontalpodautoscaler" @@ -47,7 +48,7 @@ type ReplicaSetDetail struct { // GetReplicaSetDetail gets replica set details. func GetReplicaSetDetail(client k8sClient.Interface, metricClient metricapi.MetricClient, namespace, name string) (*ReplicaSetDetail, error) { - log.Printf("Getting details of %s service in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s service in %s namespace", name, namespace) rs, err := client.AppsV1().ReplicaSets(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/replicaset/list.go b/modules/api/pkg/resource/replicaset/list.go index 2355d9a38a92..7597f9f6ad02 100644 --- a/modules/api/pkg/resource/replicaset/list.go +++ b/modules/api/pkg/resource/replicaset/list.go @@ -15,11 +15,10 @@ package replicaset import ( - "log" - apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -73,7 +72,7 @@ func ToReplicaSet(replicaSet *apps.ReplicaSet, podInfo *common.PodInfo) ReplicaS // GetReplicaSetList returns a list of all Replica Sets in the cluster. func GetReplicaSetList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery, metricClient metricapi.MetricClient) (*ReplicaSetList, error) { - log.Print("Getting list of all replica sets in the cluster") + klog.V(4).Infof("Getting list of all replica sets in the cluster") channels := &common.ResourceChannels{ ReplicaSetList: common.GetReplicaSetListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/replicaset/pods.go b/modules/api/pkg/resource/replicaset/pods.go index e295dd0f10a8..eafb67ed7356 100644 --- a/modules/api/pkg/resource/replicaset/pods.go +++ b/modules/api/pkg/resource/replicaset/pods.go @@ -16,13 +16,14 @@ package replicaset import ( "context" - "log" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -34,7 +35,7 @@ import ( // GetReplicaSetPods return list of pods targeting replica set. func GetReplicaSetPods(client k8sClient.Interface, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery, replicaSetName, namespace string) (*pod.PodList, error) { - log.Printf("Getting replication controller %s pods in namespace %s", replicaSetName, namespace) + klog.V(4).Infof("Getting replication controller %s pods in namespace %s", replicaSetName, namespace) pods, err := getRawReplicaSetPods(client, replicaSetName, namespace) if err != nil { diff --git a/modules/api/pkg/resource/replicationcontroller/detail.go b/modules/api/pkg/resource/replicationcontroller/detail.go index 72a9e39803f3..355c531518ff 100644 --- a/modules/api/pkg/resource/replicationcontroller/detail.go +++ b/modules/api/pkg/resource/replicationcontroller/detail.go @@ -16,12 +16,13 @@ package replicationcontroller import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/errors" ) @@ -46,7 +47,7 @@ type ReplicationControllerSpec struct { // GetReplicationControllerDetail returns detailed information about the given replication controller // in the given namespace. func GetReplicationControllerDetail(client k8sClient.Interface, namespace, name string) (*ReplicationControllerDetail, error) { - log.Printf("Getting details of %s replication controller in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s replication controller in %s namespace", name, namespace) replicationController, err := client.CoreV1().ReplicationControllers(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { @@ -65,7 +66,7 @@ func GetReplicationControllerDetail(client k8sClient.Interface, namespace, name // UpdateReplicasCount updates number of replicas in Replication Controller based on Replication Controller Spec func UpdateReplicasCount(client k8sClient.Interface, namespace, name string, spec *ReplicationControllerSpec) error { - log.Printf("Updating replicas count to %d for %s replication controller from %s namespace", + klog.V(args.LogLevelVerbose).Infof("Updating replicas count to %d for %s replication controller from %s namespace", spec.Replicas, name, namespace) replicationController, err := client.CoreV1().ReplicationControllers(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) @@ -80,7 +81,7 @@ func UpdateReplicasCount(client k8sClient.Interface, namespace, name string, spe return err } - log.Printf("Successfully updated replicas count to %d for %s replication controller from %s namespace", + klog.V(args.LogLevelVerbose).Infof("Successfully updated replicas count to %d for %s replication controller from %s namespace", spec.Replicas, name, namespace) return nil diff --git a/modules/api/pkg/resource/replicationcontroller/list.go b/modules/api/pkg/resource/replicationcontroller/list.go index 48080aa0b895..c3be872678d8 100644 --- a/modules/api/pkg/resource/replicationcontroller/list.go +++ b/modules/api/pkg/resource/replicationcontroller/list.go @@ -15,10 +15,9 @@ package replicationcontroller import ( - "log" - v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -62,7 +61,7 @@ type ReplicationController struct { // GetReplicationControllerList returns a list of all Replication Controllers in the cluster. func GetReplicationControllerList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery, metricClient metricapi.MetricClient) (*ReplicationControllerList, error) { - log.Print("Getting list of all replication controllers in the cluster") + klog.V(4).Infof("Getting list of all replication controllers in the cluster") channels := &common.ResourceChannels{ ReplicationControllerList: common.GetReplicationControllerListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/replicationcontroller/pods.go b/modules/api/pkg/resource/replicationcontroller/pods.go index 77a9afb89258..ebe9968a0c17 100644 --- a/modules/api/pkg/resource/replicationcontroller/pods.go +++ b/modules/api/pkg/resource/replicationcontroller/pods.go @@ -16,11 +16,12 @@ package replicationcontroller import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -34,7 +35,7 @@ import ( func GetReplicationControllerPods(client k8sClient.Interface, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery, rcName, namespace string) (*pod.PodList, error) { - log.Printf("Getting replication controller %s pods in namespace %s", rcName, namespace) + klog.V(4).Infof("Getting replication controller %s pods in namespace %s", rcName, namespace) pods, err := getRawReplicationControllerPods(client, rcName, namespace) if err != nil { diff --git a/modules/api/pkg/resource/role/list.go b/modules/api/pkg/resource/role/list.go index 6c55f6ddd9dd..f106215b786c 100644 --- a/modules/api/pkg/resource/role/list.go +++ b/modules/api/pkg/resource/role/list.go @@ -15,10 +15,9 @@ package role import ( - "log" - rbac "k8s.io/api/rbac/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -44,7 +43,7 @@ type Role struct { // GetRoleList returns a list of all Roles in the cluster. func GetRoleList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*RoleList, error) { - log.Print("Getting list of all roles in the cluster") + klog.V(4).Infof("Getting list of all roles in the cluster") channels := &common.ResourceChannels{ RoleList: common.GetRoleListChannel(client, nsQuery, 1), } diff --git a/modules/api/pkg/resource/rolebinding/list.go b/modules/api/pkg/resource/rolebinding/list.go index 1c34759fabd8..ae55027aecd1 100644 --- a/modules/api/pkg/resource/rolebinding/list.go +++ b/modules/api/pkg/resource/rolebinding/list.go @@ -15,10 +15,9 @@ package rolebinding import ( - "log" - rbac "k8s.io/api/rbac/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -44,7 +43,7 @@ type RoleBinding struct { // GetRoleBindingList returns a list of all RoleBindings in the cluster. func GetRoleBindingList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*RoleBindingList, error) { - log.Print("Getting list of all roleBindings in the cluster") + klog.V(4).Infof("Getting list of all roleBindings in the cluster") channels := &common.ResourceChannels{ RoleBindingList: common.GetRoleBindingListChannel(client, nsQuery, 1), } diff --git a/modules/api/pkg/resource/secret/detail.go b/modules/api/pkg/resource/secret/detail.go index 9ef4b6d209d0..e182917ceebb 100644 --- a/modules/api/pkg/resource/secret/detail.go +++ b/modules/api/pkg/resource/secret/detail.go @@ -16,11 +16,11 @@ package secret import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // SecretDetail API resource provides mechanisms to inject containers with configuration data while keeping @@ -38,7 +38,7 @@ type SecretDetail struct { // GetSecretDetail returns detailed information about a secret func GetSecretDetail(client kubernetes.Interface, namespace, name string) (*SecretDetail, error) { - log.Printf("Getting details of %s secret in %s namespace\n", name, namespace) + klog.V(4).Infof("Getting details of %s secret in %s namespace\n", name, namespace) rawSecret, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/secret/list.go b/modules/api/pkg/resource/secret/list.go index 787b9a98b5ea..7e56dd7b6be0 100644 --- a/modules/api/pkg/resource/secret/list.go +++ b/modules/api/pkg/resource/secret/list.go @@ -16,11 +16,11 @@ package secret import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -87,7 +87,7 @@ type SecretList struct { // GetSecretList returns all secrets in the given namespace. func GetSecretList(client kubernetes.Interface, namespace *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*SecretList, error) { - log.Printf("Getting list of secrets in %s namespace\n", namespace) + klog.V(4).Infof("Getting list of secrets in %s namespace\n", namespace) secretList, err := client.CoreV1().Secrets(namespace.ToRequestParam()).List(context.TODO(), helpers.ListEverything) nonCriticalErrors, criticalError := errors.ExtractErrors(err) diff --git a/modules/api/pkg/resource/service/detail.go b/modules/api/pkg/resource/service/detail.go index 650dc607eafc..bc41930d4b59 100644 --- a/modules/api/pkg/resource/service/detail.go +++ b/modules/api/pkg/resource/service/detail.go @@ -16,11 +16,12 @@ package service import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sClient "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/resource/endpoint" "k8s.io/dashboard/errors" ) @@ -42,7 +43,7 @@ type ServiceDetail struct { // GetServiceDetail gets service details. func GetServiceDetail(client k8sClient.Interface, namespace, name string) (*ServiceDetail, error) { - log.Printf("Getting details of %s service in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s service in %s namespace", name, namespace) serviceData, err := client.CoreV1().Services(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { return nil, err diff --git a/modules/api/pkg/resource/service/events.go b/modules/api/pkg/resource/service/events.go index 1f76bf780347..cfbbf1f1bb96 100644 --- a/modules/api/pkg/resource/service/events.go +++ b/modules/api/pkg/resource/service/events.go @@ -15,10 +15,10 @@ package service import ( - "log" - client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" "k8s.io/dashboard/api/pkg/resource/event" @@ -39,6 +39,6 @@ func GetServiceEvents(client client.Interface, dsQuery *dataselect.DataSelectQue } eventList = event.CreateEventList(event.FillEventsType(serviceEvents), dsQuery) - log.Printf("Found %d events related to %s service in %s namespace", len(eventList.Events), name, namespace) + klog.V(args.LogLevelVerbose).Infof("Found %d events related to %s service in %s namespace", len(eventList.Events), name, namespace) return &eventList, nil } diff --git a/modules/api/pkg/resource/service/list.go b/modules/api/pkg/resource/service/list.go index 4e8fa4e6d5a8..76e2d2a10f42 100644 --- a/modules/api/pkg/resource/service/list.go +++ b/modules/api/pkg/resource/service/list.go @@ -15,10 +15,9 @@ package service import ( - "log" - v1 "k8s.io/api/core/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -64,7 +63,7 @@ type ServiceList struct { // GetServiceList returns a list of all services in the cluster. func GetServiceList(client client.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*ServiceList, error) { - log.Print("Getting list of all services in the cluster") + klog.V(4).Infof("Getting list of all services in the cluster") channels := &common.ResourceChannels{ ServiceList: common.GetServiceListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/serviceaccount/detail.go b/modules/api/pkg/resource/serviceaccount/detail.go index db53b94ca736..6302db8d978f 100644 --- a/modules/api/pkg/resource/serviceaccount/detail.go +++ b/modules/api/pkg/resource/serviceaccount/detail.go @@ -16,11 +16,11 @@ package serviceaccount import ( "context" - "log" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // ServiceAccountDetail contains detailed information about a service account. @@ -31,7 +31,7 @@ type ServiceAccountDetail struct { // GetServiceAccountDetail returns detailed information about a service account. func GetServiceAccountDetail(client client.Interface, namespace, name string) (*ServiceAccountDetail, error) { - log.Printf("Getting details of %s service account in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s service account in %s namespace", name, namespace) raw, err := client.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/statefulset/detail.go b/modules/api/pkg/resource/statefulset/detail.go index 45aadc6a8f6f..3f8d50e7f18b 100644 --- a/modules/api/pkg/resource/statefulset/detail.go +++ b/modules/api/pkg/resource/statefulset/detail.go @@ -16,11 +16,12 @@ package statefulset import ( "context" - "log" apps "k8s.io/api/apps/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/errors" @@ -38,7 +39,7 @@ type StatefulSetDetail struct { // GetStatefulSetDetail gets Stateful Set details. func GetStatefulSetDetail(client kubernetes.Interface, metricClient metricapi.MetricClient, namespace, name string) (*StatefulSetDetail, error) { - log.Printf("Getting details of %s statefulset in %s namespace", name, namespace) + klog.V(4).Infof("Getting details of %s statefulset in %s namespace", name, namespace) ss, err := client.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/statefulset/list.go b/modules/api/pkg/resource/statefulset/list.go index 52fc7a66356e..c3c538c68a26 100644 --- a/modules/api/pkg/resource/statefulset/list.go +++ b/modules/api/pkg/resource/statefulset/list.go @@ -15,11 +15,10 @@ package statefulset import ( - "log" - apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" @@ -53,7 +52,7 @@ type StatefulSet struct { // GetStatefulSetList returns a list of all Stateful Sets in the cluster. func GetStatefulSetList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery, metricClient metricapi.MetricClient) (*StatefulSetList, error) { - log.Print("Getting list of all stateful sets in the cluster") + klog.V(4).Infof("Getting list of all stateful sets in the cluster") channels := &common.ResourceChannels{ StatefulSetList: common.GetStatefulSetListChannel(client, nsQuery, 1), diff --git a/modules/api/pkg/resource/statefulset/pods.go b/modules/api/pkg/resource/statefulset/pods.go index f056a7bfc971..5bb25a64c8d6 100644 --- a/modules/api/pkg/resource/statefulset/pods.go +++ b/modules/api/pkg/resource/statefulset/pods.go @@ -16,12 +16,13 @@ package statefulset import ( "context" - "log" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + metricapi "k8s.io/dashboard/api/pkg/integration/metric/api" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -34,7 +35,7 @@ import ( func GetStatefulSetPods(client kubernetes.Interface, metricClient metricapi.MetricClient, dsQuery *dataselect.DataSelectQuery, name, namespace string) (*pod.PodList, error) { - log.Printf("Getting replication controller %s pods in namespace %s", name, namespace) + klog.V(4).Infof("Getting replication controller %s pods in namespace %s", name, namespace) pods, err := getRawStatefulSetPods(client, name, namespace) if err != nil { diff --git a/modules/api/pkg/resource/storageclass/detail.go b/modules/api/pkg/resource/storageclass/detail.go index e4cb68e802a4..17bf4ff53b39 100644 --- a/modules/api/pkg/resource/storageclass/detail.go +++ b/modules/api/pkg/resource/storageclass/detail.go @@ -16,11 +16,11 @@ package storageclass import ( "context" - "log" storage "k8s.io/api/storage/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" ) // StorageClassDetail provides the presentation layer view of Storage Class resource. @@ -31,7 +31,7 @@ type StorageClassDetail struct { // GetStorageClass returns Storage Class resource. func GetStorageClass(client kubernetes.Interface, name string) (*StorageClassDetail, error) { - log.Printf("Getting details of %s storage class", name) + klog.V(4).Infof("Getting details of %s storage class", name) sc, err := client.StorageV1().StorageClasses().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { diff --git a/modules/api/pkg/resource/storageclass/list.go b/modules/api/pkg/resource/storageclass/list.go index 9b7d444f5d4a..d26fc1e87fb3 100644 --- a/modules/api/pkg/resource/storageclass/list.go +++ b/modules/api/pkg/resource/storageclass/list.go @@ -15,10 +15,9 @@ package storageclass import ( - "log" - storage "k8s.io/api/storage/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" "k8s.io/dashboard/api/pkg/resource/common" "k8s.io/dashboard/api/pkg/resource/dataselect" @@ -46,7 +45,7 @@ type StorageClass struct { // GetStorageClassList returns a list of all storage class objects in the cluster. func GetStorageClassList(client kubernetes.Interface, dsQuery *dataselect.DataSelectQuery) ( *StorageClassList, error) { - log.Print("Getting list of storage classes in the cluster") + klog.V(4).Infof("Getting list of storage classes in the cluster") channels := &common.ResourceChannels{ StorageClassList: common.GetStorageClassListChannel(client, 1), diff --git a/modules/api/pkg/validation/validateappname.go b/modules/api/pkg/validation/validateappname.go index 474b8644c6de..35752211a02f 100644 --- a/modules/api/pkg/validation/validateappname.go +++ b/modules/api/pkg/validation/validateappname.go @@ -16,11 +16,12 @@ package validation import ( "context" - "log" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" + "k8s.io/dashboard/api/pkg/args" "k8s.io/dashboard/errors" ) @@ -39,7 +40,7 @@ type AppNameValidity struct { // ValidateAppName validates application name. When error is returned, name validity could not be // determined. func ValidateAppName(spec *AppNameValiditySpec, client client.Interface) (*AppNameValidity, error) { - log.Printf("Validating %s application name in %s namespace", spec.Name, spec.Namespace) + klog.V(args.LogLevelVerbose).Infof("Validating %s application name in %s namespace", spec.Name, spec.Namespace) isValidDeployment := false isValidService := false @@ -64,7 +65,7 @@ func ValidateAppName(spec *AppNameValiditySpec, client client.Interface) (*AppNa isValid := isValidDeployment && isValidService - log.Printf("Validation result for %s application name in %s namespace is %t", spec.Name, + klog.V(args.LogLevelVerbose).Infof("Validation result for %s application name in %s namespace is %t", spec.Name, spec.Namespace, isValid) return &AppNameValidity{Valid: isValid}, nil diff --git a/modules/api/pkg/validation/validateprotocol.go b/modules/api/pkg/validation/validateprotocol.go index 5a0538e28e26..a27d3bd0c729 100644 --- a/modules/api/pkg/validation/validateprotocol.go +++ b/modules/api/pkg/validation/validateprotocol.go @@ -15,9 +15,10 @@ package validation import ( - "log" - api "k8s.io/api/core/v1" + "k8s.io/klog/v2" + + "k8s.io/dashboard/api/pkg/args" ) // ProtocolValiditySpec is a specification of protocol validation request. @@ -37,13 +38,13 @@ type ProtocolValidity struct { // ValidateProtocol validates protocol based on whether created service is set to NodePort or NodeBalancer type. func ValidateProtocol(spec *ProtocolValiditySpec) *ProtocolValidity { - log.Printf("Validating %s protocol for service with external set to %v", spec.Protocol, spec.IsExternal) + klog.V(args.LogLevelVerbose).Infof("Validating %s protocol for service with external set to %v", spec.Protocol, spec.IsExternal) isValid := true if spec.Protocol == api.ProtocolUDP && spec.IsExternal { isValid = false } - log.Printf("Validation result for %s protocol is %v", spec.Protocol, isValid) + klog.V(args.LogLevelVerbose).Infof("Validation result for %s protocol is %v", spec.Protocol, isValid) return &ProtocolValidity{Valid: isValid} } diff --git a/modules/auth/go.mod b/modules/auth/go.mod index b3df231044ae..9544c4a42d18 100644 --- a/modules/auth/go.mod +++ b/modules/auth/go.mod @@ -16,6 +16,7 @@ require ( ) require ( + github.com/Yiling-J/theine-go v0.5.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -24,6 +25,7 @@ require ( github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -56,6 +58,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect diff --git a/modules/auth/go.sum b/modules/auth/go.sum index d5b45a254f7f..4d6b53738101 100644 --- a/modules/auth/go.sum +++ b/modules/auth/go.sum @@ -1,7 +1,11 @@ +github.com/Yiling-J/theine-go v0.5.0 h1:ZIyQDTMRBWEqEopViwmScgSUKBNatTNcIfniN/hXMbo= +github.com/Yiling-J/theine-go v0.5.0/go.mod h1:mdch1vjgGWd7s3rWKvY+MF5InRLfRv/CWVI9RVNQ8wY= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -17,6 +21,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -131,6 +137,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -152,6 +162,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/modules/auth/pkg/router/setup.go b/modules/auth/pkg/router/setup.go index e933816c53a5..a8563d3ce57f 100644 --- a/modules/auth/pkg/router/setup.go +++ b/modules/auth/pkg/router/setup.go @@ -32,12 +32,13 @@ func init() { gin.SetMode(gin.ReleaseMode) } - router = gin.Default() + router = gin.New() + router.Use(gin.Recovery()) _ = router.SetTrustedProxies(nil) v1 = router.Group("/api/v1") v1.Use(csrf.Gin().CSRF( - csrf.Gin().WithCSRFActionGetter(helpers.GetResourceFromPath)), - ) + csrf.Gin().WithCSRFActionGetter(helpers.GetResourceFromPath), + )) } func V1() *gin.RouterGroup { diff --git a/modules/common/client/args/args.go b/modules/common/client/args/args.go new file mode 100644 index 000000000000..48b9c36f3f62 --- /dev/null +++ b/modules/common/client/args/args.go @@ -0,0 +1,60 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package args + +import ( + "time" + + "github.com/spf13/pflag" +) + +var ( + argCacheEnabled = pflag.Bool("cache-enabled", true, "whether client cache should be enabled or not") + argClusterContextEnabled = pflag.Bool("cluster-context-enabled", false, "whether multi-cluster cache context support should be enabled or not") + argTokenExchangeEndpoint = pflag.String("token-exchange-endpoint", "", "endpoint used in multi-cluster cache to exchange tokens for context identifiers") + argCacheSize = pflag.Int("cache-size", 1000, "max number of cache entries") + argCacheTTL = pflag.Duration("cache-ttl", 10*time.Minute, "cache entry TTL") + argCacheRefreshDebounce = pflag.Duration("cache-refresh-debounce", 5*time.Second, "minimal time between cache refreshes in the background") +) + +func Ensure() { + if *argClusterContextEnabled && len(*argTokenExchangeEndpoint) == 0 { + panic("token-exchange-endpoint must be set when cluster-context-enabled is set to true") + } +} + +func CacheEnabled() bool { + return *argCacheEnabled +} + +func ClusterContextEnabled() bool { + return *argClusterContextEnabled +} + +func TokenExchangeEndpoint() string { + return *argTokenExchangeEndpoint +} + +func CacheSize() int { + return *argCacheSize +} + +func CacheTTL() time.Duration { + return *argCacheTTL +} + +func CacheRefreshDebounce() time.Duration { + return *argCacheRefreshDebounce +} diff --git a/modules/common/client/cache/cache.go b/modules/common/client/cache/cache.go new file mode 100644 index 000000000000..6a374eb846d4 --- /dev/null +++ b/modules/common/client/cache/cache.go @@ -0,0 +1,153 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "sync" + "time" + + "github.com/Yiling-J/theine-go" + "k8s.io/klog/v2" + + "k8s.io/dashboard/client/args" +) + +var ( + // cache is a global resource cache that maps our custom keys + // created based on information provided by the specific resource client. + // It stores resource lists to speed up the client response times. + cache *theine.Cache[string, any] + + // cacheLocks is used as a set that holds information about + // cache keys that are currently fetching the latest data from the + // Kubernetes API server in the background. + // It allows us to avoid multiple concurrent update calls being sent + // to the Kubernetes API. + // Once the lock is removed, the next update call can be initiated. + cacheLocks sync.Map + + // syncedLoadLocks is used to synchronize the initial cache hydration phase + // and avoid putting extra pressure on the API server. + // It maps cache keys to mutexes. + syncedLoadLocks sync.Map +) + +func init() { + var err error + if cache, err = theine.NewBuilder[string, any](int64(args.CacheSize())).Build(); err != nil { + panic(err) + } +} + +// Get gives access to cache entries. +// It requires a Key to be provided which is used to calculate cache key SHA. +func Get[T any](key Key) (*T, bool, error) { + typedValue := new(T) + + cacheKey, err := key.SHA() + if err != nil { + return typedValue, false, err + } + + value, exists := cache.Get(cacheKey) + if exists { + typedValue = value.(*T) + } + + return typedValue, exists, nil +} + +// Set allows updating cache with specific values. +// It requires a Key to be provided which is used to calculate cache key SHA. +func Set[T any](key Key, value T) error { + cacheKey, err := key.SHA() + if err != nil { + return err + } + + set(cacheKey, value) + return nil +} + +func set[T any](key string, value T) bool { + return cache.SetWithTTL(key, value, 1, args.CacheTTL()) +} + +// DeferredLoad updates cache in the background with the data fetched using the loadFunc. +func DeferredLoad[T any](key Key, loadFunc func() (T, error)) { + go func() { + cacheKey, err := key.SHA() + if err != nil { + klog.ErrorS(err, "failed loading cache key", "key", cacheKey) + return + } + + if _, locked := cacheLocks.LoadOrStore(cacheKey, struct{}{}); locked { + klog.V(4).InfoS("cache is already being updated, skipping", "key", cacheKey) + return + } + + defer time.AfterFunc(args.CacheRefreshDebounce(), func() { + cacheLocks.Delete(cacheKey) + klog.V(4).InfoS("released cache update lock", "key", cacheKey) + }) + + cacheValue, err := loadFunc() + if err != nil { + klog.ErrorS(err, "failed loading cache data", "key", cacheKey) + return + } + + set(cacheKey, cacheValue) + klog.V(4).InfoS("cache updated successfully", "key", cacheKey) + }() +} + +// SyncedLoad initializes the cache using the [loadFunc]. It ensures that there will be no concurrent +// calls to the [loadFunc]. The first call will call the [loadFunc] and initialize the cache while +// concurrent calls will be waiting for the first call to finish. Once cache is updated and lock is freed +// other routines will return the value from cache without making any extra calls to the [loadFunc]. +func SyncedLoad[T any](key Key, loadFunc func() (*T, error)) (*T, error) { + cacheKey, err := key.SHA() + if err != nil { + klog.ErrorS(err, "failed loading cache key", "key", cacheKey) + return new(T), err + } + + l, _ := syncedLoadLocks.LoadOrStore(cacheKey, &sync.Mutex{}) + lock := l.(*sync.Mutex) + lock.Lock() + + defer func() { + lock.Unlock() + syncedLoadLocks.Delete(cacheKey) + }() + + if value, exists := cache.Get(cacheKey); exists { + klog.V(4).InfoS("synced from the cache", "key", cacheKey) + return value.(*T), nil + } + + cacheValue, err := loadFunc() + if err != nil { + klog.ErrorS(err, "failed loading cache data", "key", cacheKey) + return new(T), err + } + + set(cacheKey, cacheValue) + klog.V(4).InfoS("cache initialized successfully", "key", cacheKey) + + return cacheValue, nil +} diff --git a/modules/common/client/cache/client/client.go b/modules/common/client/cache/client/client.go new file mode 100644 index 000000000000..55c64cc83203 --- /dev/null +++ b/modules/common/client/cache/client/client.go @@ -0,0 +1,64 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + client "k8s.io/client-go/kubernetes" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + + "k8s.io/dashboard/client/cache/client/core" +) + +// CachedInterface is a custom wrapper around the [client.Interface]. +// It allows certain requests such as LIST to be cached to optimize +// the response time. It is especially helpful when dealing with +// clusters with big amount of resources. +type CachedInterface interface { + client.Interface +} + +type cachedClientset struct { + *client.Clientset + + coreV1 v1.CoreV1Interface +} + +func (in *cachedClientset) CoreV1() v1.CoreV1Interface { + return in.coreV1 +} + +func New(config *rest.Config, token string) (CachedInterface, error) { + var cs cachedClientset + var err error + + configShallowCopy := *config + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + clientset, err := client.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + + cs.coreV1, err = core.NewClient(&configShallowCopy, clientset.AuthorizationV1(), token) + if err != nil { + return nil, err + } + + cs.Clientset = clientset + return &cs, nil +} diff --git a/modules/common/client/cache/client/common/resourcelister.go b/modules/common/client/cache/client/common/resourcelister.go new file mode 100644 index 000000000000..5849d81f8f2c --- /dev/null +++ b/modules/common/client/cache/client/common/resourcelister.go @@ -0,0 +1,120 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "fmt" + + authorizationapiv1 "k8s.io/api/authorization/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + "k8s.io/klog/v2" + + "k8s.io/dashboard/client/cache" + + "k8s.io/dashboard/errors" + "k8s.io/dashboard/types" +) + +type ResourceListerInterface[T any] interface { + List(ctx context.Context, opts metav1.ListOptions) (*T, error) +} + +type CachedResourceLister[T any] struct { + authorizationV1 authorizationv1.AuthorizationV1Interface + token string + ssar *authorizationapiv1.SelfSubjectAccessReview +} + +func (in CachedResourceLister[T]) List(ctx context.Context, lister ResourceListerInterface[T], opts metav1.ListOptions) (*T, error) { + cacheKey := in.cacheKey(opts) + cachedList, found, err := cache.Get[T](cacheKey) + if err != nil { + return new(T), err + } + if !found { + klog.V(3).InfoS("resource not found in cache, initializing", "kind", in.kind(), "namespace", in.namespace()) + return cache.SyncedLoad(cacheKey, func() (*T, error) { + return lister.List(ctx, opts) + }) + } + + review, err := in.authorizationV1.SelfSubjectAccessReviews().Create(ctx, in.selfSubjectAccessReview(types.VerbList), metav1.CreateOptions{}) + if err != nil { + return new(T), err + } + + if review.Status.Allowed { + klog.V(3).InfoS("resource found in cache, updating in background", "kind", in.kind(), "namespace", in.namespace()) + cache.DeferredLoad[*T](cacheKey, func() (*T, error) { + return lister.List(ctx, opts) + }) + return cachedList, nil + } + + return new(T), errors.NewForbidden( + errors.MsgForbiddenError, + fmt.Errorf("%s: %s", review.Status.Reason, review.Status.EvaluationError), + ) +} + +func (in CachedResourceLister[_]) selfSubjectAccessReview(verb types.Verb) *authorizationapiv1.SelfSubjectAccessReview { + in.ssar.Spec.ResourceAttributes.Verb = verb.String() + return in.ssar +} + +func (in CachedResourceLister[_]) cacheKey(opts metav1.ListOptions) cache.Key { + return cache.NewKey(in.kind(), in.namespace(), in.token, opts) +} + +func (in CachedResourceLister[T]) ensure() { + if len(in.token) == 0 { + panic("token arg is required when creating CachedResourceLister") + } + + if len(in.ssar.Spec.ResourceAttributes.Resource) == 0 { + panic("resource kind arg is required when creating CachedResourceLister") + } +} + +func (in CachedResourceLister[_]) namespace() string { + return in.ssar.Spec.ResourceAttributes.Namespace +} + +func (in CachedResourceLister[T]) kind() types.ResourceKind { + return types.ResourceKind(in.ssar.Spec.ResourceAttributes.Resource) +} + +func NewCachedResourceLister[T any]( + authorization authorizationv1.AuthorizationV1Interface, + options ...Option[T], +) CachedResourceLister[T] { + result := CachedResourceLister[T]{ + authorizationV1: authorization, + ssar: &authorizationapiv1.SelfSubjectAccessReview{ + Spec: authorizationapiv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationapiv1.ResourceAttributes{}, + }, + }, + } + + for _, opt := range options { + opt(&result) + } + + result.ensure() + return result +} diff --git a/modules/common/client/cache/client/common/resourcelister_options.go b/modules/common/client/cache/client/common/resourcelister_options.go new file mode 100644 index 000000000000..c06f7c59d0fb --- /dev/null +++ b/modules/common/client/cache/client/common/resourcelister_options.go @@ -0,0 +1,51 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "k8s.io/dashboard/types" +) + +type Option[T any] func(*CachedResourceLister[T]) + +func WithNamespace[T any](namespace string) Option[T] { + return func(lister *CachedResourceLister[T]) { + lister.ssar.Spec.ResourceAttributes.Namespace = namespace + } +} + +func WithResourceKind[T any](kind types.ResourceKind) Option[T] { + return func(lister *CachedResourceLister[T]) { + lister.ssar.Spec.ResourceAttributes.Resource = kind.String() + } +} + +func WithGroup[T any](group string) Option[T] { + return func(lister *CachedResourceLister[T]) { + lister.ssar.Spec.ResourceAttributes.Group = group + } +} + +func WithVersion[T any](version string) Option[T] { + return func(lister *CachedResourceLister[T]) { + lister.ssar.Spec.ResourceAttributes.Version = version + } +} + +func WithToken[T any](token string) Option[T] { + return func(lister *CachedResourceLister[T]) { + lister.token = token + } +} diff --git a/modules/common/client/cache/client/core/configmaps.go b/modules/common/client/cache/client/core/configmaps.go new file mode 100644 index 000000000000..e22b60201256 --- /dev/null +++ b/modules/common/client/cache/client/core/configmaps.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type configmaps struct { + v1.ConfigMapInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + namespace string + token string +} + +func (in *configmaps) List(ctx context.Context, opts metav1.ListOptions) (*corev1.ConfigMapList, error) { + return common.NewCachedResourceLister[corev1.ConfigMapList]( + in.authorizationV1, + common.WithNamespace[corev1.ConfigMapList](in.namespace), + common.WithToken[corev1.ConfigMapList](in.token), + common.WithGroup[corev1.ConfigMapList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.ConfigMapList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.ConfigMapList](types.ResourceKindConfigMap), + ).List(ctx, in.ConfigMapInterface, opts) +} + +func newConfigMaps(c *Client, namespace, token string) v1.ConfigMapInterface { + return &configmaps{c.CoreV1Client.ConfigMaps(namespace), c.authorizationV1, namespace, token} +} diff --git a/modules/common/client/cache/client/core/core.go b/modules/common/client/cache/client/core/core.go new file mode 100644 index 000000000000..a91c8496a35e --- /dev/null +++ b/modules/common/client/cache/client/core/core.go @@ -0,0 +1,74 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" +) + +type Client struct { + *corev1.CoreV1Client + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *Client) Pods(namespace string) corev1.PodInterface { + return newPods(in, namespace, in.token) +} + +func (in *Client) ConfigMaps(namespace string) corev1.ConfigMapInterface { + return newConfigMaps(in, namespace, in.token) +} + +func (in *Client) Secrets(namespace string) corev1.SecretInterface { + return newSecrets(in, namespace, in.token) +} + +func (in *Client) Namespaces() corev1.NamespaceInterface { + return newNamespaces(in, in.token) +} + +func (in *Client) Nodes() corev1.NodeInterface { + return newNodes(in, in.token) +} + +func (in *Client) PersistentVolumes() corev1.PersistentVolumeInterface { + return newPersistentVolumes(in, in.token) +} + +func (in *Client) PersistentVolumeClaims(namespace string) corev1.PersistentVolumeClaimInterface { + return newPersistentVolumeClaims(in, namespace, in.token) +} + +func NewClient(c *rest.Config, authorizationV1 authorizationv1.AuthorizationV1Interface, token string) (corev1.CoreV1Interface, error) { + httpClient, err := rest.HTTPClientFor(c) + if err != nil { + return nil, err + } + + client, err := corev1.NewForConfigAndClient(c, httpClient) + if err != nil { + return nil, err + } + + return &Client{ + client, + authorizationV1, + token, + }, nil +} diff --git a/modules/common/client/cache/client/core/namespaces.go b/modules/common/client/cache/client/core/namespaces.go new file mode 100644 index 000000000000..8762483a842a --- /dev/null +++ b/modules/common/client/cache/client/core/namespaces.go @@ -0,0 +1,49 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + + "k8s.io/dashboard/types" +) + +type namespaces struct { + v1.NamespaceInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *namespaces) List(ctx context.Context, opts metav1.ListOptions) (*corev1.NamespaceList, error) { + return common.NewCachedResourceLister[corev1.NamespaceList]( + in.authorizationV1, + common.WithToken[corev1.NamespaceList](in.token), + common.WithGroup[corev1.NamespaceList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.NamespaceList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.NamespaceList](types.ResourceKindNamespace), + ).List(ctx, in.NamespaceInterface, opts) +} + +func newNamespaces(c *Client, token string) v1.NamespaceInterface { + return &namespaces{c.CoreV1Client.Namespaces(), c.authorizationV1, token} +} diff --git a/modules/common/client/cache/client/core/nodes.go b/modules/common/client/cache/client/core/nodes.go new file mode 100644 index 000000000000..f0383ece2d8a --- /dev/null +++ b/modules/common/client/cache/client/core/nodes.go @@ -0,0 +1,48 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type nodes struct { + v1.NodeInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *nodes) List(ctx context.Context, opts metav1.ListOptions) (*corev1.NodeList, error) { + return common.NewCachedResourceLister[corev1.NodeList]( + in.authorizationV1, + common.WithToken[corev1.NodeList](in.token), + common.WithGroup[corev1.NodeList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.NodeList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.NodeList](types.ResourceKindNode), + ).List(ctx, in.NodeInterface, opts) +} + +func newNodes(c *Client, token string) v1.NodeInterface { + return &nodes{c.CoreV1Client.Nodes(), c.authorizationV1, token} +} diff --git a/modules/common/client/cache/client/core/persistentvolumeclaims.go b/modules/common/client/cache/client/core/persistentvolumeclaims.go new file mode 100644 index 000000000000..e9ffd45cd634 --- /dev/null +++ b/modules/common/client/cache/client/core/persistentvolumeclaims.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type persistentVolumeClaims struct { + v1.PersistentVolumeClaimInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + namespace string + token string +} + +func (in *persistentVolumeClaims) List(ctx context.Context, opts metav1.ListOptions) (*corev1.PersistentVolumeClaimList, error) { + return common.NewCachedResourceLister[corev1.PersistentVolumeClaimList]( + in.authorizationV1, + common.WithNamespace[corev1.PersistentVolumeClaimList](in.namespace), + common.WithToken[corev1.PersistentVolumeClaimList](in.token), + common.WithGroup[corev1.PersistentVolumeClaimList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.PersistentVolumeClaimList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.PersistentVolumeClaimList](types.ResourceKindPersistentVolumeClaim), + ).List(ctx, in.PersistentVolumeClaimInterface, opts) +} + +func newPersistentVolumeClaims(c *Client, namespace, token string) v1.PersistentVolumeClaimInterface { + return &persistentVolumeClaims{c.CoreV1Client.PersistentVolumeClaims(namespace), c.authorizationV1, namespace, token} +} diff --git a/modules/common/client/cache/client/core/persistentvolumes.go b/modules/common/client/cache/client/core/persistentvolumes.go new file mode 100644 index 000000000000..1428fbb6e11a --- /dev/null +++ b/modules/common/client/cache/client/core/persistentvolumes.go @@ -0,0 +1,48 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type persistentVolumes struct { + v1.PersistentVolumeInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *persistentVolumes) List(ctx context.Context, opts metav1.ListOptions) (*corev1.PersistentVolumeList, error) { + return common.NewCachedResourceLister[corev1.PersistentVolumeList]( + in.authorizationV1, + common.WithToken[corev1.PersistentVolumeList](in.token), + common.WithGroup[corev1.PersistentVolumeList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.PersistentVolumeList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.PersistentVolumeList](types.ResourceKindPersistentVolume), + ).List(ctx, in.PersistentVolumeInterface, opts) +} + +func newPersistentVolumes(c *Client, token string) v1.PersistentVolumeInterface { + return &persistentVolumes{c.CoreV1Client.PersistentVolumes(), c.authorizationV1, token} +} diff --git a/modules/common/client/cache/client/core/pods.go b/modules/common/client/cache/client/core/pods.go new file mode 100644 index 000000000000..db84fe69ae51 --- /dev/null +++ b/modules/common/client/cache/client/core/pods.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type pods struct { + v1.PodInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + namespace string + token string +} + +func (in *pods) List(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) { + return common.NewCachedResourceLister[corev1.PodList]( + in.authorizationV1, + common.WithNamespace[corev1.PodList](in.namespace), + common.WithToken[corev1.PodList](in.token), + common.WithGroup[corev1.PodList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.PodList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.PodList](types.ResourceKindPod), + ).List(ctx, in.PodInterface, opts) +} + +func newPods(c *Client, namespace, token string) v1.PodInterface { + return &pods{c.CoreV1Client.Pods(namespace), c.authorizationV1, namespace, token} +} diff --git a/modules/common/client/cache/client/core/secrets.go b/modules/common/client/cache/client/core/secrets.go new file mode 100644 index 000000000000..f626d350b6b8 --- /dev/null +++ b/modules/common/client/cache/client/core/secrets.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type secrets struct { + v1.SecretInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + namespace string + token string +} + +func (in *secrets) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) { + return common.NewCachedResourceLister[corev1.SecretList]( + in.authorizationV1, + common.WithNamespace[corev1.SecretList](in.namespace), + common.WithToken[corev1.SecretList](in.token), + common.WithGroup[corev1.SecretList](corev1.SchemeGroupVersion.Group), + common.WithVersion[corev1.SecretList](corev1.SchemeGroupVersion.Version), + common.WithResourceKind[corev1.SecretList](types.ResourceKindSecret), + ).List(ctx, in.SecretInterface, opts) +} + +func newSecrets(c *Client, namespace, token string) v1.SecretInterface { + return &secrets{c.CoreV1Client.Secrets(namespace), c.authorizationV1, namespace, token} +} diff --git a/modules/common/client/cache/client/extensions/crds.go b/modules/common/client/cache/client/extensions/crds.go new file mode 100644 index 000000000000..46ddc93afaa4 --- /dev/null +++ b/modules/common/client/cache/client/extensions/crds.go @@ -0,0 +1,52 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "context" + + extensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + v1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + + "k8s.io/dashboard/client/cache/client/common" + "k8s.io/dashboard/types" +) + +type customResourceDefinitions struct { + v1.CustomResourceDefinitionInterface + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *customResourceDefinitions) List(ctx context.Context, opts metav1.ListOptions) (*extensionsv1.CustomResourceDefinitionList, error) { + return common.NewCachedResourceLister[extensionsv1.CustomResourceDefinitionList]( + in.authorizationV1, + common.WithToken[extensionsv1.CustomResourceDefinitionList](in.token), + common.WithGroup[extensionsv1.CustomResourceDefinitionList](extensionsv1.SchemeGroupVersion.Group), + common.WithVersion[extensionsv1.CustomResourceDefinitionList](extensionsv1.SchemeGroupVersion.Version), + common.WithResourceKind[extensionsv1.CustomResourceDefinitionList](types.ResourceKindCustomResourceDefinition), + ).List(ctx, in.CustomResourceDefinitionInterface, opts) +} + +func newCustomResourceDefinitions(c *Client, token string) v1.CustomResourceDefinitionInterface { + return &customResourceDefinitions{ + CustomResourceDefinitionInterface: c.ApiextensionsV1Client.CustomResourceDefinitions(), + authorizationV1: c.authorizationV1, + token: token, + } +} diff --git a/modules/common/client/cache/client/extensions/extensions.go b/modules/common/client/cache/client/extensions/extensions.go new file mode 100644 index 000000000000..d4dbc5123b45 --- /dev/null +++ b/modules/common/client/cache/client/extensions/extensions.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + v1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + "k8s.io/client-go/rest" +) + +type Client struct { + *v1.ApiextensionsV1Client + + authorizationV1 authorizationv1.AuthorizationV1Interface + token string +} + +func (in *Client) CustomResourceDefinitions() v1.CustomResourceDefinitionInterface { + return newCustomResourceDefinitions(in, in.token) +} + +func NewClient(c *rest.Config, authorizationV1 authorizationv1.AuthorizationV1Interface, token string) (v1.ApiextensionsV1Interface, error) { + httpClient, err := rest.HTTPClientFor(c) + if err != nil { + return nil, err + } + + client, err := v1.NewForConfigAndClient(c, httpClient) + if err != nil { + return nil, err + } + + return &Client{ + client, + authorizationV1, + token, + }, nil +} diff --git a/modules/common/client/cache/client/extensionsclient.go b/modules/common/client/cache/client/extensionsclient.go new file mode 100644 index 000000000000..17d147196992 --- /dev/null +++ b/modules/common/client/cache/client/extensionsclient.go @@ -0,0 +1,65 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + extensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + v1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" + "k8s.io/client-go/rest" + + "k8s.io/dashboard/client/cache/client/extensions" +) + +// CachedExtensionsInterface is a custom wrapper around the [extensionsclient.Interface]. +// It allows certain requests such as CRD LIST to be cached to optimize +// the response time. It is especially helpful when dealing with +// clusters with big amount of resources. +type CachedExtensionsInterface interface { + extensionsclient.Interface +} + +type cachedExtensionsClientset struct { + *extensionsclient.Clientset + + apiextensionsV1 v1.ApiextensionsV1Interface +} + +func (in *cachedExtensionsClientset) ApiextensionsV1() v1.ApiextensionsV1Interface { + return in.apiextensionsV1 +} + +func NewCachedExtensionsClient(config *rest.Config, authorizationV1 authorizationv1.AuthorizationV1Interface, token string) (CachedExtensionsInterface, error) { + var cs cachedExtensionsClientset + var err error + + configShallowCopy := *config + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + clientset, err := extensionsclient.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + + cs.apiextensionsV1, err = extensions.NewClient(&configShallowCopy, authorizationV1, token) + if err != nil { + return nil, err + } + + cs.Clientset = clientset + return &cs, nil +} diff --git a/modules/common/client/cache/key.go b/modules/common/client/cache/key.go new file mode 100644 index 000000000000..e93d4ae5ed2b --- /dev/null +++ b/modules/common/client/cache/key.go @@ -0,0 +1,173 @@ +// Copyright 2017 The Kubernetes Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/Yiling-J/theine-go" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" + + "k8s.io/dashboard/client/args" + "k8s.io/dashboard/errors" + "k8s.io/dashboard/helpers" + "k8s.io/dashboard/types" +) + +// contextCache is used in multi-cluster setup to map tokens to context (cluster) identifiers. +// Multi-cluster setup is enabled by providing `cluster-context-enabled=true` argument. +var contextCache *theine.Cache[string, string] + +func init() { + var err error + if contextCache, err = theine.NewBuilder[string, string](int64(args.CacheSize())).Build(); err != nil { + panic(err) + } +} + +// key used in cache as request identifier in single-cluster setup. +// In multi-cluster setup Key is used instead. +type key struct { + // kind is a Kubernetes resource kind. + kind types.ResourceKind + + // namespace is a Kubernetes resource namespace. + namespace string + + // opts is a list options object used by the Kubernetes client. + opts metav1.ListOptions +} + +// SHA calculates key SHA based on its internal fields. +func (k key) SHA() (string, error) { + return helpers.HashObject(k) +} + +// MarshalJSON is a custom marshall implementation that allows to marshall internal key fields. +// It is required during SHA calculation. +func (k key) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind types.ResourceKind + Namespace string + Opts metav1.ListOptions + }{ + Kind: k.kind, + Namespace: k.namespace, + Opts: metav1.ListOptions{LabelSelector: k.opts.LabelSelector, FieldSelector: k.opts.FieldSelector}, + }) +} + +// Key embeds an internal key structure and extends it with the support +// for the multi-cluster cache key creation. +type Key struct { + key + + // token is an auth token used to exchange it for the context ID. + token string + + // context is an internal identifier used in conjunction with the key + // structure fields to create a cache key SHA that will be unique across + // all clusters. + context string +} + +// MarshalJSON is a custom marshall implementation that allows to marshall internal Key fields. +// It is required during SHA calculation. +func (k Key) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + K key + Context string + }{ + K: k.key, + Context: k.context, + }) +} + +// SHA calculates Key SHA based on its internal fields. +// It is also responsible for exchanging the token for the context identifier with the external source of truth +// configured via `token-exchange-endpoint` flag. +func (k Key) SHA() (sha string, err error) { + if !args.ClusterContextEnabled() { + return k.key.SHA() + } + + contextKey, exists := contextCache.Get(k.token) + if !exists { + contextKey, err = k.exchangeToken(k.token) + if err != nil { + return "", err + } + + contextCache.SetWithTTL(k.token, contextKey, 1, args.CacheTTL()) + } + + k.context = contextKey + return helpers.HashObject(k) +} + +// exchangeToken exchanges the token for context identifier using the external source of truth +// configured via `token-exchange-endpoint` flag. +func (k Key) exchangeToken(token string) (string, error) { + client := &http.Client{Transport: &tokenExchangeTransport{token, http.DefaultTransport}} + response, err := client.Get(args.TokenExchangeEndpoint()) + if err != nil { + return "", err + } + + if response.StatusCode == http.StatusUnauthorized || response.StatusCode == http.StatusForbidden { + return "", errors.NewUnauthorized(fmt.Sprintf("could not exchange token: %s", response.Status)) + } + + if response.StatusCode != http.StatusOK { + klog.ErrorS(errors.NewBadRequest(response.Status), "could not exchange token", "url", args.TokenExchangeEndpoint()) + return "", errors.NewBadRequest(response.Status) + } + + defer func(body io.ReadCloser) { + if err := body.Close(); err != nil { + klog.ErrorS(err, "could not close response body writer") + } + }(response.Body) + + contextKey, err := io.ReadAll(response.Body) + if err != nil { + return "", err + } + + klog.V(3).InfoS("token exchange successful", "context", contextKey) + return string(contextKey), nil +} + +// NewKey creates a new cache Key. +func NewKey(kind types.ResourceKind, namespace, token string, opts metav1.ListOptions) Key { + return Key{key: key{kind, namespace, opts}, token: token} +} + +// tokenExchangeTransport implements the mechanism +// by which individual HTTP requests to token exchange endpoint are made. +type tokenExchangeTransport struct { + token string + transport http.RoundTripper +} + +// RoundTrip executes a single HTTP transaction. +func (in *tokenExchangeTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+in.token) + return in.transport.RoundTrip(req) +} diff --git a/modules/common/client/client.go b/modules/common/client/client.go index 0c978a9e360c..6ac56677d0e2 100644 --- a/modules/common/client/client.go +++ b/modules/common/client/client.go @@ -27,6 +27,9 @@ import ( client "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/klog/v2" + + "k8s.io/dashboard/client/args" + cacheclient "k8s.io/dashboard/client/cache/client" ) func InClusterClient() client.Interface { @@ -55,7 +58,16 @@ func Client(request *http.Request) (client.Interface, error) { return nil, fmt.Errorf("client package not initialized") } - return clientFromRequest(request) + config, err := configFromRequest(request) + if err != nil { + return nil, err + } + + if args.CacheEnabled() { + return cacheclient.New(config, GetBearerToken(request)) + } + + return client.NewForConfig(config) } func APIExtensionsClient(request *http.Request) (apiextensionsclientset.Interface, error) { @@ -68,6 +80,15 @@ func APIExtensionsClient(request *http.Request) (apiextensionsclientset.Interfac return nil, err } + kubeClient, err := client.NewForConfig(config) + if err != nil { + return nil, err + } + + if args.CacheEnabled() { + return cacheclient.NewCachedExtensionsClient(config, kubeClient.AuthorizationV1(), GetBearerToken(request)) + } + return apiextensionsclientset.NewForConfig(config) } diff --git a/modules/common/client/go.mod b/modules/common/client/go.mod index b165cb492c09..6ec18fc7293f 100644 --- a/modules/common/client/go.mod +++ b/modules/common/client/go.mod @@ -3,12 +3,16 @@ module k8s.io/dashboard/client go 1.23.0 require ( + github.com/Yiling-J/theine-go v0.5.0 github.com/gobuffalo/flect v1.0.3 + github.com/spf13/pflag v1.0.5 k8s.io/api v0.31.1 k8s.io/apiextensions-apiserver v0.31.1 k8s.io/apimachinery v0.31.1 k8s.io/client-go v0.31.1 k8s.io/dashboard/errors v0.0.0-00010101000000-000000000000 + k8s.io/dashboard/helpers v0.0.0-00010101000000-000000000000 + k8s.io/dashboard/types v0.0.0-00010101000000-000000000000 k8s.io/klog/v2 v2.130.1 ) @@ -16,6 +20,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -29,13 +34,15 @@ require ( github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/samber/lo v1.47.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.23.0 // indirect @@ -54,4 +61,8 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace k8s.io/dashboard/errors => ../errors +replace ( + k8s.io/dashboard/errors => ../errors + k8s.io/dashboard/helpers => ../helpers + k8s.io/dashboard/types => ../types +) diff --git a/modules/common/client/go.sum b/modules/common/client/go.sum index a771d41d5f95..ef4f84b8d773 100644 --- a/modules/common/client/go.sum +++ b/modules/common/client/go.sum @@ -1,3 +1,7 @@ +github.com/Yiling-J/theine-go v0.5.0 h1:ZIyQDTMRBWEqEopViwmScgSUKBNatTNcIfniN/hXMbo= +github.com/Yiling-J/theine-go v0.5.0/go.mod h1:mdch1vjgGWd7s3rWKvY+MF5InRLfRv/CWVI9RVNQ8wY= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,6 +11,8 @@ github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtz github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -44,6 +50,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -71,6 +79,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -86,6 +96,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -102,6 +116,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/modules/common/client/init.go b/modules/common/client/init.go index fa56ce47b7e7..b965398b8558 100644 --- a/modules/common/client/init.go +++ b/modules/common/client/init.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/tools/clientcmd/api" "k8s.io/klog/v2" + "k8s.io/dashboard/client/args" "k8s.io/dashboard/errors" ) @@ -131,15 +132,6 @@ func configFromRequest(request *http.Request) (*rest.Config, error) { return buildConfigFromAuthInfo(authInfo) } -func clientFromRequest(request *http.Request) (client.Interface, error) { - config, err := configFromRequest(request) - if err != nil { - return nil, err - } - - return client.NewForConfig(config) -} - func buildConfigFromAuthInfo(authInfo *api.AuthInfo) (*rest.Config, error) { cmdCfg := api.NewConfig() @@ -206,8 +198,9 @@ func handleImpersonation(authInfo *api.AuthInfo, request *http.Request) { } func Init(options ...Option) { - builder := newConfigBuilder(options...) + args.Ensure() + builder := newConfigBuilder(options...) config, err := builder.buildBaseConfig() if err != nil { klog.Errorf("Could not init kubernetes client config: %s", err) diff --git a/modules/common/client/types.go b/modules/common/client/types.go index 5e835e2329ce..04449b8f65ca 100644 --- a/modules/common/client/types.go +++ b/modules/common/client/types.go @@ -40,6 +40,11 @@ const ( // ImpersonateUserExtraHeader is the header name used to associate extra fields with the user. // It is optional, and it requires ImpersonateUserHeader to be set. ImpersonateUserExtraHeader = "Impersonate-Extra-" + // ClusterContextHeader is the header name used to associate client request with specific cluster. + // It can be used in environments that use a proxy between Dashboard and API server to + // forward requests to the specific cluster. Internally it ensures that the client cache + // always matches the correct cluster. + ClusterContextHeader = "Cluster-Context" ) // ResourceVerber is responsible for performing generic CRUD operations on all supported resources. diff --git a/modules/common/client/verber.go b/modules/common/client/verber.go index 76b9ddd22e3e..a6eedf08788d 100644 --- a/modules/common/client/verber.go +++ b/modules/common/client/verber.go @@ -56,11 +56,11 @@ func (v *resourceVerber) groupVersionResourceFromUnstructured(object *unstructur func (v *resourceVerber) groupVersionResourceFromKind(kind string) (schema.GroupVersionResource, error) { if gvr, exists := kindToGroupVersionResource[kind]; exists { - klog.V(3).InfoS("GroupVersionResource cache hit", "kind", kind) + klog.V(4).InfoS("GroupVersionResource cache hit", "kind", kind) return gvr, nil } - klog.V(3).InfoS("GroupVersionResource cache miss", "kind", kind) + klog.V(4).InfoS("GroupVersionResource cache miss", "kind", kind) _, resourceList, err := v.discovery.ServerGroupsAndResources() if err != nil { return schema.GroupVersionResource{}, err @@ -140,7 +140,7 @@ func (v *resourceVerber) Delete(kind string, namespace string, name string, prop defaultDeleteOptions.GracePeriodSeconds = &gracePeriodSeconds } - klog.V(1).InfoS("deleting resource", "kind", kind, "namespace", namespace, "name", name, "propagationPolicy", propagationPolicy, "deleteNow", deleteNow) + klog.V(2).InfoS("deleting resource", "kind", kind, "namespace", namespace, "name", name, "propagationPolicy", propagationPolicy, "deleteNow", deleteNow) return v.client.Resource(gvr).Namespace(namespace).Delete(context.TODO(), name, defaultDeleteOptions) } @@ -151,7 +151,7 @@ func (v *resourceVerber) Update(object *unstructured.Unstructured) error { gvr := v.groupVersionResourceFromUnstructured(object) return retry.RetryOnConflict(retry.DefaultRetry, func() error { - klog.V(2).InfoS("fetching latest resource version", "group", gvr.Group, "version", gvr.Version, "resource", gvr.Resource, "name", name, "namespace", namespace) + klog.V(4).InfoS("fetching latest resource version", "group", gvr.Group, "version", gvr.Version, "resource", gvr.Resource, "name", name, "namespace", namespace) result, getErr := v.client.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) if getErr != nil { return fmt.Errorf("failed to get latest %s version: %w", gvr.Resource, getErr) @@ -174,7 +174,7 @@ func (v *resourceVerber) Update(object *unstructured.Unstructured) error { return fmt.Errorf("failed creating merge patch: %w", err) } - klog.V(3).InfoS("patching resource", "group", gvr.Group, "version", gvr.Version, "resource", gvr.Resource, "name", name, "namespace", namespace, "patch", string(patchBytes)) + klog.V(2).InfoS("patching resource", "group", gvr.Group, "version", gvr.Version, "resource", gvr.Resource, "name", name, "namespace", namespace, "patch", string(patchBytes)) _, updateErr := v.client.Resource(gvr).Namespace(namespace).Patch(context.TODO(), name, k8stypes.MergePatchType, patchBytes, metav1.PatchOptions{}) return updateErr }) diff --git a/modules/common/helpers/common.go b/modules/common/helpers/common.go index 735d8d2846ec..d8372f48bff5 100644 --- a/modules/common/helpers/common.go +++ b/modules/common/helpers/common.go @@ -16,7 +16,10 @@ package helpers import ( "crypto/rand" + "crypto/sha256" + "encoding/base32" "encoding/base64" + "encoding/json" "os" "strings" ) @@ -56,3 +59,12 @@ func Random64BaseEncodedBytes(size int) string { bytes := RandomBytes(size) return base64.StdEncoding.EncodeToString(bytes) } + +func HashObject(any interface{}) (string, error) { + out, err := json.Marshal(any) + if err != nil { + return "", err + } + sha := sha256.Sum256(out) + return base32.StdEncoding.EncodeToString(sha[:]), nil +} diff --git a/modules/common/types/resourcekind.go b/modules/common/types/resourcekind.go index d42d8529ee3d..68946c62818d 100644 --- a/modules/common/types/resourcekind.go +++ b/modules/common/types/resourcekind.go @@ -85,3 +85,17 @@ func (k ResourceKind) Restartable() bool { return false } + +func (k ResourceKind) String() string { + return string(k) +} + +type Verb string + +func (in Verb) String() string { + return string(in) +} + +const ( + VerbList = "list" +) diff --git a/modules/web/go.mod b/modules/web/go.mod index a31617d2d16e..fd233282cacd 100644 --- a/modules/web/go.mod +++ b/modules/web/go.mod @@ -20,6 +20,7 @@ require ( ) require ( + github.com/Yiling-J/theine-go v0.5.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect @@ -28,6 +29,7 @@ require ( github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -59,6 +61,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/net v0.28.0 // indirect @@ -72,6 +75,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.31.1 // indirect + k8s.io/dashboard/types v0.0.0-00010101000000-000000000000 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect @@ -84,4 +88,5 @@ replace ( k8s.io/dashboard/client => ../common/client k8s.io/dashboard/errors => ../common/errors k8s.io/dashboard/helpers => ../common/helpers + k8s.io/dashboard/types => ../common/types ) diff --git a/modules/web/go.sum b/modules/web/go.sum index 931fb8f39475..b16872b5a5f1 100644 --- a/modules/web/go.sum +++ b/modules/web/go.sum @@ -1,9 +1,13 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Yiling-J/theine-go v0.5.0 h1:ZIyQDTMRBWEqEopViwmScgSUKBNatTNcIfniN/hXMbo= +github.com/Yiling-J/theine-go v0.5.0/go.mod h1:mdch1vjgGWd7s3rWKvY+MF5InRLfRv/CWVI9RVNQ8wY= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -19,6 +23,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnSAmaUZd9HZYZSw9w/bFjp3FiNaJ5WmE= @@ -133,6 +139,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -154,6 +164,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/modules/web/hack/postinstall.sh b/modules/web/hack/postinstall.sh index 0a9426ef130b..5d0a2f1cbd5c 100755 --- a/modules/web/hack/postinstall.sh +++ b/modules/web/hack/postinstall.sh @@ -20,7 +20,7 @@ fi if [ -z "${DEPENDABOT}" ]; then # TODO: When dependabot will support yarn postinstall properly, move following line into .yarnrc.yml. - cd ../../ && npx husky install modules/web/.husky + cd ../../ && npx husky modules/web/.husky exit $? fi diff --git a/modules/web/pkg/args/args.go b/modules/web/pkg/args/args.go index 8750380d53b4..77831fc3099a 100644 --- a/modules/web/pkg/args/args.go +++ b/modules/web/pkg/args/args.go @@ -28,20 +28,23 @@ import ( ) var ( - argNamespace = pflag.String("namespace", helpers.GetEnv("POD_NAMESPACE", "kubernetes-dashboard"), "Namespace to use when creating Dashboard specific resources, i.e. settings config map") - argInsecurePort = pflag.Int("insecure-port", 8000, "port to listen to for incoming HTTP requests") - argPort = pflag.Int("port", 8001, "secure port to listen to for incoming HTTPS requests") - argInsecureBindAddress = pflag.IP("insecure-bind-address", net.IPv4(127, 0, 0, 1), "IP address on which to serve the --insecure-port, set to 127.0.0.1 for all interfaces") - argBindAddress = pflag.IP("bind-address", net.IPv4(0, 0, 0, 0), "IP address on which to serve the --port, set to 0.0.0.0 for all interfaces") - argDefaultCertDir = pflag.String("default-cert-dir", "/certs", "directory path containing files from --tls-cert-file and --tls-key-file, used also when auto-generating certificates flag is set") - argCertFile = pflag.String("tls-cert-file", "", "file containing the default x509 certificate for HTTPS") - argKeyFile = pflag.String("tls-key-file", "", "file containing the default x509 private key matching --tls-cert-file") - argSettingsConfigMapName = pflag.String("settings-config-map-name", "kubernetes-dashboard-settings", "Name of a config map, that stores settings") - argSystemBanner = pflag.String("system-banner", "", "system banner message displayed in the app if non-empty, it accepts simple HTML") - argSystemBannerSeverity = pflag.String("system-banner-severity", "INFO", "severity of system banner, should be one of 'INFO', 'WARNING' or 'ERROR'") argAutoGenerateCertificates = pflag.Bool("auto-generate-certificates", false, "enables automatic certificates generation used to serve HTTPS") - argLocaleConfig = pflag.String("locale-config", "/locale_conf.json", "path to file containing the locale configuration") - argKubeconfig = pflag.String("kubeconfig", "", "Path to kubeconfig file") + + argInsecurePort = pflag.Int("insecure-port", 8000, "port to listen to for incoming HTTP requests") + argPort = pflag.Int("port", 8001, "secure port to listen to for incoming HTTPS requests") + + argInsecureBindAddress = pflag.IP("insecure-bind-address", net.IPv4(127, 0, 0, 1), "IP address on which to serve the --insecure-port, set to 127.0.0.1 for all interfaces") + argBindAddress = pflag.IP("bind-address", net.IPv4(0, 0, 0, 0), "IP address on which to serve the --port, set to 0.0.0.0 for all interfaces") + + argNamespace = pflag.String("namespace", helpers.GetEnv("POD_NAMESPACE", "kubernetes-dashboard"), "Namespace to use when creating Dashboard specific resources, i.e. settings config map") + argDefaultCertDir = pflag.String("default-cert-dir", "/certs", "directory path containing files from --tls-cert-file and --tls-key-file, used also when auto-generating certificates flag is set") + argCertFile = pflag.String("tls-cert-file", "", "file containing the default x509 certificate for HTTPS") + argKeyFile = pflag.String("tls-key-file", "", "file containing the default x509 private key matching --tls-cert-file") + argSettingsConfigMapName = pflag.String("settings-config-map-name", "kubernetes-dashboard-settings", "Name of a config map, that stores settings") + argSystemBanner = pflag.String("system-banner", "", "system banner message displayed in the app if non-empty, it accepts simple HTML") + argSystemBannerSeverity = pflag.String("system-banner-severity", "INFO", "severity of system banner, should be one of 'INFO', 'WARNING' or 'ERROR'") + argLocaleConfig = pflag.String("locale-config", "/locale_conf.json", "path to file containing the locale configuration") + argKubeconfig = pflag.String("kubeconfig", "", "Path to kubeconfig file") ) func init() { diff --git a/modules/web/pkg/router/setup.go b/modules/web/pkg/router/setup.go index 87283cc882b2..67525eecf1ee 100644 --- a/modules/web/pkg/router/setup.go +++ b/modules/web/pkg/router/setup.go @@ -16,6 +16,7 @@ package router import ( "github.com/gin-gonic/gin" + "k8s.io/dashboard/web/pkg/environment" ) @@ -29,7 +30,8 @@ func init() { gin.SetMode(gin.ReleaseMode) } - router = gin.Default() + router = gin.New() + router.Use(gin.Recovery()) _ = router.SetTrustedProxies(nil) root = router.Group("/") } diff --git a/modules/web/pkg/settings/manager.go b/modules/web/pkg/settings/manager.go index d0e10775a21e..5b5148567fdf 100644 --- a/modules/web/pkg/settings/manager.go +++ b/modules/web/pkg/settings/manager.go @@ -81,7 +81,7 @@ func (sm *SettingsManager) load(client kubernetes.Interface) (changed bool) { configMap, err := client.CoreV1().ConfigMaps(args.Namespace()). Get(context.Background(), args.SettingsConfigMapName(), metav1.GetOptions{}) if err != nil { - klog.V(1).Infof("Could not get settings config map: %s", err.Error()) + klog.ErrorS(err, "could not get settings config map") return } @@ -95,7 +95,7 @@ func (sm *SettingsManager) load(client kubernetes.Interface) (changed bool) { if pinnedResources, ok := sm.rawSettings[PinnedResourcesKey]; ok { if p, err := UnmarshalPinnedResources(pinnedResources); err != nil { - klog.InfoS("Cannot unmarshal pinned resources", "pinnedResources", pinnedResources, "error", err) + klog.ErrorS(err, "cannot unmarshal pinned resources", "pinnedResources", pinnedResources) } else { sm.pinnedResources = *p } @@ -103,7 +103,7 @@ func (sm *SettingsManager) load(client kubernetes.Interface) (changed bool) { if settings, ok := sm.rawSettings[ConfigMapSettingsKey]; ok { if s, err := UnmarshalSettings(settings); err != nil { - klog.InfoS("Cannot unmarshal settings", "settings", settings, "error", err) + klog.ErrorS(err, "cannot unmarshal settings", "settings", settings) } else { sm.settings = s }