-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(extensions): add install extensions from registry (#223)
* "feat(extensions): add install extensions from registry This pr includes everything up to actually downloading the extension from the upstream registry Such as pulling an example registry entry ```json { "name": "SuperExtender", "description": "Enhances your experience with superpowers.", "version": "1.0.3", "downloadUrls": [ { "architecture": "amd64", "os": "linux", "url": "https://example.com/linux/amd64/superextender.tar.gz", "checksum": "efgh5678" }, { "architecture": "x86", "os": "windows", "url": "https://example.com/windows/x86/superextender.zip", "checksum": "ijkl91011" }, { "architecture": "arm64", "os": "darwin", "url": "https://example.com/windows/x86/superextender.zip", "checksum": "ijkl91011" } ] } ``` Which will match on the current os using $GOOS and $GOARCH set by golang itself. We may however have to depend on some other variables if go is not installed by I believe it should work anyway" * feat: print error Signed-off-by: Kasper J. Hermansen <[email protected]> * fix: review comments, wording etc. Signed-off-by: Kasper J. Hermansen <[email protected]> * feat(extensions): add github release downloader (#224) * feat(extensions): add github release downloader This specifically adds a downloader for github releases it uses either the SHUTTLE_EXTENSIONS_GITHUB_ACCESS_TOKEN or GITHUB_ACCESS_TOKEN for access to the releases The downloader expects a full url from the registry as seen in a previous pr Signed-off-by: Kasper J. Hermansen <[email protected]> * chore: reword bearer Signed-off-by: Kasper J. Hermansen <[email protected]> * feat(extensions): add execute command (#225) * feat(extensions): add execute command this adds Extensions to the global cmd.go resulting in another section showing up when doing a shuttle --help To execute an extension simply shuttle myExtension where myExtension is the name of the downloaded extension. All args are passed to the child as well Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: remember to add error Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: without empty line Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: adds github remote registry index (#226) * feat: with github registry Signed-off-by: Kasper J. Hermansen <[email protected]> * fix: shuttle extensions Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: can download private files Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: remove fluff Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: fix review comments Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: it needs to implement the functions Signed-off-by: Kasper J. Hermansen <[email protected]> --------- Signed-off-by: Kasper J. Hermansen <[email protected]> --------- Signed-off-by: Kasper J. Hermansen <[email protected]> --------- Signed-off-by: Kasper J. Hermansen <[email protected]> --------- Signed-off-by: Kasper J. Hermansen <[email protected]>
- Loading branch information
Showing
13 changed files
with
812 additions
and
5 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,75 @@ | ||
package extensions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"time" | ||
) | ||
|
||
type Downloader interface { | ||
Download(ctx context.Context, dest string) error | ||
} | ||
|
||
func NewDownloader(downloadLink *registryExtensionDownloadLink) (Downloader, error) { | ||
switch downloadLink.Provider { | ||
case "github-release": | ||
return newGitHubReleaseDownloader(downloadLink), nil | ||
default: | ||
return nil, fmt.Errorf("invalid provider type: %s", downloadLink.Provider) | ||
} | ||
} | ||
|
||
type gitHubReleaseDownloader struct { | ||
link *registryExtensionDownloadLink | ||
} | ||
|
||
func newGitHubReleaseDownloader(downloadLink *registryExtensionDownloadLink) Downloader { | ||
return &gitHubReleaseDownloader{ | ||
link: downloadLink, | ||
} | ||
} | ||
|
||
func (d *gitHubReleaseDownloader) Download(ctx context.Context, dest string) error { | ||
client := http.DefaultClient | ||
client.Timeout = time.Second * 60 | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, d.link.Url, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
bearer, err := getGithubToken() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
req.Header.Add("Authorization", fmt.Sprintf("token %s", bearer)) | ||
req.Header.Add("Accept", "application/octet-stream") | ||
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if err := os.RemoveAll(dest); err != nil { | ||
log.Printf("failed to remove extension before downloading new: %s, please try again", err.Error()) | ||
} | ||
|
||
extensionBinary, err := os.Create(dest) | ||
if err != nil { | ||
return err | ||
} | ||
defer extensionBinary.Close() | ||
extensionBinary.Chmod(0o755) | ||
|
||
if _, err := io.Copy(extensionBinary, resp.Body); err != nil { | ||
return err | ||
} | ||
|
||
return 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 |
---|---|---|
@@ -1,4 +1,88 @@ | ||
package extensions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"path" | ||
"runtime" | ||
|
||
"github.com/lunarway/shuttle/internal/global" | ||
) | ||
|
||
// Extension is the descriptor of a single extension, it is used to add description to the cli, as well as calling the specific extension in question | ||
type Extension struct{} | ||
type Extension struct { | ||
os string | ||
arch string | ||
globalStore *global.GlobalStore | ||
remote *registryExtension | ||
} | ||
|
||
func newExtensionFromRegistry(globalStore *global.GlobalStore, registryExtension *registryExtension) (*Extension, error) { | ||
return &Extension{ | ||
os: runtime.GOOS, | ||
arch: runtime.GOARCH, | ||
globalStore: globalStore, | ||
remote: registryExtension, | ||
}, nil | ||
} | ||
|
||
func (e *Extension) Ensure(ctx context.Context) error { | ||
extensionsCachePath := getExtensionsCachePath(e.globalStore) | ||
binaryName := e.getExtensionBinaryName() | ||
if err := ensureExists(extensionsCachePath); err != nil { | ||
return fmt.Errorf("failed to create cache path: %w", err) | ||
} | ||
|
||
binaryPath := path.Join(extensionsCachePath, binaryName) | ||
if exists(binaryPath) { | ||
// TODO: do a checksum chck | ||
//return nil | ||
} | ||
|
||
downloadLink := e.getRemoteBinaryDownloadLink() | ||
if downloadLink == nil { | ||
return fmt.Errorf("failed to find a valid extension matching your os and architecture") | ||
} | ||
|
||
downloader, err := NewDownloader(downloadLink) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := downloader.Download(ctx, binaryPath); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (e *Extension) Name() string { | ||
return e.remote.Name | ||
} | ||
|
||
func (e *Extension) Version() string { | ||
return e.remote.Version | ||
} | ||
|
||
func (e *Extension) Description() string { | ||
return e.remote.Description | ||
} | ||
|
||
func (e *Extension) getExtensionBinaryName() string { | ||
return e.remote.Name | ||
} | ||
|
||
func (e *Extension) FullPath() string { | ||
return path.Join(getExtensionsCachePath(e.globalStore), e.Name()) | ||
} | ||
|
||
func (e *Extension) getRemoteBinaryDownloadLink() *registryExtensionDownloadLink { | ||
for _, download := range e.remote.DownloadUrls { | ||
if download.Os == e.os && | ||
download.Architecture == e.arch { | ||
return &download | ||
} | ||
} | ||
|
||
return 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,44 @@ | ||
package extensions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
type shuttleExtensionsRegistry struct { | ||
GitHub *string `json:"github" yaml:"github"` | ||
} | ||
|
||
type shuttleExtensionProviderGitHubRelease struct { | ||
Owner string `json:"owner" yaml:"owner"` | ||
Repo string `json:"repo" yaml:"repo"` | ||
} | ||
|
||
type shuttleExtensionsProvider struct { | ||
GitHubRelease *shuttleExtensionProviderGitHubRelease `json:"github-release" yaml:"github-release"` | ||
} | ||
|
||
type shuttleExtensionsFile struct { | ||
Name string `json:"name" yaml:"name"` | ||
Description string `json:"description" yaml:"description"` | ||
|
||
Provider shuttleExtensionsProvider `json:"provider" yaml:"provider"` | ||
Registry shuttleExtensionsRegistry `json:"registry" yaml:"registry"` | ||
} | ||
|
||
func getExtensionsFile(_ context.Context) (*shuttleExtensionsFile, error) { | ||
templateFileContent, err := os.ReadFile("shuttle.template.yaml") | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to find shuttle.template.yaml: %w", err) | ||
} | ||
|
||
var templateFile shuttleExtensionsFile | ||
if err := yaml.Unmarshal(templateFileContent, &templateFile); err != nil { | ||
return nil, fmt.Errorf("failed to parse shuttle.template.yaml: %w", err) | ||
} | ||
|
||
return &templateFile, nil | ||
} |
Oops, something went wrong.