github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap/cmd_download.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 "github.com/jessevdk/go-flags" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/asserts/sysdb" 32 "github.com/snapcore/snapd/i18n" 33 "github.com/snapcore/snapd/image" 34 "github.com/snapcore/snapd/snap" 35 ) 36 37 type cmdDownload struct { 38 channelMixin 39 Revision string `long:"revision"` 40 Basename string `long:"basename"` 41 TargetDir string `long:"target-directory"` 42 43 CohortKey string `long:"cohort"` 44 Positional struct { 45 Snap remoteSnapName 46 } `positional-args:"true" required:"true"` 47 } 48 49 var shortDownloadHelp = i18n.G("Download the given snap") 50 var longDownloadHelp = i18n.G(` 51 The download command downloads the given snap and its supporting assertions 52 to the current directory with .snap and .assert file extensions, respectively. 53 `) 54 55 func init() { 56 addCommand("download", shortDownloadHelp, longDownloadHelp, func() flags.Commander { 57 return &cmdDownload{} 58 }, channelDescs.also(map[string]string{ 59 // TRANSLATORS: This should not start with a lowercase letter. 60 "revision": i18n.G("Download the given revision of a snap, to which you must have developer access"), 61 // TRANSLATORS: This should not start with a lowercase letter. 62 "cohort": i18n.G("Download from the given cohort"), 63 // TRANSLATORS: This should not start with a lowercase letter. 64 "basename": i18n.G("Use this basename for the snap and assertion files (defaults to <snap>_<revision>)"), 65 // TRANSLATORS: This should not start with a lowercase letter. 66 "target-directory": i18n.G("Download to this directory (defaults to the current directory)"), 67 }), []argDesc{{ 68 name: "<snap>", 69 // TRANSLATORS: This should not start with a lowercase letter. 70 desc: i18n.G("Snap name"), 71 }}) 72 } 73 74 func fetchSnapAssertionsDirect(tsto *image.ToolingStore, snapPath string, snapInfo *snap.Info) (string, error) { 75 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 76 Backstore: asserts.NewMemoryBackstore(), 77 Trusted: sysdb.Trusted(), 78 }) 79 if err != nil { 80 return "", err 81 } 82 83 assertPath := strings.TrimSuffix(snapPath, filepath.Ext(snapPath)) + ".assert" 84 w, err := os.Create(assertPath) 85 if err != nil { 86 return "", fmt.Errorf(i18n.G("cannot create assertions file: %v"), err) 87 } 88 defer w.Close() 89 90 encoder := asserts.NewEncoder(w) 91 save := func(a asserts.Assertion) error { 92 return encoder.Encode(a) 93 } 94 f := tsto.AssertionFetcher(db, save) 95 96 _, err = image.FetchAndCheckSnapAssertions(snapPath, snapInfo, f, db) 97 return assertPath, err 98 } 99 100 func printInstallHint(assertPath, snapPath string) { 101 // simplify paths 102 wd, _ := os.Getwd() 103 if p, err := filepath.Rel(wd, assertPath); err == nil { 104 assertPath = p 105 } 106 if p, err := filepath.Rel(wd, snapPath); err == nil { 107 snapPath = p 108 } 109 // add a hint what to do with the downloaded snap (LP:1676707) 110 fmt.Fprintf(Stdout, i18n.G(`Install the snap with: 111 snap ack %s 112 snap install %s 113 `), assertPath, snapPath) 114 } 115 116 // for testing 117 var downloadDirect = downloadDirectImpl 118 119 func downloadDirectImpl(snapName string, revision snap.Revision, dlOpts image.DownloadOptions) error { 120 tsto, err := image.NewToolingStore() 121 if err != nil { 122 return err 123 } 124 125 fmt.Fprintf(Stdout, i18n.G("Fetching snap %q\n"), snapName) 126 snapPath, snapInfo, _, err := tsto.DownloadSnap(snapName, dlOpts) 127 if err != nil { 128 return err 129 } 130 131 fmt.Fprintf(Stdout, i18n.G("Fetching assertions for %q\n"), snapName) 132 assertPath, err := fetchSnapAssertionsDirect(tsto, snapPath, snapInfo) 133 if err != nil { 134 return err 135 } 136 printInstallHint(assertPath, snapPath) 137 return nil 138 } 139 140 func (x *cmdDownload) downloadFromStore(snapName string, revision snap.Revision) error { 141 dlOpts := image.DownloadOptions{ 142 TargetDir: x.TargetDir, 143 Basename: x.Basename, 144 Channel: x.Channel, 145 CohortKey: x.CohortKey, 146 Revision: revision, 147 // if something goes wrong, don't force it to start over again 148 LeavePartialOnError: true, 149 } 150 return downloadDirect(snapName, revision, dlOpts) 151 } 152 153 func (x *cmdDownload) Execute(args []string) error { 154 if strings.ContainsRune(x.Basename, filepath.Separator) { 155 return fmt.Errorf(i18n.G("cannot specify a path in basename (use --target-dir for that)")) 156 } 157 if err := x.setChannelFromCommandline(); err != nil { 158 return err 159 } 160 161 if len(args) > 0 { 162 return ErrExtraArgs 163 } 164 165 var revision snap.Revision 166 if x.Revision == "" { 167 revision = snap.R(0) 168 } else { 169 if x.Channel != "" { 170 return fmt.Errorf(i18n.G("cannot specify both channel and revision")) 171 } 172 if x.CohortKey != "" { 173 return fmt.Errorf(i18n.G("cannot specify both cohort and revision")) 174 } 175 var err error 176 revision, err = snap.ParseRevision(x.Revision) 177 if err != nil { 178 return err 179 } 180 } 181 182 snapName := string(x.Positional.Snap) 183 return x.downloadFromStore(snapName, revision) 184 }