Files
kubevela/pkg/appfile/appfile.go
Jianbo Sun bb6b050d15 Merge pull request #745 from ryanzhang-oss/ack-cleanup
make Vela work with ACK and clean up imports and scripts
2020-12-09 11:59:35 +08:00

196 lines
5.6 KiB
Go

package appfile
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/ghodss/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/pkg/appfile/template"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/oam"
)
// error msg used in Appfile
var (
ErrImageNotDefined = errors.New("image not defined")
)
// DefaultAppfilePath defines the default file path that used by `vela up` command
const (
DefaultJSONAppfilePath = "./vela.json"
DefaultAppfilePath = "./vela.yaml"
DefaultUnknowFormatAppfilePath = "./Appfile"
)
// AppFile defines the spec of KubeVela Appfile
type AppFile struct {
Name string `json:"name"`
CreateTime time.Time `json:"createTime,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
Services map[string]Service `json:"services"`
Secrets map[string]string `json:"secrets,omitempty"`
configGetter configGetter
}
// NewAppFile init an empty AppFile struct
func NewAppFile() *AppFile {
return &AppFile{
Services: make(map[string]Service),
Secrets: make(map[string]string),
configGetter: defaultConfigGetter{},
}
}
// Load will load appfile from default path
func Load() (*AppFile, error) {
if _, err := os.Stat(DefaultAppfilePath); err == nil {
return LoadFromFile(DefaultAppfilePath)
}
if _, err := os.Stat(DefaultJSONAppfilePath); err == nil {
return LoadFromFile(DefaultJSONAppfilePath)
}
return LoadFromFile(DefaultUnknowFormatAppfilePath)
}
// JSONToYaml will convert JSON format appfile to yaml and load the AppFile struct
func JSONToYaml(data []byte, appFile *AppFile) (*AppFile, error) {
j, e := yaml.JSONToYAML(data)
if e != nil {
return nil, e
}
err := yaml.Unmarshal(j, appFile)
if err != nil {
return nil, err
}
return appFile, nil
}
// LoadFromFile will read the file and load the AppFile struct
func LoadFromFile(filename string) (*AppFile, error) {
b, err := ioutil.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, err
}
af := NewAppFile()
// Add JSON format appfile support
ext := filepath.Ext(filename)
switch ext {
case ".yaml", ".yml":
err = yaml.Unmarshal(b, af)
case ".json":
af, err = JSONToYaml(b, af)
default:
if json.Valid(b) {
af, err = JSONToYaml(b, af)
} else {
err = yaml.Unmarshal(b, af)
}
}
if err != nil {
return nil, err
}
return af, nil
}
// BuildOAM renders Appfile into AppConfig, Components. It also builds images for services if defined.
func (app *AppFile) BuildOAM(ns string, io cmdutil.IOStreams, tm template.Manager, slience bool) (
[]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, []oam.Object, error) {
return app.buildOAM(ns, io, tm, slience)
}
func (app *AppFile) buildOAM(ns string, io cmdutil.IOStreams, tm template.Manager, silence bool) (
[]*v1alpha2.Component, *v1alpha2.ApplicationConfiguration, []oam.Object, error) {
appConfig := &v1alpha2.ApplicationConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: app.Name,
Namespace: ns,
},
}
var comps []*v1alpha2.Component
for sname, svc := range app.GetServices() {
var image string
v, ok := svc["image"]
if ok {
image = v.(string)
}
if b := svc.GetBuild(); b != nil {
if image == "" {
return nil, nil, nil, ErrImageNotDefined
}
io.Infof("\nBuilding service (%s)...\n", sname)
if err := b.BuildImage(io, image); err != nil {
return nil, nil, nil, err
}
}
if !silence {
io.Infof("\nRendering configs for service (%s)...\n", sname)
}
acComp, comp, err := svc.RenderService(tm, sname, ns, app.configGetter)
if err != nil {
return nil, nil, nil, err
}
appConfig.Spec.Components = append(appConfig.Spec.Components, *acComp)
comps = append(comps, comp)
}
addWorkloadTypeLabel(comps, app.Services)
health := addHealthScope(appConfig)
return comps, appConfig, []oam.Object{health}, nil
}
func addWorkloadTypeLabel(comps []*v1alpha2.Component, services map[string]Service) {
for _, comp := range comps {
workloadType := services[comp.Name].GetType()
workloadObject := comp.Spec.Workload.Object.(*unstructured.Unstructured)
labels := workloadObject.GetLabels()
if labels == nil {
labels = map[string]string{oam.WorkloadTypeLabel: workloadType}
} else {
labels[oam.WorkloadTypeLabel] = workloadType
}
workloadObject.SetLabels(labels)
}
}
func addHealthScope(appConfig *v1alpha2.ApplicationConfiguration) *v1alpha2.HealthScope {
health := &v1alpha2.HealthScope{
TypeMeta: metav1.TypeMeta{
APIVersion: v1alpha2.HealthScopeGroupVersionKind.GroupVersion().String(),
Kind: v1alpha2.HealthScopeKind,
},
}
health.Name = FormatDefaultHealthScopeName(appConfig.Name)
health.Namespace = appConfig.Namespace
health.Spec.WorkloadReferences = make([]v1alpha1.TypedReference, 0)
for i := range appConfig.Spec.Components {
// TODO(wonderflow): Temporarily we add health scope here, should change to use scope framework
appConfig.Spec.Components[i].Scopes = append(appConfig.Spec.Components[i].Scopes, v1alpha2.ComponentScope{
ScopeReference: v1alpha1.TypedReference{
APIVersion: v1alpha2.SchemeGroupVersion.String(),
Kind: v1alpha2.HealthScopeKind,
Name: health.Name,
},
})
}
return health
}
// FormatDefaultHealthScopeName will create a default health scope name.
func FormatDefaultHealthScopeName(appName string) string {
return appName + "-default-health"
}