fix: verify installation definition

This fixes the possibility of panicing on a nil pointer by running the
verification steps earlier.

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
This commit is contained in:
Andrew Rynhard 2019-08-15 15:21:55 +00:00
parent 1c7e86ce5c
commit 6940aaf233
10 changed files with 116 additions and 143 deletions

View File

@ -63,7 +63,10 @@ var installCmd = &cobra.Command{
log.Fatal(err)
}
i := installer.NewInstaller(cmdline, data)
i, err := installer.NewInstaller(cmdline, data)
if err != nil {
log.Fatal(err)
}
if err = i.Install(); err != nil {
log.Fatal(err)
}

View File

@ -98,7 +98,11 @@ func (b *BareMetal) Initialize(data *userdata.UserData) (err error) {
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
// No previous installation was found, attempt an install
i := installer.NewInstaller(cmdline, data)
var i *installer.Installer
i, err = installer.NewInstaller(cmdline, data)
if err != nil {
return err
}
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
}

View File

@ -96,7 +96,10 @@ func (i *ISO) Initialize(data *userdata.UserData) (err error) {
cmdline.Append(constants.KernelParamPlatform, "bare-metal")
cmdline.Append(constants.KernelParamUserData, endpoint)
inst := installer.NewInstaller(cmdline, data)
inst, err := installer.NewInstaller(cmdline, data)
if err != nil {
return err
}
if err = inst.Install(); err != nil {
return errors.Wrap(err, "failed to install")
}

View File

@ -59,7 +59,11 @@ func (p *Packet) Initialize(data *userdata.UserData) (err error) {
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
// No previous installation was found, attempt an install
i := installer.NewInstaller(cmdline, data)
var i *installer.Installer
i, err = installer.NewInstaller(cmdline, data)
if err != nil {
return err
}
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
}

View File

@ -20,28 +20,9 @@ import (
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
"github.com/talos-systems/talos/pkg/version"
"golang.org/x/sys/unix"
)
const (
// DefaultSizeBootDevice is the default size of the boot partition.
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
DefaultSizeBootDevice = 512 * 1000 * 1000
)
var (
// DefaultURLBase is the base URL for all default artifacts.
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
// DefaultKernelURL is the URL to the kernel.
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
// DefaultInitramfsURL is the URL to the initramfs.
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
)
// Installer represents the installer logic. It serves as the entrypoint to all
// installation methods.
type Installer struct {
@ -51,15 +32,18 @@ type Installer struct {
}
// NewInstaller initializes and returns an Installer.
func NewInstaller(cmdline *kernel.Cmdline, data *userdata.UserData) *Installer {
i := &Installer{
func NewInstaller(cmdline *kernel.Cmdline, data *userdata.UserData) (i *Installer, err error) {
i = &Installer{
cmdline: cmdline,
data: data,
}
i.manifest = manifest.NewManifest(data)
i.manifest, err = manifest.NewManifest(data)
if err != nil {
return nil, errors.Wrap(err, "failed to create installation manifest")
}
return i
return i, nil
}
// Install fetches the necessary data locations and copies or extracts
@ -70,16 +54,6 @@ func (i *Installer) Install() (err error) {
return nil
}
// Verify that the target device(s) can satisify the requested options.
if err = VerifyBootDevice(i.data); err != nil {
return errors.Wrap(err, "failed to prepare boot device")
}
if err = VerifyDataDevice(i.data); err != nil {
return errors.Wrap(err, "failed to prepare data device")
}
if i.data.Install.Wipe {
if err = wipe(i.manifest); err != nil {
return errors.Wrap(err, "failed to wipe device(s)")

View File

@ -2,106 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package installer
package installer_test
import (
"testing"
import "testing"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/pkg/userdata"
"gopkg.in/yaml.v2"
)
type validateSuite struct {
suite.Suite
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
}
func TestValidateSuite(t *testing.T) {
suite.Run(t, new(validateSuite))
}
func (suite *validateSuite) TestVerifyDevice() {
// Start off with success and then remove bits
data := &userdata.UserData{}
err := yaml.Unmarshal([]byte(testConfig), data)
suite.Require().NoError(err)
suite.Require().NoError(VerifyBootDevice(data))
suite.Require().NoError(VerifyDataDevice(data))
// No impact because we can infer all data from the data device and
// defaults.
data.Install.Boot = nil
suite.Require().NoError(VerifyBootDevice(data))
data.Install.Ephemeral = &userdata.InstallDevice{
Device: "/dev/sda",
}
suite.Require().NoError(VerifyDataDevice(data))
}
// TODO we should move this to a well defined location
// Copied from userdata_test.go
const testConfig = `version: "1"
security:
os:
ca:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
identity:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
kubernetes:
ca:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
sa:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
frontproxy:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
etcd:
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
networking:
os: {}
kubernetes: {}
services:
init:
cni: flannel
kubeadm:
certificateKey: 'test'
configuration: |
apiVersion: kubeadm.k8s.io/v1beta1
kind: InitConfiguration
localAPIEndpoint:
bindPort: 6443
bootstrapTokens:
- token: '1qbsj9.3oz5hsk6grdfp98b'
ttl: 0s
---
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
clusterName: test
kubernetesVersion: v1.16.0-alpha.3
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
scheduler: lc
trustd:
username: 'test'
password: 'test'
endpoints: [ "1.2.3.4" ]
certSANs: []
install:
wipe: true
force: true
boot:
device: /dev/sda
size: 1024000000
ephemeral:
device: /dev/sda
size: 1024000000
`

View File

@ -25,6 +25,25 @@ import (
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
"github.com/talos-systems/talos/pkg/version"
)
const (
// DefaultSizeBootDevice is the default size of the boot partition.
// TODO(andrewrynhard): We should inspect the sizes of the artifacts and dynamically set the boot partition's size.
DefaultSizeBootDevice = 512 * 1000 * 1000
)
var (
// DefaultURLBase is the base URL for all default artifacts.
// TODO(andrewrynhard): We need to setup infrastructure for publishing artifacts and not depend on GitHub.
DefaultURLBase = "https://github.com/talos-systems/talos/releases/download/" + version.Tag
// DefaultKernelURL is the URL to the kernel.
DefaultKernelURL = DefaultURLBase + "/vmlinuz"
// DefaultInitramfsURL is the URL to the initramfs.
DefaultInitramfsURL = DefaultURLBase + "/initramfs.xz"
)
// Manifest represents the instructions for preparing all block devices
@ -54,11 +73,21 @@ type Asset struct {
}
// NewManifest initializes and returns a Manifest.
func NewManifest(data *userdata.UserData) (manifest *Manifest) {
func NewManifest(data *userdata.UserData) (manifest *Manifest, err error) {
manifest = &Manifest{
Targets: map[string][]*Target{},
}
// Verify that the target device(s) can satisify the requested options.
if err = VerifyDataDevice(data); err != nil {
return nil, errors.Wrap(err, "failed to prepare ephemeral partition")
}
if err = VerifyBootDevice(data); err != nil {
return nil, errors.Wrap(err, "failed to prepare boot partition")
}
// Initialize any slices we need. Note that a boot paritition is not
// required.
@ -121,7 +150,7 @@ func NewManifest(data *userdata.UserData) (manifest *Manifest) {
}
}
return manifest
return manifest, nil
}
// ExecuteManifest partitions and formats all disks in a manifest.

View File

@ -32,7 +32,8 @@ func (suite *manifestSuite) TestNewManifest() {
err := yaml.Unmarshal([]byte(testConfig), data)
suite.Require().NoError(err)
manifests := NewManifest(data)
manifests, err := NewManifest(data)
suite.Require().NoError(err)
assert.Equal(suite.T(), 2, len(manifests.Targets["/dev/sda"]))
}

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package installer
package manifest
import (
"github.com/pkg/errors"
@ -13,13 +13,17 @@ import (
// VerifyDataDevice verifies the supplied data device options.
func VerifyDataDevice(data *userdata.UserData) (err error) {
// Ensure that an installation is specified
if data.Install == nil {
return errors.New("missing installation definition")
}
// Set data device to root device if not specified
if data.Install.Ephemeral == nil {
data.Install.Ephemeral = &userdata.InstallDevice{}
return errors.New("missing definition")
}
if data.Install.Ephemeral.Device == "" {
return errors.New("an ephemeral device is required")
return errors.New("missing disk")
}
if !data.Install.Force {
@ -33,6 +37,10 @@ func VerifyDataDevice(data *userdata.UserData) (err error) {
// VerifyBootDevice verifies the supplied boot device options.
func VerifyBootDevice(data *userdata.UserData) (err error) {
if data.Install == nil {
return nil
}
if data.Install.Boot == nil {
return nil
}

View File

@ -0,0 +1,40 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package manifest
import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/pkg/userdata"
"gopkg.in/yaml.v2"
)
type validateSuite struct {
suite.Suite
}
func TestValidateSuite(t *testing.T) {
suite.Run(t, new(validateSuite))
}
func (suite *validateSuite) TestVerifyDevice() {
// Start off with success and then remove bits
data := &userdata.UserData{}
err := yaml.Unmarshal([]byte(testConfig), data)
suite.Require().NoError(err)
suite.Require().NoError(VerifyBootDevice(data))
suite.Require().NoError(VerifyDataDevice(data))
// No impact because we can infer all data from the data device and
// defaults.
data.Install.Boot = nil
suite.Require().NoError(VerifyBootDevice(data))
data.Install.Ephemeral = &userdata.InstallDevice{
Device: "/dev/sda",
}
suite.Require().NoError(VerifyDataDevice(data))
}