Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine LXD socket before retrieving a client #355

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 69 additions & 28 deletions lxd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"log"
"os"
"path"
"strings"
"sync"
"time"

Expand All @@ -15,6 +14,7 @@ import (
"github.com/canonical/lxd/shared"
lxd_api "github.com/canonical/lxd/shared/api"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/sys/unix"
)

// A global mutex.
Expand Down Expand Up @@ -521,51 +521,42 @@ func (p *lxdProvider) GetServer(remoteName string) (lxd.Server, error) {
}

// Check and see if a client was already created and cached.
if client, ok := p.getLXDClient(remoteName); ok {
client, ok := p.getLXDClient(remoteName)
if ok {
return client, nil
}

// If a client was not already created, create a new one.
if v, ok := p.getTerraformLXDConfig(remoteName); ok && !v.bootstrapped {
remote, ok := p.getTerraformLXDConfig(remoteName)
if ok && !remote.bootstrapped {
err := p.createClient(remoteName)
if err != nil {
return nil, fmt.Errorf("Unable to create client for remote [%s]: %s",
remoteName, err)
return nil, fmt.Errorf("Unable to create client for remote [%s]: %s", remoteName, err)
}
}

var client lxd.Server
// If scheme is set to unix, but address (socket path) is not provided
// then determine which LXD directory contains a writable unix socket.
if (remote.scheme == "" || remote.scheme == "unix") && remote.address == "" {
lxdDir, err := determineLxdDir()
if err != nil {
return nil, err
}

_ = os.Setenv("LXD_DIR", lxdDir)
}

var err error

remoteConfig := p.getRemoteConfig(remoteName)
switch remoteConfig.Protocol {
switch p.getRemoteConfig(remoteName).Protocol {
case "simplestreams":
client, err = p.getLXDImageClient(remoteName)
default:
client, err = p.getLXDInstanceClient(remoteName)
}

if err != nil {
// If the reported error contained the path /var/lib/lxd/unix.socket,
// it's possible that the user did not define any remotes in order to
// use the implicit "local" remote which connects via a unix socket.
//
// When LXD is installed via a snap package, this path no longer works.
// Therefore, we try to retry using the laziest method possible:
// set the LXD_SOCKET environment variable to the snap path and see if
// the connection works again.
if strings.Contains(err.Error(), "/var/lib/lxd/unix.socket") {
v := os.Getenv("LXD_SOCKET")
os.Setenv("LXD_SOCKET", "/var/snap/lxd/common/lxd/unix.socket")
defer os.Setenv("LXD_SOCKET", v)

client, err = p.getLXDInstanceClient(remoteName)
if err != nil {
return nil, err
}
} else {
return nil, err
}
return nil, err
}

// Add the client to the clientMap cache.
Expand Down Expand Up @@ -766,3 +757,53 @@ func determineDaemonAddr(lxdRemote terraformLXDConfig) (string, error) {

return daemonAddr, nil
}

// determineLxdDir determines which standard LXD directory contains a writable UNIX socket.
// If environment variable LXD_DIR or LXD_SOCKET is set, the function will return LXD directory
// based on the value from any of those variables.
func determineLxdDir() (string, error) {
lxdSocket, ok := os.LookupEnv("LXD_SOCKET")
if ok {
if isSocketWritable(lxdSocket) {
return path.Dir(lxdSocket), nil
}

return "", fmt.Errorf("Environment variable LXD_SOCKET points to either a non-existing or non-writable unix socket")
}

lxdDir, ok := os.LookupEnv("LXD_DIR")
if ok {
socketPath := path.Join(lxdDir, "unix.socket")
if isSocketWritable(socketPath) {
return lxdDir, nil
}

return "", fmt.Errorf("Environment variable LXD_DIR points to a LXD directory that does not contain a writable unix socket")
}

lxdDirs := []string{
"/var/lib/lxd",
"/var/snap/lxd/common/lxd",
}

// Iterate over LXD directories and find a writable unix socket.
for _, lxdDir := range lxdDirs {
socketPath := path.Join(lxdDir, "unix.socket")
if isSocketWritable(socketPath) {
return lxdDir, nil
}
}

return "", fmt.Errorf("LXD socket with write permissions not found. Searched LXD directories: %v", lxdDirs)
}

// isSocketWritable returns true if user has write permissions for socket on the given path.
func isSocketWritable(socketPath string) bool {
err := unix.Access(socketPath, unix.W_OK)
if err != nil {
log.Printf("[DEBUG] Unix socket %q: %v", socketPath, err)
return false
}

return true
}