blob: 753fe3e2e73d18fe9bd86d3822dcd43af5d0e9e4 [file] [log] [blame]
/*
Licensed to the 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.
The 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 client
import (
"io/ioutil"
"os"
"path/filepath"
camelv1 "github.com/apache/camel-k/pkg/client/camel/clientset/versioned/typed/camel/v1"
camelv1alpha1 "github.com/apache/camel-k/pkg/client/camel/clientset/versioned/typed/camel/v1alpha1"
user "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
clientscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest"
controller "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"github.com/apache/camel-k/pkg/apis"
camel "github.com/apache/camel-k/pkg/client/camel/clientset/versioned"
)
const (
inContainerNamespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
kubeConfigEnvVar = "KUBECONFIG"
)
// Client is an abstraction for a k8s client
type Client interface {
controller.Client
kubernetes.Interface
CamelV1() camelv1.CamelV1Interface
CamelV1alpha1() camelv1alpha1.CamelV1alpha1Interface
GetScheme() *runtime.Scheme
GetConfig() *rest.Config
GetCurrentNamespace(kubeConfig string) (string, error)
}
// Injectable identifies objects that can receive a Client
type Injectable interface {
InjectClient(Client)
}
// Provider is used to provide a new instance of the Client each time it's required
type Provider struct {
Get func() (Client, error)
}
type defaultClient struct {
controller.Client
kubernetes.Interface
camel camel.Interface
scheme *runtime.Scheme
config *rest.Config
}
// Check interface compliance
var _ Client = &defaultClient{}
func (c *defaultClient) CamelV1() camelv1.CamelV1Interface {
return c.camel.CamelV1()
}
func (c *defaultClient) CamelV1alpha1() camelv1alpha1.CamelV1alpha1Interface {
return c.camel.CamelV1alpha1()
}
func (c *defaultClient) GetScheme() *runtime.Scheme {
return c.scheme
}
func (c *defaultClient) GetConfig() *rest.Config {
return c.config
}
func (c *defaultClient) GetCurrentNamespace(kubeConfig string) (string, error) {
return GetCurrentNamespace(kubeConfig)
}
// NewOutOfClusterClient creates a new k8s client that can be used from outside the cluster
func NewOutOfClusterClient(kubeconfig string) (Client, error) {
initialize(kubeconfig)
// using fast discovery from outside the cluster
return NewClient(true)
}
// NewClient creates a new k8s client that can be used from outside or in the cluster
func NewClient(fastDiscovery bool) (Client, error) {
// Get a config to talk to the apiserver
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
scheme := clientscheme.Scheme
// Setup Scheme for all resources
if err := apis.AddToScheme(scheme); err != nil {
return nil, err
}
var clientset kubernetes.Interface
if clientset, err = kubernetes.NewForConfig(cfg); err != nil {
return nil, err
}
var camelClientset camel.Interface
if camelClientset, err = camel.NewForConfig(cfg); err != nil {
return nil, err
}
var mapper meta.RESTMapper
if fastDiscovery {
mapper = newFastDiscoveryRESTMapper(cfg)
}
// Create a new client to avoid using cache (enabled by default with controller-runtime client)
clientOptions := controller.Options{
Scheme: scheme,
Mapper: mapper,
}
dynClient, err := controller.New(cfg, clientOptions)
if err != nil {
return nil, err
}
return &defaultClient{
Client: dynClient,
Interface: clientset,
camel: camelClientset,
scheme: clientOptions.Scheme,
config: cfg,
}, nil
}
// FromManager creates a new k8s client from a manager object
func FromManager(manager manager.Manager) (Client, error) {
var err error
var clientset kubernetes.Interface
if clientset, err = kubernetes.NewForConfig(manager.GetConfig()); err != nil {
return nil, err
}
var camelClientset camel.Interface
if camelClientset, err = camel.NewForConfig(manager.GetConfig()); err != nil {
return nil, err
}
return &defaultClient{
Client: manager.GetClient(),
Interface: clientset,
camel: camelClientset,
scheme: manager.GetScheme(),
config: manager.GetConfig(),
}, nil
}
// init initialize the k8s client for usage outside the cluster
func initialize(kubeconfig string) {
if kubeconfig == "" {
// skip out-of-cluster initialization if inside the container
if kc, err := shouldUseContainerMode(); kc && err == nil {
return
} else if err != nil {
logrus.Errorf("could not determine if running in a container: %v", err)
}
var err error
kubeconfig, err = getDefaultKubeConfigFile()
if err != nil {
panic(err)
}
}
os.Setenv(kubeConfigEnvVar, kubeconfig)
}
func getDefaultKubeConfigFile() (string, error) {
dir, err := user.Dir()
if err != nil {
return "", err
}
return filepath.Join(dir, ".kube", "config"), nil
}
// GetCurrentNamespace --
func GetCurrentNamespace(kubeconfig string) (string, error) {
if kubeconfig == "" {
kubeContainer, err := shouldUseContainerMode()
if err != nil {
return "", err
}
if kubeContainer {
return getNamespaceFromKubernetesContainer()
}
}
if kubeconfig == "" {
var err error
kubeconfig, err = getDefaultKubeConfigFile()
if err != nil {
logrus.Errorf("Cannot get information about current user: %v", err)
}
}
if kubeconfig == "" {
return "default", nil
}
data, err := ioutil.ReadFile(kubeconfig)
if err != nil {
return "", err
}
conf := clientcmdapi.NewConfig()
if len(data) == 0 {
return "", errors.New("kubernetes config file is empty")
}
decoded, _, err := clientcmdlatest.Codec.Decode(data, &schema.GroupVersionKind{Version: clientcmdlatest.Version, Kind: "Config"}, conf)
if err != nil {
return "", err
}
clientcmdconfig := decoded.(*clientcmdapi.Config)
cc := clientcmd.NewDefaultClientConfig(*clientcmdconfig, &clientcmd.ConfigOverrides{})
ns, _, err := cc.Namespace()
return ns, err
}
func shouldUseContainerMode() (bool, error) {
// When kube config is set, container mode is not used
if os.Getenv(kubeConfigEnvVar) != "" {
return false, nil
}
// Use container mode only when the kubeConfigFile does not exist and the container namespace file is present
configFile, err := getDefaultKubeConfigFile()
if err != nil {
return false, err
}
configFilePresent := true
_, err = os.Stat(configFile)
if err != nil && os.IsNotExist(err) {
configFilePresent = false
} else if err != nil {
return false, err
}
if !configFilePresent {
_, err := os.Stat(inContainerNamespaceFile)
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
return false, nil
}
func getNamespaceFromKubernetesContainer() (string, error) {
var nsba []byte
var err error
if nsba, err = ioutil.ReadFile(inContainerNamespaceFile); err != nil {
return "", err
}
return string(nsba), nil
}