Skip to content

Commit 4a1f713

Browse files
Merge pull request #1 from github/chava/add-cli
Iniital CLI implementation
2 parents 1954e17 + 19eeb28 commit 4a1f713

12 files changed

Lines changed: 453 additions & 28 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
2-
"name": "Default Linux Universal",
3-
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
42
"features": {
53
"ghcr.io/devcontainers/features/github-cli:1": {},
6-
"ghcr.io/devcontainers/features/go:1": {},
4+
"ghcr.io/devcontainers/features/go:1": {}
75
}
86
}

.github/release.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: release
2+
on:
3+
push:
4+
tags:
5+
- "v*"
6+
permissions:
7+
contents: write
8+
id-token: write
9+
attestations: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: cli/gh-extension-precompile@v2
17+
with:
18+
generate_attestations: true
19+
go_version_file: go.mod

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/gh-runtime-cli
22
/gh-runtime-cli.exe
3+
/test

cmd/create.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/MakeNowJust/heredoc"
9+
"github.com/cli/go-gh/v2/pkg/api"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
type createCmdFlags struct {
14+
app string
15+
}
16+
17+
type createReq struct {
18+
EnvironmentVariables map[string]string `json:"environment_variables"`
19+
Secrets map[string]string `json:"secrets"`
20+
}
21+
22+
type createResp struct {
23+
}
24+
25+
func init() {
26+
createCmdFlags := createCmdFlags{}
27+
createCmd := &cobra.Command{
28+
Use: "create",
29+
Short: "Create a GitHub Runtime app",
30+
Long: heredoc.Doc(`
31+
Create a GitHub Runtime app
32+
`),
33+
Example: heredoc.Doc(`
34+
$ gh runtime create --app my-app
35+
# => Creates the app named 'my-app'
36+
`),
37+
Run: func(cmd *cobra.Command, args []string) {
38+
if createCmdFlags.app == "" {
39+
fmt.Println("Error: --app flag is required")
40+
return
41+
}
42+
43+
// Construct the request body
44+
requestBody := createReq{
45+
EnvironmentVariables: map[string]string{
46+
"EXAMPLE_ENV": "value1",
47+
},
48+
Secrets: map[string]string{
49+
"SECRET_KEY": "secret_value",
50+
},
51+
}
52+
53+
body, err := json.Marshal(requestBody)
54+
if err != nil {
55+
fmt.Printf("Error marshalling request body: %v\n", err)
56+
return
57+
}
58+
59+
createUrl := fmt.Sprintf("runtime/%s/deployment", createCmdFlags.app)
60+
client, err := api.DefaultRESTClient()
61+
if err != nil {
62+
fmt.Println(err)
63+
return
64+
}
65+
var response string
66+
err = client.Put(createUrl, bytes.NewReader(body), &response)
67+
if err != nil {
68+
fmt.Printf("Error creating app: %v\n", err)
69+
return
70+
}
71+
72+
fmt.Printf("App created: %s\n", response) // TODO pretty print details
73+
},
74+
}
75+
76+
createCmd.Flags().StringVarP(&createCmdFlags.app, "app", "a", "", "The app to create")
77+
rootCmd.AddCommand(createCmd)
78+
}

cmd/delete.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/MakeNowJust/heredoc"
7+
"github.com/cli/go-gh/v2/pkg/api"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type deleteCmdFlags struct {
12+
app string
13+
}
14+
15+
type deleteResp struct {
16+
}
17+
18+
func init() {
19+
deleteCmdFlags := deleteCmdFlags{}
20+
deleteCmd := &cobra.Command{
21+
Use: "delete",
22+
Short: "Delete a GitHub Runtime app",
23+
Long: heredoc.Doc(`
24+
Delete a GitHub Runtime app
25+
`),
26+
Example: heredoc.Doc(`
27+
$ gh runtime delete --app my-app
28+
# => Deletes the app named 'my-app'
29+
`),
30+
Run: func(cmd *cobra.Command, args []string) {
31+
if deleteCmdFlags.app == "" {
32+
fmt.Println("Error: --app flag is required")
33+
return
34+
}
35+
36+
deleteUrl := fmt.Sprintf("runtime/%s/deployment", deleteCmdFlags.app)
37+
client, err := api.DefaultRESTClient()
38+
if err != nil {
39+
fmt.Println(err)
40+
return
41+
}
42+
var response string
43+
err = client.Delete(deleteUrl, &response)
44+
if err != nil {
45+
// print err and response
46+
fmt.Printf("Error deleting app: %v\n", err)
47+
fmt.Printf("Response: %v\n", response)
48+
return
49+
}
50+
51+
fmt.Printf("App deleted: %s\n", response)
52+
},
53+
}
54+
55+
deleteCmd.Flags().StringVarP(&deleteCmdFlags.app, "app", "a", "", "The app to delete")
56+
rootCmd.AddCommand(deleteCmd)
57+
}

cmd/deploy.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package cmd
2+
3+
import (
4+
"archive/zip"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/MakeNowJust/heredoc"
12+
"github.com/cli/go-gh/v2/pkg/api"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type deployCmdFlags struct {
17+
dir string
18+
app string
19+
}
20+
21+
func zipDirectory(sourceDir, destinationZip string) error {
22+
zipFile, err := os.Create(destinationZip)
23+
if err != nil {
24+
return fmt.Errorf("error creating zip file '%s': %w", destinationZip, err)
25+
}
26+
defer zipFile.Close()
27+
28+
zipWriter := zip.NewWriter(zipFile)
29+
defer zipWriter.Close()
30+
31+
err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
32+
if err != nil {
33+
return fmt.Errorf("error accessing path '%s': %w", path, err)
34+
}
35+
36+
relPath, err := filepath.Rel(sourceDir, path)
37+
if err != nil {
38+
return fmt.Errorf("error calculating relative path for '%s': %w", path, err)
39+
}
40+
41+
if info.IsDir() {
42+
if relPath == "." {
43+
return nil
44+
}
45+
relPath += "/"
46+
}
47+
48+
header, err := zip.FileInfoHeader(info)
49+
if err != nil {
50+
return fmt.Errorf("error creating zip header for '%s': %w", path, err)
51+
}
52+
header.Name = relPath
53+
if !info.IsDir() {
54+
header.Method = zip.Deflate
55+
}
56+
57+
writer, err := zipWriter.CreateHeader(header)
58+
if err != nil {
59+
return fmt.Errorf("error creating zip writer for '%s': %w", path, err)
60+
}
61+
62+
if !info.IsDir() {
63+
file, err := os.Open(path)
64+
if err != nil {
65+
return fmt.Errorf("error opening file '%s': %w", path, err)
66+
}
67+
defer file.Close()
68+
69+
_, err = io.Copy(writer, file)
70+
if err != nil {
71+
return fmt.Errorf("error writing file '%s' to zip: %w", path, err)
72+
}
73+
}
74+
75+
return nil
76+
})
77+
78+
if err != nil {
79+
return fmt.Errorf("error zipping directory '%s': %w", sourceDir, err)
80+
}
81+
82+
return nil
83+
}
84+
85+
func init() {
86+
deployCmdFlags := deployCmdFlags{}
87+
deployCmd := &cobra.Command{
88+
Use: "deploy",
89+
Short: "Deploy app to GitHub Runtime",
90+
Long: heredoc.Doc(`
91+
Deploys a directory to a GitHub Runtime app
92+
`),
93+
Example: heredoc.Doc(`
94+
$ gh runtime deploy --dir ./dist --app my-app
95+
# => Deploys the contents of the 'dist' directory to the app named 'my-app'
96+
`),
97+
Run: func(cmd *cobra.Command, args []string) {
98+
if deployCmdFlags.dir == "" {
99+
fmt.Println("Error: --dir flag is required")
100+
return
101+
}
102+
if deployCmdFlags.app == "" {
103+
fmt.Println("Error: --app flag is required")
104+
return
105+
}
106+
107+
if _, err := os.Stat(deployCmdFlags.dir); os.IsNotExist(err) {
108+
fmt.Printf("Error: directory '%s' does not exist\n", deployCmdFlags.dir)
109+
return
110+
}
111+
112+
_, err := os.ReadDir(deployCmdFlags.dir)
113+
if err != nil {
114+
fmt.Printf("Error reading directory '%s': %v\n", deployCmdFlags.dir, err)
115+
return
116+
}
117+
118+
// Zip the directory
119+
zipPath := fmt.Sprintf("%s.zip", deployCmdFlags.dir)
120+
err = zipDirectory(deployCmdFlags.dir, zipPath)
121+
if err != nil {
122+
fmt.Printf("Error zipping directory '%s': %v\n", deployCmdFlags.dir, err)
123+
return
124+
}
125+
defer os.Remove(zipPath)
126+
127+
client, err := api.DefaultRESTClient()
128+
if err != nil {
129+
fmt.Println(err)
130+
return
131+
}
132+
133+
deploymentsUrl := fmt.Sprintf("runtime/%s/deployment/bundle", deployCmdFlags.app)
134+
fmt.Printf("Deploying app to %s\n", deploymentsUrl)
135+
136+
// body is the full zip RAW
137+
body, err := os.ReadFile(zipPath)
138+
if err != nil {
139+
fmt.Printf("Error reading zip file '%s': %v\n", zipPath, err)
140+
return
141+
}
142+
143+
err = client.Post(deploymentsUrl, bytes.NewReader(body), nil)
144+
if err != nil {
145+
fmt.Printf("Error deploying app: %v\n", err)
146+
return
147+
}
148+
149+
fmt.Printf("Successfully deployed app\n")
150+
},
151+
}
152+
deployCmd.Flags().StringVarP(&deployCmdFlags.dir, "dir", "d", "", "The directory to deploy")
153+
deployCmd.Flags().StringVarP(&deployCmdFlags.app, "app", "a", "", "The app to deploy")
154+
rootCmd.AddCommand(deployCmd)
155+
}

cmd/get.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/MakeNowJust/heredoc"
7+
"github.com/cli/go-gh/v2/pkg/api"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type getCmdFlags struct {
12+
app string
13+
}
14+
15+
func init() {
16+
getCmdFlags := getCmdFlags{}
17+
getCmd := &cobra.Command{
18+
Use: "get",
19+
Short: "Get details of a GitHub Runtime app",
20+
Long: heredoc.Doc(`
21+
Get details of a GitHub Runtime app
22+
`),
23+
Example: heredoc.Doc(`
24+
$ gh runtime get --app my-app
25+
# => Retrieves details of the app named 'my-app'
26+
`),
27+
Run: func(cmd *cobra.Command, args []string) {
28+
if getCmdFlags.app == "" {
29+
fmt.Println("Error: --app flag is required")
30+
return
31+
}
32+
33+
getUrl := fmt.Sprintf("runtime/%s/deployment", getCmdFlags.app)
34+
client, err := api.DefaultRESTClient()
35+
if err != nil {
36+
fmt.Println(err)
37+
return
38+
}
39+
40+
var response string
41+
err = client.Get(getUrl, &response)
42+
if err != nil {
43+
fmt.Printf("Error retrieving app details: %v\n", err)
44+
return
45+
}
46+
47+
fmt.Printf("App Details: %s\n", response)
48+
},
49+
}
50+
51+
getCmd.Flags().StringVarP(&getCmdFlags.app, "app", "a", "", "The app to retrieve details for")
52+
rootCmd.AddCommand(getCmd)
53+
}

0 commit comments

Comments
 (0)