-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5924 from mook-as/wsl/docker-plugin-integration-go
WSL: Implement docker plugin integration in go
- Loading branch information
Showing
7 changed files
with
300 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
src/go/wsl-helper/cmd/wsl_integration_docker_plugin_linux.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
Copyright © 2023 SUSE LLC | ||
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 cmd | ||
|
||
import ( | ||
"github.com/rancher-sandbox/rancher-desktop/src/go/wsl-helper/pkg/integration" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var wslIntegrationDockerPluginViper = viper.New() | ||
|
||
// wslIntegrationDockerPluginCmd represents the `wsl integration docker-plugin` command | ||
var wslIntegrationDockerPluginCmd = &cobra.Command{ | ||
Use: "docker-plugin", | ||
Short: "Commands for managing docker plugin WSL integration", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
cmd.SilenceUsage = true | ||
|
||
state := wslIntegrationDockerPluginViper.GetBool("state") | ||
pluginPath := wslIntegrationDockerPluginViper.GetString("plugin") | ||
|
||
if err := integration.DockerPlugin(pluginPath, state); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
wslIntegrationDockerPluginCmd.Flags().String("plugin", "", "Full path to plugin") | ||
wslIntegrationDockerPluginCmd.Flags().Bool("state", false, "Desired state") | ||
wslIntegrationDockerPluginCmd.MarkFlagRequired("plugin") | ||
wslIntegrationDockerPluginViper.AutomaticEnv() | ||
wslIntegrationDockerPluginViper.BindPFlags(wslIntegrationDockerPluginCmd.Flags()) | ||
wslIntegrationCmd.AddCommand(wslIntegrationDockerPluginCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
Copyright © 2023 SUSE LLC | ||
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 cmd | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// wslIntegrationCmd represents the `wsl integration` command | ||
var wslIntegrationCmd = &cobra.Command{ | ||
Use: "integration", | ||
Short: "Commands for managing with WSL integration", | ||
} | ||
|
||
func init() { | ||
wslCmd.AddCommand(wslIntegrationCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
Copyright © 2023 SUSE LLC | ||
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 integration | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
// DockerPlugin manages a specific docker plugin (given in pluginPath), either | ||
// enabling it or disabling it in the WSL distribution the process is running in. | ||
func DockerPlugin(pluginPath string, enabled bool) error { | ||
homeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
return fmt.Errorf("could not get home directory: %w", err) | ||
} | ||
pluginDir := filepath.Join(homeDir, ".docker", "cli-plugins") | ||
if err = os.MkdirAll(pluginDir, 0o755); err != nil { | ||
return fmt.Errorf("failed to create docker plugins directory: %w", err) | ||
} | ||
destPath := filepath.Join(pluginDir, filepath.Base(pluginPath)) | ||
|
||
if enabled { | ||
if _, err := os.Readlink(destPath); err == nil { | ||
if _, err := os.Stat(destPath); errors.Is(err, os.ErrNotExist) { | ||
// The destination is a dangling symlink | ||
if err = os.Remove(destPath); err != nil { | ||
return fmt.Errorf("could not remove dangling symlink %q: %w", destPath, err) | ||
} | ||
} | ||
} | ||
|
||
if err = os.Symlink(pluginPath, destPath); err != nil { | ||
// ErrExist is fine, that means there's a user-created file there. | ||
if !errors.Is(err, os.ErrExist) { | ||
return fmt.Errorf("failed to create symlink %q: %w", destPath, err) | ||
} | ||
} | ||
} else { | ||
link, err := os.Readlink(destPath) | ||
if err == nil && link == pluginPath { | ||
if err = os.Remove(destPath); err != nil { | ||
return fmt.Errorf("failed to remove link %q: %w", destPath, err) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |
134 changes: 134 additions & 0 deletions
134
src/go/wsl-helper/pkg/integration/docker_plugin_linux_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
Copyright © 2023 SUSE LLC | ||
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 integration_test | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/rancher-sandbox/rancher-desktop/src/go/wsl-helper/pkg/integration" | ||
) | ||
|
||
func TestDockerPlugin(t *testing.T) { | ||
t.Run("create symlink", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, integration.DockerPlugin(pluginPath, true)) | ||
link, err := os.Readlink(destPath) | ||
if assert.NoError(t, err, "error reading created symlink") { | ||
assert.Equal(t, pluginPath, link) | ||
} | ||
}) | ||
t.Run("remove dangling symlink", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.Symlink(filepath.Join(pluginDir, "missing"), destPath)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, true)) | ||
link, err := os.Readlink(destPath) | ||
if assert.NoError(t, err, "error reading created symlink") { | ||
assert.Equal(t, pluginPath, link) | ||
} | ||
}) | ||
t.Run("leave existing symlink", func(t *testing.T) { | ||
executable, err := os.Executable() | ||
require.NoError(t, err, "failed to locate executable") | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.Symlink(executable, destPath)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, true)) | ||
link, err := os.Readlink(destPath) | ||
if assert.NoError(t, err, "error reading created symlink") { | ||
assert.Equal(t, executable, link) | ||
} | ||
}) | ||
t.Run("leave existing file", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.WriteFile(destPath, []byte("hello"), 0o644)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, true)) | ||
buf, err := os.ReadFile(destPath) | ||
if assert.NoError(t, err, "failed to read destination file") { | ||
assert.Equal(t, []byte("hello"), buf) | ||
} | ||
}) | ||
t.Run("remove correct symlink", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.Symlink(pluginPath, destPath)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, false)) | ||
_, err := os.Lstat(destPath) | ||
assert.ErrorIs(t, err, os.ErrNotExist, "symlink was not removed") | ||
}) | ||
t.Run("do not remove incorrect symlink", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.Symlink(destPath, destPath)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, false)) | ||
result, err := os.Readlink(destPath) | ||
if assert.NoError(t, err, "error reading symlink") { | ||
assert.Equal(t, destPath, result, "unexpected symlink contents") | ||
} | ||
}) | ||
t.Run("do not remove file", func(t *testing.T) { | ||
homeDir := t.TempDir() | ||
pluginDir := t.TempDir() | ||
pluginPath := filepath.Join(pluginDir, "docker-something") | ||
destPath := filepath.Join(homeDir, ".docker", "cli-plugins", "docker-something") | ||
t.Setenv("HOME", homeDir) | ||
|
||
require.NoError(t, os.MkdirAll(filepath.Dir(destPath), 0o755)) | ||
require.NoError(t, os.WriteFile(destPath, []byte("hello"), 0o644)) | ||
require.NoError(t, integration.DockerPlugin(pluginPath, false)) | ||
buf, err := os.ReadFile(destPath) | ||
if assert.NoError(t, err, "failed to read destination file") { | ||
assert.Equal(t, []byte("hello"), buf) | ||
} | ||
}) | ||
} |