-
Jessica Yuen authored
This commit will append both mandatory and optional prototype parameters to the component params.libsonnet file on `ks gen foo ...`. Default values will be used for optional params where the user does not specify flags to `ks gen foo ...`. Because we are trying to append to jsonnet, we will have to traverse the AST to first identify the location of where to insert the new component params. New components will be inserted at the bottom of the components object, with the params ordered alphabetically.
82617551
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
manager.go 7.57 KiB
// Copyright 2017 The kubecfg authors
//
//
// 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 metadata
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/ksonnet/ksonnet/metadata/snippet"
"github.com/ksonnet/ksonnet/prototype"
log "github.com/sirupsen/logrus"
"github.com/spf13/afero"
)
func appendToAbsPath(originalPath AbsPath, toAppend ...string) AbsPath {
paths := append([]string{string(originalPath)}, toAppend...)
return AbsPath(path.Join(paths...))
}
const (
ksonnetDir = ".ksonnet"
libDir = "lib"
componentsDir = "components"
environmentsDir = "environments"
vendorDir = "vendor"
componentParamsFile = "params.libsonnet"
baseLibsonnetFile = "base.libsonnet"
// ComponentsExtCodeKey is the ExtCode key for component imports
ComponentsExtCodeKey = "__ksonnet/components"
// ParamsExtCodeKey is the ExtCode key for importing environment parameters
ParamsExtCodeKey = "__ksonnet/params"
)
type manager struct {
appFS afero.Fs
rootPath AbsPath
ksonnetPath AbsPath
libPath AbsPath
componentsPath AbsPath
environmentsPath AbsPath
vendorDir AbsPath
componentParamsPath AbsPath
baseLibsonnetPath AbsPath
}
func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) {
var lastBase string
currBase := string(abs)
for {
currPath := path.Join(currBase, ksonnetDir)
exists, err := afero.Exists(appFS, currPath)
if err != nil {
return nil, err
}
if exists {
return newManager(AbsPath(currBase), appFS), nil
}
lastBase = currBase
currBase = filepath.Dir(currBase)
if lastBase == currBase {
return nil, fmt.Errorf("No ksonnet application found")
}
}
}
func initManager(rootPath AbsPath, spec ClusterSpec, serverURI, namespace *string, appFS afero.Fs) (*manager, error) {
m := newManager(rootPath, appFS)
// Generate the program text for ksonnet-lib.
//
// IMPLEMENTATION NOTE: We get the cluster specification and generate
// ksonnet-lib before initializing the directory structure so that failure of
// either (e.g., GET'ing the spec from a live cluster returns 404) does not
// result in a partially-initialized directory structure.
//
extensionsLibData, k8sLibData, specData, err := m.generateKsonnetLibData(spec)
if err != nil {
return nil, err
}
// Initialize directory structure.
if err := m.createAppDirTree(); err != nil {
return nil, err
}
// Initialize environment, and cache specification data.
if serverURI != nil {
err := m.createEnvironment(defaultEnvName, *serverURI, *namespace, extensionsLibData, k8sLibData, specData)
if err != nil {
return nil, err
}
}
return m, nil
}
func newManager(rootPath AbsPath, appFS afero.Fs) *manager {
return &manager{
appFS: appFS,
rootPath: rootPath,
ksonnetPath: appendToAbsPath(rootPath, ksonnetDir),
libPath: appendToAbsPath(rootPath, libDir),
componentsPath: appendToAbsPath(rootPath, componentsDir),
environmentsPath: appendToAbsPath(rootPath, environmentsDir),
vendorDir: appendToAbsPath(rootPath, vendorDir),
componentParamsPath: appendToAbsPath(rootPath, componentsDir, componentParamsFile),
baseLibsonnetPath: appendToAbsPath(rootPath, environmentsDir, baseLibsonnetFile),
}
}
func (m *manager) Root() AbsPath {
return m.rootPath
}
func (m *manager) ComponentPaths() (AbsPaths, error) {
paths := AbsPaths{}
err := afero.Walk(m.appFS, string(m.componentsPath), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
paths = append(paths, path)
}
return nil
})
if err != nil {
return nil, err
}
return paths, nil
}
func (m *manager) CreateComponent(name string, text string, params map[string]string, templateType prototype.TemplateType) error {
if !isValidName(name) || strings.Contains(name, "/") {
return fmt.Errorf("Component name '%s' is not valid; must not contain punctuation, spaces, or begin or end with a slash", name)
}
componentPath := string(appendToAbsPath(m.componentsPath, name))
switch templateType {
case prototype.YAML:
componentPath = componentPath + ".yaml"
case prototype.JSON:
componentPath = componentPath + ".json"
case prototype.Jsonnet:
componentPath = componentPath + ".jsonnet"
default:
return fmt.Errorf("Unrecognized prototype template type '%s'", templateType)
}
if exists, err := afero.Exists(m.appFS, componentPath); exists {
return fmt.Errorf("Component with name '%s' already exists", name)
} else if err != nil {
return fmt.Errorf("Could not check whether component '%s' exists:\n\n%v", name, err)
}
log.Infof("Writing component at '%s/%s'", componentsDir, name)
err := afero.WriteFile(m.appFS, componentPath, []byte(text), defaultFilePermissions)
if err != nil {
return err
}
log.Debugf("Writing component parameters at '%s/%s", componentsDir, name)
return m.writeComponentParams(name, params)
}
func (m *manager) LibPaths(envName string) (libPath, envLibPath, envComponentPath, envParamsPath AbsPath) {
envPath := appendToAbsPath(m.environmentsPath, envName)
return m.libPath, appendToAbsPath(envPath, metadataDirName),
appendToAbsPath(envPath, path.Base(envName)+".jsonnet"), appendToAbsPath(envPath, componentParamsFile)
}
func (m *manager) createAppDirTree() error {
exists, err := afero.DirExists(m.appFS, string(m.rootPath))
if err != nil {
return fmt.Errorf("Could not check existance of directory '%s':\n%v", m.rootPath, err)
} else if exists {
return fmt.Errorf("Could not create app; directory '%s' already exists", m.rootPath)
}
dirPaths := []AbsPath{
m.rootPath,
m.ksonnetPath,
m.libPath,
m.componentsPath,
m.environmentsPath,
m.vendorDir,
}
for _, p := range dirPaths {
if err := m.appFS.MkdirAll(string(p), defaultFolderPermissions); err != nil {
return err
}
}
filePaths := []struct {
path AbsPath
content []byte
}{
{
m.componentParamsPath,
genComponentParamsContent(),
},
{
m.baseLibsonnetPath,
genBaseLibsonnetContent(),
},
}
for _, f := range filePaths {
if err := afero.WriteFile(m.appFS, string(f.path), f.content, defaultFilePermissions); err != nil {
return err
}
}
return nil
}
func (m *manager) writeComponentParams(componentName string, params map[string]string) error {
text, err := afero.ReadFile(m.appFS, string(m.componentParamsPath))
if err != nil {
return err
}
appended, err := snippet.AppendComponent(componentName, string(text), params)
if err != nil {
return err
}
return afero.WriteFile(m.appFS, string(m.componentParamsPath), []byte(appended), defaultFilePermissions)
}
func genComponentParamsContent() []byte {
return []byte(`{
global: {
// User-defined global parameters; accessible to all component and environments, Ex:
// replicas: 4,
},
components: {
// Component-level parameters, defined initially from 'ks prototype use ...'
// Each object below should correspond to a component in the components/ directory
},
}
`)
}
func genBaseLibsonnetContent() []byte {
return []byte(`local components = std.extVar("` + ComponentsExtCodeKey + `");
components + {
// Insert user-specified overrides here.
}
`)
}