vorteil/vorteil

View on GitHub
pkg/vkern/manager.go

Summary

Maintainability
A
0 mins
Test Coverage
package vkern

/**
 * SPDX-License-Identifier: Apache-2.0
 * Copyright 2020 vorteil.io Pty Ltd
 */

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "io"
    "os"
    "path/filepath"
    "sort"
    "strings"
    "time"

    "golang.org/x/crypto/openpgp"
)

// Global ..
var Global Manager

var prefix = "kernel-"

func versionFromFilename(s string) (CalVer, error) {
    if !strings.HasPrefix(s, prefix) {
        return "", errors.New("bad kernel file name")
    }
    s = strings.TrimPrefix(s, prefix)
    s = filepath.Base(s)
    return Parse(s)
}

func filenameFromVersion(v CalVer) string {
    return prefix + v.String()
}

// Logger ..
var Logger = func(fmt string, x ...interface{}) {
    return
}

// ManagedBundle ..
type ManagedBundle struct {
    bundle   *Bundle
    closer   io.Closer
    location string
}

// Bundle ..
func (bundle *ManagedBundle) Bundle() *Bundle {
    return bundle.bundle
}

// Close ..
func (bundle *ManagedBundle) Close() error {
    return bundle.closer.Close()
}

// Location ..
func (bundle *ManagedBundle) Location() string {
    return bundle.location
}

// Tuple ..
type Tuple struct {
    Idx      int
    Version  CalVer    `json:"version"`
    Location string    `json:"source"`
    ModTime  time.Time `json:"release"`
}

// List ..
type List []Tuple

func (l List) Len() int {
    return len(l)
}

func (l List) Less(i, j int) bool {
    if l[i].Version.Less(l[j].Version) {
        return true
    }
    if !l[j].Version.Less(l[i].Version) {
        // two identical versions, so we go based off ModTime
        return l[i].ModTime.Before(l[j].ModTime)
    }
    return false
}

func (l List) Swap(i, j int) {
    tmp := l[i]
    l[i] = l[j]
    l[j] = tmp
}

// BestMatch ..
func (l List) BestMatch(v CalVer) (*Tuple, error) {
    Idx := sort.Search(l.Len(), func(arg1 int) bool {
        return v.LessEq(l[arg1].Version)
    })

    if Idx < l.Len() && l[Idx].Version.String() == v.String() {
        // check later versions since duplicate versions might exist in compound source lists
        for {
            Idx++
            if Idx >= l.Len() || l[Idx].Version.String() != v.String() {
                Idx--
                return &l[Idx], nil // exact match
            }
        }
    }
    // if v has a modifier it demands an exact match
    if v.Modifier() != "" {
        return nil, fmt.Errorf("no match for kernel %s", v.String())
    }

    var candidate *Tuple
    var cErr error = fmt.Errorf("no match for kernel %s", v.String()) // Place holder error for if there is no valid candidate
    if Idx != 0 {
        potentialCandidate := &l[Idx-1]
        if potentialCandidate.Version.Major() == v.Major() &&
            potentialCandidate.Version.Minor() == v.Minor() {
            if v.Patch() == -1 || potentialCandidate.Version.Patch() == v.Patch() {
                // Candidate found, set error to nil
                candidate = potentialCandidate
                cErr = nil
            }
        }
    }

    return candidate, cErr

}

// Manager ..
type Manager interface {
    Get(ctx context.Context, version CalVer) (*ManagedBundle, error)
    List(ctx context.Context) (List, error)
    Latest() (string, error)
}

// ConstructGetLastestKernelsFunc : Given a Kernel Manager will construct a function that returns the latest Kernel Version
func ConstructGetLastestKernelsFunc(ksrc *Manager) func(ctx context.Context) (CalVer, error) {
    return func(ctx context.Context) (CalVer, error) {
        s, err := (*ksrc).Latest()
        if err != nil {
            return CalVer(""), err
        }
        k, err := Parse(s)
        if err != nil {
            return CalVer(""), err
        }
        return k, nil
    }
}

// Utils
func removeFiles(files ...string) error {
    for _, fPath := range files {
        err := os.Remove(fPath)
        if err != nil && !os.IsNotExist(err) {
            return fmt.Errorf("could not remove %s , error: %v", fPath, err)
        }
    }

    return nil
}

func validateKernelSignature(kernelFile string, signatureFile string) error {
    krrData := mustGetAsset("vorteil.gpg")
    krr := bytes.NewReader(krrData)

    ker, err := os.Open(kernelFile)
    if err != nil {
        return err
    }
    defer ker.Close()

    sig, err := os.Open(signatureFile)
    if err != nil {
        return err
    }
    defer sig.Close()

    kr, err := openpgp.ReadArmoredKeyRing(krr)
    if err != nil {
        return err
    }

    _, err = openpgp.CheckArmoredDetachedSignature(kr, ker, sig)
    if err == nil {
        err = ker.Close()
        if err == nil {
            err = sig.Close()
        }
    }

    return err
}

// AdvancedArgs ..
type AdvancedArgs struct {
    Directory          string   `toml:"directory"`
    DropPath           string   `toml:"drop-path"`
    RemoteRepositories []string `toml:"remote-repositories"`
    // UpgradeStrategy    string   `toml:"upgrade-strategy"`
}

// Advanced ..
func Advanced(args AdvancedArgs) (Manager, error) {

    var mgrs []Manager

    if args.DropPath != "" {
        m, err := NewLocalManager(args.DropPath)
        if err != nil {
            return nil, err
        }
        mgrs = append(mgrs, m)
    }

    for _, s := range args.RemoteRepositories {
        m, err := NewRemoteManager(s, filepath.Join(args.Directory, strings.ReplaceAll(strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://"), "/", "_")))
        if err != nil {
            return nil, err
        }
        mgrs = append(mgrs, m)
    }

    return NewCompoundManager(mgrs...)
}