Skip to content

Commit

Permalink
Merge pull request #148 from guilhem/akamai
Browse files Browse the repository at this point in the history
Akamai/linode provider
  • Loading branch information
danielfoehrKn authored Nov 8, 2024
2 parents ca003a0 + 9fb4590 commit e2e379c
Show file tree
Hide file tree
Showing 365 changed files with 50,851 additions and 18,889 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Designed as a [drop-in replacement](#difference-to-kubectx) for [kubectx](https:
- [OVH](docs/stores/ovh/ovh.md)
- [Rancher](docs/stores/rancher/rancher.md)
- Scaleway (documentation tbd)
- [Akamai / Linode](docs/stores/akamai/akamai.md)
- Your favorite Cloud Provider or Managed Kubernetes Platform is not supported yet? Looking for contributions!
- **Change the namespace**
- **Change to any context and namespace from the history**
Expand Down
9 changes: 9 additions & 0 deletions cmd/switcher/switcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ func initialize() ([]store.KubeconfigStore, *types.Config, error) {
}
s = doStore
digitalOceanStoreAddedViaConfig = true
case types.StoreKindAkamai:
akamaiStore, err := store.NewAkamaiStore(kubeconfigStoreFromConfig)
if err != nil {
if kubeconfigStoreFromConfig.Required != nil && !*kubeconfigStoreFromConfig.Required {
continue
}
return nil, nil, err
}
s = akamaiStore
default:
return nil, nil, fmt.Errorf("unknown store %q", kubeconfigStoreFromConfig.Kind)
}
Expand Down
23 changes: 23 additions & 0 deletions docs/stores/akamai/akamai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Akamai store

To use the Akamai store a token should be created [on linode's website](https://cloud.linode.com/profile/tokens)

In order to create this token you also need to specify the scope.
The required permissions for this plugin to work are the following:
- `read/write` for Kubernetes

## Configuration

The Akamai store configuration is defined in the `kubeswitch` configuration file.
An example configuration is shown below:

```yaml
kind: SwitchConfig
version: v1alpha1
kubeconfigStores:
- kind: akamai
config:
linode_token: "your-linode-token"
```
`linode_token` can be ignored if set with the environment variable `LINODE_TOKEN`.
25 changes: 13 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/rancher/rancher/pkg/client v0.0.0-20240416202124-a6da228939da
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
golang.org/x/tools v0.13.0
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
google.golang.org/api v0.126.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand All @@ -48,15 +48,15 @@ require (
require (
github.com/digitalocean/doctl v1.105.0
github.com/digitalocean/godo v1.113.0
github.com/linode/linodego v1.42.0
github.com/ovh/go-ovh v1.4.3
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
golang.org/x/oauth2 v0.10.0
golang.org/x/oauth2 v0.23.0
)

require (
cloud.google.com/go/compute v1.21.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
Expand Down Expand Up @@ -98,6 +98,7 @@ require (
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-resty/resty/v2 v2.13.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand Down Expand Up @@ -167,14 +168,14 @@ require (
github.com/subosito/gotenv v1.4.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/mock v0.2.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
Expand Down
75 changes: 49 additions & 26 deletions go.sum

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions pkg/store/kubeconfig_store_akamai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2024 The Kubeswitch 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 store

import (
"context"
"encoding/base64"
"fmt"
"net/http"
"os"
"strconv"
"time"

"golang.org/x/oauth2"
"gopkg.in/yaml.v3"

"github.com/danielfoehrkn/kubeswitch/types"
"github.com/linode/linodego"
"github.com/sirupsen/logrus"
)

func NewAkamaiStore(store types.KubeconfigStore) (*AkamaiStore, error) {
akamaiStoreConfig := &types.StoreConfigAkamai{}
if store.Config != nil {
buf, err := yaml.Marshal(store.Config)
if err != nil {
return nil, fmt.Errorf("failed to process akamai store config: %w", err)
}

err = yaml.Unmarshal(buf, akamaiStoreConfig)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal akami config: %w", err)
}
}

return &AkamaiStore{
Logger: logrus.New().WithField("store", types.StoreKindAkamai),
KubeconfigStore: store,
Config: akamaiStoreConfig,
}, nil
}

// InitializeAkamaiStore the Akamai client
func (s *AkamaiStore) InitializeAkamaiStore() error {
// use environment variables if token is not set
token := s.Config.LinodeToken
if token == "" {
envToken, ok := os.LookupEnv("LINODE_TOKEN")
if !ok {
return fmt.Errorf("linode token not set")
}
token = envToken
}

tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})

oauth2Client := &http.Client{
Transport: &oauth2.Transport{
Source: tokenSource,
},
}

linodeClient := linodego.NewClient(oauth2Client)

s.Client = &linodeClient

return nil
}

// GetID returns the unique store ID
func (s *AkamaiStore) GetID() string {
return fmt.Sprintf("%s.default", s.GetKind())
}

func (s *AkamaiStore) GetKind() types.StoreKind {
return types.StoreKindAkamai
}

func (s *AkamaiStore) GetContextPrefix(path string) string {
return fmt.Sprintf("%s/%s", s.GetKind(), path)
}

func (s *AkamaiStore) VerifyKubeconfigPaths() error {
// NOOP
return nil
}

func (s *AkamaiStore) GetStoreConfig() types.KubeconfigStore {
return s.KubeconfigStore
}

func (s *AkamaiStore) GetLogger() *logrus.Entry {
return s.Logger
}

func (s *AkamaiStore) StartSearch(channel chan SearchResult) {
s.Logger.Debug("Akamai: start search")

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := s.InitializeAkamaiStore(); err != nil {
channel <- SearchResult{
KubeconfigPath: "",
Error: err,
}
return
}

// list linode instances
instances, err := s.Client.ListLKEClusters(ctx, nil)
if err != nil {
channel <- SearchResult{
KubeconfigPath: "",
Error: err,
}
return
}

for _, instance := range instances {
channel <- SearchResult{
KubeconfigPath: instance.Label,
Tags: map[string]string{
"clusterID": strconv.Itoa(instance.ID),
"region": instance.Region,
},
}
}
}

func (s *AkamaiStore) GetKubeconfigForPath(path string, tags map[string]string) ([]byte, error) {
s.Logger.Debugf("Akamai: get kubeconfig for path %s", path)

// initialize client
if err := s.InitializeAkamaiStore(); err != nil {
return nil, err
}

clusterID, err := strconv.Atoi(tags["clusterID"])
if err != nil {
return nil, fmt.Errorf("failed to get clusterID: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// get kubeconfig
LKEkubeconfig, err := s.Client.GetLKEClusterKubeconfig(ctx, clusterID)
if err != nil {
return nil, err
}

// decode base64 kubeconfig
kubeconfig, err := base64.StdEncoding.DecodeString(LKEkubeconfig.KubeConfig)
if err != nil {
return nil, err
}

return kubeconfig, nil
}
8 changes: 8 additions & 0 deletions pkg/store/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1"
vaultapi "github.com/hashicorp/vault/api"
"github.com/linode/linodego"
"github.com/ovh/go-ovh/ovh"
"github.com/rancher/norman/clientbase"
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
Expand Down Expand Up @@ -203,3 +204,10 @@ type DigitalOceanStore struct {
ContextToKubernetesService map[string]do.KubernetesService
Config doks.DoctlConfig
}

type AkamaiStore struct {
Logger *logrus.Entry
KubeconfigStore types.KubeconfigStore
Client *linodego.Client
Config *types.StoreConfigAkamai
}
8 changes: 7 additions & 1 deletion types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
type StoreKind string

// ValidStoreKinds contains all valid store kinds
var ValidStoreKinds = sets.NewString(string(StoreKindVault), string(StoreKindFilesystem), string(StoreKindGardener), string(StoreKindGKE), string(StoreKindAzure), string(StoreKindEKS), string(StoreKindRancher), string(StoreKindOVH), string(StoreKindScaleway), string(StoreKindDigitalOcean))
var ValidStoreKinds = sets.NewString(string(StoreKindVault), string(StoreKindFilesystem), string(StoreKindGardener), string(StoreKindGKE), string(StoreKindAzure), string(StoreKindEKS), string(StoreKindRancher), string(StoreKindOVH), string(StoreKindScaleway), string(StoreKindDigitalOcean), string(StoreKindAkamai))

// ValidConfigVersions contains all valid config versions
var ValidConfigVersions = sets.NewString("v1alpha1")
Expand All @@ -50,6 +50,8 @@ const (
StoreKindScaleway StoreKind = "scaleway"
// StoreKindDigitalOcean is an identifier for the Azure store
StoreKindDigitalOcean StoreKind = "digitalocean"
// StoreKindAkamai is an identifier for the Akamai store
StoreKindAkamai StoreKind = "akamai"
)

type Config struct {
Expand Down Expand Up @@ -250,3 +252,7 @@ type StoreConfigScaleway struct {
ScalewaySecretKey string `yaml:"secret_key"`
ScalewayRegion string `yaml:"region"`
}

type StoreConfigAkamai struct {
LinodeToken string `yaml:"linode_token"`
}
Loading

0 comments on commit e2e379c

Please sign in to comment.