Skip to content
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
4 changes: 1 addition & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"name": "Default Linux Universal",
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/go:1": {},
"ghcr.io/devcontainers/features/go:1": {}
}
}
19 changes: 19 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: release
on:
push:
tags:
- "v*"
permissions:
contents: write
id-token: write
attestations: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cli/gh-extension-precompile@v2
with:
generate_attestations: true
go_version_file: go.mod
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/gh-runtime-cli
/gh-runtime-cli.exe
/test
78 changes: 78 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/api"
"github.com/spf13/cobra"
)

type createCmdFlags struct {
app string
}

type createReq struct {
EnvironmentVariables map[string]string `json:"environment_variables"`
Secrets map[string]string `json:"secrets"`
}

type createResp struct {
}

func init() {
createCmdFlags := createCmdFlags{}
createCmd := &cobra.Command{
Use: "create",
Short: "Create a GitHub Runtime app",
Long: heredoc.Doc(`
Create a GitHub Runtime app
`),
Example: heredoc.Doc(`
$ gh runtime create --app my-app
# => Creates the app named 'my-app'
`),
Run: func(cmd *cobra.Command, args []string) {
if createCmdFlags.app == "" {
fmt.Println("Error: --app flag is required")
return
}

// Construct the request body
requestBody := createReq{
EnvironmentVariables: map[string]string{
"EXAMPLE_ENV": "value1",
},
Secrets: map[string]string{
"SECRET_KEY": "secret_value",
},
}

body, err := json.Marshal(requestBody)
if err != nil {
fmt.Printf("Error marshalling request body: %v\n", err)
return
}

createUrl := fmt.Sprintf("runtime/%s/deployment", createCmdFlags.app)
client, err := api.DefaultRESTClient()
if err != nil {
fmt.Println(err)
return
}
var response string
err = client.Put(createUrl, bytes.NewReader(body), &response)
if err != nil {
fmt.Printf("Error creating app: %v\n", err)
return
}

fmt.Printf("App created: %s\n", response) // TODO pretty print details
},
}

createCmd.Flags().StringVarP(&createCmdFlags.app, "app", "a", "", "The app to create")
rootCmd.AddCommand(createCmd)
}
57 changes: 57 additions & 0 deletions cmd/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"fmt"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/api"
"github.com/spf13/cobra"
)

type deleteCmdFlags struct {
app string
}

type deleteResp struct {
}

func init() {
deleteCmdFlags := deleteCmdFlags{}
deleteCmd := &cobra.Command{
Use: "delete",
Short: "Delete a GitHub Runtime app",
Long: heredoc.Doc(`
Delete a GitHub Runtime app
`),
Example: heredoc.Doc(`
$ gh runtime delete --app my-app
# => Deletes the app named 'my-app'
`),
Run: func(cmd *cobra.Command, args []string) {
if deleteCmdFlags.app == "" {
fmt.Println("Error: --app flag is required")
return
}

deleteUrl := fmt.Sprintf("runtime/%s/deployment", deleteCmdFlags.app)
client, err := api.DefaultRESTClient()
if err != nil {
fmt.Println(err)
return
}
var response string
err = client.Delete(deleteUrl, &response)
if err != nil {
// print err and response
fmt.Printf("Error deleting app: %v\n", err)
fmt.Printf("Response: %v\n", response)
return
}

fmt.Printf("App deleted: %s\n", response)
},
}

deleteCmd.Flags().StringVarP(&deleteCmdFlags.app, "app", "a", "", "The app to delete")
rootCmd.AddCommand(deleteCmd)
}
155 changes: 155 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package cmd

import (
"archive/zip"
"bytes"
"fmt"
"io"
"os"
"path/filepath"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/api"
"github.com/spf13/cobra"
)

type deployCmdFlags struct {
dir string
app string
}

func zipDirectory(sourceDir, destinationZip string) error {
zipFile, err := os.Create(destinationZip)
if err != nil {
return fmt.Errorf("error creating zip file '%s': %w", destinationZip, err)
}
defer zipFile.Close()

zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()

err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error accessing path '%s': %w", path, err)
}

relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return fmt.Errorf("error calculating relative path for '%s': %w", path, err)
}

if info.IsDir() {
if relPath == "." {
return nil
}
relPath += "/"
}

header, err := zip.FileInfoHeader(info)
if err != nil {
return fmt.Errorf("error creating zip header for '%s': %w", path, err)
}
header.Name = relPath
if !info.IsDir() {
header.Method = zip.Deflate
}

writer, err := zipWriter.CreateHeader(header)
if err != nil {
return fmt.Errorf("error creating zip writer for '%s': %w", path, err)
}

if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("error opening file '%s': %w", path, err)
}
defer file.Close()

_, err = io.Copy(writer, file)
if err != nil {
return fmt.Errorf("error writing file '%s' to zip: %w", path, err)
}
}

return nil
})

if err != nil {
return fmt.Errorf("error zipping directory '%s': %w", sourceDir, err)
}

return nil
}

func init() {
deployCmdFlags := deployCmdFlags{}
deployCmd := &cobra.Command{
Use: "deploy",
Short: "Deploy app to GitHub Runtime",
Long: heredoc.Doc(`
Deploys a directory to a GitHub Runtime app
`),
Example: heredoc.Doc(`
$ gh runtime deploy --dir ./dist --app my-app
# => Deploys the contents of the 'dist' directory to the app named 'my-app'
`),
Run: func(cmd *cobra.Command, args []string) {
if deployCmdFlags.dir == "" {
fmt.Println("Error: --dir flag is required")
return
}
if deployCmdFlags.app == "" {
fmt.Println("Error: --app flag is required")
return
}

if _, err := os.Stat(deployCmdFlags.dir); os.IsNotExist(err) {
fmt.Printf("Error: directory '%s' does not exist\n", deployCmdFlags.dir)
return
}

_, err := os.ReadDir(deployCmdFlags.dir)
if err != nil {
fmt.Printf("Error reading directory '%s': %v\n", deployCmdFlags.dir, err)
return
}

// Zip the directory
zipPath := fmt.Sprintf("%s.zip", deployCmdFlags.dir)
err = zipDirectory(deployCmdFlags.dir, zipPath)
if err != nil {
fmt.Printf("Error zipping directory '%s': %v\n", deployCmdFlags.dir, err)
return
}
defer os.Remove(zipPath)

client, err := api.DefaultRESTClient()
if err != nil {
fmt.Println(err)
return
}

deploymentsUrl := fmt.Sprintf("runtime/%s/deployment/bundle", deployCmdFlags.app)
fmt.Printf("Deploying app to %s\n", deploymentsUrl)

// body is the full zip RAW
body, err := os.ReadFile(zipPath)
if err != nil {
fmt.Printf("Error reading zip file '%s': %v\n", zipPath, err)
return
}

err = client.Post(deploymentsUrl, bytes.NewReader(body), nil)
if err != nil {
fmt.Printf("Error deploying app: %v\n", err)
return
}

fmt.Printf("Successfully deployed app\n")
},
}
deployCmd.Flags().StringVarP(&deployCmdFlags.dir, "dir", "d", "", "The directory to deploy")
deployCmd.Flags().StringVarP(&deployCmdFlags.app, "app", "a", "", "The app to deploy")
rootCmd.AddCommand(deployCmd)
}
53 changes: 53 additions & 0 deletions cmd/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"fmt"

"github.com/MakeNowJust/heredoc"
"github.com/cli/go-gh/v2/pkg/api"
"github.com/spf13/cobra"
)

type getCmdFlags struct {
app string
}

func init() {
getCmdFlags := getCmdFlags{}
getCmd := &cobra.Command{
Use: "get",
Short: "Get details of a GitHub Runtime app",
Long: heredoc.Doc(`
Get details of a GitHub Runtime app
`),
Example: heredoc.Doc(`
$ gh runtime get --app my-app
# => Retrieves details of the app named 'my-app'
`),
Run: func(cmd *cobra.Command, args []string) {
if getCmdFlags.app == "" {
fmt.Println("Error: --app flag is required")
return
}

getUrl := fmt.Sprintf("runtime/%s/deployment", getCmdFlags.app)
client, err := api.DefaultRESTClient()
if err != nil {
fmt.Println(err)
return
}

var response string
err = client.Get(getUrl, &response)
if err != nil {
fmt.Printf("Error retrieving app details: %v\n", err)
return
}

fmt.Printf("App Details: %s\n", response)
},
}

getCmd.Flags().StringVarP(&getCmdFlags.app, "app", "a", "", "The app to retrieve details for")
rootCmd.AddCommand(getCmd)
}
Loading
Loading