You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2021/11/01 06:32:44 UTC
[skywalking-infra-e2e] branch main updated: Support save
pod/container std log on the Environment (#62)
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-infra-e2e.git
The following commit(s) were added to refs/heads/main by this push:
new 5adf908 Support save pod/container std log on the Environment (#62)
5adf908 is described below
commit 5adf908561e2c93e8ee0d82e6eb549cd4cb3b234
Author: mrproliu <74...@qq.com>
AuthorDate: Mon Nov 1 14:32:36 2021 +0800
Support save pod/container std log on the Environment (#62)
---
action.yaml | 46 +++++++-
commands/root.go | 27 +++--
commands/setup/setup.go | 4 +
docs/en/setup/Configuration-File.md | 8 ++
docs/en/setup/Run-E2E-Tests.md | 16 ++-
internal/components/setup/common.go | 48 ++++-----
internal/components/setup/compose.go | 145 ++++++++++++++++++--------
internal/components/setup/compose_listener.go | 103 ++++++++++++++++++
internal/components/setup/kind.go | 112 ++++++++++++++++----
internal/components/setup/kind_listener.go | 135 ++++++++++++++++++++++++
internal/util/config.go | 1 +
internal/util/env_log.go | 112 ++++++++++++++++++++
internal/util/k8s.go | 72 +++++++++++--
internal/util/restClientGetter.go | 83 ---------------
14 files changed, 728 insertions(+), 184 deletions(-)
diff --git a/action.yaml b/action.yaml
index 681b8b8..20f91e9 100644
--- a/action.yaml
+++ b/action.yaml
@@ -21,6 +21,9 @@ inputs:
e2e-file:
description: File path of e2e file
required: true
+ log-dir:
+ description: The container logs directory
+ required: false
runs:
using: "composite"
@@ -30,5 +33,46 @@ runs:
make -C $GITHUB_ACTION_PATH docker
docker run -v $(pwd):/tmp -w /tmp --entrypoint=sh docker.io/apache/e2e:latest -c "cp /usr/local/bin/e2e /tmp/e2e"
install ./e2e /usr/local/bin
+ - name: E2E Dir Generator
+ id: 'e2e-dir-generator'
+ shell: bash
+ run: |
+ WORK_DIR="${{ runner.temp }}/skywalking-infra-e2e"
+ echo "::set-output name=work::$WORK_DIR"
+
+ LOG_DIR=""
+ LOG_JOB_DIR=""
+ if [[ "${{ inputs.log-dir }}" == "" ]]
+ then
+ matrix='${{ toJSON(matrix) }}'
+ if [[ "$matrix" == "null" ]]
+ then
+ LOG_DIR="$WORK_DIR/logs"
+ LOG_JOB_DIR="$LOG_DIR/${{ github.job }}"
+ else
+ combine_matrix=$(echo $matrix|jq -r 'to_entries|map(.value)|tostring')
+ # remove json syntax
+ combine_matrix=`echo $combine_matrix|sed -e 's/\[\|\]\|\"//g'`
+ combine_matrix=`echo $combine_matrix|sed -e 's/[\{|\}]//g'`
+ # replace to path
+ combine_matrix=`echo $combine_matrix|sed -e 's/[^A-Za-z0-9_-]/_/g'`
+ LOG_DIR="$WORK_DIR/logs"
+ LOG_JOB_DIR="$LOG_DIR/${{ github.job }}_$combine_matrix"
+ fi
+ elif [[ "${{ inputs.log-dir }}" == /* ]]
+ then
+ LOG_DIR="${{ inputs.log-dir }}"
+ LOG_JOB_DIR="${{ inputs.log-dir }}"
+ else
+ LOG_DIR="$WORK_DIR/${{ inputs.log-dir }}"
+ LOG_JOB_DIR="$WORK_DIR/${{ inputs.log-dir }}"
+ fi
+ echo "::set-output name=log::$LOG_DIR"
+ echo "::set-output name=log-case::$LOG_JOB_DIR"
+ echo "SW_INFRA_E2E_LOG_DIR=$LOG_DIR" >> $GITHUB_ENV
- shell: bash
- run: e2e run -c "${{ inputs.e2e-file }}"
+ run: |
+ e2e run \
+ -c "${{ inputs.e2e-file }}" \
+ -w "${{ steps.e2e-dir-generator.outputs.work }}" \
+ -l "${{ steps.e2e-dir-generator.outputs.log-case }}"
\ No newline at end of file
diff --git a/commands/root.go b/commands/root.go
index ff625db..29572f7 100644
--- a/commands/root.go
+++ b/commands/root.go
@@ -55,18 +55,32 @@ var Root = &cobra.Command{
}
logger.Log.SetLevel(level)
- util.WorkDir = util.ExpandFilePath(util.WorkDir)
- if _, err := os.Stat(util.WorkDir); os.IsNotExist(err) {
- if err := os.MkdirAll(util.WorkDir, os.ModePerm); err != nil {
- logger.Log.Warnf("failed to create working directory %v", util.WorkDir)
- return err
- }
+ util.WorkDir, err = ExpandPathAndCreate(util.WorkDir)
+ if err != nil {
+ logger.Log.Warnf("failed to create working directory %v", util.WorkDir)
+ return err
+ }
+
+ util.LogDir, err = ExpandPathAndCreate(util.LogDir)
+ if err != nil {
+ logger.Log.Warnf("failed to create logging directory %v", util.LogDir)
+ return err
}
return nil
},
}
+func ExpandPathAndCreate(path string) (string, error) {
+ path = util.ExpandFilePath(path)
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ if err := os.MkdirAll(path, os.ModePerm); err != nil {
+ return path, err
+ }
+ }
+ return path, nil
+}
+
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() error {
@@ -78,6 +92,7 @@ func Execute() error {
Root.PersistentFlags().StringVarP(&verbosity, "verbosity", "v", logrus.InfoLevel.String(), "log level (debug, info, warn, error, fatal, panic")
Root.PersistentFlags().StringVarP(&util.WorkDir, "work-dir", "w", "~/.skywalking-infra-e2e", "the working directory for skywalking-infra-e2e")
+ Root.PersistentFlags().StringVarP(&util.LogDir, "log-dir", "l", "~/.skywalking-infra-e2e/logs", "the container logs directory for environment")
Root.PersistentFlags().StringVarP(&util.CfgFile, "config", "c", constant.E2EDefaultFile, "the config file")
return Root.Execute()
diff --git a/commands/setup/setup.go b/commands/setup/setup.go
index 4dc6adf..a932d4d 100644
--- a/commands/setup/setup.go
+++ b/commands/setup/setup.go
@@ -38,6 +38,7 @@ var Setup = &cobra.Command{
return err
}
+ defer setup.CloseLogFollower()
if err := DoSetupAccordingE2E(); err != nil {
return fmt.Errorf("[Setup] %s", err)
}
@@ -61,6 +62,7 @@ func DoSetupAccordingE2E() error {
e2eConfig := config.GlobalConfig.E2EConfig
+ setup.InitLogFollower()
if e2eConfig.Setup.Env == constant.Kind {
err := setup.KindSetup(&e2eConfig)
if err != nil {
@@ -80,6 +82,8 @@ func DoSetupAccordingE2E() error {
}
func DoStopSetup() {
+ // close log follower
+ setup.CloseLogFollower()
// notify clean up
setup.KindCleanNotify()
}
diff --git a/docs/en/setup/Configuration-File.md b/docs/en/setup/Configuration-File.md
index d1c5a6d..969c682 100644
--- a/docs/en/setup/Configuration-File.md
+++ b/docs/en/setup/Configuration-File.md
@@ -85,6 +85,10 @@ If you want to access the resource from host, should follow these steps:
url: http://${pod_foo_host}:${pod_foo_8080}/
```
+#### Log
+
+The console output of each pod could be found in `${workDir}/logs/${namespace}/${podName}.log`.
+
### Compose
```yaml
@@ -123,6 +127,10 @@ If you want to get the service host and port mapping, should follow these steps:
url: http://${oap_host}:${oap_8080}/
```
+#### Log
+
+The console output of each service could be found in `${workDir}/logs/{serviceName}/std.log`.
+
## Trigger
After the `Setup` step is finished, use the `Trigger` step to generate traffic.
diff --git a/docs/en/setup/Run-E2E-Tests.md b/docs/en/setup/Run-E2E-Tests.md
index 682dcd5..28353f8 100644
--- a/docs/en/setup/Run-E2E-Tests.md
+++ b/docs/en/setup/Run-E2E-Tests.md
@@ -31,9 +31,23 @@ e2e cleanup
To use skywalking-infra-e2e in GitHub Actions, add a step in your GitHub workflow.
+The working directory could be uploaded to GitHub Action Artifact after the task is completed, which contains environment variables and container logs in the environment.
+
```yaml
- name: Run E2E Test
uses: apache/skywalking-infra-e2e@main # always prefer to use a revision instead of `main`.
with:
- e2e-file: e2e.yaml # need to run E2E file path
+ e2e-file: e2e.yaml # (required)need to run E2E file path
+ log-dir: /path/to/log/dir # (Optional)Use `<work_dir>/logs/<job_name>_<matrix_value>`(if have GHA matrix) or `<work_dir>/logs/<job_name>` in GHA, and output logs into `<work_dir>/logs` out of GHA env, such as running locally.
+```
+
+If you want to upload the log directory to the GitHub Action Artifact when this E2E test failure, you could define the below content in your GitHub Action Job.
+
+```yaml
+- name: Upload E2E Log
+ uses: actions/upload-artifact@v2
+ if: ${{ failure() }} # Only upload the artifact when E2E testing failure
+ with:
+ name: e2e-log
+ path: "${{ env.SW_INFRA_E2E_LOG_DIR }}" # The SkyWalking Infra E2E action sets SW_INFRA_E2E_LOG_DIR automatically.
```
\ No newline at end of file
diff --git a/internal/components/setup/common.go b/internal/components/setup/common.go
index 86fcef6..87039d4 100644
--- a/internal/components/setup/common.go
+++ b/internal/components/setup/common.go
@@ -19,20 +19,21 @@
package setup
import (
+ "context"
"fmt"
- "io/ioutil"
"os"
"strings"
"time"
- "k8s.io/client-go/dynamic"
- "k8s.io/client-go/kubernetes"
-
"github.com/apache/skywalking-infra-e2e/internal/config"
"github.com/apache/skywalking-infra-e2e/internal/logger"
"github.com/apache/skywalking-infra-e2e/internal/util"
)
+var (
+ logFollower *util.ResourceLogFollower
+)
+
func RunStepsAndWait(steps []config.Step, waitTimeout time.Duration, k8sCluster *util.K8sClusterInfo) error {
logger.Log.Debugf("wait timeout is %v", waitTimeout.String())
@@ -50,7 +51,7 @@ func RunStepsAndWait(steps []config.Step, waitTimeout time.Duration, k8sCluster
Path: step.Path,
Waits: step.Waits,
}
- err := createManifestAndWait(k8sCluster.Client, k8sCluster.Interface, manifest, waitTimeout)
+ err := createManifestAndWait(k8sCluster, manifest, waitTimeout)
if err != nil {
return err
}
@@ -60,7 +61,7 @@ func RunStepsAndWait(steps []config.Step, waitTimeout time.Duration, k8sCluster
Waits: step.Waits,
}
- err := RunCommandsAndWait(command, waitTimeout)
+ err := RunCommandsAndWait(command, waitTimeout, k8sCluster)
if err != nil {
return err
}
@@ -79,16 +80,11 @@ func RunStepsAndWait(steps []config.Step, waitTimeout time.Duration, k8sCluster
}
// createManifestAndWait creates manifests in k8s cluster and concurrent waits according to the manifests' wait conditions.
-func createManifestAndWait(c *kubernetes.Clientset, dc dynamic.Interface, manifest config.Manifest, timeout time.Duration) error {
+func createManifestAndWait(c *util.K8sClusterInfo, manifest config.Manifest, timeout time.Duration) error {
waitSet := util.NewWaitSet(timeout)
- kubeConfigYaml, err := ioutil.ReadFile(kubeConfigPath)
- if err != nil {
- return err
- }
-
waits := manifest.Waits
- err = createByManifest(c, dc, manifest)
+ err := createByManifest(c, manifest)
if err != nil {
return err
}
@@ -103,7 +99,7 @@ func createManifestAndWait(c *kubernetes.Clientset, dc dynamic.Interface, manife
wait := waits[idx]
logger.Log.Infof("waiting for %+v", wait)
- options, err := getWaitOptions(kubeConfigYaml, &wait)
+ options, err := getWaitOptions(c, &wait)
if err != nil {
return err
}
@@ -131,7 +127,7 @@ func createManifestAndWait(c *kubernetes.Clientset, dc dynamic.Interface, manife
}
// RunCommandsAndWait Concurrently run commands and wait for conditions.
-func RunCommandsAndWait(run config.Run, timeout time.Duration) error {
+func RunCommandsAndWait(run config.Run, timeout time.Duration, cluster *util.K8sClusterInfo) error {
waitSet := util.NewWaitSet(timeout)
commands := run.Command
@@ -140,7 +136,7 @@ func RunCommandsAndWait(run config.Run, timeout time.Duration) error {
}
waitSet.WaitGroup.Add(1)
- go executeCommandsAndWait(commands, run.Waits, waitSet)
+ go executeCommandsAndWait(commands, run.Waits, waitSet, cluster)
go func() {
waitSet.WaitGroup.Wait()
@@ -160,7 +156,7 @@ func RunCommandsAndWait(run config.Run, timeout time.Duration) error {
return nil
}
-func executeCommandsAndWait(commands string, waits []config.Wait, waitSet *util.WaitSet) {
+func executeCommandsAndWait(commands string, waits []config.Wait, waitSet *util.WaitSet, cluster *util.K8sClusterInfo) {
defer waitSet.WaitGroup.Done()
// executes commands
@@ -177,13 +173,7 @@ func executeCommandsAndWait(commands string, waits []config.Wait, waitSet *util.
wait := waits[idx]
logger.Log.Infof("waiting for %+v", wait)
- kubeConfigYaml, err := ioutil.ReadFile(kubeConfigPath)
- if err != nil {
- err = fmt.Errorf("read kube config failed: %s", err)
- waitSet.ErrChan <- err
- }
-
- options, err := getWaitOptions(kubeConfigYaml, &wait)
+ options, err := getWaitOptions(cluster, &wait)
if err != nil {
err = fmt.Errorf("commands: [%s] get wait options error: %s", commands, err)
waitSet.ErrChan <- err
@@ -213,3 +203,13 @@ func GetIdentity() string {
}
return runID
}
+
+func InitLogFollower() {
+ logFollower = util.NewResourceLogFollower(context.Background(), util.LogDir)
+}
+
+func CloseLogFollower() {
+ if logFollower != nil {
+ logFollower.Close()
+ }
+}
diff --git a/internal/components/setup/compose.go b/internal/components/setup/compose.go
index 0543187..12b2d14 100644
--- a/internal/components/setup/compose.go
+++ b/internal/components/setup/compose.go
@@ -36,6 +36,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
+ "github.com/docker/docker/pkg/stdcopy"
"github.com/testcontainers/testcontainers-go"
)
@@ -61,7 +62,7 @@ func ComposeSetup(e2eConfig *config.E2EConfig) error {
compose := testcontainers.NewLocalDockerCompose(composeFilePaths, identifier)
// bind wait port
- serviceWithPorts, err := bindWaitPort(e2eConfig, compose)
+ services, err := buildComposeServices(e2eConfig, compose)
if err != nil {
return fmt.Errorf("bind wait ports error: %v", err)
}
@@ -75,6 +76,18 @@ func ComposeSetup(e2eConfig *config.E2EConfig) error {
}
cmd = append(cmd, "up", "-d")
+ // Listen container create
+ listener := NewComposeContainerListener(context.Background(), cli, services)
+ defer listener.Stop()
+ err = listener.Listen(func(container *ComposeContainer) {
+ if err = exposeComposeLog(cli, container.Service, container.ID, logFollower); err == nil {
+ container.Service.beenFollowLog = true
+ }
+ })
+ if err != nil {
+ return err
+ }
+
// setup
execError := compose.WithCommand(cmd).Invoke()
if execError.Error != nil {
@@ -82,7 +95,7 @@ func ComposeSetup(e2eConfig *config.E2EConfig) error {
}
// find exported port and build env
- err = exposeServiceEnv(serviceWithPorts, cli, identifier, e2eConfig)
+ err = exposeComposeService(services, cli, identifier, e2eConfig)
if err != nil {
return err
}
@@ -97,53 +110,103 @@ func ComposeSetup(e2eConfig *config.E2EConfig) error {
return nil
}
-func exposeServiceEnv(serviceWithPorts map[string][]*hostPortCachedStrategy, cli *client.Client, identity string, e2eConfig *config.E2EConfig) error {
+type ComposeService struct {
+ Name string
+ waitStrategies []*hostPortCachedStrategy
+ beenFollowLog bool
+}
+
+func exposeComposeService(services []*ComposeService, cli *client.Client,
+ identity string, e2eConfig *config.E2EConfig) error {
dockerProvider := &DockerProvider{client: cli}
+
// find exported port and build env
- for service, portList := range serviceWithPorts {
- container, err := findContainer(cli, fmt.Sprintf("%s_%s", identity, getInstanceName(service)))
+ for _, service := range services {
+ container, err := findContainer(cli, fmt.Sprintf("%s_%s", identity, getInstanceName(service.Name)))
if err != nil {
return err
}
- if len(portList) == 0 {
- continue
- }
-
- containerPorts := container.Ports
- // get real ip address for access and export to env
- host, err := dockerProvider.daemonHost(context.Background())
- if err != nil {
+ // expose port
+ if err := exposeComposePort(dockerProvider, service, container, e2eConfig); err != nil {
return err
}
- // format: <service_name>_host
- if err := exportComposeEnv(fmt.Sprintf("%s_host", service), host, service); err != nil {
- return err
+ // if service log not follow, expose log
+ if !service.beenFollowLog {
+ if err := exposeComposeLog(dockerProvider.client, service, container.ID, logFollower); err != nil {
+ return err
+ }
+ service.beenFollowLog = true
}
+ }
+ return nil
+}
- for inx := range portList {
- for _, containerPort := range containerPorts {
- if int(containerPort.PrivatePort) != portList[inx].expectPort {
- continue
- }
-
- if err := waitPortUntilReady(e2eConfig, container, dockerProvider, portList[inx].expectPort); err != nil {
- return err
- }
-
- // expose env config to env
- // format: <service_name>_<port>
- if err := exportComposeEnv(
- fmt.Sprintf("%s_%d", service, containerPort.PrivatePort),
- fmt.Sprintf("%d", containerPort.PublicPort),
- service); err != nil {
- return err
- }
- break
+func exposeComposePort(dockerProvider *DockerProvider, service *ComposeService, container *types.Container,
+ e2eConfig *config.E2EConfig) error {
+ if len(service.waitStrategies) == 0 {
+ return nil
+ }
+
+ // get real ip address for access and export to env
+ host, err := dockerProvider.daemonHost(context.Background())
+ if err != nil {
+ return err
+ }
+
+ // format: <service_name>_host
+ if err := exportComposeEnv(fmt.Sprintf("%s_host", service.Name), host, service.Name); err != nil {
+ return err
+ }
+
+ for inx := range service.waitStrategies {
+ for _, containerPort := range container.Ports {
+ if int(containerPort.PrivatePort) != service.waitStrategies[inx].expectPort {
+ continue
}
+
+ if err := waitPortUntilReady(e2eConfig, container, dockerProvider, service.waitStrategies[inx].expectPort); err != nil {
+ return err
+ }
+
+ // expose env config to env
+ // format: <service_name>_<port>
+ if err := exportComposeEnv(
+ fmt.Sprintf("%s_%d", service.Name, containerPort.PrivatePort),
+ fmt.Sprintf("%d", containerPort.PublicPort),
+ service.Name); err != nil {
+ return err
+ }
+ break
}
}
+
+ return nil
+}
+
+// export container log to local path
+func exposeComposeLog(cli *client.Client, service *ComposeService, containerID string, logFollower *util.ResourceLogFollower) error {
+ logs, err := cli.ContainerLogs(logFollower.Ctx, containerID, types.ContainerLogsOptions{
+ ShowStdout: true,
+ ShowStderr: true,
+ Follow: true,
+ Details: false,
+ })
+ if err != nil {
+ return err
+ }
+ writer, err := logFollower.BuildLogWriter(fmt.Sprintf("%s/std.log", service.Name))
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ defer writer.Close()
+ if _, err := stdcopy.StdCopy(writer, writer, logs); err != nil {
+ logger.Log.Warnf("write %s std log error: %v", service.Name, err)
+ }
+ }()
return nil
}
@@ -156,16 +219,17 @@ func exportComposeEnv(key, value, service string) error {
return nil
}
-func bindWaitPort(e2eConfig *config.E2EConfig, compose *testcontainers.LocalDockerCompose) (map[string][]*hostPortCachedStrategy, error) {
+func buildComposeServices(e2eConfig *config.E2EConfig, compose *testcontainers.LocalDockerCompose) ([]*ComposeService, error) {
waitTimeout := e2eConfig.Setup.GetTimeout()
- serviceWithPorts := make(map[string][]*hostPortCachedStrategy)
+ services := make([]*ComposeService, 0)
for service, content := range compose.Services {
serviceConfig := content.(map[interface{}]interface{})
ports := serviceConfig["ports"]
+ serviceContext := &ComposeService{Name: service}
+ services = append(services, serviceContext)
if ports == nil {
continue
}
- serviceWithPorts[service] = []*hostPortCachedStrategy{}
portList := ports.([]interface{})
for inx := range portList {
@@ -180,11 +244,10 @@ func bindWaitPort(e2eConfig *config.E2EConfig, compose *testcontainers.LocalDock
}
// temporary don't use testcontainers-go framework wait strategy until fix docker-in-docker bug
// compose.WithExposedService(service, exportPort, strategy)
-
- serviceWithPorts[service] = append(serviceWithPorts[service], strategy)
+ serviceContext.waitStrategies = append(serviceContext.waitStrategies, strategy)
}
}
- return serviceWithPorts, nil
+ return services, nil
}
func getExpectPort(portConfig interface{}) (int, error) {
diff --git a/internal/components/setup/compose_listener.go b/internal/components/setup/compose_listener.go
new file mode 100644
index 0000000..4f1fef2
--- /dev/null
+++ b/internal/components/setup/compose_listener.go
@@ -0,0 +1,103 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you 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 setup
+
+import (
+ "context"
+
+ "github.com/docker/docker/api/types/events"
+
+ "github.com/apache/skywalking-infra-e2e/internal/logger"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/client"
+)
+
+type ComposeContainerListener struct {
+ client *client.Client
+ services []*ComposeService
+ ctx context.Context
+ cancel context.CancelFunc
+}
+
+type ComposeContainer struct {
+ Service *ComposeService
+ ID string
+}
+
+func NewComposeContainerListener(ctx context.Context, cli *client.Client, services []*ComposeService) *ComposeContainerListener {
+ childCtx, cancelFunc := context.WithCancel(ctx)
+ return &ComposeContainerListener{
+ client: cli,
+ services: services,
+ ctx: childCtx,
+ cancel: cancelFunc,
+ }
+}
+
+func (c *ComposeContainerListener) Listen(consumer func(container *ComposeContainer)) error {
+ containerEvents, errors := c.client.Events(c.ctx, types.EventsOptions{
+ Filters: filters.NewArgs(
+ filters.Arg("type", "container"),
+ filters.Arg("event", "start"),
+ ),
+ })
+
+ if len(errors) > 0 {
+ return <-errors
+ }
+
+ go func() {
+ for {
+ select {
+ case msg := <-containerEvents:
+ container := c.foundMessage(&msg)
+ if container != nil {
+ consumer(container)
+ }
+ case err := <-errors:
+ if err != nil {
+ logger.Log.Warnf("Listen docker container failed, %v", err)
+ }
+ case <-c.ctx.Done():
+ c.cancel()
+ return
+ }
+ }
+ }()
+ return nil
+}
+
+func (c *ComposeContainerListener) Stop() {
+ c.cancel()
+}
+
+func (c *ComposeContainerListener) foundMessage(message *events.Message) *ComposeContainer {
+ serviceName := message.Actor.Attributes["com.docker.compose.service"]
+ for _, service := range c.services {
+ if service.Name == serviceName {
+ return &ComposeContainer{
+ Service: service,
+ ID: message.ID,
+ }
+ }
+ }
+ return nil
+}
diff --git a/internal/components/setup/kind.go b/internal/components/setup/kind.go
index d7e09ec..8f186be 100644
--- a/internal/components/setup/kind.go
+++ b/internal/components/setup/kind.go
@@ -21,12 +21,14 @@ package setup
import (
"bufio"
"bytes"
+ "context"
"fmt"
- "io/ioutil"
"net/http"
"os"
+ "path/filepath"
"strconv"
"strings"
+ "sync"
"time"
apiv1 "k8s.io/api/admission/v1"
@@ -35,8 +37,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
- "k8s.io/client-go/dynamic"
- "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
@@ -117,6 +117,17 @@ func KindSetup(e2eConfig *config.E2EConfig) error {
return err
}
+ listener := NewKindContainerListener(context.Background(), cluster)
+ defer listener.Stop()
+ err = listener.Listen(func(pod *v1.Pod) {
+ if err = exposePerContainerLog(cluster, pod, e2eConfig.Setup.GetTimeout()); err != nil {
+ logger.Log.Warnf("export kubernetes pod log failure: %v", err)
+ }
+ })
+ if err != nil {
+ logger.Log.Warnf("listen kubernetes pod event failure: %v", err)
+ }
+
// run steps
err = RunStepsAndWait(e2eConfig.Setup.Steps, e2eConfig.Setup.GetTimeout(), cluster)
if err != nil {
@@ -124,8 +135,14 @@ func KindSetup(e2eConfig *config.E2EConfig) error {
return err
}
+ // expose logs
+ if err = exposeLogs(cluster, listener, e2eConfig.Setup.GetTimeout()); err != nil {
+ logger.Log.Errorf("export logs error: %v", err)
+ return err
+ }
+
// expose ports
- err = exposeKindService(e2eConfig.Setup.Kind.ExposePorts, e2eConfig.Setup.GetTimeout(), kubeConfigPath)
+ err = exposeKindService(e2eConfig.Setup.Kind.ExposePorts, e2eConfig.Setup.GetTimeout(), cluster)
if err != nil {
logger.Log.Errorf("export ports error: %v", err)
return err
@@ -174,12 +191,12 @@ func createKindCluster(kindConfigPath string, e2eConfig *config.E2EConfig) error
return nil
}
-func getWaitOptions(kubeConfigYaml []byte, wait *config.Wait) (options *ctlwait.WaitOptions, err error) {
+func getWaitOptions(cluster *util.K8sClusterInfo, wait *config.Wait) (options *ctlwait.WaitOptions, err error) {
if strings.Contains(wait.Resource, "/") && wait.LabelSelector != "" {
return nil, fmt.Errorf("when passing resource.group/resource.name in Resource, the labelSelector can not be set at the same time")
}
- restClientGetter := util.NewSimpleRESTClientGetter(wait.Namespace, string(kubeConfigYaml))
+ restClientGetter := cluster.CopyClusterToNamespace(wait.Namespace)
silenceOutput, _ := os.Open(os.DevNull)
ioStreams := genericclioptions.IOStreams{In: os.Stdin, Out: silenceOutput, ErrOut: os.Stderr}
waitFlags := ctlwait.NewWaitFlags(restClientGetter, ioStreams)
@@ -209,7 +226,7 @@ func getWaitOptions(kubeConfigYaml []byte, wait *config.Wait) (options *ctlwait.
return options, nil
}
-func createByManifest(c *kubernetes.Clientset, dc dynamic.Interface, manifest config.Manifest) error {
+func createByManifest(c *util.K8sClusterInfo, manifest config.Manifest) error {
files, err := util.GetManifests(manifest.Path)
if err != nil {
logger.Log.Error("get manifests failed")
@@ -218,7 +235,7 @@ func createByManifest(c *kubernetes.Clientset, dc dynamic.Interface, manifest co
for _, f := range files {
logger.Log.Infof("creating manifest %s", f)
- err = util.OperateManifest(c, dc, f, apiv1.Create)
+ err = util.OperateManifest(c.Client, c.Interface, f, apiv1.Create)
if err != nil {
logger.Log.Errorf("create manifest %s failed", f)
return err
@@ -302,10 +319,10 @@ func buildKindPort(port string, ro runtime.Object, pod *v1.Pod) (*kindPort, erro
}, nil
}
-func exposePerKindService(port config.KindExposePort, timeout time.Duration, clientGetter *util.SimpleRESTClientGetter,
+func exposePerKindService(port config.KindExposePort, timeout time.Duration, cluster *util.K8sClusterInfo,
client *rest.RESTClient, roundTripper http.RoundTripper, upgrader spdy.Upgrader, forward *kindPortForwardContext) error {
// find resource
- builder := resource.NewBuilder(clientGetter).
+ builder := resource.NewBuilder(cluster).
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
ContinueOnError().
NamespaceParam(port.Namespace).DefaultNamespace()
@@ -314,7 +331,7 @@ func exposePerKindService(port config.KindExposePort, timeout time.Duration, cli
if err != nil {
return err
}
- forwardablePod, err := polymorphichelpers.AttachablePodForObjectFn(clientGetter, obj, timeout)
+ forwardablePod, err := polymorphichelpers.AttachablePodForObjectFn(cluster, obj, timeout)
if err != nil {
return err
}
@@ -393,15 +410,8 @@ func exposePerKindService(port config.KindExposePort, timeout time.Duration, cli
return nil
}
-func exposeKindService(exports []config.KindExposePort, timeout time.Duration, kubeConfig string) error {
- // round tripper
- kubeConfigYaml, err := ioutil.ReadFile(kubeConfig)
- if err != nil {
- return err
- }
- clientGetter := util.NewSimpleRESTClientGetter("", string(kubeConfigYaml))
-
- restConf, err := clientGetter.ToRESTConfig()
+func exposeKindService(exports []config.KindExposePort, timeout time.Duration, cluster *util.K8sClusterInfo) error {
+ restConf, err := cluster.ToRESTConfig()
if err != nil {
return err
}
@@ -436,7 +446,7 @@ func exposeKindService(exports []config.KindExposePort, timeout time.Duration, k
resourceCount: len(exports),
}
for _, p := range exports {
- if err := exposePerKindService(p, waitTimeout, clientGetter, client, tripperFor, upgrader, forwardContext); err != nil {
+ if err := exposePerKindService(p, waitTimeout, cluster, client, tripperFor, upgrader, forwardContext); err != nil {
return err
}
}
@@ -446,6 +456,66 @@ func exposeKindService(exports []config.KindExposePort, timeout time.Duration, k
return nil
}
+func exposePerContainerLog(clientGetter *util.K8sClusterInfo, pod *v1.Pod, timeout time.Duration) error {
+ if pod.Status.Phase != v1.PodRunning {
+ return nil
+ }
+
+ file := filepath.Join(pod.Namespace, fmt.Sprintf("%s.log", pod.Name))
+ // check is followed
+ if logFollower.IsFollowed(file) {
+ return nil
+ }
+
+ logOptions := &v1.PodLogOptions{
+ Follow: true,
+ }
+ data, err := polymorphichelpers.LogsForObjectFn(clientGetter, pod, logOptions, timeout, true)
+ if err != nil {
+ return err
+ }
+
+ writer, err := logFollower.BuildLogWriter(file)
+ if err != nil {
+ return err
+ }
+ wg := &sync.WaitGroup{}
+ wg.Add(len(data))
+ // following each container
+ for _, resp := range data {
+ stream, err := resp.Stream(logFollower.Ctx)
+ if err != nil {
+ return err
+ }
+ go func() {
+ if finish := logFollower.ConsumeLog(writer, stream); finish != nil {
+ <-finish
+ }
+ wg.Done()
+ }()
+ }
+
+ go func() {
+ wg.Wait()
+ writer.Close()
+ }()
+
+ return nil
+}
+
+func exposeLogs(clientGetter *util.K8sClusterInfo, listener *KindContainerListener, timeout time.Duration) error {
+ pods, err := listener.GetAllPods()
+ if err != nil {
+ return err
+ }
+ for _, pod := range pods {
+ if err := exposePerContainerLog(clientGetter, pod, timeout); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func exportKindEnv(key, value, res string) error {
err := os.Setenv(key, value)
if err != nil {
diff --git a/internal/components/setup/kind_listener.go b/internal/components/setup/kind_listener.go
new file mode 100644
index 0000000..b55da0f
--- /dev/null
+++ b/internal/components/setup/kind_listener.go
@@ -0,0 +1,135 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you 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 setup
+
+import (
+ "context"
+
+ v1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/cli-runtime/pkg/resource"
+
+ "github.com/apache/skywalking-infra-e2e/internal/logger"
+ "github.com/apache/skywalking-infra-e2e/internal/util"
+)
+
+// KindContainerListener listen or get all kubernetes pod
+type KindContainerListener struct {
+ clientGetter *util.K8sClusterInfo
+ ctx context.Context
+ ctxCancel context.CancelFunc
+}
+
+func NewKindContainerListener(ctx context.Context, clientGetter *util.K8sClusterInfo) *KindContainerListener {
+ childCtx, cancelFunc := context.WithCancel(ctx)
+ return &KindContainerListener{
+ clientGetter: clientGetter,
+ ctx: childCtx,
+ ctxCancel: cancelFunc,
+ }
+}
+
+// Listen pod event
+func (c *KindContainerListener) Listen(consumer func(pod *v1.Pod)) error {
+ result := c.buildSearchResult()
+
+ runtimeObject, err := result.Object()
+ if err != nil {
+ return err
+ }
+ watchVersion, err := meta.NewAccessor().ResourceVersion(runtimeObject)
+ if err != nil {
+ return err
+ }
+
+ watcher, err := result.Watch(watchVersion)
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ for {
+ select {
+ case event := <-watcher.ResultChan():
+ switch event.Type {
+ case watch.Added, watch.Modified:
+ pod, err := c.unstructuredToPod(event.Object.(*unstructured.Unstructured))
+ if err != nil {
+ continue
+ }
+ consumer(pod)
+ case watch.Error:
+ errObject := apierrors.FromObject(event.Object)
+ statusErr := errObject.(*apierrors.StatusError)
+ logger.Log.Warnf("watch kubernetes pod error, %v", statusErr)
+ }
+ case <-c.ctx.Done():
+ watcher.Stop()
+ c.ctxCancel()
+ return
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (c *KindContainerListener) GetAllPods() ([]*v1.Pod, error) {
+ result := c.buildSearchResult()
+ infos, err := result.Infos()
+ if err != nil {
+ return nil, err
+ }
+
+ pods := make([]*v1.Pod, 0)
+ for _, info := range infos {
+ pod, err := c.unstructuredToPod(info.Object.(*unstructured.Unstructured))
+ if err != nil {
+ return nil, err
+ }
+ pods = append(pods, pod)
+ }
+ return pods, nil
+}
+
+func (c *KindContainerListener) Stop() {
+ c.ctxCancel()
+}
+
+func (c *KindContainerListener) buildSearchResult() *resource.Result {
+ return resource.NewBuilder(c.clientGetter).
+ Unstructured().
+ AllNamespaces(true).
+ ResourceTypeOrNameArgs(true, "pods").
+ Latest().
+ Flatten().
+ Do()
+}
+
+func (c *KindContainerListener) unstructuredToPod(object *unstructured.Unstructured) (*v1.Pod, error) {
+ var pod v1.Pod
+ if err := runtime.DefaultUnstructuredConverter.FromUnstructured(object.UnstructuredContent(), &pod); err != nil {
+ return nil, err
+ }
+ return &pod, nil
+}
diff --git a/internal/util/config.go b/internal/util/config.go
index 679235d..ad082b5 100644
--- a/internal/util/config.go
+++ b/internal/util/config.go
@@ -28,6 +28,7 @@ import (
var (
CfgFile string
WorkDir string
+ LogDir string
)
// ResolveAbs resolves the relative path (relative to CfgFile) to an absolute file path.
diff --git a/internal/util/env_log.go b/internal/util/env_log.go
new file mode 100644
index 0000000..a696c47
--- /dev/null
+++ b/internal/util/env_log.go
@@ -0,0 +1,112 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you 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 util
+
+import (
+ "bufio"
+ "context"
+ "io"
+ "os"
+ "path/filepath"
+ "sync"
+)
+
+type ResourceLogFollower struct {
+ Ctx context.Context
+ cancelFunc context.CancelFunc
+ basePath string
+ followLock *sync.RWMutex
+ following map[string]bool
+}
+
+func NewResourceLogFollower(ctx context.Context, basePath string) *ResourceLogFollower {
+ childCtx, cancelFunc := context.WithCancel(ctx)
+ return &ResourceLogFollower{
+ Ctx: childCtx,
+ cancelFunc: cancelFunc,
+ basePath: basePath,
+ followLock: &sync.RWMutex{},
+ following: make(map[string]bool),
+ }
+}
+
+func (l *ResourceLogFollower) BuildLogWriter(path string) (*os.File, error) {
+ logFile := l.buildLogFilename(path)
+ if err := os.MkdirAll(filepath.Dir(logFile), os.ModePerm); err != nil {
+ return nil, err
+ }
+ if _, err := os.Stat(logFile); os.IsExist(err) {
+ if err := os.Remove(logFile); err != nil {
+ return nil, err
+ }
+ }
+
+ return os.Create(logFile)
+}
+
+func (l *ResourceLogFollower) ConsumeLog(logWriter *os.File, stream io.ReadCloser) <-chan struct{} {
+ if l.IsFollowed(logWriter.Name()) {
+ return nil
+ }
+
+ finished := make(chan struct{}, 1)
+ go func() {
+ defer func() {
+ stream.Close()
+ close(finished)
+ }()
+
+ r := bufio.NewReader(stream)
+ for {
+ bytes, err := r.ReadBytes('\n')
+ if err != nil {
+ if err != io.EOF {
+ return
+ }
+ return
+ }
+
+ l.writeFollowed(logWriter)
+ if _, err := logWriter.Write(bytes); err != nil {
+ return
+ }
+ }
+ }()
+ return finished
+}
+
+func (l *ResourceLogFollower) IsFollowed(path string) bool {
+ l.followLock.RLock()
+ defer l.followLock.RUnlock()
+ return l.following[l.buildLogFilename(path)]
+}
+
+func (l *ResourceLogFollower) Close() {
+ l.cancelFunc()
+}
+
+func (l *ResourceLogFollower) buildLogFilename(path string) string {
+ return filepath.Join(l.basePath, path)
+}
+
+func (l *ResourceLogFollower) writeFollowed(writer *os.File) {
+ l.followLock.Lock()
+ defer l.followLock.Unlock()
+ l.following[writer.Name()] = true
+}
diff --git a/internal/util/k8s.go b/internal/util/k8s.go
index 8c4be1f..2da0496 100644
--- a/internal/util/k8s.go
+++ b/internal/util/k8s.go
@@ -27,16 +27,17 @@ import (
"strings"
apiv1 "k8s.io/api/admission/v1"
-
"k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
- "k8s.io/client-go/dynamic"
-
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
+ "k8s.io/client-go/discovery"
+ "k8s.io/client-go/discovery/cached/memory"
+ "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
@@ -45,8 +46,10 @@ import (
// K8sClusterInfo created when connect to cluster
type K8sClusterInfo struct {
- Client *kubernetes.Clientset
- Interface dynamic.Interface
+ Client *kubernetes.Clientset
+ Interface dynamic.Interface
+ restConfig *rest.Config
+ namespace string
}
// ConnectToK8sCluster gets clientSet and dynamic client from k8s config file.
@@ -65,9 +68,64 @@ func ConnectToK8sCluster(kubeConfigPath string) (info *K8sClusterInfo, err error
return nil, err
}
+ kubeConfigYaml, err := ioutil.ReadFile(kubeConfigPath)
+ if err != nil {
+ return nil, err
+ }
+ restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigYaml)
+ if err != nil {
+ return nil, err
+ }
+
logger.Log.Info("connect to k8s cluster succeeded")
- return &K8sClusterInfo{c, dc}, nil
+ return &K8sClusterInfo{c, dc, restConfig, ""}, nil
+}
+
+func (c *K8sClusterInfo) CopyClusterToNamespace(namespace string) *K8sClusterInfo {
+ return &K8sClusterInfo{
+ Client: c.Client,
+ Interface: c.Interface,
+ restConfig: c.restConfig,
+ namespace: namespace,
+ }
+}
+
+func (c *K8sClusterInfo) ToRESTConfig() (*rest.Config, error) {
+ return c.restConfig, nil
+}
+
+func (c *K8sClusterInfo) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
+ config, err := c.ToRESTConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ config.Burst = 100
+
+ discoveryClient, _ := discovery.NewDiscoveryClientForConfig(config)
+ return memory.NewMemCacheClient(discoveryClient), nil
+}
+
+func (c *K8sClusterInfo) ToRESTMapper() (meta.RESTMapper, error) {
+ discoveryClient, err := c.ToDiscoveryClient()
+ if err != nil {
+ return nil, err
+ }
+
+ mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
+ expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
+ return expander, nil
+}
+
+func (c *K8sClusterInfo) ToRawKubeConfigLoader() clientcmd.ClientConfig {
+ loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+ loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
+
+ overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
+ overrides.Context.Namespace = c.namespace
+
+ return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
}
// GetManifests recursively gets all yml and yaml files from manifests string.
diff --git a/internal/util/restClientGetter.go b/internal/util/restClientGetter.go
deleted file mode 100644
index 09b0236..0000000
--- a/internal/util/restClientGetter.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Licensed to Apache Software Foundation (ASF) under one or more contributor
-// license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright
-// ownership. Apache Software Foundation (ASF) licenses this file to you 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 util
-
-import (
- "k8s.io/apimachinery/pkg/api/meta"
- "k8s.io/client-go/discovery"
- "k8s.io/client-go/discovery/cached/memory"
- "k8s.io/client-go/rest"
- "k8s.io/client-go/restmapper"
- "k8s.io/client-go/tools/clientcmd"
-)
-
-// SimpleRESTClientGetter implements genericclioptions.RESTClientGetter,
-// so that we can use kubectl's functions just by passing kubeConfig and namespace.
-type SimpleRESTClientGetter struct {
- Namespace string
- KubeConfig string
-}
-
-func NewSimpleRESTClientGetter(namespace, kubeConfig string) *SimpleRESTClientGetter {
- return &SimpleRESTClientGetter{
- Namespace: namespace,
- KubeConfig: kubeConfig,
- }
-}
-
-func (c *SimpleRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
- config, err := clientcmd.RESTConfigFromKubeConfig([]byte(c.KubeConfig))
- if err != nil {
- return nil, err
- }
- return config, nil
-}
-
-func (c *SimpleRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
- config, err := c.ToRESTConfig()
- if err != nil {
- return nil, err
- }
-
- config.Burst = 100
-
- discoveryClient, _ := discovery.NewDiscoveryClientForConfig(config)
- return memory.NewMemCacheClient(discoveryClient), nil
-}
-
-func (c *SimpleRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
- discoveryClient, err := c.ToDiscoveryClient()
- if err != nil {
- return nil, err
- }
-
- mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
- expander := restmapper.NewShortcutExpander(mapper, discoveryClient)
- return expander, nil
-}
-
-func (c *SimpleRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
- loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
- loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
-
- overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
- overrides.Context.Namespace = c.Namespace
-
- return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)
-}