github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/builtin.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  
    14  	jujuclock "github.com/juju/clock"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/utils/v3"
    17  	"github.com/juju/utils/v3/exec"
    18  
    19  	k8s "github.com/juju/juju/caas/kubernetes"
    20  	"github.com/juju/juju/caas/kubernetes/clientconfig"
    21  	k8scloud "github.com/juju/juju/caas/kubernetes/cloud"
    22  	jujucloud "github.com/juju/juju/cloud"
    23  	envtools "github.com/juju/juju/environs/tools"
    24  	"github.com/juju/juju/version"
    25  )
    26  
    27  func attemptMicroK8sCloud(cmdRunner CommandRunner, getKubeConfigDir func() (string, error)) (jujucloud.Cloud, error) {
    28  	microk8sConfig, err := getLocalMicroK8sConfig(cmdRunner, getKubeConfigDir)
    29  	if err != nil {
    30  		return jujucloud.Cloud{}, err
    31  	}
    32  
    33  	k8sCloud, err := k8scloud.CloudFromKubeConfigClusterReader(
    34  		k8s.MicroK8sClusterName,
    35  		bytes.NewReader(microk8sConfig),
    36  		k8scloud.CloudParamaters{
    37  			Description: jujucloud.DefaultCloudDescription(jujucloud.CloudTypeKubernetes),
    38  			Name:        k8s.K8sCloudMicrok8s,
    39  			Regions: []jujucloud.Region{{
    40  				Name: k8s.Microk8sRegion,
    41  			}},
    42  		},
    43  	)
    44  
    45  	return k8sCloud, err
    46  }
    47  
    48  func attemptMicroK8sCredential(cmdRunner CommandRunner, getKubeConfigDir func() (string, error)) (jujucloud.Credential, error) {
    49  	microk8sConfig, err := getLocalMicroK8sConfig(cmdRunner, getKubeConfigDir)
    50  	if err != nil {
    51  		return jujucloud.Credential{}, err
    52  	}
    53  
    54  	k8sConfig, err := k8scloud.ConfigFromReader(bytes.NewReader(microk8sConfig))
    55  	if err != nil {
    56  		return jujucloud.Credential{}, errors.Annotate(err, "processing microk8s config to make juju credentials")
    57  	}
    58  
    59  	contextName, err := k8scloud.PickContextByClusterName(k8sConfig, k8s.MicroK8sClusterName)
    60  	if err != nil {
    61  		return jujucloud.Credential{}, errors.Trace(err)
    62  	}
    63  
    64  	context := k8sConfig.Contexts[contextName]
    65  	resolver := clientconfig.GetJujuAdminServiceAccountResolver(jujuclock.WallClock)
    66  	conf, err := resolver(k8s.K8sCloudMicrok8s, k8sConfig, contextName)
    67  	if err != nil {
    68  		return jujucloud.Credential{}, errors.Annotate(err, "resolving microk8s credentials")
    69  	}
    70  
    71  	return k8scloud.CredentialFromKubeConfig(context.AuthInfo, conf)
    72  }
    73  
    74  // For testing.
    75  var CheckJujuOfficial = envtools.JujudVersion
    76  
    77  func decideKubeConfigDir() (string, error) {
    78  	jujuDir, err := envtools.ExistingJujuLocation()
    79  	if err != nil {
    80  		return "", errors.Annotate(err, "cannot find juju binary")
    81  	}
    82  	_, isOffical, err := CheckJujuOfficial(jujuDir)
    83  	if err != nil && !errors.IsNotFound(err) {
    84  		return "", errors.Trace(err)
    85  	}
    86  	if isOffical {
    87  		return filepath.Join(os.Getenv("SNAP_DATA"), "microk8s", "credentials", "client.config"), nil
    88  	}
    89  	return filepath.Join("/var/snap/microk8s/current/", "credentials", "client.config"), nil
    90  }
    91  
    92  var microk8sGroupError = `
    93  Insufficient permissions to access MicroK8s.
    94  You can either try again with sudo or add the user %s to the 'snap_microk8s' group:
    95  
    96      sudo usermod -a -G snap_microk8s %s
    97  
    98  After this, reload the user groups either via a reboot or by running 'newgrp snap_microk8s'.
    99  `[1:]
   100  
   101  func getLocalMicroK8sConfig(cmdRunner CommandRunner, getKubeConfigDir func() (string, error)) ([]byte, error) {
   102  	if runtime.GOOS != "linux" {
   103  		return getLocalMicroK8sConfigNonLinux(cmdRunner)
   104  	}
   105  
   106  	notSupportErr := errors.NewNotSupported(nil, fmt.Sprintf("juju %q can only work with strictly confined microk8s", version.Current))
   107  	clientConfigPath, err := getKubeConfigDir()
   108  	if err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  	logger.Tracef("reading kubeconfig %q", clientConfigPath)
   112  	content, err := os.ReadFile(clientConfigPath)
   113  	if os.IsNotExist(err) {
   114  		return nil, errors.Annotatef(notSupportErr, "%q does not exist", clientConfigPath)
   115  	}
   116  	if os.IsPermission(err) {
   117  		user, err := utils.LocalUsername()
   118  		if err != nil {
   119  			user = "<user>"
   120  		}
   121  		return nil, errors.Errorf(microk8sGroupError, user, user)
   122  	}
   123  	if err != nil {
   124  		return nil, errors.Annotatef(err, "cannot read %q", clientConfigPath)
   125  	}
   126  	return content, nil
   127  }
   128  
   129  func getLocalMicroK8sConfigNonLinux(cmdRunner CommandRunner) ([]byte, error) {
   130  	_, err := cmdRunner.LookPath("microk8s")
   131  	if err != nil {
   132  		return []byte{}, errors.NotFoundf("microk8s")
   133  	}
   134  	execParams := exec.RunParams{
   135  		Commands: "microk8s config",
   136  	}
   137  	result, err := cmdRunner.RunCommands(execParams)
   138  	if err != nil {
   139  		return []byte{}, err
   140  	}
   141  	if result.Code != 0 {
   142  		// TODO - confined snaps can't execute other commands.
   143  		errMessage := strings.ReplaceAll(string(result.Stderr), "\n", "")
   144  		if strings.HasSuffix(strings.ToLower(errMessage), "permission denied") {
   145  			return []byte{}, errors.NotFoundf("microk8s")
   146  		}
   147  		return []byte{}, errors.New(string(result.Stderr))
   148  	} else {
   149  		if strings.HasPrefix(strings.ToLower(string(result.Stdout)), "microk8s is not running") {
   150  			return []byte{}, errors.NotFoundf("microk8s is not running")
   151  		}
   152  	}
   153  	return result.Stdout, nil
   154  }