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

Dynamic quarto #176

Merged
merged 6 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ dist/

dist/
*.log
*.sh
*.sh
*.py
5 changes: 5 additions & 0 deletions cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ func TestInstallParamsValidate(t *testing.T) {
if err != nil {
t.Fatalf("failed to retrieve valid Python versions: %v", err)
}

validQuartoVersions, err := quarto.RetrieveValidQuartoVersions()
if err != nil {
t.Fatalf("error retrieving valid Quarto versions: %v", err)
}

if err != nil {
t.Fatalf("failed to retrieve valid Quarto versions: %v", err)
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/net v0.7.0 // indirect
Expand Down
105 changes: 94 additions & 11 deletions internal/quarto/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,98 @@ package quarto

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/samber/lo"
"io"
"net/http"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/samber/lo"
log "github.com/sirupsen/logrus"
"github.com/sol-eng/wbi/internal/config"
cmdlog "github.com/sol-eng/wbi/internal/logging"
"github.com/sol-eng/wbi/internal/system"
)

type Assets []struct {
BrowserDownloadURL string `json:"browser_download_url"`
}
type Quarto []struct {
Assets Assets `json:"assets"`
Name string `json:"name"`
Prerelease bool `json:"prerelease"`
}

type result struct {
index int
res http.Response
err error
}

func RetrieveValidQuartoVersions() ([]string, error) {
// TODO automate the retrieving the list of valid versions
return []string{"1.3.340", "1.2.475", "1.1.189", "1.0.38"}, nil
var availQuartoVersions []string
var results []result
var quarto Quarto
var urls []string

for pagenum := 1; pagenum < 5; pagenum++ {
urls = append(urls, "https://api.github.com/repos/quarto-dev/quarto-cli/releases?per_page=100&page="+strconv.Itoa(pagenum))
}
wg := sync.WaitGroup{}

for i, url := range urls {
wg.Add(1)

go func(i int, url string) {

res, err := http.Get(url)
if err != nil {
return
}
result := &result{i, *res, err}
results = append(results, *result)
wg.Done()

}(i, url)

}

wg.Wait()

// let's sort these results real quick
sort.Slice(results, func(i, j int) bool {
return results[i].index < results[j].index
})

for _, result := range results {

err := json.NewDecoder(result.res.Body).Decode(&quarto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for result.res.Body to not exist and we should check for that or is the only case that would happen the error would be non nil in results which already gets checked earlier?

if err != nil {
return nil, err
}
for _, release := range quarto {
if release.Prerelease == false {
availQuartoVersions = append(availQuartoVersions, release.Name)
}
}
}
return availQuartoVersions, nil
}

func ValidateQuartoVersions(quartoVersions []string) error {

availQuartoVersions, err := RetrieveValidQuartoVersions()
if err != nil {
return fmt.Errorf("error retrieving valid Quarto versions: %w", err)
}

for _, quartoVersion := range quartoVersions {
if !lo.Contains(availQuartoVersions, quartoVersion) {
return errors.New("version " + quartoVersion + " is not a valid Quarto version")
Expand All @@ -49,12 +116,13 @@ func DownloadAndInstallQuarto(quartoVersion string, osType config.OperatingSyste
// Find URL
quartoURL := generateQuartoInstallURL(quartoVersion, osType)
// Download installer
installerPath, err := downloadFileQuarto(quartoURL, quartoVersion, osType)
installerPath, err := downloadFileQuarto(quartoURL, quartoVersion)
if err != nil {
return fmt.Errorf("DownloadFileQuarto: %w", err)
}
// Install Quarto
err = installQuarto(installerPath, osType, quartoVersion, true)

if err != nil {
return fmt.Errorf("InstallQuarto: %w", err)
}
Expand All @@ -71,14 +139,14 @@ func generateQuartoInstallURL(quartoVersion string, osType config.OperatingSyste
// treat RHEL 7 differently as specified here: https://docs.posit.co/resources/install-quarto/#specify-quarto-version-tar
var url string
if osType == config.Redhat7 {
url = fmt.Sprintf("https://github.com/quarto-dev/quarto-cli/releases/download/v%s/quarto-%s-linux-rhel7-amd64.tar.gz", quartoVersion, quartoVersion)
url = fmt.Sprintf("https://github.com/quarto-dev/quarto-cli/releases/download/%s/quarto-%s-linux-rhel7-amd64.tar.gz", quartoVersion, strings.Replace(quartoVersion, "v", "", -1))
} else {
url = fmt.Sprintf("https://github.com/quarto-dev/quarto-cli/releases/download/v%s/quarto-%s-linux-amd64.tar.gz", quartoVersion, quartoVersion)
url = fmt.Sprintf("https://github.com/quarto-dev/quarto-cli/releases/download/%s/quarto-%s-linux-amd64.tar.gz", quartoVersion, strings.Replace(quartoVersion, "v", "", -1))
}
return url
}

func downloadFileQuarto(url string, version string, osType config.OperatingSystem) (string, error) {
func downloadFileQuarto(url string, version string) (string, error) {
system.PrintAndLogInfo("Downloading Quarto Version: " + version + " installer from: " + url)

// Create the file
Expand All @@ -87,7 +155,16 @@ func downloadFileQuarto(url string, version string, osType config.OperatingSyste
if err != nil {
return tmpFile.Name(), err
}
defer tmpFile.Close()

defer func() {
if tempErr := tmpFile.Close(); tempErr != nil {
err = tempErr
}
}()

if err != nil {
return tmpFile.Name(), err
}

client := &http.Client{
Timeout: 30 * time.Second,
Expand All @@ -101,7 +178,12 @@ func downloadFileQuarto(url string, version string, osType config.OperatingSyste
if err != nil {
return "", errors.New("error downloading " + filename + " installer")
}
defer res.Body.Close()
defer func() {
if tempErr := res.Body.Close(); tempErr != nil {
err = tempErr
}
}()

if res.StatusCode != http.StatusOK {
return "", errors.New("error retrieving " + filename + " installer")
}
Expand All @@ -117,6 +199,7 @@ func downloadFileQuarto(url string, version string, osType config.OperatingSyste

// Installs Quarto
func installQuarto(filepath string, osType config.OperatingSystem, version string, save bool) error {

// create the /opt/quarto directory if it doesn't exist
path := fmt.Sprintf("/opt/quarto/%s", version)
if _, err := os.Stat(path); os.IsNotExist(err) {
Expand Down Expand Up @@ -180,7 +263,7 @@ func quartoLocationSymlinksPrompt(quartoPaths []string) (string, error) {
}
err := survey.AskOne(prompt, &target)
if err != nil {
return "", errors.New("there was an issue with the Quarto selection prompt for symlinking")
return "", errors.New("there was an issue with the Quarto selection prompt for symlink")
}
if target == "" {
return target, errors.New("no Quarto binary selected to be symlinked")
Expand Down
5 changes: 5 additions & 0 deletions internal/quarto/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ func ScanAndHandleQuartoVersions(osType config.OperatingSystem) error {

if quartoInstall {
// retrieve other versions and present them to the user
fmt.Printf("Retrieving available Quarto versions...")
validQuartoVersions, err := RetrieveValidQuartoVersions()
if err != nil {
return fmt.Errorf("error retrieving valid Quarto versions: %w", err)
}

if err != nil {
return fmt.Errorf("there was an issue retrieving valid Quarto versions: %w", err)
}
Expand Down
Loading