Skip to content
Snippets Groups Projects
Commit 56777310 authored by Jessica Yuen's avatar Jessica Yuen
Browse files

Add subcommand 'env add'

'env add <env-name> <env-uri>' will create a new environment within a
ksonnet project, by generating a new directory, 'env-name', within the
'envs' directory. Each environment will contain environment-specfic
files. Notably, a new environment-specific file is 'spec.json'.
'spec.json' currently only contains the 'env-uri' of the Kubernetes
cluster located at the added environment.

Below is an example directory structure for the environment
'us-west/staging':

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/                 ...
parent 70af2cd3
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"
)
func init() {
RootCmd.AddCommand(envCmd)
envCmd.AddCommand(envAddCmd)
// 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")
}
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())
},
}
// TODO Currently, by default, this command will overwrite the environment with
// the same name, if it exists. May want to extend the behavior later to provide
// a user prompt (y/n).
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`,
}
// 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"
"os"
"path/filepath"
"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"
)
type Environment struct {
Path string
Name string
URI string
}
type EnvironmentSpec struct {
URI string `json:"uri"`
}
func (m *manager) CreateEnvironment(name, uri string, spec ClusterSpec, extensionsLibData, k8sLibData []byte) error {
envPath := appendToAbsPath(m.environmentsDir, name)
err := m.appFS.MkdirAll(string(envPath), os.ModePerm)
if err != nil {
return err
}
// Get cluster specification data, possibly from the network.
specData, err := spec.data()
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) GenerateKsonnetLibData(spec ClusterSpec) ([]byte, []byte, error) {
// Get cluster specification data, possibly from the network.
text, err := spec.data()
if err != nil {
return nil, nil, err
}
ksonnetLibDir := appendToAbsPath(m.environmentsDir, defaultEnvName)
// 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)
}
func generateSpecData(uri string) ([]byte, error) {
// Format the spec json and return; preface keys with 2 space idents.
return json.MarshalIndent(EnvironmentSpec{URI: uri}, "", " ")
}
......@@ -21,13 +21,14 @@ type Manager interface {
Root() AbsPath
ComponentPaths() (AbsPaths, error)
LibPaths(envName string) (libPath, envLibPath AbsPath)
GenerateKsonnetLibData(spec ClusterSpec) ([]byte, []byte, error)
CreateEnvironment(name, uri string, spec ClusterSpec, extensionsLibData, k8sLibData []byte) 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
//
}
......
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,14 +20,6 @@ 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 {
......@@ -67,12 +56,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 +65,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, 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, "", spec, extensionsLibData, k8sLibData); err != nil {
return nil, err
}
......@@ -141,31 +124,6 @@ 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
}
func (m *manager) createAppDirTree() error {
exists, err := afero.DirExists(m.appFS, string(m.rootPath))
if err != nil {
......@@ -190,18 +148,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 kubecfg
import (
"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
}
extensionsLibData, k8sLibData, err := manager.GenerateKsonnetLibData(c.spec)
if err != nil {
return err
}
return manager.CreateEnvironment(c.name, c.uri, c.spec, extensionsLibData, k8sLibData)
}
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