Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add boot-firmware command #176

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions cmd/pebble/cmd_boot_firmware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2022 Canonical Ltd
anpep marked this conversation as resolved.
Show resolved Hide resolved
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

import (
"fmt"
"os"
"syscall"
"time"

"github.com/jessevdk/go-flags"

"github.com/canonical/pebble/client"
. "github.com/canonical/pebble/cmd"
"github.com/canonical/pebble/internal/boot"
"github.com/canonical/pebble/internal/daemon"
"github.com/canonical/pebble/internal/logger"
"github.com/canonical/pebble/internal/plan"
)

type cmdBootFirmware struct {
clientMixin

Force bool `long:"force"`
Verbose bool `short:"v" long:"verbose"`
}

var bootFirmwareDescs = map[string]string{
"force": `Skip all checks`,
anpep marked this conversation as resolved.
Show resolved Hide resolved
"verbose": `Log all output from services to stdout`,
}

var shortBootFirmwareHelp = `Bootstrap a system with Pebble running as PID 1`

var longBootFirmwareHelp = `
The boot-firmware command performs checks on the running system, prepares the
environment to get a working system and starts the Pebble daemon.
anpep marked this conversation as resolved.
Show resolved Hide resolved
`

func (cmd *cmdBootFirmware) Execute(args []string) error {
if len(args) > 1 {
return ErrExtraArgs
}

if !cmd.Force {
if err := boot.CheckBootstrap(); err != nil {
return err
}
}

if err := boot.Bootstrap(); err != nil {
return err
}

t0 := time.Now().Truncate(time.Millisecond)
anpep marked this conversation as resolved.
Show resolved Hide resolved

pebbleDir, socketPath := getEnvPaths()
if err := os.MkdirAll(pebbleDir, 0755); err != nil {
anpep marked this conversation as resolved.
Show resolved Hide resolved
return err
}
if _, err := plan.ReadDir(pebbleDir); err != nil {
anpep marked this conversation as resolved.
Show resolved Hide resolved
return err
}

dopts := daemon.Options{
Dir: pebbleDir,
SocketPath: socketPath,
}
if cmd.Verbose {
dopts.ServiceOutput = os.Stdout
}

d, err := daemon.New(&dopts)
if err != nil {
return err
}
if err := d.Init(); err != nil {
return err
}

d.Version = Version
d.Start()

logger.Debugf("activation done in %v", time.Now().Truncate(time.Millisecond).Sub(t0))

servopts := client.ServiceOptions{}
changeID, err := cmd.client.AutoStart(&servopts)
if err != nil {
logger.Noticef("Cannot start default services: %v", err)
} else {
logger.Noticef("Started default services with change %s.", changeID)
}
anpep marked this conversation as resolved.
Show resolved Hide resolved

out:
for {
select {
anpep marked this conversation as resolved.
Show resolved Hide resolved
case <-d.Dying():
logger.Noticef("Server exiting!")
break out
}
}

cmd.client.CloseIdleConnections()
if err := d.Stop(nil); err != nil {
return err
}

if err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART); err != nil {
return fmt.Errorf("cannot reboot: %w", err)
}
return nil
}

func init() {
addCommand("boot-firmware", shortBootFirmwareHelp, longBootFirmwareHelp, func() flags.Commander { return &cmdBootFirmware{} }, bootFirmwareDescs, nil)
}
48 changes: 48 additions & 0 deletions cmd/pebble/cmd_boot_firmware_common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//go:build !termus
// +build !termus
anpep marked this conversation as resolved.
Show resolved Hide resolved

// Copyright (c) 2023 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main_test

import (
. "gopkg.in/check.v1"

pebble "github.com/canonical/pebble/cmd/pebble"
)

func (s *PebbleSuite) TestBootFirmwareExtraArgs(c *C) {
rest, err := pebble.Parser(pebble.Client()).ParseArgs([]string{"boot-firmware", "extra", "args"})
c.Assert(err, Equals, pebble.ErrExtraArgs)
c.Assert(rest, HasLen, 1)
c.Check(s.Stdout(), Equals, "")
c.Check(s.Stderr(), Equals, "")
}

func (s *PebbleSuite) TestBootFirmware(c *C) {
rest, err := pebble.Parser(pebble.Client()).ParseArgs([]string{"boot-firmware"})
c.Assert(err, ErrorMatches, "cannot bootstrap an unsupported platform")
c.Assert(rest, HasLen, 1)
c.Check(s.Stdout(), Equals, "")
c.Check(s.Stderr(), Equals, "")
}

func (s *PebbleSuite) TestBootFirmwareForce(c *C) {
rest, err := pebble.Parser(pebble.Client()).ParseArgs([]string{"boot-firmware", "--force"})
c.Assert(err, ErrorMatches, "cannot bootstrap an unsupported platform")
c.Assert(rest, HasLen, 1)
c.Check(s.Stdout(), Equals, "")
c.Check(s.Stderr(), Equals, "")
}
40 changes: 40 additions & 0 deletions cmd/pebble/cmd_boot_firmware_termus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//go:build termus
// +build termus

// Copyright (c) 2023 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main_test

import (
. "gopkg.in/check.v1"

pebble "github.com/canonical/pebble/cmd/pebble"
)

func (s *PebbleSuite) TestBootFirmwareExtraArgs(c *C) {
rest, err := pebble.Parser(pebble.Client()).ParseArgs([]string{"boot-firmware", "extra", "args"})
c.Assert(err, Equals, pebble.ErrExtraArgs)
c.Assert(rest, HasLen, 1)
c.Check(s.Stdout(), Equals, "")
c.Check(s.Stderr(), Equals, "")
}

func (s *PebbleSuite) TestBootFirmware(c *C) {
rest, err := pebble.Parser(pebble.Client()).ParseArgs([]string{"boot-firmware"})
c.Assert(err, ErrorMatches, "must run as PID 1. Use --force to suppress this check")
c.Assert(rest, HasLen, 1)
c.Check(s.Stdout(), Equals, "")
c.Check(s.Stderr(), Equals, "")
}
23 changes: 23 additions & 0 deletions cmd/pebble/defaults_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build !termus
// +build !termus

// Copyright (c) 2022 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

// defaultPebbleDir is the Pebble directory used if $PEBBLE is not set. It is
// created by the daemon ("pebble run") if it doesn't exist, and also used by
// the pebble client.
const defaultPebbleDir = "/var/lib/pebble/default"
23 changes: 23 additions & 0 deletions cmd/pebble/defaults_termus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build termus
// +build termus

// Copyright (c) 2022 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

// defaultPebbleDir is the Pebble directory used if $PEBBLE is not set. It is
// created by the daemon ("pebble run") if it doesn't exist, and also used by
// the pebble client.
const defaultPebbleDir = "/termus/var/lib/pebble"
15 changes: 9 additions & 6 deletions cmd/pebble/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"golang.org/x/crypto/ssh/terminal"

"github.com/canonical/pebble/client"
"github.com/canonical/pebble/internal/boot"
"github.com/canonical/pebble/internal/logger"
)

Expand All @@ -44,11 +45,6 @@ var (
noticef = logger.Noticef
)

// defaultPebbleDir is the Pebble directory used if $PEBBLE is not set. It is
// created by the daemon ("pebble run") if it doesn't exist, and also used by
// the pebble client.
const defaultPebbleDir = "/var/lib/pebble/default"

type options struct {
Version func() `long:"version"`
}
Expand Down Expand Up @@ -348,8 +344,15 @@ func run() error {
return fmt.Errorf("cannot create client: %v", err)
}

var xtra []string
anpep marked this conversation as resolved.
Show resolved Hide resolved
parser := Parser(cli)
xtra, err := parser.Parse()
if err2 := boot.CheckBootstrap(); err2 == nil {
anpep marked this conversation as resolved.
Show resolved Hide resolved
args := append([]string{"boot-firmware"}, os.Args[1:]...)
xtra, err = parser.ParseArgs(args)
} else {
xtra, err = parser.Parse()
}

if err != nil {
if e, ok := err.(*flags.Error); ok {
switch e.Type {
Expand Down
45 changes: 45 additions & 0 deletions cmd/pebble/main_common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build !termus
// +build !termus

// Copyright (c) 2023 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main_test

import (
"os"

. "gopkg.in/check.v1"

pebble "github.com/canonical/pebble/cmd/pebble"
)

func (s *PebbleSuite) TestGetEnvPaths(c *C) {
os.Setenv("PEBBLE", "")
os.Setenv("PEBBLE_SOCKET", "")
pebbleDir, socketPath := pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/var/lib/pebble/default")
c.Assert(socketPath, Equals, "/var/lib/pebble/default/.pebble.socket")

os.Setenv("PEBBLE", "/foo")
pebbleDir, socketPath = pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/foo")
c.Assert(socketPath, Equals, "/foo/.pebble.socket")

os.Setenv("PEBBLE", "/bar")
os.Setenv("PEBBLE_SOCKET", "/path/to/socket")
pebbleDir, socketPath = pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/bar")
c.Assert(socketPath, Equals, "/path/to/socket")
}
45 changes: 45 additions & 0 deletions cmd/pebble/main_termus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build termus
// +build termus

// Copyright (c) 2023 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main_test

import (
"os"

. "gopkg.in/check.v1"

pebble "github.com/canonical/pebble/cmd/pebble"
)

func (s *PebbleSuite) TestGetEnvPaths(c *C) {
os.Setenv("PEBBLE", "")
os.Setenv("PEBBLE_SOCKET", "")
pebbleDir, socketPath := pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/termus/var/lib/pebble")
c.Assert(socketPath, Equals, "/termus/var/lib/pebble/.pebble.socket")

os.Setenv("PEBBLE", "/foo")
pebbleDir, socketPath = pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/foo")
c.Assert(socketPath, Equals, "/foo/.pebble.socket")

os.Setenv("PEBBLE", "/bar")
os.Setenv("PEBBLE_SOCKET", "/path/to/socket")
pebbleDir, socketPath = pebble.GetEnvPaths()
c.Assert(pebbleDir, Equals, "/bar")
c.Assert(socketPath, Equals, "/path/to/socket")
}
Loading