diff --git a/cmd/pro/login.go b/cmd/pro/login.go index aec76a098..c271ee9ae 100644 --- a/cmd/pro/login.go +++ b/cmd/pro/login.go @@ -2,9 +2,7 @@ package pro import ( "context" - "encoding/json" "fmt" - "io" "net/url" "path/filepath" "strings" @@ -12,7 +10,7 @@ import ( proflags "github.com/loft-sh/devpod/cmd/pro/flags" providercmd "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/pkg/config" - "github.com/loft-sh/devpod/pkg/http" + "github.com/loft-sh/devpod/pkg/loft" "github.com/loft-sh/devpod/pkg/loft/client" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/types" @@ -222,33 +220,11 @@ func (cmd *LoginCmd) resolveProviderSource(url string) error { return nil } - resp, err := http.GetHTTPClient().Get(url + "/version") + version, err := loft.GetDevPodVersion(url) if err != nil { - return fmt.Errorf("get %s: %w", url, err) - } else if resp.StatusCode != 200 { - out, _ := io.ReadAll(resp.Body) - return fmt.Errorf("get %s: %s (Status: %d)", url, string(out), resp.StatusCode) + return fmt.Errorf("get version: %w", err) } - - versionRaw, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("read %s: %w", url, err) - } - - version := &versionObject{} - err = json.Unmarshal(versionRaw, version) - if err != nil { - return fmt.Errorf("parse %s: %w", url, err) - } else if version.DevPodVersion == "" { - return fmt.Errorf("unexpected version '%s', please use --version to define a provider version", version.DevPodVersion) - } - - // make sure it starts with a v - if !strings.HasPrefix(version.DevPodVersion, "v") { - version.DevPodVersion = "v" + version.DevPodVersion - } - - cmd.ProviderSource = providerRepo + "@" + version.DevPodVersion + cmd.ProviderSource = providerRepo + "@" + version return nil } @@ -299,11 +275,6 @@ func LoftConfigPath(devPodConfig *config.Config, providerName string) (string, e return configPath, nil } -type versionObject struct { - // Version is the remote devpod version - DevPodVersion string `json:"devPodVersion,omitempty"` -} - var fallbackProvider = `name: devpod-pro version: v0.0.0 icon: https://devpod.sh/assets/devpod.svg diff --git a/cmd/up.go b/cmd/up.go index 0a9e8e414..7247bfbe2 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "github.com/blang/semver" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" @@ -26,6 +27,7 @@ import ( "github.com/loft-sh/devpod/pkg/ide/jupyter" "github.com/loft-sh/devpod/pkg/ide/openvscode" "github.com/loft-sh/devpod/pkg/ide/vscode" + "github.com/loft-sh/devpod/pkg/loft" open2 "github.com/loft-sh/devpod/pkg/open" "github.com/loft-sh/devpod/pkg/port" provider2 "github.com/loft-sh/devpod/pkg/provider" @@ -125,6 +127,13 @@ func NewUpCmd(flags *flags.GlobalFlags) *cobra.Command { return err } + if !cmd.Proxy { + err = checkProviderUpdate(devPodConfig, client.Provider(), logger) + if err != nil { + return err + } + } + return cmd.Run(ctx, devPodConfig, client, logger) }, } @@ -961,3 +970,62 @@ func performGpgForwarding( return nil } + +// checkProviderUpdate currently only ensures the local provider is in sync with the remote for DevPod Pro instances +// Potentially auto-upgrade other providers in the future. +func checkProviderUpdate(devPodConfig *config.Config, providerName string, log log.Logger) error { + fmt.Printf("potentially update %s \n", providerName) + proInstances, err := workspace2.ListProInstances(devPodConfig, log) + if err != nil { + return fmt.Errorf("list pro instances: %w", err) + } else if len(proInstances) == 0 { + return nil + } + + proInstance, ok := workspace2.FindProviderProInstance(proInstances, providerName) + if !ok { + return nil + } + + // compare versions + newVersion, err := loft.GetProInstanceDevPodVersion(proInstance) + if err != nil { + return fmt.Errorf("version for pro instance %s: %w", proInstance.Host, err) + } + + p, err := workspace2.FindProvider(devPodConfig, proInstance.Provider, log) + if err != nil { + return fmt.Errorf("get provider config for pro provider %s: %w", proInstance.Provider, err) + } + v1, err := semver.Parse(strings.TrimPrefix(newVersion, "v")) + if err != nil { + return fmt.Errorf("parse version %s: %w", newVersion, err) + } + v2, err := semver.Parse(strings.TrimPrefix(p.Config.Version, "v")) + if err != nil { + return fmt.Errorf("parse version %s: %w", p.Config.Version, err) + } + if v1.Compare(v2) == 0 { + return nil + } + log.Infof("New provider version available, attempting to update %s", proInstance.Provider) + + providerSource, err := workspace2.ResolveProviderSource(devPodConfig, proInstance.Provider, log) + if err != nil { + return fmt.Errorf("resolve provider source %s: %w", proInstance.Provider, err) + } + + splitted := strings.Split(providerSource, "@") + if len(splitted) == 0 { + return fmt.Errorf("no provider source found %s", providerSource) + } + providerSource = splitted[0] + "@" + newVersion + + _, err = workspace2.UpdateProvider(devPodConfig, providerName, providerSource, log) + if err != nil { + return fmt.Errorf("update provider %s: %w", proInstance.Provider, err) + } + + log.Donef("Successfully updated provider %s", proInstance.Provider) + return nil +} diff --git a/pkg/loft/version.go b/pkg/loft/version.go new file mode 100644 index 000000000..f0fa02f2a --- /dev/null +++ b/pkg/loft/version.go @@ -0,0 +1,51 @@ +package loft + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/loft-sh/devpod/pkg/http" + "github.com/loft-sh/devpod/pkg/provider" +) + +type VersionObject struct { + // Version is the remote devpod version + DevPodVersion string `json:"devPodVersion,omitempty"` +} + +func GetProInstanceDevPodVersion(proInstance *provider.ProInstance) (string, error) { + url := "https://" + proInstance.Host + return GetDevPodVersion(url) +} + +func GetDevPodVersion(url string) (string, error) { + resp, err := http.GetHTTPClient().Get(url + "/version") + if err != nil { + return "", fmt.Errorf("get %s: %w", url, err) + } else if resp.StatusCode != 200 { + out, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("get %s: %s (Status: %d)", url, string(out), resp.StatusCode) + } + + versionRaw, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read %s: %w", url, err) + } + + version := &VersionObject{} + err = json.Unmarshal(versionRaw, version) + if err != nil { + return "", fmt.Errorf("parse %s: %w", url, err) + } else if version.DevPodVersion == "" { + return "", fmt.Errorf("unexpected version '%s', please use --version to define a provider version", version.DevPodVersion) + } + + // make sure it starts with a v + if !strings.HasPrefix(version.DevPodVersion, "v") { + version.DevPodVersion = "v" + version.DevPodVersion + } + + return version.DevPodVersion, nil +} diff --git a/pkg/workspace/pro.go b/pkg/workspace/pro.go index 663dc765d..7ce8dfe12 100644 --- a/pkg/workspace/pro.go +++ b/pkg/workspace/pro.go @@ -32,3 +32,13 @@ func ListProInstances(devPodConfig *config.Config, log log.Logger) ([]*provider2 return retProInstances, nil } + +func FindProviderProInstance(proInstances []*provider2.ProInstance, providerName string) (*provider2.ProInstance, bool) { + for _, instance := range proInstances { + if instance.Provider == providerName { + return instance, true + } + } + + return nil, false +}