Skip to content

Commit

Permalink
Add support to package studio projects
Browse files Browse the repository at this point in the history
  • Loading branch information
thschmitt committed Dec 12, 2024
1 parent d2babcc commit e1b8b44
Show file tree
Hide file tree
Showing 28 changed files with 960 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
with:
go-version: '1.22.2'
cache: true
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '6.0.33'
- name: Version
id: version
run: |
Expand Down
13 changes: 7 additions & 6 deletions cache/file_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"strconv"
"strings"
"time"

"github.com/UiPath/uipathcli/utils"
)

const cacheFilePermissions = 0600
const cacheDirectoryPermissions = 0700
const cacheDirectory = "uipath"
const separator = "|"

// The FileCache stores data on disk in order to preserve them across
Expand Down Expand Up @@ -63,16 +64,16 @@ func (c FileCache) readValue(key string) (int64, string, error) {
}

func (c FileCache) cacheFilePath(key string) (string, error) {
userCacheDirectory, err := os.UserCacheDir()
cacheDirectory, err := utils.Directories{}.Cache()
if err != nil {
return "", err
}
cacheDirectory := filepath.Join(userCacheDirectory, cacheDirectory)
_ = os.MkdirAll(cacheDirectory, cacheDirectoryPermissions)
fileCacheDirectory := filepath.Join(cacheDirectory, "cache")
_ = os.MkdirAll(fileCacheDirectory, cacheDirectoryPermissions)

hash := sha256.Sum256([]byte(key))
fileName := fmt.Sprintf("%x.cache", hash)
return filepath.Join(cacheDirectory, fileName), nil
fileName := fmt.Sprintf("%x", hash)
return filepath.Join(fileCacheDirectory, fileName), nil
}

func NewFileCache() *FileCache {
Expand Down
9 changes: 9 additions & 0 deletions definitions/studio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
openapi: 3.0.1
info:
title: UiPath Studio
description: UiPath Studio
version: v1
servers:
- url: https://cloud.uipath.com/identity_
paths:
{}
4 changes: 4 additions & 0 deletions log/debug_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (l DebugLogger) LogResponse(response ResponseInfo) {
fmt.Fprint(l.writer, "\n\n\n")
}

func (l DebugLogger) Log(message string) {
fmt.Fprint(l.writer, message)
}

func (l DebugLogger) LogError(message string) {
fmt.Fprint(l.writer, message)
}
Expand Down
3 changes: 3 additions & 0 deletions log/default_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func (l *DefaultLogger) LogRequest(request RequestInfo) {
func (l DefaultLogger) LogResponse(response ResponseInfo) {
}

func (l DefaultLogger) Log(message string) {
}

func (l DefaultLogger) LogError(message string) {
fmt.Fprint(l.writer, message)
}
Expand Down
1 change: 1 addition & 0 deletions log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package log
// The Logger interface which is used to provide additional information to the
// user about what operations the CLI is performing.
type Logger interface {
Log(message string)
LogError(message string)
LogRequest(request RequestInfo)
LogResponse(response ResponseInfo)
Expand Down
8 changes: 5 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/UiPath/uipathcli/plugin"
plugin_digitizer "github.com/UiPath/uipathcli/plugin/digitizer"
plugin_orchestrator "github.com/UiPath/uipathcli/plugin/orchestrator"
plugin_studio "github.com/UiPath/uipathcli/plugin/studio"
"github.com/UiPath/uipathcli/utils"
)

Expand Down Expand Up @@ -63,9 +64,10 @@ func main() {
commandline.NewDefinitionFileStore(os.Getenv("UIPATH_DEFINITIONS_PATH"), embedded),
parser.NewOpenApiParser(),
[]plugin.CommandPlugin{
plugin_digitizer.DigitizeCommand{},
plugin_orchestrator.UploadCommand{},
plugin_orchestrator.DownloadCommand{},
plugin_digitizer.NewDigitizeCommand(),
plugin_orchestrator.NewUploadCommand(),
plugin_orchestrator.NewDownloadCommand(),
plugin_studio.NewPackagePackCommand(),
},
),
*configProvider,
Expand Down
4 changes: 4 additions & 0 deletions plugin/digitizer/digitize_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,7 @@ func (c DigitizeCommand) logResponse(logger log.Logger, response *http.Response,
responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
logger.LogResponse(*responseInfo)
}

func NewDigitizeCommand() *DigitizeCommand {
return &DigitizeCommand{}
}
22 changes: 11 additions & 11 deletions plugin/digitizer/digitizer_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ paths:

context := test.NewContextBuilder().
WithDefinition("du", definition).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
Build()

result := test.RunCli([]string{"du", "digitization", "digitize", "--file", "myfile"}, context)
Expand All @@ -40,7 +40,7 @@ paths:

context := test.NewContextBuilder().
WithDefinition("du", definition).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
Build()

result := test.RunCli([]string{"du", "digitization", "digitize", "--project-id", "1234"}, context)
Expand All @@ -67,7 +67,7 @@ paths:
context := test.NewContextBuilder().
WithConfig(config).
WithDefinition("du", definition).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
Build()

result := test.RunCli([]string{"du", "digitization", "digitize", "--project-id", "1234", "--file", "does-not-exist"}, context)
Expand All @@ -87,7 +87,7 @@ paths:

context := test.NewContextBuilder().
WithDefinition("du", definition).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
Build()

result := test.RunCli([]string{"du", "digitization", "digitize", "--project-id", "1234", "--file", "hello-world"}, context)
Expand All @@ -107,7 +107,7 @@ paths:

context := test.NewContextBuilder().
WithDefinition("du", definition).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
Build()

result := test.RunCli([]string{"du", "digitization", "digitize", "--organization", "myorg", "--project-id", "1234", "--file", "hello-world"}, context)
Expand Down Expand Up @@ -137,7 +137,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithResponse(400, "validation error").
Build()

Expand Down Expand Up @@ -168,7 +168,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithResponse(202, `{"documentId":"04908673-2b65-4647-8ab3-dde8a3aa7885"}`).
WithUrlResponse("/my-org/my-tenant/du_/api/framework/projects/1234/digitization/result/04908673-2b65-4647-8ab3-dde8a3aa7885?api-version=1", 400, `validation error`).
Build()
Expand Down Expand Up @@ -210,7 +210,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithResponse(202, `{"documentId":"eb80e441-05de-4a13-9aaa-f65b1babba05"}`).
WithUrlResponse("/my-org/my-tenant/du_/api/framework/projects/1234/digitization/result/eb80e441-05de-4a13-9aaa-f65b1babba05?api-version=1", 200, `{"status":"Done"}`).
Build()
Expand Down Expand Up @@ -246,7 +246,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithResponse(202, `{"documentId":"eb80e441-05de-4a13-9aaa-f65b1babba05"}`).
WithUrlResponse("/my-org/my-tenant/du_/api/framework/projects/1234/digitization/result/eb80e441-05de-4a13-9aaa-f65b1babba05?api-version=1", 200, `{"pages":[],"status":"Done"}`).
Build()
Expand Down Expand Up @@ -287,7 +287,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithStdIn(stdIn).
WithResponse(202, `{"documentId":"eb80e441-05de-4a13-9aaa-f65b1babba05"}`).
WithUrlResponse("/my-org/my-tenant/du_/api/framework/projects/1234/digitization/result/eb80e441-05de-4a13-9aaa-f65b1babba05?api-version=1", 200, `{"status":"Done"}`).
Expand Down Expand Up @@ -334,7 +334,7 @@ paths:
context := test.NewContextBuilder().
WithDefinition("du", definition).
WithConfig(config).
WithCommandPlugin(DigitizeCommand{}).
WithCommandPlugin(NewDigitizeCommand()).
WithResponse(202, `{"documentId":"eb80e441-05de-4a13-9aaa-f65b1babba05"}`).
WithUrlResponse("/my-org/my-tenant/du_/api/framework/projects/1234/digitization/result/eb80e441-05de-4a13-9aaa-f65b1babba05?api-version=1", 200, `{"status":"Done"}`).
Build()
Expand Down
113 changes: 113 additions & 0 deletions plugin/external_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package plugin

import (
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"math"
"math/big"
"net/http"
"os"
"path/filepath"

"github.com/UiPath/uipathcli/log"
"github.com/UiPath/uipathcli/utils"
)

const pluginDirectoryPermissions = 0700

type ExternalPlugin struct {
Logger log.Logger
}

func (p ExternalPlugin) GetTool(name string, url string, executable string) (string, error) {
pluginDirectory, err := p.cacheFilePath(name, url)
if err != nil {
return "", fmt.Errorf("Could not download %s: %v", name, err)
}
path := filepath.Join(pluginDirectory, executable)
if _, err := os.Stat(path); err == nil {
return path, nil
}

tmpPluginDirectory := pluginDirectory + "-" + p.randomFolderName()
_ = os.MkdirAll(tmpPluginDirectory, pluginDirectoryPermissions)

progressBar := utils.NewProgressBar(p.Logger)
defer progressBar.Remove()
progressBar.Tick("downloading...")
zipArchivePath := filepath.Join(tmpPluginDirectory, name)
err = p.download(name, url, zipArchivePath, progressBar)
if err != nil {
return "", err
}
err = newZipArchive().Extract(zipArchivePath, tmpPluginDirectory)
if err != nil {
return "", fmt.Errorf("Could not extract %s archive: %v", name, err)
}
os.Remove(zipArchivePath)
err = os.Chmod(filepath.Join(tmpPluginDirectory, executable), 0700)
if err != nil {
return "", fmt.Errorf("Could not install %s: %v", name, err)
}
err = os.Rename(tmpPluginDirectory, pluginDirectory)
if err != nil {
return "", fmt.Errorf("Could not install %s: %v", name, err)
}
return path, nil
}

func (p ExternalPlugin) download(name string, url string, destination string, progressBar *utils.ProgressBar) error {
out, err := os.Create(destination)
if err != nil {
return fmt.Errorf("Could not download %s: %v", name, err)
}
defer out.Close()

request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("Could not download %s: %v", name, err)
}
response, err := http.DefaultClient.Do(request)
if err != nil {
return fmt.Errorf("Could not download %s: %v", name, err)
}
downloadReader := p.progressReader("downloading...", "installing... ", response.Body, response.ContentLength, progressBar)
_, err = io.Copy(out, downloadReader)
if err != nil {
return fmt.Errorf("Could not download %s: %v", name, err)
}
return nil
}

func (p ExternalPlugin) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader {
progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) {
displayText := text
if progress.Completed {
displayText = completedText
}
progressBar.Update(displayText, progress.BytesRead, length, progress.BytesPerSecond)
})
return progressReader
}

func (p ExternalPlugin) cacheFilePath(name string, url string) (string, error) {
cacheDirectory, err := utils.Directories{}.Cache()
if err != nil {
return "", err
}
hash := sha256.Sum256([]byte(url))
subdirectory := fmt.Sprintf("%s-%x", name, hash)
pluginDirectory := filepath.Join(cacheDirectory, "plugins", subdirectory)
return pluginDirectory, nil
}

func (p ExternalPlugin) randomFolderName() string {
value, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
return value.String()
}

func NewExternalPlugin(logger log.Logger) *ExternalPlugin {
return &ExternalPlugin{logger}
}
4 changes: 4 additions & 0 deletions plugin/orchestrator/download_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,7 @@ func (c DownloadCommand) logResponse(logger log.Logger, response *http.Response,
responseInfo := log.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))
logger.LogResponse(*responseInfo)
}

func NewDownloadCommand() *DownloadCommand {
return &DownloadCommand{}
}
Loading

0 comments on commit e1b8b44

Please sign in to comment.