-
Notifications
You must be signed in to change notification settings - Fork 341
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
POD-779 | Initial integration of devpod-crane (#1230)
* Add config-source flag to workspace up command * Init crane client implementation * Switch to switch for detecting client type * Init usage of crane * Move signing management fully to crane * Simplify usage of crane * Factor out reading raw config in workspace run implementation * Move devcontainer config methods to a separate file * Add docstrings in crane package
- Loading branch information
1 parent
fbffe09
commit 6812c57
Showing
7 changed files
with
283 additions
and
154 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
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,147 @@ | ||
package devcontainer | ||
|
||
import ( | ||
"os" | ||
"path" | ||
"path/filepath" | ||
|
||
"github.com/loft-sh/devpod/pkg/devcontainer/config" | ||
"github.com/loft-sh/devpod/pkg/devcontainer/crane" | ||
"github.com/loft-sh/devpod/pkg/language" | ||
provider2 "github.com/loft-sh/devpod/pkg/provider" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func (r *runner) getRawConfig(options provider2.CLIOptions) (*config.DevContainerConfig, error) { | ||
if r.WorkspaceConfig.Workspace.DevContainerConfig != nil { | ||
rawParsedConfig := config.CloneDevContainerConfig(r.WorkspaceConfig.Workspace.DevContainerConfig) | ||
if r.WorkspaceConfig.Workspace.DevContainerPath != "" { | ||
rawParsedConfig.Origin = path.Join(filepath.ToSlash(r.LocalWorkspaceFolder), r.WorkspaceConfig.Workspace.DevContainerPath) | ||
} else { | ||
rawParsedConfig.Origin = path.Join(filepath.ToSlash(r.LocalWorkspaceFolder), ".devcontainer.devpod.json") | ||
} | ||
return rawParsedConfig, nil | ||
} else if r.WorkspaceConfig.Workspace.Source.Container != "" { | ||
return &config.DevContainerConfig{ | ||
DevContainerConfigBase: config.DevContainerConfigBase{ | ||
// Default workspace directory for containers | ||
// Upon inspecting the container, this would be updated to the correct folder, if found set | ||
WorkspaceFolder: "/", | ||
}, | ||
RunningContainer: config.RunningContainer{ | ||
ContainerID: r.WorkspaceConfig.Workspace.Source.Container, | ||
}, | ||
Origin: "", | ||
}, nil | ||
} else if options.DevContainerSource != "" && crane.IsAvailable() { | ||
localWorkspaceFolder, err := crane.PullConfigFromSource(options.DevContainerSource, r.Log) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return config.ParseDevContainerJSON( | ||
localWorkspaceFolder, | ||
r.WorkspaceConfig.Workspace.DevContainerPath, | ||
) | ||
} | ||
|
||
localWorkspaceFolder := r.LocalWorkspaceFolder | ||
// if a subpath is specified, let's move to it | ||
|
||
if r.WorkspaceConfig.Workspace.Source.GitSubPath != "" { | ||
localWorkspaceFolder = filepath.Join(r.LocalWorkspaceFolder, r.WorkspaceConfig.Workspace.Source.GitSubPath) | ||
} | ||
|
||
// parse the devcontainer json | ||
rawParsedConfig, err := config.ParseDevContainerJSON( | ||
localWorkspaceFolder, | ||
r.WorkspaceConfig.Workspace.DevContainerPath, | ||
) | ||
|
||
// We want to fail only in case of real errors, non-existing devcontainer.jon | ||
// will be gracefully handled by the auto-detection mechanism | ||
if err != nil && !os.IsNotExist(err) { | ||
return nil, errors.Wrap(err, "parsing devcontainer.json") | ||
} else if rawParsedConfig == nil { | ||
r.Log.Infof("Couldn't find a devcontainer.json") | ||
return r.getDefaultConfig(options) | ||
} | ||
return rawParsedConfig, nil | ||
} | ||
|
||
func (r *runner) getDefaultConfig(options provider2.CLIOptions) (*config.DevContainerConfig, error) { | ||
defaultConfig := &config.DevContainerConfig{} | ||
if options.FallbackImage != "" { | ||
r.Log.Infof("Using fallback image %s", options.FallbackImage) | ||
defaultConfig.ImageContainer = config.ImageContainer{ | ||
Image: options.FallbackImage, | ||
} | ||
} else { | ||
r.Log.Infof("Try detecting project programming language...") | ||
defaultConfig = language.DefaultConfig(r.LocalWorkspaceFolder, r.Log) | ||
} | ||
|
||
defaultConfig.Origin = path.Join(filepath.ToSlash(r.LocalWorkspaceFolder), ".devcontainer.json") | ||
err := config.SaveDevContainerJSON(defaultConfig) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "write default devcontainer.json") | ||
} | ||
return defaultConfig, nil | ||
} | ||
|
||
func (r *runner) getSubstitutedConfig(options provider2.CLIOptions) (*config.SubstitutedConfig, *config.SubstitutionContext, error) { | ||
rawConfig, err := r.getRawConfig(options) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
return r.substitute(options, rawConfig) | ||
} | ||
|
||
func (r *runner) substitute( | ||
options provider2.CLIOptions, | ||
rawParsedConfig *config.DevContainerConfig, | ||
) (*config.SubstitutedConfig, *config.SubstitutionContext, error) { | ||
configFile := rawParsedConfig.Origin | ||
|
||
// get workspace folder within container | ||
workspaceMount, containerWorkspaceFolder := getWorkspace( | ||
r.LocalWorkspaceFolder, | ||
r.WorkspaceConfig.Workspace.ID, | ||
rawParsedConfig, | ||
) | ||
substitutionContext := &config.SubstitutionContext{ | ||
DevContainerID: r.ID, | ||
LocalWorkspaceFolder: r.LocalWorkspaceFolder, | ||
ContainerWorkspaceFolder: containerWorkspaceFolder, | ||
Env: config.ListToObject(os.Environ()), | ||
|
||
WorkspaceMount: workspaceMount, | ||
} | ||
|
||
// substitute & load | ||
parsedConfig := &config.DevContainerConfig{} | ||
err := config.Substitute(substitutionContext, rawParsedConfig, parsedConfig) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
if parsedConfig.WorkspaceFolder != "" { | ||
substitutionContext.ContainerWorkspaceFolder = parsedConfig.WorkspaceFolder | ||
} | ||
if parsedConfig.WorkspaceMount != "" { | ||
substitutionContext.WorkspaceMount = parsedConfig.WorkspaceMount | ||
} | ||
|
||
if options.DevContainerImage != "" { | ||
parsedConfig.Build = nil | ||
parsedConfig.Dockerfile = "" | ||
parsedConfig.DockerfileContainer = config.DockerfileContainer{} | ||
parsedConfig.ImageContainer = config.ImageContainer{Image: options.DevContainerImage} | ||
} | ||
|
||
parsedConfig.Origin = configFile | ||
return &config.SubstitutedConfig{ | ||
Config: parsedConfig, | ||
Raw: rawParsedConfig, | ||
}, substitutionContext, nil | ||
} |
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,97 @@ | ||
package crane | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/loft-sh/log" | ||
) | ||
|
||
var ( | ||
craneSigningKey string | ||
) | ||
|
||
const ( | ||
PullCommand = "pull" | ||
DecryptCommand = "decrypt" | ||
|
||
GitCrane = "git" | ||
|
||
BinPath = "devpod-crane" // FIXME | ||
|
||
tmpDirTemplate = "devpod-crane-*" | ||
) | ||
|
||
type Content struct { | ||
Files map[string]string `json:"files"` | ||
} | ||
|
||
// IsAvailable checks if devpod crane is installed in host system | ||
func IsAvailable() bool { | ||
_, err := exec.LookPath(BinPath) | ||
return err == nil | ||
} | ||
|
||
func runCommand(command string, args ...string) (string, error) { | ||
cmd := exec.Command(BinPath, append([]string{command}, args...)...) | ||
|
||
var outBuf, errBuf bytes.Buffer | ||
cmd.Stdout = &outBuf | ||
cmd.Stderr = &errBuf | ||
|
||
if err := cmd.Run(); err != nil { | ||
return "", fmt.Errorf("failed to execute command: %v, error: %w", errBuf.String(), err) | ||
} | ||
|
||
return outBuf.String(), nil | ||
} | ||
|
||
// PullConfigFromSource pulls devcontainer config from configSource using git crane and returns config path | ||
func PullConfigFromSource(configSource string, log log.Logger) (string, error) { | ||
data, err := runCommand(PullCommand, GitCrane, configSource) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if craneSigningKey != "" { | ||
data, err = runCommand(DecryptCommand, data, "--key", craneSigningKey) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
|
||
content := &Content{} | ||
if err := json.Unmarshal([]byte(data), content); err != nil { | ||
return "", err | ||
} | ||
|
||
return createContentDirectory(content) | ||
} | ||
|
||
func createContentDirectory(content *Content) (string, error) { | ||
tmpDir, err := os.MkdirTemp("", tmpDirTemplate) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create temporary directory: %w", err) | ||
} | ||
|
||
for filename, fileContent := range content.Files { | ||
filePath := filepath.Join(tmpDir, filename) | ||
|
||
dir := filepath.Dir(filePath) | ||
if err := os.MkdirAll(dir, os.ModePerm); err != nil { | ||
return "", err | ||
} | ||
|
||
err := os.WriteFile(filePath, []byte(fileContent), os.ModePerm) | ||
if err != nil { | ||
os.RemoveAll(tmpDir) | ||
return "", fmt.Errorf("failed to write file %s: %w", filename, err) | ||
} | ||
} | ||
|
||
return tmpDir, nil | ||
} |
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
Oops, something went wrong.