Skip to content
This repository was archived by the owner on Jan 16, 2021. It is now read-only.

Commit 7887012

Browse files
committed
enable downloading code deployed to parse
* fetches cloud code for existing app during new app creation * add a download command that can download the current deployed cloud code
1 parent e77b4ea commit 7887012

File tree

7 files changed

+457
-52
lines changed

7 files changed

+457
-52
lines changed

commands.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ http://parse.com`,
2929
c.AddCommand(newDefaultCmd(e))
3030
c.AddCommand(newDeployCmd(e))
3131
c.AddCommand(newDevelopCmd(e))
32+
c.AddCommand(newDownloadCmd(e))
3233
c.AddCommand(newFunctionHooksCmd(e))
3334
c.AddCommand(newGenerateCmd(e))
3435
c.AddCommand(newJsSdkCmd(e))

deploy_cmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ type deployInfo struct {
309309
ParseVersion string `json:"parseVersion,omitempty"`
310310
Checksums deployFileData `json:"checksums,omitempty"`
311311
Versions deployFileData `json:"userFiles,omitempty"`
312-
Warning string `json:"warning,omitempty"`
312+
Timestamp string `json:"timestamp,omitempty"` // only populated by get from releases
313+
Warning string `json:"warning,omitempty"` // only populated by post to deploy
313314
}
314315

315316
func (d *deployCmd) makeNewRelease(info *deployInfo, e *env) (deployInfo, error) {

download_cmd.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package main
2+
3+
import (
4+
"crypto/md5"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"net/url"
9+
"os"
10+
"path"
11+
"path/filepath"
12+
13+
"github.com/facebookgo/errgroup"
14+
"github.com/facebookgo/stackerr"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
type downloadCmd struct {
19+
force bool
20+
release *deployInfo
21+
}
22+
23+
func (d *downloadCmd) verifyChecksum(path, checksum string) error {
24+
file, err := os.Open(path)
25+
if err != nil {
26+
return stackerr.Wrap(err)
27+
}
28+
defer file.Close()
29+
30+
h := md5.New()
31+
_, err = io.Copy(h, file)
32+
if err != nil {
33+
return stackerr.Wrap(err)
34+
}
35+
36+
sum := fmt.Sprintf("%x", h.Sum(nil))
37+
if sum != checksum {
38+
return stackerr.Newf("Invalid checksum for %s", path)
39+
}
40+
41+
return nil
42+
}
43+
44+
func (d *downloadCmd) moveFiles(
45+
e *env,
46+
tempDir string,
47+
release *deployInfo) error {
48+
var wg errgroup.Group
49+
50+
maxParallel := make(chan struct{}, maxOpenFD)
51+
wg.Add(len(release.Versions.Cloud) + len(release.Versions.Public))
52+
53+
moveFile := func(tempDir, kind, file, checksum string) {
54+
defer func() {
55+
wg.Done()
56+
<-maxParallel
57+
}()
58+
59+
err := os.MkdirAll(
60+
filepath.Join(e.Root, kind, filepath.Dir(file)),
61+
0755,
62+
)
63+
if err != nil {
64+
wg.Error(stackerr.Wrap(err))
65+
return
66+
}
67+
68+
err = os.Rename(
69+
filepath.Join(tempDir, kind, file),
70+
filepath.Join(e.Root, kind, file),
71+
)
72+
if err != nil {
73+
wg.Error(stackerr.Wrap(err))
74+
return
75+
}
76+
77+
err = d.verifyChecksum(
78+
filepath.Join(e.Root, kind, file),
79+
checksum,
80+
)
81+
if err != nil {
82+
wg.Error(err)
83+
return
84+
}
85+
}
86+
87+
for file, checksum := range release.Checksums.Cloud {
88+
maxParallel <- struct{}{}
89+
go moveFile(
90+
tempDir,
91+
cloudDir,
92+
file,
93+
checksum,
94+
)
95+
}
96+
for file, checksum := range release.Checksums.Public {
97+
maxParallel <- struct{}{}
98+
go moveFile(
99+
tempDir,
100+
hostingDir,
101+
file,
102+
checksum,
103+
)
104+
}
105+
return wg.Wait()
106+
}
107+
108+
func (d *downloadCmd) download(e *env, tempDir string, release *deployInfo) error {
109+
var wg errgroup.Group
110+
maxParallel := make(chan struct{}, maxOpenFD)
111+
wg.Add(len(release.Versions.Cloud) + len(release.Versions.Public))
112+
113+
downloadHosted := func(file, version, checksum string) {
114+
defer func() {
115+
wg.Done()
116+
<-maxParallel
117+
}()
118+
119+
v := make(url.Values)
120+
v.Set("version", version)
121+
v.Set("checksum", checksum)
122+
u := &url.URL{
123+
Path: path.Join("hosted_files", file),
124+
RawQuery: v.Encode(),
125+
}
126+
var content []byte
127+
_, err := e.ParseAPIClient.Get(u, &content)
128+
if err != nil {
129+
wg.Error(err)
130+
return
131+
}
132+
133+
path := path.Join(tempDir, hostingDir, file)
134+
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
135+
wg.Error(stackerr.Wrap(err))
136+
return
137+
}
138+
if err := ioutil.WriteFile(path, content, 0644); err != nil {
139+
wg.Error(stackerr.Wrap(err))
140+
return
141+
}
142+
if err := d.verifyChecksum(path, checksum); err != nil {
143+
wg.Error(err)
144+
return
145+
}
146+
}
147+
148+
downloadScript := func(file, version, checksum string) {
149+
defer func() {
150+
wg.Done()
151+
<-maxParallel
152+
}()
153+
154+
v := make(url.Values)
155+
v.Set("version", version)
156+
v.Set("checksum", checksum)
157+
u := &url.URL{
158+
Path: path.Join("scripts", file),
159+
RawQuery: v.Encode(),
160+
}
161+
var content string
162+
_, err := e.ParseAPIClient.Get(u, &content)
163+
if err != nil {
164+
wg.Error(err)
165+
return
166+
}
167+
168+
path := path.Join(tempDir, cloudDir, file)
169+
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
170+
wg.Error(stackerr.Wrap(err))
171+
return
172+
}
173+
if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil {
174+
wg.Error(stackerr.Wrap(err))
175+
return
176+
}
177+
}
178+
179+
for file, version := range release.Versions.Public {
180+
checksum, ok := release.Checksums.Public[file]
181+
if !ok {
182+
continue
183+
}
184+
maxParallel <- struct{}{}
185+
go downloadHosted(file, version, checksum)
186+
}
187+
188+
for file, version := range release.Versions.Cloud {
189+
checksum, ok := release.Checksums.Cloud[file]
190+
if !ok {
191+
continue
192+
}
193+
maxParallel <- struct{}{}
194+
go downloadScript(file, version, checksum)
195+
}
196+
197+
return wg.Wait()
198+
}
199+
200+
func (d *downloadCmd) run(e *env, c *context) error {
201+
if d.release == nil {
202+
latestRelease, err := (&deployCmd{}).getPrevDeplInfo(e)
203+
if err != nil {
204+
return err
205+
}
206+
d.release = latestRelease
207+
}
208+
209+
tempDir, err := ioutil.TempDir("", "parse_code_")
210+
if err != nil {
211+
return stackerr.Wrap(err)
212+
}
213+
214+
err = d.download(e, tempDir, d.release)
215+
if err != nil {
216+
fmt.Fprintln(e.Err, "Failed to download Cloud Code.")
217+
return stackerr.Wrap(err)
218+
}
219+
if !d.force {
220+
fmt.Fprintf(e.Out, "Successfully downloaded Cloud Code to %q.\n", tempDir)
221+
return nil
222+
}
223+
224+
err = d.moveFiles(e, tempDir, d.release)
225+
if err != nil {
226+
fmt.Fprintf(
227+
e.Out,
228+
`Failed to download Cloud Code to %q.
229+
Sorry! but %s might have corrupted contents.
230+
If you want to download Cloud Code from Parse,
231+
try again without the "-f" option.
232+
`,
233+
e.Root,
234+
e.Root,
235+
)
236+
return stackerr.Wrap(err)
237+
}
238+
return nil
239+
}
240+
241+
func newDownloadCmd(e *env) *cobra.Command {
242+
d := &downloadCmd{}
243+
cmd := &cobra.Command{
244+
Use: "download [app]",
245+
Short: "Downloads the Cloud Code project",
246+
Long: "Downloads the Cloud Code project at a temporary location.",
247+
Run: runWithClient(e, d.run),
248+
}
249+
cmd.Flags().BoolVarP(&d.force, "force", "f", d.force,
250+
"Force will overwrite any content in the current project directory")
251+
return cmd
252+
}

0 commit comments

Comments
 (0)