Skip to content
Snippets Groups Projects
Commit 1953d8bf authored by Alex Clemmer's avatar Alex Clemmer Committed by GitHub
Browse files

Merge pull request #131 from jessicayuen/env-subcommands

Introduce env subcommands 'env add', 'env rm', 'env set' & 'env list'
parents 70af2cd3 f86667a6
No related branches found
No related tags found
No related merge requests found
// 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 cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/ksonnet/kubecfg/metadata"
"github.com/ksonnet/kubecfg/pkg/kubecfg"
)
const (
flagEnvName = "name"
flagEnvURI = "uri"
)
func init() {
RootCmd.AddCommand(envCmd)
envCmd.AddCommand(envAddCmd)
envCmd.AddCommand(envRmCmd)
envCmd.AddCommand(envListCmd)
envCmd.AddCommand(envSetCmd)
// TODO: We need to make this default to checking the `kubeconfig` file.
envAddCmd.PersistentFlags().String(flagAPISpec, "version:v1.7.0",
"Manually specify API version from OpenAPI schema, cluster, or Kubernetes version")
envSetCmd.PersistentFlags().String(flagEnvName, "",
"Specify name to rename environment to. Name must not already exist")
envSetCmd.PersistentFlags().String(flagEnvURI, "",
"Specify URI to point environment cluster to a new location")
}
var envCmd = &cobra.Command{
Use: "env",
Short: `Create, remove, modify, and list ksonnet environments`,
RunE: func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Command 'env' requires a subcommand\n\n%s", cmd.UsageString())
},
}
var envAddCmd = &cobra.Command{
Use: "add <env-name> <env-uri>",
Short: "Add a new environment within a ksonnet project",
RunE: func(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()
if len(args) != 2 {
return fmt.Errorf("'env add' takes two arguments, the name and the uri of the environment, respectively")
}
envName := args[0]
envURI := args[1]
appDir, err := os.Getwd()
if err != nil {
return err
}
appRoot := metadata.AbsPath(appDir)
specFlag, err := flags.GetString(flagAPISpec)
if err != nil {
return err
}
c, err := kubecfg.NewEnvAddCmd(envName, envURI, specFlag, appRoot)
if err != nil {
return err
}
return c.Run()
},
Long: `Create a new environment within a ksonnet project. This will
generate a new directory, 'env-name', within the 'environmentss' directory,
containing the environment-specific files. 'env-uri' is the URI which the
Kubernete's cluster is located for the added environment.
Below is an example directory structure:
app-name/
.gitignore Default .gitignore; can customize VCS
.ksonnet/ Metadata for ksonnet
environments/ Env specs (defaults: dev, test, prod)
default/ [Default generated environment]
us-west/ [Example of user-generated env]
staging/
k.libsonnet
k8s.libsonnet
swagger.json
spec.json [This will contain the uri of the environment]
components/ Top-level Kubernetes objects defining application
lib/ user-written .libsonnet files
vendor/ mixin libraries, prototypes
`,
Example: ` # Initialize a new staging environment at us-west. The directory
# structure rooted at 'us-west' in the documentation above will be generated.
ksonnet env add us-west/staging https://kubecfg-1.us-west.elb.amazonaws.com
# Initialize a new staging environment at us-west, using the OpenAPI specification
# generated in the Kubernetes v1.7.1 build to generate 'ksonnet-lib'.
ksonnet env add us-west/staging https://kubecfg-1.us-west.elb.amazonaws.com --api-spec=version:v1.7.1
# Initialize a new development environment locally. This will overwrite the
# default 'default' directory structure generated by 'ksonnet-init'.
ksonnet env add default localhost:8000`,
}
var envRmCmd = &cobra.Command{
Use: "rm <env-name>",
Short: "Delete an environment within a ksonnet project",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("'env rm' takes a single argument, that is the name of the environment")
}
envName := args[0]
appDir, err := os.Getwd()
if err != nil {
return err
}
appRoot := metadata.AbsPath(appDir)
c, err := kubecfg.NewEnvRmCmd(envName, appRoot)
if err != nil {
return err
}
return c.Run()
},
Long: `Delete an environment within a ksonnet project. This is the same
as removing the <env-name> environment directory and all files contained.
Empty parent directories will also be removed.
`,
Example: ` # Remove the directory 'us-west/staging' and all contents
# in the 'environments' directory. This will also remove the parent directory
# 'us-west' if it is empty.
ksonnet env rm us-west/staging`,
}
var envListCmd = &cobra.Command{
Use: "list",
Short: "List all environments within a ksonnet project",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("'env list' takes zero arguments")
}
appDir, err := os.Getwd()
if err != nil {
return err
}
appRoot := metadata.AbsPath(appDir)
c, err := kubecfg.NewEnvListCmd(appRoot)
if err != nil {
return err
}
return c.Run(cmd.OutOrStdout())
},
Long: `List all environments within a ksonnet project. This will
display the name and the URI of each environment within the ksonnet project.`,
}
var envSetCmd = &cobra.Command{
Use: "set <env-name> [parameter-flags]",
Short: "Set environment fields such as the name, and cluster URI.",
RunE: func(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()
if len(args) != 1 {
return fmt.Errorf("'env set' takes a single argument, that is the name of the environment")
}
envName := args[0]
appDir, err := os.Getwd()
if err != nil {
return err
}
appRoot := metadata.AbsPath(appDir)
desiredEnvName, err := flags.GetString(flagEnvName)
if err != nil {
return err
}
desiredEnvURI, err := flags.GetString(flagEnvURI)
if err != nil {
return err
}
c, err := kubecfg.NewEnvSetCmd(envName, desiredEnvName, desiredEnvURI, appRoot)
if err != nil {
return err
}
return c.Run()
},
Long: `Set environment fields such as the name, and cluster URI. Changing
the name of an environment will also update the directory structure in
'environments'.
`,
Example: ` # Updates the URI of the environment 'us-west/staging'.
ksonnet env set us-west/staging --uri=http://example.com
# Updates both the name and the URI of the environment 'us-west/staging'.
# Updating the name will update the directory structure in 'environments'
ksonnet env set us-west/staging --uri=http://example.com --name=us-east/staging`,
}
......@@ -25,14 +25,11 @@ import (
"github.com/spf13/cobra"
)
const (
flagAPISpec = "api-spec"
)
func init() {
RootCmd.AddCommand(initCmd)
// TODO: We need to make this default to checking the `kubeconfig` file.
initCmd.PersistentFlags().String(flagAPISpec, "version:v1.7.0", "Manually specify API version from OpenAPI schema, cluster, or Kubernetes version")
initCmd.PersistentFlags().String(flagAPISpec, "version:v1.7.0",
"Manually specify API version from OpenAPI schema, cluster, or Kubernetes version")
}
var initCmd = &cobra.Command{
......
......@@ -51,6 +51,7 @@ const (
flagTlaVarFile = "tla-str-file"
flagResolver = "resolve-images"
flagResolvFail = "resolve-images-error"
flagAPISpec = "api-spec"
// For use in the commands (e.g., diff, apply, delete) that require either an
// environment or the -f flag.
......
// 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 (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/spf13/afero"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubespec"
)
const (
defaultEnvName = "default"
schemaFilename = "swagger.json"
extensionsLibFilename = "k.libsonnet"
k8sLibFilename = "k8s.libsonnet"
specFilename = "spec.json"
)
// Environment represents all fields of a ksonnet environment
type Environment struct {
Path string
Name string
URI string
}
// EnvironmentSpec represents the contents in spec.json.
type EnvironmentSpec struct {
URI string `json:"uri"`
}
func (m *manager) CreateEnvironment(name, uri string, spec ClusterSpec) error {
extensionsLibData, k8sLibData, specData, err := m.generateKsonnetLibData(spec)
if err != nil {
return err
}
return m.createEnvironment(name, uri, extensionsLibData, k8sLibData, specData)
}
func (m *manager) createEnvironment(name, uri string, extensionsLibData, k8sLibData, specData []byte) error {
exists, err := m.environmentExists(name)
if err != nil {
return err
}
if exists {
return fmt.Errorf("Environment '%s' already exists", name)
}
// ensure environment name does not contain punctuation
if !isValidName(name) {
return fmt.Errorf("Environment '%s' is not valid; must not contain punctuation or trailing slashes", name)
}
envPath := appendToAbsPath(m.environmentsPath, name)
err = m.appFS.MkdirAll(string(envPath), os.ModePerm)
if err != nil {
return err
}
// Generate the schema file.
schemaPath := appendToAbsPath(envPath, schemaFilename)
err = afero.WriteFile(m.appFS, string(schemaPath), specData, os.ModePerm)
if err != nil {
return err
}
k8sLibPath := appendToAbsPath(envPath, k8sLibFilename)
err = afero.WriteFile(m.appFS, string(k8sLibPath), k8sLibData, 0644)
if err != nil {
return err
}
extensionsLibPath := appendToAbsPath(envPath, extensionsLibFilename)
err = afero.WriteFile(m.appFS, string(extensionsLibPath), extensionsLibData, 0644)
if err != nil {
return err
}
// Generate the environment spec file.
envSpecData, err := generateSpecData(uri)
if err != nil {
return err
}
envSpecPath := appendToAbsPath(envPath, specFilename)
return afero.WriteFile(m.appFS, string(envSpecPath), envSpecData, os.ModePerm)
}
func (m *manager) DeleteEnvironment(name string) error {
envPath := string(appendToAbsPath(m.environmentsPath, name))
// Check whether this environment exists
envExists, err := m.environmentExists(name)
if err != nil {
return err
}
if !envExists {
return fmt.Errorf("Environment '%s' does not exist", name)
}
// Remove the directory and all files within the environment path.
err = m.appFS.RemoveAll(envPath)
if err != nil {
return err
}
// Need to ensure empty parent directories are also removed.
parentDir := name
for parentDir != "." {
parentDir = filepath.Dir(parentDir)
parentPath := string(appendToAbsPath(m.environmentsPath, parentDir))
isEmpty, err := afero.IsEmpty(m.appFS, parentPath)
if err != nil {
return err
}
if isEmpty {
err := m.appFS.RemoveAll(parentPath)
if err != nil {
return err
}
}
}
return nil
}
func (m *manager) GetEnvironments() ([]Environment, error) {
envs := []Environment{}
err := afero.Walk(m.appFS, string(m.environmentsPath), func(path string, f os.FileInfo, err error) error {
isDir, err := afero.IsDir(m.appFS, path)
if err != nil {
return err
}
if isDir {
// Only want leaf directories containing a spec.json
specPath := filepath.Join(path, specFilename)
specFileExists, err := afero.Exists(m.appFS, specPath)
if err != nil {
return err
}
if specFileExists {
envName := filepath.Clean(strings.TrimPrefix(path, string(m.environmentsPath)+"/"))
specFile, err := afero.ReadFile(m.appFS, specPath)
if err != nil {
return err
}
var envSpec EnvironmentSpec
err = json.Unmarshal(specFile, &envSpec)
if err != nil {
return err
}
envs = append(envs, Environment{Name: envName, Path: path, URI: envSpec.URI})
}
}
return nil
})
if err != nil {
return nil, err
}
return envs, nil
}
func (m *manager) SetEnvironment(name string, desired Environment) error {
// Check whether this environment exists
envExists, err := m.environmentExists(name)
if err != nil {
return err
}
if !envExists {
return fmt.Errorf("Environment '%s' does not exist", name)
}
if name == desired.Name {
return nil
}
// ensure new environment name does not contain punctuation
if !isValidName(desired.Name) {
return fmt.Errorf("Environment '%s' is not valid; must not contain punctuation or trailing slashes", desired.Name)
}
// If the name has changed, the directory location needs to be moved to
// reflect the change.
if len(desired.Name) != 0 {
// Ensure not overwriting another environment
desiredExists, err := m.environmentExists(desired.Name)
if err != nil {
return err
}
if desiredExists {
return fmt.Errorf("Can not update '%s' to '%s', it already exists", name, desired.Name)
}
// Move the directory
pathOld := string(appendToAbsPath(m.environmentsPath, name))
pathNew := string(appendToAbsPath(m.environmentsPath, desired.Name))
err = m.appFS.Rename(pathOld, pathNew)
if err != nil {
return err
}
name = desired.Name
}
// Update fields in spec.json
if len(desired.URI) != 0 {
newSpec, err := generateSpecData(desired.URI)
if err != nil {
return err
}
envPath := appendToAbsPath(m.environmentsPath, name)
specPath := appendToAbsPath(envPath, specFilename)
return afero.WriteFile(m.appFS, string(specPath), newSpec, os.ModePerm)
}
return nil
}
func (m *manager) generateKsonnetLibData(spec ClusterSpec) ([]byte, []byte, []byte, error) {
// Get cluster specification data, possibly from the network.
text, err := spec.data()
if err != nil {
return nil, nil, nil, err
}
ksonnetLibDir := appendToAbsPath(m.environmentsPath, defaultEnvName)
// Deserialize the API object.
s := kubespec.APISpec{}
err = json.Unmarshal(text, &s)
if err != nil {
return nil, nil, nil, err
}
s.Text = text
s.FilePath = filepath.Dir(string(ksonnetLibDir))
// Emit Jsonnet code.
extensionsLibData, k8sLibData, err := ksonnet.Emit(&s, nil, nil)
return extensionsLibData, k8sLibData, text, err
}
func generateSpecData(uri string) ([]byte, error) {
// Format the spec json and return; preface keys with 2 space idents.
return json.MarshalIndent(EnvironmentSpec{URI: uri}, "", " ")
}
func (m *manager) environmentExists(name string) (bool, error) {
envs, err := m.GetEnvironments()
if err != nil {
return false, err
}
envExists := false
for _, env := range envs {
if env.Name == name {
envExists = true
break
}
}
return envExists, nil
}
// regex matcher to ensure environment name does not contain punctuation
func isValidName(envName string) bool {
hasPunctuation := regexp.MustCompile(`[,;.':!()?"{}\[\]*&%@$]+`).MatchString
hasTrailingSlashes := regexp.MustCompile(`/+$`).MatchString
return !hasPunctuation(envName) && !hasTrailingSlashes(envName)
}
// 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 (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/spf13/afero"
)
const (
mockSpecJSON = "spec.json"
mockSpecJSONURI = "localhost:8080"
mockEnvName = "us-west/test"
mockEnvName2 = "us-west/prod"
mockEnvName3 = "us-east/test"
)
func mockEnvironments(t *testing.T, appName string) *manager {
spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS)
if err != nil {
t.Fatalf("Failed to parse cluster spec: %v", err)
}
appPath := AbsPath(appName)
m, err := initManager(appPath, spec, testFS)
if err != nil {
t.Fatalf("Failed to init cluster spec: %v", err)
}
envNames := []string{defaultEnvName, mockEnvName, mockEnvName2, mockEnvName3}
for _, env := range envNames {
envPath := appendToAbsPath(m.environmentsPath, env)
specPath := appendToAbsPath(envPath, mockSpecJSON)
specData, err := generateSpecData(mockSpecJSONURI)
if err != nil {
t.Fatalf("Expected to marshal:\n%s\n, but failed", mockSpecJSONURI)
}
err = afero.WriteFile(testFS, string(specPath), specData, os.ModePerm)
if err != nil {
t.Fatalf("Could not write file at path: %s", specPath)
}
testDirExists(t, string(envPath))
}
return m
}
func testDirExists(t *testing.T, path string) {
exists, err := afero.DirExists(testFS, path)
if err != nil {
t.Fatalf("Expected directory at '%s' to exist, but failed:\n%v", path, err)
} else if !exists {
t.Fatalf("Expected directory at '%s' to exist, but it does not", path)
}
}
func testDirNotExists(t *testing.T, path string) {
exists, err := afero.DirExists(testFS, path)
if err != nil {
t.Fatalf("Expected directory at '%s' to be removed, but failed:\n%v", path, err)
} else if exists {
t.Fatalf("Expected directory at '%s' to be removed, but it exists", path)
}
}
func TestDeleteEnvironment(t *testing.T) {
appName := "test-delete-envs"
m := mockEnvironments(t, appName)
// Test that both directory and empty parent directory is deleted.
expectedPath := appendToAbsPath(m.environmentsPath, mockEnvName3)
parentDir := strings.Split(mockEnvName3, "/")[0]
expectedParentPath := appendToAbsPath(m.environmentsPath, parentDir)
err := m.DeleteEnvironment(mockEnvName3)
if err != nil {
t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err)
}
testDirNotExists(t, string(expectedPath))
testDirNotExists(t, string(expectedParentPath))
// Test that only leaf directory is deleted if parent directory is shared
expectedPath = appendToAbsPath(m.environmentsPath, mockEnvName2)
parentDir = strings.Split(mockEnvName2, "/")[0]
expectedParentPath = appendToAbsPath(m.environmentsPath, parentDir)
err = m.DeleteEnvironment(mockEnvName2)
if err != nil {
t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err)
}
testDirNotExists(t, string(expectedPath))
testDirExists(t, string(expectedParentPath))
}
func TestGetEnvironments(t *testing.T) {
m := mockEnvironments(t, "test-get-envs")
envs, err := m.GetEnvironments()
if err != nil {
t.Fatalf("Expected to successfully get environments but failed:\n %s", err)
}
if len(envs) != 4 {
t.Fatalf("Expected to get %d environments, got %d", 4, len(envs))
}
if envs[0].URI != mockSpecJSONURI {
t.Fatalf("Expected env URI to be %s, got %s", mockSpecJSONURI, envs[0].URI)
}
}
func TestSetEnvironment(t *testing.T) {
appName := "test-set-envs"
m := mockEnvironments(t, appName)
setName := "new-env"
setURI := "http://example.com"
set := Environment{Name: setName, URI: setURI}
// Test updating an environment that doesn't exist
err := m.SetEnvironment("notexists", set)
if err == nil {
t.Fatal("Expected error when setting an environment that does not exist")
}
// Test updating an environment to an environment that already exists
err = m.SetEnvironment(mockEnvName, Environment{Name: mockEnvName2})
if err == nil {
t.Fatalf("Expected error when setting \"%s\" to \"%s\", because env already exists", mockEnvName, mockEnvName2)
}
// Test changing the name and URI of a an existing environment.
// Ensure new env directory is created, and old directory no longer exists.
// Also ensure URI is set in spec.json
err = m.SetEnvironment(mockEnvName, set)
if err != nil {
t.Fatalf("Could not set \"%s\", got:\n %s", mockEnvName, err)
}
envPath := appendToAbsPath(AbsPath(appName), environmentsDir)
expectedPathExists := appendToAbsPath(envPath, set.Name)
expectedPathNotExists := appendToAbsPath(envPath, mockEnvName)
testDirExists(t, string(expectedPathExists))
testDirNotExists(t, string(expectedPathNotExists))
expectedSpecPath := appendToAbsPath(expectedPathExists, specFilename)
specData, err := afero.ReadFile(testFS, string(expectedSpecPath))
if err != nil {
t.Fatalf("Failed to read spec file:\n %s", err)
}
var envSpec EnvironmentSpec
err = json.Unmarshal(specData, &envSpec)
if err != nil {
t.Fatalf("Failed to read spec file:\n %s", err)
}
if envSpec.URI != set.URI {
t.Fatalf("Expected set URI to be \"%s\", got:\n %s", set.URI, envSpec.URI)
}
}
// 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 (
......@@ -21,14 +36,16 @@ type Manager interface {
Root() AbsPath
ComponentPaths() (AbsPaths, error)
LibPaths(envName string) (libPath, envLibPath AbsPath)
CreateEnvironment(name, uri string, spec ClusterSpec) error
DeleteEnvironment(name string) error
GetEnvironments() ([]Environment, error)
SetEnvironment(name string, desired Environment) error
//
// TODO: Fill in methods as we need them.
//
// GetPrototype(id string) Protoype
// SearchPrototypes(query string) []Protoype
// VendorLibrary(uri, version string) error
// CreateEnv(name string, spec *ClusterSpec) error
// DeleteEnv(name string) error
//
}
......
// 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 (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/ksonnet"
"github.com/ksonnet/ksonnet-lib/ksonnet-gen/kubespec"
"github.com/spf13/afero"
)
......@@ -23,25 +35,17 @@ const (
componentsDir = "components"
environmentsDir = "environments"
vendorDir = "vendor"
defaultEnvName = "default"
// Environment-specific files
schemaFilename = "swagger.json"
extensionsLibFilename = "k.libsonnet"
k8sLibFilename = "k8s.libsonnet"
specFilename = "spec.json"
)
type manager struct {
appFS afero.Fs
rootPath AbsPath
ksonnetPath AbsPath
libPath AbsPath
componentsPath AbsPath
environmentsDir AbsPath
vendorDir AbsPath
rootPath AbsPath
ksonnetPath AbsPath
libPath AbsPath
componentsPath AbsPath
environmentsPath AbsPath
vendorDir AbsPath
}
func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) {
......@@ -67,12 +71,6 @@ func findManager(abs AbsPath, appFS afero.Fs) (*manager, error) {
}
func initManager(rootPath AbsPath, spec ClusterSpec, appFS afero.Fs) (*manager, error) {
// Get cluster specification data, possibly from the network.
specData, err := spec.data()
if err != nil {
return nil, err
}
m := newManager(rootPath, appFS)
// Generate the program text for ksonnet-lib.
......@@ -82,19 +80,19 @@ func initManager(rootPath AbsPath, spec ClusterSpec, appFS afero.Fs) (*manager,
// either (e.g., GET'ing the spec from a live cluster returns 404) does not
// result in a partially-initialized directory structure.
//
ksonnetLibDir := appendToAbsPath(m.environmentsDir, defaultEnvName)
extensionsLibData, k8sLibData, err := generateKsonnetLibData(ksonnetLibDir, specData)
extensionsLibData, k8sLibData, specData, err := m.generateKsonnetLibData(spec)
if err != nil {
return nil, err
}
// Initialize directory structure.
if err = m.createAppDirTree(); err != nil {
if err := m.createAppDirTree(); err != nil {
return nil, err
}
// Cache specification data.
if err = m.createEnvironment(defaultEnvName, specData, extensionsLibData, k8sLibData); err != nil {
// Initialize environment, and cache specification data.
// TODO the URI for the default environment needs to be generated from KUBECONFIG
if err := m.createEnvironment(defaultEnvName, "", extensionsLibData, k8sLibData, specData); err != nil {
return nil, err
}
......@@ -105,12 +103,12 @@ 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),
environmentsDir: appendToAbsPath(rootPath, environmentsDir),
vendorDir: appendToAbsPath(rootPath, vendorDir),
rootPath: rootPath,
ksonnetPath: appendToAbsPath(rootPath, ksonnetDir),
libPath: appendToAbsPath(rootPath, libDir),
componentsPath: appendToAbsPath(rootPath, componentsDir),
environmentsPath: appendToAbsPath(rootPath, environmentsDir),
vendorDir: appendToAbsPath(rootPath, vendorDir),
}
}
......@@ -138,32 +136,7 @@ func (m *manager) ComponentPaths() (AbsPaths, error) {
}
func (m *manager) LibPaths(envName string) (libPath, envLibPath AbsPath) {
return m.libPath, appendToAbsPath(m.environmentsDir, envName)
}
func (m *manager) createEnvironment(name string, specData, extensionsLibData, k8sLibData []byte) error {
envPath := appendToAbsPath(m.environmentsDir, name)
err := m.appFS.MkdirAll(string(envPath), os.ModePerm)
if err != nil {
return err
}
// Generate the schema file.
schemaPath := appendToAbsPath(envPath, schemaFilename)
err = afero.WriteFile(m.appFS, string(schemaPath), specData, os.ModePerm)
if err != nil {
return err
}
k8sLibPath := appendToAbsPath(envPath, k8sLibFilename)
err = afero.WriteFile(m.appFS, string(k8sLibPath), k8sLibData, 0644)
if err != nil {
return err
}
extensionsLibPath := appendToAbsPath(envPath, extensionsLibFilename)
err = afero.WriteFile(m.appFS, string(extensionsLibPath), extensionsLibData, 0644)
return err
return m.libPath, appendToAbsPath(m.environmentsPath, envName)
}
func (m *manager) createAppDirTree() error {
......@@ -179,6 +152,7 @@ func (m *manager) createAppDirTree() error {
m.ksonnetPath,
m.libPath,
m.componentsPath,
m.environmentsPath,
m.vendorDir,
}
......@@ -190,18 +164,3 @@ func (m *manager) createAppDirTree() error {
return nil
}
func generateKsonnetLibData(ksonnetLibDir AbsPath, text []byte) ([]byte, []byte, error) {
// Deserialize the API object.
s := kubespec.APISpec{}
err := json.Unmarshal(text, &s)
if err != nil {
return nil, nil, err
}
s.Text = text
s.FilePath = filepath.Dir(string(ksonnetLibDir))
// Emit Jsonnet code.
return ksonnet.Emit(&s, nil, nil)
}
// 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 (
......
// 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 kubecfg
import (
"fmt"
"io"
"sort"
"strings"
"github.com/ksonnet/kubecfg/metadata"
)
type EnvAddCmd struct {
name string
uri string
rootPath metadata.AbsPath
spec metadata.ClusterSpec
}
func NewEnvAddCmd(name, uri, specFlag string, rootPath metadata.AbsPath) (*EnvAddCmd, error) {
spec, err := metadata.ParseClusterSpec(specFlag)
if err != nil {
return nil, err
}
return &EnvAddCmd{name: name, uri: uri, spec: spec, rootPath: rootPath}, nil
}
func (c *EnvAddCmd) Run() error {
manager, err := metadata.Find(c.rootPath)
if err != nil {
return err
}
return manager.CreateEnvironment(c.name, c.uri, c.spec)
}
// ==================================================================
type EnvRmCmd struct {
name string
rootPath metadata.AbsPath
}
func NewEnvRmCmd(name string, rootPath metadata.AbsPath) (*EnvRmCmd, error) {
return &EnvRmCmd{name: name, rootPath: rootPath}, nil
}
func (c *EnvRmCmd) Run() error {
manager, err := metadata.Find(c.rootPath)
if err != nil {
return err
}
return manager.DeleteEnvironment(c.name)
}
// ==================================================================
type EnvListCmd struct {
rootPath metadata.AbsPath
}
func NewEnvListCmd(rootPath metadata.AbsPath) (*EnvListCmd, error) {
return &EnvListCmd{rootPath: rootPath}, nil
}
func (c *EnvListCmd) Run(out io.Writer) error {
manager, err := metadata.Find(c.rootPath)
if err != nil {
return err
}
envs, err := manager.GetEnvironments()
if err != nil {
return err
}
// Sort environments by ascending alphabetical name
sort.Slice(envs, func(i, j int) bool { return envs[i].Name < envs[j].Name })
// Format each environment information for pretty printing.
// Each environment should be outputted like the following:
//
// us-west/dev localhost:8080
// us-west/staging http://example.com
//
// To accomplish this, need to find the longest env name for proper padding.
maxNameLen := 0
for _, env := range envs {
if l := len(env.Name); l > maxNameLen {
maxNameLen = l
}
}
lines := []string{}
for _, env := range envs {
nameSpacing := strings.Repeat(" ", maxNameLen-len(env.Name)+1)
lines = append(lines, env.Name+nameSpacing+env.URI+"\n")
}
formattedEnvsList := strings.Join(lines, "")
_, err = fmt.Fprint(out, formattedEnvsList)
return err
}
// ==================================================================
type EnvSetCmd struct {
name string
desiredName string
desiredURI string
rootPath metadata.AbsPath
}
func NewEnvSetCmd(name, desiredName, desiredURI string, rootPath metadata.AbsPath) (*EnvSetCmd, error) {
return &EnvSetCmd{name: name, desiredName: desiredName, desiredURI: desiredURI, rootPath: rootPath}, nil
}
func (c *EnvSetCmd) Run() error {
manager, err := metadata.Find(c.rootPath)
if err != nil {
return err
}
desired := metadata.Environment{Name: c.desiredName, URI: c.desiredURI}
return manager.SetEnvironment(c.name, desired)
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment