Skip to content

Commit

Permalink
Merge pull request #566 from Bubblemelon/status-report-packetcloud
Browse files Browse the repository at this point in the history
Status Report for Packet Instance
  • Loading branch information
Bubblemelon authored Aug 1, 2018
2 parents 231cb92 + d604cd9 commit 6fe6988
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 34 deletions.
11 changes: 6 additions & 5 deletions config/shared/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ var (
ErrInvalidNetworkdDropinExt = errors.New("invalid networkd drop-in extension")

// Misc errors
ErrInvalidScheme = errors.New("invalid url scheme")
ErrInvalidUrl = errors.New("unable to parse url")
ErrHashMalformed = errors.New("malformed hash specifier")
ErrHashWrongSize = errors.New("incorrect size for hash sum")
ErrHashUnrecognized = errors.New("unrecognized hash function")
ErrInvalidScheme = errors.New("invalid url scheme")
ErrInvalidUrl = errors.New("unable to parse url")
ErrHashMalformed = errors.New("malformed hash specifier")
ErrHashWrongSize = errors.New("incorrect size for hash sum")
ErrHashUnrecognized = errors.New("unrecognized hash function")
ErrEngineConfiguration = errors.New("engine incorrectly configured")
)

// NewNoInstallSectionError produces an error indicating the given unit, named
Expand Down
18 changes: 12 additions & 6 deletions internal/exec/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ type Engine struct {

// Run executes the stage of the given name. It returns true if the stage
// successfully ran and false if there were any errors.
func (e Engine) Run(stageName string) bool {
func (e Engine) Run(stageName string) error {
if e.Fetcher == nil || e.Logger == nil {
fmt.Fprintf(os.Stderr, "engine incorrectly configured\n")
return false
return errors.ErrEngineConfiguration
}
baseConfig := types.Config{
Ignition: types.Ignition{Version: types.MaxVersion.String()},
Expand All @@ -72,7 +72,7 @@ func (e Engine) Run(stageName string) bool {
e.logReport(r)
if err != nil && err != providers.ErrNoProvider {
e.Logger.Crit("failed to acquire system base config: %v", err)
return false
return err
}

cfg, err := e.acquireConfig()
Expand All @@ -84,17 +84,23 @@ func (e Engine) Run(stageName string) bool {
e.logReport(r)
if err != nil && err != providers.ErrNoProvider {
e.Logger.Crit("failed to acquire default config: %v", err)
return false
return err
}
default:
e.Logger.Crit("failed to acquire config: %v", err)
return false
return err
}

e.Logger.PushPrefix(stageName)
defer e.Logger.PopPrefix()

return stages.Get(stageName).Create(e.Logger, e.Root, *e.Fetcher).Run(config.Append(baseConfig, config.Append(systemBaseConfig, cfg)))
if err = stages.Get(stageName).Create(e.Logger, e.Root, *e.Fetcher).Run(config.Append(baseConfig, config.Append(systemBaseConfig, cfg))); err != nil {
// e.Logger could be nil
fmt.Fprintf(os.Stderr, "%s failed", stageName)
return err
}
e.Logger.Info("%s passed", stageName)
return nil
}

// acquireConfig returns the configuration, first checking a local cache
Expand Down
18 changes: 7 additions & 11 deletions internal/exec/stages/disks/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,26 @@ func (stage) Name() string {
return name
}

func (s stage) Run(config types.Config) bool {
func (s stage) Run(config types.Config) error {
// Interacting with disks/partitions/raids/filesystems in general can cause
// udev races. If we do not need to do anything, we also do not need to
// do the udevadm settle and can just return here.
if len(config.Storage.Disks) == 0 &&
len(config.Storage.Raid) == 0 &&
len(config.Storage.Filesystems) == 0 {
return true
return nil
}

if err := s.createPartitions(config); err != nil {
s.Logger.Crit("create partitions failed: %v", err)
return false
return fmt.Errorf("create partitions failed: %v", err)
}

if err := s.createRaids(config); err != nil {
s.Logger.Crit("failed to create raids: %v", err)
return false
return fmt.Errorf("failed to create raids: %v", err)
}

if err := s.createFilesystems(config); err != nil {
s.Logger.Crit("failed to create filesystems: %v", err)
return false
return fmt.Errorf("failed to create filesystems: %v", err)
}

// udevd registers an IN_CLOSE_WRITE inotify watch on block device
Expand Down Expand Up @@ -118,11 +115,10 @@ func (s stage) Run(config types.Config) bool {
exec.Command(distro.UdevadmCmd(), "settle"),
"waiting for udev to settle",
); err != nil {
s.Logger.Crit("udevadm settle failed: %v", err)
return false
return fmt.Errorf("udevadm settle failed: %v", err)
}

return true
return nil
}

// waitOnDevices waits for the devices enumerated in devs as a logged operation
Expand Down
14 changes: 6 additions & 8 deletions internal/exec/stages/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package files

import (
"errors"
"fmt"

"github.com/coreos/ignition/internal/config/types"
"github.com/coreos/ignition/internal/exec/stages"
Expand Down Expand Up @@ -60,21 +61,18 @@ func (stage) Name() string {
return name
}

func (s stage) Run(config types.Config) bool {
func (s stage) Run(config types.Config) error {
if err := s.createPasswd(config); err != nil {
s.Logger.Crit("failed to create users/groups: %v", err)
return false
return fmt.Errorf("failed to create users/groups: %v", err)
}

if err := s.createFilesystemsEntries(config); err != nil {
s.Logger.Crit("failed to create files: %v", err)
return false
return fmt.Errorf("failed to create files: %v", err)
}

if err := s.createUnits(config); err != nil {
s.Logger.Crit("failed to create units: %v", err)
return false
return fmt.Errorf("failed to create units: %v", err)
}

return true
return nil
}
2 changes: 1 addition & 1 deletion internal/exec/stages/stages.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

// Stage is responsible for actually executing a stage of the configuration.
type Stage interface {
Run(config types.Config) bool
Run(config types.Config) error
Name() string
}

Expand Down
8 changes: 7 additions & 1 deletion internal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ func main() {
Fetcher: &fetcher,
}

if !engine.Run(flags.stage.String()) {
err = engine.Run(flags.stage.String())
if statusErr := engine.OEMConfig.Status(flags.stage.String(), *engine.Fetcher, err); statusErr != nil {
logger.Err("POST Status error: ", statusErr.Error())
}
if err != nil {
logger.Crit("Ignition failed: %v", err.Error())
os.Exit(1)
}
logger.Info("Ignition finished successfully")
}
14 changes: 12 additions & 2 deletions internal/oem/oem.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Config struct {
name string
fetch providers.FuncFetchConfig
newFetcher providers.FuncNewFetcher
status providers.FuncPostStatus
}

func (c Config) Name() string {
Expand All @@ -61,6 +62,14 @@ func (c Config) NewFetcherFunc() providers.FuncNewFetcher {
}
}

// Status takes a Fetcher and the error from Run (from engine)
func (c Config) Status(stageName string, f resource.Fetcher, statusErr error) error {
if c.status != nil {
return c.status(stageName, f, statusErr)
}
return nil
}

var configs = registry.Create("oem configs")

func init() {
Expand Down Expand Up @@ -110,8 +119,9 @@ func init() {
fetch: noop.FetchConfig,
})
configs.Register(Config{
name: "packet",
fetch: packet.FetchConfig,
name: "packet",
fetch: packet.FetchConfig,
status: packet.PostStatus,
})
configs.Register(Config{
name: "pxe",
Expand Down
85 changes: 85 additions & 0 deletions internal/providers/packet/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,23 @@
package packet

import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"

"github.com/coreos/ignition/config/validate/report"
"github.com/coreos/ignition/internal/config/types"
"github.com/coreos/ignition/internal/providers/util"
"github.com/coreos/ignition/internal/resource"
)

var (
ErrValidFetchEmptyData = errors.New("fetch successful but fetched data empty")
)

var (
userdataUrl = url.URL{
Scheme: "https",
Expand All @@ -34,6 +43,14 @@ var (
}
)

var (
metadataUrl = url.URL{
Scheme: "https",
Host: "metadata.packet.net",
Path: "metadata",
}
)

func FetchConfig(f resource.Fetcher) (types.Config, report.Report, error) {
// Packet's metadata service returns "Not Acceptable" when queried
// with the default Accept header.
Expand All @@ -48,3 +65,71 @@ func FetchConfig(f resource.Fetcher) (types.Config, report.Report, error) {

return util.ParseConfig(f.Logger, data)
}

// PostStatus posts a message that will show on the Packet Instance Timeline
func PostStatus(stageName string, f resource.Fetcher, errMsg error) error {
f.Logger.Info("POST message to Packet Timeline")
// fetch JSON from https://metadata.packet.net/metadata
data, err := f.FetchToBuffer(metadataUrl, resource.FetchOptions{
Headers: nil,
})
if err != nil {
return err
}
if data == nil {
return ErrValidFetchEmptyData
}
metadata := struct {
PhoneHomeURL string `json:"phone_home_url"`
}{}
err = json.Unmarshal(data, &metadata)
if err != nil {
return err
}
phonehomeURL := metadata.PhoneHomeURL
// to get phonehome IPv4
phonehomeURL = strings.TrimSuffix(phonehomeURL, "/phone-home")
// POST Message to phonehome IP
postMessageURL := phonehomeURL + "/events"

return postMessage(stageName, errMsg, postMessageURL)
}

// postMessage makes a post request with the supplied message to the url
func postMessage(stageName string, e error, url string) error {

stageName = "[" + stageName + "]"

type mStruct struct {
State string `json:"state"`
Message string `json:"message"`
}
m := mStruct{}
if e != nil {
m = mStruct{
State: "failed",
Message: stageName + " Ignition error: " + e.Error(),
}
} else {
m = mStruct{
State: "succeeded",
Message: stageName + " Ignition status: finished successfully",
}
}
messageJSON, err := json.Marshal(m)
if err != nil {
return err
}
postReq, err := http.NewRequest("POST", url, bytes.NewBuffer(messageJSON))
if err != nil {
return err
}
postReq.Header.Set("Content-Type", "application/json")
client := &http.Client{}
respPost, err := client.Do(postReq)
if err != nil {
return err
}
defer respPost.Body.Close()
return err
}
1 change: 1 addition & 0 deletions internal/providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ var (

type FuncFetchConfig func(f resource.Fetcher) (types.Config, report.Report, error)
type FuncNewFetcher func(logger *log.Logger) (resource.Fetcher, error)
type FuncPostStatus func(stageName string, f resource.Fetcher, e error) error
5 changes: 5 additions & 0 deletions internal/resource/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,14 @@ func (f *Fetcher) FetchFromTFTP(u url.URL, dest *os.File, opts FetchOptions) err
// FetchFromHTTP fetches a resource from u via HTTP(S) into dest, returning an
// error if one is encountered.
func (f *Fetcher) FetchFromHTTP(u url.URL, dest *os.File, opts FetchOptions) error {
// for the case when "config is not valid"
// this if necessary if not spawned through kola (e.g. Packet Dashboard)
if f.client == nil {
logger := log.New(true)
f.Logger = &logger
f.newHttpClient()
}

dataReader, status, ctxCancel, err := f.client.getReaderWithHeader(u.String(), opts.Headers)
if ctxCancel != nil {
// whatever context getReaderWithHeader created for the request should
Expand Down

0 comments on commit 6fe6988

Please sign in to comment.