Skip to content

Commit

Permalink
feat(cli): ensure pro providers are up to date before running devpod up
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbreuninger committed Jun 10, 2024
1 parent 5142bcf commit bb2f25d
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 33 deletions.
37 changes: 4 additions & 33 deletions cmd/pro/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package pro

import (
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"path/filepath"
"strings"

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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
68 changes: 68 additions & 0 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
},
}
Expand Down Expand Up @@ -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
}
51 changes: 51 additions & 0 deletions pkg/loft/version.go
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions pkg/workspace/pro.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit bb2f25d

Please sign in to comment.