github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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  }