blob: 5301c32089cbc97db12dc9b5c3b35b06b016a2ea [file] [log] [blame]
/*
Copyright 2015 Runtime Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cli
import (
"bytes"
"fmt"
"github.com/spf13/viper"
"io/ioutil"
"log"
"os"
"path/filepath"
)
type Clutch struct {
Name string
LarvaFile string
EggShells []*EggShell
RemoteUrl string
nest *Nest
}
type EggShell struct {
FullName string
Version *Version
Deps []*DependencyRequirement
Caps []*DependencyRequirement
ReqCaps []*DependencyRequirement
}
type Nest struct {
// Path to the Nest
Path string
// Store of Larvas
Clutches map[string]*Clutch
// Repository for this Nest
repo *Repo
}
func NewNest(repo *Repo) (*Nest, error) {
nest := &Nest{}
nest.Path = repo.BasePath + "/.nest/"
if NodeNotExist(nest.Path) {
if err := os.MkdirAll(nest.Path, 0755); err != nil {
return nil, err
}
}
nest.Clutches = map[string]*Clutch{}
nest.repo = repo
if err := nest.LoadNest(); err != nil {
return nil, err
}
return nest, nil
}
func NewEggShell() (*EggShell, error) {
eShell := &EggShell{}
return eShell, nil
}
func (nest *Nest) LoadNest() error {
files, err := ioutil.ReadDir(nest.Path)
if err != nil {
return err
}
for _, fileInfo := range files {
file := fileInfo.Name()
if filepath.Ext(file) == ".yml" {
name := file[:len(filepath.Base(file))-len(".yml")]
log.Printf("[DEBUG] Loading Clutch %s", name)
clutch, err := NewClutch(nest)
if err != nil {
return err
}
if err := clutch.Load(name); err != nil {
return err
}
}
}
return nil
}
func (nest *Nest) GetClutches() (map[string]*Clutch, error) {
return nest.Clutches, nil
}
func (es *EggShell) serializeDepReq(name string,
drList []*DependencyRequirement, indent string) string {
drStr := ""
if len(drList) > 0 {
drStr += fmt.Sprintf("%s%s:\n", indent, name)
for _, dr := range drList {
drStr += fmt.Sprintf("%s - %s\n", indent, dr)
}
}
return drStr
}
func (es *EggShell) Serialize(indent string) string {
esStr := fmt.Sprintf("%s%s:\n", indent, es.FullName)
indent += " "
if es.Version == nil {
es.Version = &Version{0, 0, 0}
}
esStr += fmt.Sprintf("%svers: %s\n", indent, es.Version)
esStr += es.serializeDepReq("deps", es.Deps, indent)
esStr += es.serializeDepReq("caps", es.Caps, indent)
esStr += es.serializeDepReq("req_caps", es.ReqCaps, indent)
return esStr
}
func NewClutch(nest *Nest) (*Clutch, error) {
clutch := &Clutch{}
clutch.nest = nest
return clutch, nil
}
func (cl *Clutch) LoadFromPM(em *EggMgr) error {
for _, pkg := range em.Eggs {
eggShell := &EggShell{}
eggShell.FullName = pkg.FullName
eggShell.Deps = pkg.Deps
eggShell.Caps = pkg.Capabilities
eggShell.ReqCaps = pkg.ReqCapabilities
eggShell.Version = pkg.Version
cl.EggShells = append(cl.EggShells, eggShell)
}
return nil
}
func (cl *Clutch) Serialize() (string, error) {
clStr := "name: " + cl.Name + "\n"
clStr = clStr + "url: " + cl.RemoteUrl + "\n"
clStr = clStr + "eggs:\n"
buf := bytes.Buffer{}
indent := " "
for _, eggShell := range cl.EggShells {
buf.WriteString(eggShell.Serialize(indent))
}
return clStr + buf.String(), nil
}
func (cl *Clutch) strSliceToDr(list []string) ([]*DependencyRequirement, error) {
drList := []*DependencyRequirement{}
for _, name := range list {
req, err := NewDependencyRequirementParseString(name)
if err != nil {
return nil, err
}
drList = append(drList, req)
}
if len(drList) == 0 {
return nil, nil
} else {
return drList, nil
}
}
func (cl *Clutch) fileToEggList(cfg *viper.Viper) ([]*EggShell,
error) {
eggMap := cfg.GetStringMap("eggs")
eggList := []*EggShell{}
for name, _ := range eggMap {
eggShell, err := NewEggShell()
if err != nil {
return nil, err
}
eggShell.FullName = name
eggDef := cfg.GetStringMap("eggs." + name)
eggShell.Version, err = NewVersParseString(eggDef["vers"].(string))
if err != nil {
return nil, err
}
eggShell.Deps, err = cl.strSliceToDr(cfg.GetStringSlice("eggs." + name + ".deps"))
if err != nil {
return nil, err
}
eggShell.Caps, err = cl.strSliceToDr(cfg.GetStringSlice("eggs." + name + ".caps"))
if err != nil {
return nil, err
}
eggShell.ReqCaps, err = cl.strSliceToDr(cfg.GetStringSlice("eggs." + name +
".req_caps"))
if err != nil {
return nil, err
}
eggList = append(eggList, eggShell)
}
return eggList, nil
}
// Create the manifest file name, it's the manifest dir + manifest name and a
// .yml extension
func (cl *Clutch) GetClutchFile(name string) string {
return cl.nest.Path + name + ".yml"
}
func (cl *Clutch) Load(name string) error {
cfg, err := ReadConfig(cl.nest.Path, name)
if err != nil {
return nil
}
if cfg.GetString("name") != name {
return NewNewtError(
fmt.Sprintf("Wrong name %s in remote larva file (expected %s)",
cfg.GetString("name"), name))
}
cl.Name = cfg.GetString("name")
cl.RemoteUrl = cfg.GetString("url")
cl.EggShells, err = cl.fileToEggList(cfg)
if err != nil {
return err
}
cl.nest.Clutches[name] = cl
return nil
}
func (cl *Clutch) Install(name string, url string) error {
clutchFile := cl.GetClutchFile(name)
// XXX: Should warn if file already exists, and require force option
os.Remove(clutchFile)
// Download the manifest
dl, err := NewDownloader()
if err != nil {
return err
}
if err := dl.DownloadFile(url, clutchFile); err != nil {
return err
}
// Load the manifest, and ensure that it is in the correct format
if err := cl.Load(name); err != nil {
return err
}
return nil
}