Skip to content

Commit

Permalink
Add support for uploading local snaps
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim McNamara committed Feb 28, 2019
1 parent 184d771 commit c3c542c
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 25 deletions.
38 changes: 38 additions & 0 deletions cloudconfig/instancecfg/instancecfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"path"
"reflect"
"strconv"
Expand Down Expand Up @@ -214,6 +215,14 @@ type BootstrapConfig struct {
// subsequently will acquire their serving info from another
// server.
StateServingInfo params.StateServingInfo

// JujuDbSnapPath is the path to a .snap file that will be used as the juju-db
// service.
JujuDbSnapPath string

// JujuDbSnapAssertions is a path to a .assert file that will be used
// to verify the .snap at JujuDbSnapPath
JujuDbSnapAssertionsPath string
}

// SSHHostKeys contains the SSH host keys to configure for a bootstrap host.
Expand Down Expand Up @@ -451,6 +460,11 @@ func (cfg *InstanceConfig) JujuTools() string {
return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion())
}

// SnapDir returns the directory where snaps should be uploaded to.
func (cfg *InstanceConfig) SnapDir() string {
return path.Join(cfg.DataDir, "snap")
}

// GUITools returns the directory where the Juju GUI release is stored.
func (cfg *InstanceConfig) GUITools() string {
return agenttools.SharedGUIDir(cfg.DataDir)
Expand Down Expand Up @@ -558,6 +572,30 @@ func copyToolsList(in coretools.List) coretools.List {
return out
}

// SetSnapSource annotates the instance configuration
// with the location of a local .snap to upload during
// the instance's provisioning.
func (cfg *InstanceConfig) SetSnapSource(snapPath string, snapAssertionsPath string) error {
if snapPath == "" {
return nil
}

_, err := os.Stat(snapPath)
if err != nil {
return errors.Annotatef(err, "unable set local snap (at %s)", snapPath)
}

_, err = os.Stat(snapAssertionsPath)
if err != nil {
return errors.Annotatef(err, "unable set local snap .assert (at %s)", snapAssertionsPath)
}

cfg.Bootstrap.JujuDbSnapPath = snapPath
cfg.Bootstrap.JujuDbSnapAssertionsPath = snapAssertionsPath

return nil
}

type requiresError string

func (e requiresError) Error() string {
Expand Down
35 changes: 35 additions & 0 deletions cloudconfig/userdatacfg_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ func (w *unixConfigure) ConfigureJuju() error {
if err := w.configureBootstrap(); err != nil {
return errors.Trace(err)
}

if err = w.addLocalSnapUpload(); err != nil {
return errors.Trace(err)
}
}

// Append cloudinit-userdata packages to the end of the juju created ones.
Expand Down Expand Up @@ -445,6 +449,37 @@ func (w *unixConfigure) configureBootstrap() error {
return nil
}

func (w *unixConfigure) addLocalSnapUpload() error {
if w.icfg.Bootstrap == nil {
return nil
}

snapPath := w.icfg.Bootstrap.JujuDbSnapPath
assertionsPath := w.icfg.Bootstrap.JujuDbSnapAssertionsPath

if snapPath == "" {
return nil
}

logger.Infof("preparing to upload juju-db snap from %v", snapPath)
snapData, err := ioutil.ReadFile(snapPath)
if err != nil {
return errors.Trace(err)
}
_, snapName := path.Split(snapPath)
w.conf.AddRunBinaryFile(path.Join(w.icfg.SnapDir(), snapName), snapData, 0644)

logger.Infof("preparing to upload juju-db assertions from %v", assertionsPath)
snapAssertionsData, err := ioutil.ReadFile(assertionsPath)
if err != nil {
return errors.Trace(err)
}
_, snapAssertionsName := path.Split(assertionsPath)
w.conf.AddRunBinaryFile(path.Join(w.icfg.SnapDir(), snapAssertionsName), snapAssertionsData, 0644)

return nil
}

func (w *unixConfigure) addDownloadToolsCmds() error {
tools := w.icfg.ToolsList()[0]
if strings.HasPrefix(tools.URL, fileSchemePrefix) {
Expand Down
64 changes: 49 additions & 15 deletions cmd/juju/commands/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bufio"
"fmt"
"os"
"path"
"sort"
"strings"

Expand Down Expand Up @@ -154,21 +155,23 @@ func newBootstrapCommand() cmd.Command {
type bootstrapCommand struct {
modelcmd.ModelCommandBase

Constraints constraints.Value
ConstraintsStr string
BootstrapConstraints constraints.Value
BootstrapConstraintsStr string
BootstrapSeries string
BootstrapImage string
BuildAgent bool
MetadataSource string
Placement string
KeepBrokenEnvironment bool
AutoUpgrade bool
AgentVersionParam string
AgentVersion *version.Number
config common.ConfigFlag
modelDefaults common.ConfigFlag
Constraints constraints.Value
ConstraintsStr string
BootstrapConstraints constraints.Value
BootstrapConstraintsStr string
BootstrapSeries string
BootstrapImage string
BuildAgent bool
JujuDbSnapPath string
JujuDbSnapAssertionsPath string
MetadataSource string
Placement string
KeepBrokenEnvironment bool
AutoUpgrade bool
AgentVersionParam string
AgentVersion *version.Number
config common.ConfigFlag
modelDefaults common.ConfigFlag

showClouds bool
showRegionsForCloud string
Expand Down Expand Up @@ -200,6 +203,10 @@ func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
f.StringVar(&c.BootstrapImage, "bootstrap-image", "", "Specify the image of the bootstrap machine")
}
f.BoolVar(&c.BuildAgent, "build-agent", false, "Build local version of agent binary before bootstrapping")
if featureflag.Enabled(feature.MongoDbSnap) {
f.StringVar(&c.JujuDbSnapPath, "db-snap", "", "Path to a locally built .snap to use as the internal juju-db service.")
f.StringVar(&c.JujuDbSnapAssertionsPath, "db-snap-asserts", "", "Path to a local .assert file. Requires --juju-db-snap")
}
f.StringVar(&c.MetadataSource, "metadata-source", "", "Local path to use as agent and/or image metadata source")
f.StringVar(&c.Placement, "to", "", "Placement directive indicating an instance to bootstrap")
f.BoolVar(&c.KeepBrokenEnvironment, "keep-broken", false, "Do not destroy the model if bootstrap fails")
Expand All @@ -217,6 +224,31 @@ func (c *bootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
}

func (c *bootstrapCommand) Init(args []string) (err error) {
if c.JujuDbSnapPath != "" {
_, err := os.Stat(c.JujuDbSnapPath)
if err != nil {
return errors.Annotatef(err, "problem with --db-snap")
}
}

// fill in JujuDbSnapAssertionsPath from the same directory as JujuDbSnapPath
if c.JujuDbSnapAssertionsPath == "" && c.JujuDbSnapPath != "" {
assertionsPath := strings.Replace(c.JujuDbSnapPath, path.Ext(c.JujuDbSnapPath), ".assert", -1)
logger.Debugf("--db-snap-asserts unset, assuming %v", assertionsPath)
c.JujuDbSnapAssertionsPath = assertionsPath
}

if c.JujuDbSnapAssertionsPath != "" {
_, err := os.Stat(c.JujuDbSnapAssertionsPath)
if err != nil {
return errors.Annotatef(err, "problem with --db-snap-asserts")
}
}

if c.JujuDbSnapAssertionsPath != "" && c.JujuDbSnapPath == "" {
return errors.New("--db-snap-asserts requires --db-snap")
}

if c.showClouds && c.showRegionsForCloud != "" {
return errors.New("--clouds and --regions can't be used together")
}
Expand Down Expand Up @@ -515,6 +547,8 @@ func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
RegionInheritedConfig: cloud.RegionConfig,
AdminSecret: config.bootstrap.AdminSecret,
CAPrivateKey: config.bootstrap.CAPrivateKey,
JujuDbSnapPath: c.JujuDbSnapPath,
JujuDbSnapAssertionsPath: c.JujuDbSnapAssertionsPath,
DialOpts: environs.BootstrapDialOpts{
Timeout: config.bootstrap.BootstrapTimeout,
RetryDelay: config.bootstrap.BootstrapRetryDelay,
Expand Down
15 changes: 15 additions & 0 deletions environs/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ type BootstrapParams struct {

// DialOpts contains the bootstrap dial options.
DialOpts environs.BootstrapDialOpts

// JujuDbSnapPath is the path to a local .snap file that will be used
// to run the juju-db service.
JujuDbSnapPath string

// JujuDbSnapAssertionsPath is the path to a local .assertfile that
// will be used to test the contents of the .snap at JujuDbSnap.
JujuDbSnapAssertionsPath string
}

// Validate validates the bootstrap parameters.
Expand Down Expand Up @@ -498,6 +506,11 @@ func bootstrapIAAS(
if err := instanceConfig.SetTools(selectedToolsList); err != nil {
return errors.Trace(err)
}

if err := instanceConfig.SetSnapSource(args.JujuDbSnapPath, args.JujuDbSnapAssertionsPath); err != nil {
return errors.Trace(err)
}

var environVersion int
if e, ok := environ.(environs.Environ); ok {
environVersion = e.Provider().Version()
Expand Down Expand Up @@ -608,6 +621,8 @@ func finalizeInstanceBootstrapConfig(
icfg.Bootstrap.GUI = guiArchive(args.GUIDataSourceBaseURL, func(msg string) {
ctx.Infof(msg)
})
icfg.Bootstrap.JujuDbSnapPath = args.JujuDbSnapPath
icfg.Bootstrap.JujuDbSnapAssertionsPath = args.JujuDbSnapAssertionsPath
return nil
}

Expand Down
28 changes: 24 additions & 4 deletions mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -470,8 +472,11 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
var zeroVersion Version
tweakSysctlForMongo(mongoKernelTweaks)

// TODO(tsm): clean up the args.DataDir handling. When using a snap, args.DataDir should be
// set earlier in the bootstrapping process. An extra variable is needed here because
// cloudconfig sends local snaps to /var/lib/juju/
dataDir := args.DataDir
if featureflag.Enabled(feature.MongoDbSnap) {
// TODO(tsm): push this to earlier in the bootstrapping process
if args.DataDir != dataPathForJujuDbSnap {
logger.Warningf("overwriting args.dataDir (set to %v) to %v", args.DataDir, dataPathForJujuDbSnap)
args.DataDir = dataPathForJujuDbSnap
Expand All @@ -485,7 +490,7 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)

setupDataDirectory(args)

if err := installMongod(series.MustHostSeries(), args.SetNUMAControlPolicy); err != nil {
if err := installMongod(series.MustHostSeries(), args.SetNUMAControlPolicy, dataDir); err != nil {
// This isn't treated as fatal because the Juju MongoDB
// package is likely to be already installed anyway. There
// could just be a temporary issue with apt-get/yum/whatever
Expand Down Expand Up @@ -640,12 +645,27 @@ func installPackage(pkg string, pacconfer config.PackagingConfigurer, pacman man
return pacman.Install(pkg)
}

func installMongod(operatingsystem string, numaCtl bool) error {
func installMongod(operatingsystem string, numaCtl bool, dataDir string) error {
if featureflag.Enabled(feature.MongoDbSnap) {
snapName := ServiceName
jujuDbLocalSnapPattern := regexp.MustCompile(`juju-db_[0-9]+\.snap`)

// If we're installing a local snap, then provide an absolute path
// as a snap <name>. snap install <name> will then do the Right Thing (TM).
files, err := ioutil.ReadDir(path.Join(dataDir, "snap"))
if err == nil {
for _, fullFileName := range files {
_, fileName := path.Split(fullFileName.Name())
if jujuDbLocalSnapPattern.MatchString(fileName) {
snapName = fullFileName.Name()
}
}
}

prerequisites := []snap.App{snap.NewApp("core")}
backgroundServices := []snap.BackgroundService{{"daemon", true}}
conf := common.Conf{Desc: ServiceName + " snap"}
service, err := snap.NewService(ServiceName, conf, snap.Command, "edge", "jailmode", backgroundServices, prerequisites)
service, err := snap.NewService(snapName, ServiceName, conf, snap.Command, "edge", "jailmode", backgroundServices, prerequisites)
if err != nil {
return errors.Trace(err)
}
Expand Down
28 changes: 23 additions & 5 deletions service/snap/snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,31 @@ func ListServices() ([]string, error) {

// Service is a type for services that are being managed by snapd as snaps.
type Service struct {
name string
scriptRenderer shell.Renderer
executable string
app App
conf common.Conf
}

// NewService returns a new Service defined by `conf`, with
// the name `name`. If no BackgroundServices are provided, manage all of the snap's background services together.
func NewService(name string, conf common.Conf, snapPath string, Channel string, ConfinementPolicy string, backgroundServices []BackgroundService, prerequisites []App) (Service, error) {
// NewService returns a new Service defined by `conf`, with the name `serviceName`.
// The Service abstracts service(s) provided by a snap.
//
// `serviceName` defaults to `snapName`. These two parameters are distinct to allow
// for a file path to provided as a `mainSnap`, implying that a local snap will be
// installed by snapd.
//
// If no BackgroundServices are provided, Service will wrap all of the snap's
// background services.
func NewService(mainSnap string, serviceName string, conf common.Conf, snapPath string, Channel string, ConfinementPolicy string, backgroundServices []BackgroundService, prerequisites []App) (Service, error) {
if serviceName == "" {
serviceName = mainSnap
}
if mainSnap == "" {
return Service{}, errors.New("mainSnap must be provided")
}
app := App{
Name: name,
Name: mainSnap,
ConfinementPolicy: ConfinementPolicy,
Channel: Channel,
BackgroundServices: backgroundServices,
Expand All @@ -220,6 +234,7 @@ func NewService(name string, conf common.Conf, snapPath string, Channel string,
}

svc := Service{
name: serviceName,
scriptRenderer: &shell.BashRenderer{},
executable: snapPath,
app: app,
Expand Down Expand Up @@ -248,7 +263,7 @@ func NewServiceFromName(name string, conf common.Conf) (Service, error) {
Channel := defaultChannel
ConfinementPolicy := defaultConfinementPolicy

return NewService(name, conf, Command, Channel, ConfinementPolicy, BackgroundServices, Prerequisites)
return NewService(name, name, conf, Command, Channel, ConfinementPolicy, BackgroundServices, Prerequisites)

}

Expand Down Expand Up @@ -283,6 +298,9 @@ func (s Service) Validate() error {
//
// Name is part of the service.Service interface
func (s Service) Name() string {
if s.name != "" {
return s.name
}
return s.app.Name
}

Expand Down
2 changes: 1 addition & 1 deletion service/snap/snap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (*validationSuite) TestValidateJujuDbSnap(c *gc.C) {
c.Check(err, jc.ErrorIsNil)

// via NewService
jujudbService, err := snap.NewService("juju-db", common.Conf{Desc: "juju-db snap"}, snap.Command, "edge", "jailmode", []snap.BackgroundService{}, []snap.App{})
jujudbService, err := snap.NewService("juju-db", "", common.Conf{Desc: "juju-db snap"}, snap.Command, "edge", "jailmode", []snap.BackgroundService{}, []snap.App{})
c.Check(err, jc.ErrorIsNil)
c.Check(jujudbService.Validate(), jc.ErrorIsNil)

Expand Down

0 comments on commit c3c542c

Please sign in to comment.