Refactor environment CLI command (#1265)
This commit is contained in:
@@ -22,7 +22,7 @@ require (
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-openapi/spec v0.17.2
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/gogo/protobuf v1.2.0 // indirect
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
|
||||
github.com/golang/protobuf v1.3.1
|
||||
|
||||
@@ -128,8 +128,9 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -202,6 +203,7 @@ github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwK
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
@@ -395,6 +397,7 @@ golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -101,3 +101,7 @@ const (
|
||||
// set a max number for iterations to prevent infinite processing of canary config
|
||||
MaxIterationsForCanaryConfig = 10
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSpecializationTimeOut = 120
|
||||
)
|
||||
|
||||
@@ -21,3 +21,9 @@ limitations under the License.
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=fission.io
|
||||
package v1
|
||||
|
||||
const (
|
||||
CRD_VERSION = "fission.io/v1"
|
||||
|
||||
CRD_NAME_ENVIRONMENT = "Environment"
|
||||
)
|
||||
|
||||
@@ -235,6 +235,7 @@ func TestEnvironmentApi(t *testing.T) {
|
||||
|
||||
testEnv.Metadata.ResourceVersion = ""
|
||||
testEnv.Metadata.Name = "bar"
|
||||
|
||||
m2, err := g.client.EnvironmentCreate(testEnv)
|
||||
panicIf(err)
|
||||
defer g.client.EnvironmentDelete(m2)
|
||||
|
||||
@@ -25,20 +25,25 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/generator/encoder"
|
||||
v1generator "github.com/fission/fission/pkg/generator/v1"
|
||||
)
|
||||
|
||||
func (c *Client) EnvironmentCreate(env *fv1.Environment) (*metav1.ObjectMeta, error) {
|
||||
err := env.Validate()
|
||||
func getEnvEncodingPayload(env *fv1.Environment) ([]byte, error) {
|
||||
generator, err := v1generator.CreateEnvironmentGeneratorFromObj(env)
|
||||
if err != nil {
|
||||
return nil, fv1.AggregateValidationErrors("Environment", err)
|
||||
return nil, err
|
||||
}
|
||||
return generator.StructuredGenerate(encoder.DefaultJSONEncoder())
|
||||
}
|
||||
|
||||
reqbody, err := json.Marshal(env)
|
||||
func (c *Client) EnvironmentCreate(env *fv1.Environment) (*metav1.ObjectMeta, error) {
|
||||
data, err := getEnvEncodingPayload(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(c.url("environments"), "application/json", bytes.NewReader(reqbody))
|
||||
resp, err := http.Post(c.url("environments"), "application/json", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,18 +88,14 @@ func (c *Client) EnvironmentGet(m *metav1.ObjectMeta) (*fv1.Environment, error)
|
||||
}
|
||||
|
||||
func (c *Client) EnvironmentUpdate(env *fv1.Environment) (*metav1.ObjectMeta, error) {
|
||||
err := env.Validate()
|
||||
if err != nil {
|
||||
return nil, fv1.AggregateValidationErrors("Environment", err)
|
||||
}
|
||||
|
||||
reqbody, err := json.Marshal(env)
|
||||
data, err := getEnvEncodingPayload(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
relativeUrl := fmt.Sprintf("environments/%v", env.Metadata.Name)
|
||||
|
||||
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||
resp, err := c.put(relativeUrl, "application/json", data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -38,9 +38,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
DeploymentKind = "Deployment"
|
||||
DeploymentVersion = "extensions/v1beta1"
|
||||
DEFAULT_SPECIALIZATION_TIMEOUT = 120
|
||||
DeploymentKind = "Deployment"
|
||||
DeploymentVersion = "extensions/v1beta1"
|
||||
)
|
||||
|
||||
func (deploy *NewDeploy) createOrGetDeployment(fn *fv1.Function, env *fv1.Environment,
|
||||
@@ -418,8 +417,8 @@ func (deploy *NewDeploy) deleteSvc(ns string, name string) error {
|
||||
|
||||
func (deploy *NewDeploy) waitForDeploy(depl *v1beta1.Deployment, replicas int32, specializationTimeout int) (*v1beta1.Deployment, error) {
|
||||
// if no specializationTimeout is set, use default value
|
||||
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
|
||||
specializationTimeout = DEFAULT_SPECIALIZATION_TIMEOUT
|
||||
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
|
||||
specializationTimeout = fv1.DefaultSpecializationTimeOut
|
||||
}
|
||||
|
||||
for i := 0; i < specializationTimeout; i++ {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 cli
|
||||
|
||||
type (
|
||||
Input interface {
|
||||
//Parse(input interface{}) error
|
||||
|
||||
// IsSet checks whether a flag has been set by the user
|
||||
IsSet(key string) bool
|
||||
|
||||
// Bool returns true if given flag has been set;
|
||||
// otherwise, return false.
|
||||
Bool(key string) bool
|
||||
|
||||
// String returns string value of given flag.
|
||||
String(key string) string
|
||||
|
||||
// StringSlice returns string slice of given flag..
|
||||
StringSlice(key string) []string
|
||||
|
||||
// Int returns int value of given flag.nd false.
|
||||
Int(key string) int
|
||||
|
||||
// IntSlice returns int slice of given flag.lse.
|
||||
IntSlice(key string) []int
|
||||
|
||||
// Int64 returns int64 value of given flag. false.
|
||||
Int64(key string) int64
|
||||
|
||||
// Int64Slice returns int64 slice of given flag.e.
|
||||
Int64Slice(key string) []int64
|
||||
|
||||
// GlobalBool returns true if given global flag has been set;
|
||||
// otherwise, return false.
|
||||
GlobalBool(key string) bool
|
||||
|
||||
// GlobalString returns global string value of given flag.
|
||||
GlobalString(key string) string
|
||||
|
||||
// GlobalStringSlice returns global string slice of given flag.
|
||||
GlobalStringSlice(key string) []string
|
||||
|
||||
// GlobalInt returns global int value of given flag.
|
||||
GlobalInt(key string) int
|
||||
|
||||
// GlobalIntSlice returns global int slice of given flag.
|
||||
GlobalIntSlice(key string) []int
|
||||
|
||||
// GlobalInt64 returns global int64 value of given flag.
|
||||
GlobalInt64(key string) int64
|
||||
|
||||
// GlobalInt64Slice returns global int64 slice of given flag.
|
||||
GlobalInt64Slice(key string) []int64
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 cliwrapper is a wrapper that allowing functions to access flag value in an
|
||||
// identical way no matter what underlying CLI package used. It brings couple benefits
|
||||
// for doing this:
|
||||
// 1. Separate CLI function and CLI package.
|
||||
// 2. Easier to write test no matter what CLI package actually used.
|
||||
// 3. Migrate to new CLI package without changing the way for CLI function to access flag value.
|
||||
package cliwrapper
|
||||
@@ -0,0 +1 @@
|
||||
package driver
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 urfavecli
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
fCli "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
)
|
||||
|
||||
var _ fCli.Input = &Cli{}
|
||||
|
||||
type Cli struct {
|
||||
c *cli.Context
|
||||
}
|
||||
|
||||
// Parse is only for converting urfave *cli.Context to Input and will be removed in future.
|
||||
func Parse(c *cli.Context) fCli.Input {
|
||||
return Cli{c: c}
|
||||
}
|
||||
|
||||
func Wrapper(action cmd.CommandAction) func(*cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
e := action(Cli{c: c})
|
||||
// Urfave cli doesn't exit with error code even error is not nil.
|
||||
// We have to check whether error is empty and print error log here.
|
||||
if e != nil {
|
||||
log.Fatalf("%v", e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
func (u Cli) IsSet(key string) bool {
|
||||
return u.c.IsSet(key)
|
||||
}
|
||||
|
||||
func (u Cli) Bool(key string) bool {
|
||||
return u.c.Bool(key)
|
||||
}
|
||||
|
||||
func (u Cli) String(key string) string {
|
||||
return u.c.String(key)
|
||||
}
|
||||
|
||||
func (u Cli) StringSlice(key string) []string {
|
||||
return u.c.StringSlice(key)
|
||||
}
|
||||
|
||||
func (u Cli) Int(key string) int {
|
||||
return u.c.Int(key)
|
||||
}
|
||||
|
||||
func (u Cli) IntSlice(key string) []int {
|
||||
return u.c.IntSlice(key)
|
||||
}
|
||||
|
||||
func (u Cli) Int64(key string) int64 {
|
||||
return u.c.Int64(key)
|
||||
}
|
||||
|
||||
func (u Cli) Int64Slice(key string) []int64 {
|
||||
return u.c.Int64Slice(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalBool(key string) bool {
|
||||
return u.c.GlobalBool(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalString(key string) string {
|
||||
return u.c.GlobalString(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalStringSlice(key string) []string {
|
||||
return u.c.GlobalStringSlice(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalInt(key string) int {
|
||||
return u.c.GlobalInt(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalIntSlice(key string) []int {
|
||||
return u.c.GlobalIntSlice(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalInt64(key string) int64 {
|
||||
return u.c.GlobalInt64(key)
|
||||
}
|
||||
|
||||
func (u Cli) GlobalInt64Slice(key string) []int64 {
|
||||
return u.c.GlobalInt64Slice(key)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 (
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
)
|
||||
|
||||
type (
|
||||
CommandAction func(flags cli.Input) error
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 "strings"
|
||||
|
||||
const (
|
||||
GLOBAL_VERBOSITY = "verbosity"
|
||||
GLOBAL_PLUGIN = "plugin"
|
||||
|
||||
FISSION_SERVER = "server"
|
||||
|
||||
RESOURCE_NAME = "name"
|
||||
|
||||
ENVIRONMENT_NAMESPACE = "envNamespace"
|
||||
ENVIRONMENT_NAMESPACE_ALIAS = "envns"
|
||||
ENVIRONMENT_POOLSIZE = "poolsize"
|
||||
ENVIRONMENT_IMAGE = "image"
|
||||
ENVIRONMENT_BUILDER = "builder"
|
||||
ENVIRONMENT_BUILDCOMMAND = "buildcmd"
|
||||
ENVIRONMENT_KEEPARCHIVE = "keeparchive"
|
||||
ENVIRONMENT_EXTERNAL_NETWORK = "externalnetwork"
|
||||
ENVIRONMENT_GRACE_PERIOD = "graceperiod"
|
||||
ENVIRONMENT_GRACE_PERIOD_ALIAS = "period"
|
||||
ENVIRONMENT_VERSION = "version"
|
||||
|
||||
SPEC_SPEC = "spec"
|
||||
SPEC_SPECDIR = "specdir"
|
||||
|
||||
RUNTIME_MINCPU = "mincpu"
|
||||
RUNTIME_MAXCPU = "maxcpu"
|
||||
RUNTIME_MINMEMORY = "minmemory"
|
||||
RUNTIME_MAXMEMORY = "maxmemory"
|
||||
RUNTIME_MINSCALE = "minscale"
|
||||
RUNTIME_MAXSCALE = "maxscale"
|
||||
RUNTIME_TARGETCPU = "targetcpu"
|
||||
)
|
||||
|
||||
// GetCliFlagName concatenates flag and its alias into a command flag name.
|
||||
func GetCliFlagName(flags ...string) string {
|
||||
return strings.Join(flags, ", ")
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
type CreateSubCommand struct {
|
||||
client *client.Client
|
||||
env *fv1.Environment
|
||||
}
|
||||
|
||||
func Create(flags cli.Input) error {
|
||||
opts := CreateSubCommand{
|
||||
client: cmd.GetServer(flags),
|
||||
}
|
||||
return opts.do(flags)
|
||||
}
|
||||
|
||||
func (opts *CreateSubCommand) do(flags cli.Input) error {
|
||||
err := opts.complete(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return opts.run(flags)
|
||||
}
|
||||
|
||||
func (opts *CreateSubCommand) complete(flags cli.Input) error {
|
||||
env, err := createEnvironmentFromCmd(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.env = env
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *CreateSubCommand) run(flags cli.Input) error {
|
||||
m, err := cmd.GetMetadata(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envList, err := opts.client.EnvironmentList(m.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if len(envList) > 0 {
|
||||
log.Verbose(2, "%d environment(s) are present in the %s namespace. "+
|
||||
"These environments are not isolated from each other; use separate namespaces if you need isolation.",
|
||||
len(envList), m.Namespace)
|
||||
}
|
||||
|
||||
// if we're writing a spec, don't call the API
|
||||
// save to spec file
|
||||
if flags.Bool(cmd.SPEC_SPEC) {
|
||||
specFile := fmt.Sprintf("env-%v.yaml", m.Name)
|
||||
err = spec.SpecSave(*opts.env, specFile)
|
||||
util.CheckErr(err, "create environment spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = opts.client.EnvironmentCreate(opts.env)
|
||||
util.CheckErr(err, "create environment")
|
||||
|
||||
fmt.Printf("environment '%v' created\n", m.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// createEnvironmentFromCmd creates environment initialized with CLI input.
|
||||
func createEnvironmentFromCmd(flags cli.Input) (*fv1.Environment, error) {
|
||||
e := &multierror.Error{}
|
||||
|
||||
envNamespace := flags.String(cmd.ENVIRONMENT_NAMESPACE)
|
||||
envBuildCmd := flags.String(cmd.ENVIRONMENT_BUILDCOMMAND)
|
||||
envExternalNetwork := flags.Bool(cmd.ENVIRONMENT_EXTERNAL_NETWORK)
|
||||
keepArchive := flags.Bool(cmd.ENVIRONMENT_KEEPARCHIVE)
|
||||
|
||||
envName := flags.String(cmd.RESOURCE_NAME)
|
||||
if len(envName) == 0 {
|
||||
e = multierror.Append(e, errors.New("Need a name, use --name."))
|
||||
}
|
||||
|
||||
envImg := flags.String(cmd.ENVIRONMENT_IMAGE)
|
||||
if len(envImg) == 0 {
|
||||
e = multierror.Append(e, errors.New("Need an image, use --image."))
|
||||
}
|
||||
|
||||
envGracePeriod := flags.Int64(cmd.ENVIRONMENT_GRACE_PERIOD)
|
||||
if envGracePeriod <= 0 {
|
||||
envGracePeriod = 360
|
||||
}
|
||||
|
||||
envVersion := flags.Int(cmd.ENVIRONMENT_VERSION)
|
||||
// Environment API interface version is not specified and
|
||||
// builder image is empty, set default interface version
|
||||
if envVersion == 0 {
|
||||
envVersion = 1
|
||||
}
|
||||
|
||||
envBuilderImg := flags.String(cmd.ENVIRONMENT_BUILDER)
|
||||
if len(envBuilderImg) > 0 {
|
||||
if !flags.IsSet(cmd.ENVIRONMENT_VERSION) {
|
||||
// TODO: remove set env version to 2 silently, we need to warn user to set it explicitly.
|
||||
envVersion = 2
|
||||
}
|
||||
if len(envBuildCmd) == 0 {
|
||||
envBuildCmd = "build"
|
||||
}
|
||||
}
|
||||
|
||||
poolsize := 3
|
||||
if flags.IsSet(cmd.ENVIRONMENT_POOLSIZE) {
|
||||
poolsize = flags.Int(cmd.ENVIRONMENT_POOLSIZE)
|
||||
// TODO: remove silently version 3 assignment, we need to warn user to set it explicitly.
|
||||
envVersion = 3
|
||||
}
|
||||
|
||||
resourceReq, err := cmd.GetResourceReqs(flags, nil)
|
||||
if err != nil {
|
||||
e = multierror.Append(e, err)
|
||||
}
|
||||
|
||||
if e.ErrorOrNil() != nil {
|
||||
return nil, e.ErrorOrNil()
|
||||
}
|
||||
|
||||
env := &fv1.Environment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: fv1.CRD_NAME_ENVIRONMENT,
|
||||
APIVersion: fv1.CRD_VERSION,
|
||||
},
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: envName,
|
||||
Namespace: envNamespace,
|
||||
},
|
||||
Spec: fv1.EnvironmentSpec{
|
||||
Version: envVersion,
|
||||
Runtime: fv1.Runtime{
|
||||
Image: envImg,
|
||||
},
|
||||
Builder: fv1.Builder{
|
||||
Image: envBuilderImg,
|
||||
Command: envBuildCmd,
|
||||
},
|
||||
Poolsize: poolsize,
|
||||
Resources: *resourceReq,
|
||||
AllowAccessToExternalNetwork: envExternalNetwork,
|
||||
TerminationGracePeriod: envGracePeriod,
|
||||
KeepArchive: keepArchive,
|
||||
},
|
||||
}
|
||||
|
||||
err = env.Validate()
|
||||
if err != nil {
|
||||
return nil, fv1.AggregateValidationErrors("Environment", err)
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
cmdutils "github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
type DeleteSubCommand struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func Delete(flags cli.Input) error {
|
||||
opts := DeleteSubCommand{
|
||||
client: cmdutils.GetServer(flags),
|
||||
}
|
||||
return opts.do(flags)
|
||||
}
|
||||
|
||||
func (opts *DeleteSubCommand) do(flags cli.Input) error {
|
||||
m, err := cmdutils.GetMetadata(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = opts.client.EnvironmentDelete(m)
|
||||
util.CheckErr(err, "delete environment")
|
||||
|
||||
fmt.Printf("environment '%v' deleted\n", m.Name)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
cmdutils "github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
type GetSubCommand struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func Get(flags cli.Input) error {
|
||||
opts := GetSubCommand{
|
||||
client: cmdutils.GetServer(flags),
|
||||
}
|
||||
return opts.do(flags)
|
||||
}
|
||||
|
||||
func (opts *GetSubCommand) do(flags cli.Input) error {
|
||||
m, err := cmdutils.GetMetadata(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := opts.client.EnvironmentGet(m)
|
||||
util.CheckErr(err, "get environment")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\n", "NAME", "UID", "IMAGE")
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\n",
|
||||
env.Metadata.Name, env.Metadata.UID, env.Spec.Runtime.Image)
|
||||
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 environment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
cmdutils "github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
type ListSubCommand struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func List(flags cli.Input) error {
|
||||
opts := ListSubCommand{
|
||||
client: cmdutils.GetServer(flags),
|
||||
}
|
||||
return opts.do(flags)
|
||||
}
|
||||
|
||||
func (opts *ListSubCommand) do(flags cli.Input) error {
|
||||
envNamespace := flags.String(cmdutils.ENVIRONMENT_NAMESPACE)
|
||||
|
||||
envs, err := opts.client.EnvironmentList(envNamespace)
|
||||
util.CheckErr(err, "list environments")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "UID", "IMAGE", "BUILDER_IMAGE", "POOLSIZE", "MINCPU", "MAXCPU", "MINMEMORY", "MAXMEMORY", "EXTNET", "GRACETIME")
|
||||
for _, env := range envs {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
||||
env.Metadata.Name, env.Metadata.UID, env.Spec.Runtime.Image, env.Spec.Builder.Image, env.Spec.Poolsize,
|
||||
env.Spec.Resources.Requests.Cpu(), env.Spec.Resources.Limits.Cpu(),
|
||||
env.Spec.Resources.Requests.Memory(), env.Spec.Resources.Limits.Memory(),
|
||||
env.Spec.AllowAccessToExternalNetwork, env.Spec.TerminationGracePeriod)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 environment
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
type UpdateSubCommand struct {
|
||||
client *client.Client
|
||||
env *fv1.Environment
|
||||
}
|
||||
|
||||
func Update(flags cli.Input) error {
|
||||
opts := UpdateSubCommand{
|
||||
client: cmd.GetServer(flags),
|
||||
}
|
||||
return opts.do(flags)
|
||||
}
|
||||
|
||||
func (opts *UpdateSubCommand) do(flags cli.Input) error {
|
||||
err := opts.complete(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return opts.run(flags)
|
||||
}
|
||||
|
||||
func (opts *UpdateSubCommand) complete(flags cli.Input) error {
|
||||
m, err := cmd.GetMetadata(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env, err := opts.client.EnvironmentGet(m)
|
||||
util.CheckErr(err, "find environment")
|
||||
|
||||
env, err = updateExistingEnvironmentWithCmd(env, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.env = env
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *UpdateSubCommand) run(flags cli.Input) error {
|
||||
_, err := opts.client.EnvironmentUpdate(opts.env)
|
||||
util.CheckErr(err, "update environment")
|
||||
|
||||
fmt.Printf("environment '%v' updated\n", opts.env.Metadata.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateExistingEnvironmentWithCmd updates a existing environment's value based on CLI input.
|
||||
func updateExistingEnvironmentWithCmd(env *fv1.Environment, flags cli.Input) (*fv1.Environment, error) {
|
||||
e := &multierror.Error{}
|
||||
|
||||
envImg := flags.String(cmd.ENVIRONMENT_IMAGE)
|
||||
envBuilderImg := flags.String(cmd.ENVIRONMENT_BUILDER)
|
||||
envBuildCmd := flags.String(cmd.ENVIRONMENT_BUILDCOMMAND)
|
||||
envExternalNetwork := flags.Bool(cmd.ENVIRONMENT_EXTERNAL_NETWORK)
|
||||
|
||||
if len(envImg) == 0 && len(envBuilderImg) == 0 && len(envBuildCmd) == 0 {
|
||||
e = multierror.Append(e, errors.New("need --image to specify env image, or use --builder to specify env builder, or use --buildcmd to specify new build command"))
|
||||
}
|
||||
|
||||
if len(envImg) > 0 {
|
||||
env.Spec.Runtime.Image = envImg
|
||||
}
|
||||
|
||||
if env.Spec.Version == 1 && (len(envBuilderImg) > 0 || len(envBuildCmd) > 0) {
|
||||
e = multierror.Append(e, errors.New("version 1 Environments do not support builders. Must specify --version=2"))
|
||||
}
|
||||
|
||||
if len(envBuilderImg) > 0 {
|
||||
env.Spec.Builder.Image = envBuilderImg
|
||||
}
|
||||
if len(envBuildCmd) > 0 {
|
||||
env.Spec.Builder.Command = envBuildCmd
|
||||
}
|
||||
|
||||
if flags.IsSet(cmd.ENVIRONMENT_POOLSIZE) {
|
||||
env.Spec.Poolsize = flags.Int(cmd.ENVIRONMENT_POOLSIZE)
|
||||
}
|
||||
|
||||
if flags.IsSet(cmd.ENVIRONMENT_GRACE_PERIOD) {
|
||||
env.Spec.TerminationGracePeriod = flags.Int64(cmd.ENVIRONMENT_GRACE_PERIOD)
|
||||
}
|
||||
|
||||
if flags.IsSet(cmd.ENVIRONMENT_KEEPARCHIVE) {
|
||||
env.Spec.KeepArchive = flags.Bool(cmd.ENVIRONMENT_KEEPARCHIVE)
|
||||
}
|
||||
|
||||
env.Spec.AllowAccessToExternalNetwork = envExternalNetwork
|
||||
|
||||
if flags.IsSet(cmd.RUNTIME_MINCPU) || flags.IsSet(cmd.RUNTIME_MAXCPU) ||
|
||||
flags.IsSet(cmd.RUNTIME_MINMEMORY) || flags.IsSet(cmd.RUNTIME_MAXMEMORY) ||
|
||||
flags.IsSet(cmd.RUNTIME_MINSCALE) || flags.IsSet(cmd.RUNTIME_MAXSCALE) {
|
||||
e = multierror.Append(e, errors.New("updating resource limits/requests for existing environments is currently unsupported; re-create the environment instead"))
|
||||
}
|
||||
|
||||
if e.ErrorOrNil() != nil {
|
||||
return nil, e.ErrorOrNil()
|
||||
}
|
||||
|
||||
return env, nil
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
"github.com/fission/fission/pkg/generator/encoder"
|
||||
v1generator "github.com/fission/fission/pkg/generator/v1"
|
||||
)
|
||||
|
||||
var specDefaultEncoder = encoder.DefaultYAMLEncoder()
|
||||
|
||||
const (
|
||||
FISSION_DEPLOYMENT_NAME_KEY = "fission-name"
|
||||
FISSION_DEPLOYMENT_UID_KEY = "fission-uid"
|
||||
|
||||
SPEC_API_VERSION = "fission.io/v1"
|
||||
ARCHIVE_URL_PREFIX string = "archive://"
|
||||
SPEC_README = `
|
||||
Fission Specs
|
||||
=============
|
||||
|
||||
This is a set of specifications for a Fission app. This includes functions,
|
||||
environments, and triggers; we collectively call these things "resources".
|
||||
|
||||
How to use these specs
|
||||
----------------------
|
||||
|
||||
These specs are handled with the 'fission spec' command. See 'fission spec --help'.
|
||||
|
||||
'fission spec apply' will "apply" all resources specified in this directory to your
|
||||
cluster. That means it checks what resources exist on your cluster, what resources are
|
||||
specified in the specs directory, and reconciles the difference by creating, updating or
|
||||
deleting resources on the cluster.
|
||||
|
||||
'fission spec apply' will also package up your source code (or compiled binaries) and
|
||||
upload the archives to the cluster if needed. It uses 'ArchiveUploadSpec' resources in
|
||||
this directory to figure out which files to archive.
|
||||
|
||||
You can use 'fission spec apply --watch' to watch for file changes and continuously keep
|
||||
the cluster updated.
|
||||
|
||||
You can add YAMLs to this directory by writing them manually, but it's easier to generate
|
||||
them. Use 'fission function create --spec' to generate a function spec,
|
||||
'fission environment create --spec' to generate an environment spec, and so on.
|
||||
|
||||
You can edit any of the files in this directory, except 'fission-deployment-config.yaml',
|
||||
which contains a UID that you should never change. To apply your changes simply use
|
||||
'fission spec apply'.
|
||||
|
||||
fission-deployment-config.yaml
|
||||
------------------------------
|
||||
|
||||
fission-deployment-config.yaml contains a UID. This UID is what fission uses to correlate
|
||||
resources on the cluster to resources in this directory.
|
||||
|
||||
All resources created by 'fission spec apply' are annotated with this UID. Resources on
|
||||
the cluster that are _not_ annotated with this UID are never modified or deleted by
|
||||
fission.
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
// CLI spec types
|
||||
type (
|
||||
// DeploymentConfig is the global configuration for a set of Fission specs.
|
||||
DeploymentConfig struct {
|
||||
// TypeMeta describes the type of this object. It is inlined. The Kind
|
||||
// field should always be "DeploymentConfig".
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Name is a user-friendly name for the deployment. It is also stored in
|
||||
// all uploaded resources as an annotation.
|
||||
Name string `json:"name"`
|
||||
|
||||
// UID uniquely identifies the deployment. It is stored as a label and
|
||||
// used to find resources to clean up when local specs are changed.
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
|
||||
// ArchiveUploadSpec specifies a set of files to be archived and uploaded.
|
||||
//
|
||||
// The resulting archive can be referenced as archive://<Name> in PackageSpecs,
|
||||
// using the name specified in the archive. The fission spec applier will
|
||||
// replace the archive:// URL with a real HTTP URL after uploading the file.
|
||||
ArchiveUploadSpec struct {
|
||||
// TypeMeta describes the type of this object. It is inlined. The Kind
|
||||
// field should always be "ArchiveUploadSpec".
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Name is a local name that can be used to reference this archive. It
|
||||
// must be unique; duplicate names will cause an error while handling
|
||||
// specs.
|
||||
Name string `json:"name"`
|
||||
|
||||
// RootDir specifies the root that the globs below are relative to. It
|
||||
// is optional and defaults to the parent directory of the spec
|
||||
// directory: for example, if the deployment config is at
|
||||
// /path/to/project/specs/config.yaml, the RootDir is /path/to/project.
|
||||
RootDir string `json:"rootdir,omitempty"`
|
||||
|
||||
// IncludeGlobs is a list of Unix shell globs to include
|
||||
IncludeGlobs []string `json:"include,omitempty"`
|
||||
|
||||
// ExcludeGlobs is a list of globs to exclude from the set specified by
|
||||
// IncludeGlobs.
|
||||
ExcludeGlobs []string `json:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// TypeMeta is the same as Kubernetes' TypeMeta, and allows us to version and
|
||||
// unmarshal local-only objects (like ArchiveUploadSpec) the same way that
|
||||
// Kubernetes does.
|
||||
TypeMeta struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
}
|
||||
|
||||
FissionResources struct {
|
||||
DeploymentConfig DeploymentConfig
|
||||
Packages []fv1.Package
|
||||
Functions []fv1.Function
|
||||
Environments []fv1.Environment
|
||||
HttpTriggers []fv1.HTTPTrigger
|
||||
KubernetesWatchTriggers []fv1.KubernetesWatchTrigger
|
||||
TimeTriggers []fv1.TimeTrigger
|
||||
MessageQueueTriggers []fv1.MessageQueueTrigger
|
||||
ArchiveUploadSpecs []ArchiveUploadSpec
|
||||
|
||||
SourceMap SourceMap
|
||||
}
|
||||
|
||||
ResourceApplyStatus struct {
|
||||
Created []*metav1.ObjectMeta
|
||||
Updated []*metav1.ObjectMeta
|
||||
Deleted []*metav1.ObjectMeta
|
||||
}
|
||||
|
||||
Location struct {
|
||||
Path string
|
||||
Line int
|
||||
}
|
||||
|
||||
SourceMap struct {
|
||||
// kind -> namespace -> name -> location
|
||||
Locations map[string](map[string](map[string]Location))
|
||||
}
|
||||
)
|
||||
|
||||
func MapKey(m *metav1.ObjectMeta) string {
|
||||
return fmt.Sprintf("%v:%v", m.Namespace, m.Name)
|
||||
}
|
||||
|
||||
// Save saves object encoded value to spec file under given spec directory
|
||||
func Save(data []byte, specDir string, specFile string) error {
|
||||
// verify
|
||||
if _, err := os.Stat(filepath.Join(specDir, "fission-deployment-config.yaml")); os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "Couldn't find specs, run `fission spec init` first")
|
||||
}
|
||||
|
||||
filename := filepath.Join(specDir, specFile)
|
||||
// check if the file is new
|
||||
newFile := false
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
newFile = true
|
||||
}
|
||||
|
||||
// open spec file to append or write
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "couldn't create spec file")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// if we're appending, add a yaml document separator
|
||||
if !newFile {
|
||||
_, err = f.Write([]byte("\n---\n"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "couldn't write to spec file")
|
||||
}
|
||||
}
|
||||
|
||||
// write our resource
|
||||
_, err = f.Write(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "couldn't write to spec file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// called from `fission * create --spec`
|
||||
func SpecSave(resource interface{}, specFile string) error {
|
||||
specDir := "specs"
|
||||
|
||||
// make sure we're writing a known type
|
||||
var data []byte
|
||||
var err error
|
||||
switch typedres := resource.(type) {
|
||||
case ArchiveUploadSpec:
|
||||
typedres.Kind = "ArchiveUploadSpec"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.Package:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "Package"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.Function:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "Function"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.Environment:
|
||||
env := resource.(fv1.Environment)
|
||||
var generator *v1generator.EnvironmentGenerator
|
||||
generator, err = v1generator.CreateEnvironmentGeneratorFromObj(&env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err = generator.StructuredGenerate(specDefaultEncoder)
|
||||
case fv1.HTTPTrigger:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "HTTPTrigger"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.KubernetesWatchTrigger:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "KubernetesWatchTrigger"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.MessageQueueTrigger:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "MessageQueueTrigger"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.TimeTrigger:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "TimeTrigger"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
case fv1.Recorder:
|
||||
typedres.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
typedres.TypeMeta.Kind = "Recorder"
|
||||
data, err = yaml.Marshal(typedres)
|
||||
default:
|
||||
return fmt.Errorf("can't save resource %#v", resource)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Couldn't marshal YAML")
|
||||
}
|
||||
|
||||
return Save(data, specDir, specFile)
|
||||
}
|
||||
|
||||
// validateFunctionReference checks a function reference
|
||||
func (fr *FissionResources) validateFunctionReference(functions map[string]bool, kind string, meta *metav1.ObjectMeta, funcRef fv1.FunctionReference) error {
|
||||
if funcRef.Type == fv1.FunctionReferenceTypeFunctionName {
|
||||
// triggers only reference functions in their own namespace
|
||||
namespace := meta.Namespace
|
||||
name := funcRef.Name
|
||||
m := &metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
}
|
||||
if _, ok := functions[MapKey(m)]; !ok {
|
||||
return fmt.Errorf("%v: %v '%v' references unknown function '%v'",
|
||||
fr.SourceMap.Locations[kind][meta.Namespace][meta.Name],
|
||||
kind,
|
||||
meta.Name,
|
||||
name)
|
||||
} else {
|
||||
functions[MapKey(m)] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *FissionResources) Validate(c *cli.Context) error {
|
||||
var result *multierror.Error
|
||||
|
||||
// check references: both dangling refs + garbage
|
||||
// packages -> archives
|
||||
// functions -> packages
|
||||
// functions -> environments + shared environments between functions [TODO]
|
||||
// functions -> secrets + configmaps (same ns) [TODO]
|
||||
// triggers -> functions
|
||||
|
||||
// index archives
|
||||
archives := make(map[string]bool)
|
||||
for _, a := range fr.ArchiveUploadSpecs {
|
||||
archives[a.Name] = false
|
||||
}
|
||||
|
||||
// index packages, check outgoing refs, mark archives that are referenced
|
||||
packages := make(map[string]bool)
|
||||
for _, p := range fr.Packages {
|
||||
packages[MapKey(&p.Metadata)] = false
|
||||
|
||||
// check archive refs from package
|
||||
aname := strings.TrimPrefix(p.Spec.Source.URL, ARCHIVE_URL_PREFIX)
|
||||
if len(aname) > 0 {
|
||||
if _, ok := archives[aname]; !ok {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: package '%v' references unknown source archive %v%v",
|
||||
fr.SourceMap.Locations["Package"][p.Metadata.Namespace][p.Metadata.Name],
|
||||
p.Metadata.Name,
|
||||
ARCHIVE_URL_PREFIX,
|
||||
aname))
|
||||
} else {
|
||||
archives[aname] = true
|
||||
}
|
||||
}
|
||||
|
||||
aname = strings.TrimPrefix(p.Spec.Deployment.URL, ARCHIVE_URL_PREFIX)
|
||||
if len(aname) > 0 {
|
||||
if _, ok := archives[aname]; !ok {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: package '%v' references unknown deployment archive %v%v",
|
||||
fr.SourceMap.Locations["Package"][p.Metadata.Namespace][p.Metadata.Name],
|
||||
p.Metadata.Name,
|
||||
ARCHIVE_URL_PREFIX,
|
||||
aname))
|
||||
} else {
|
||||
archives[aname] = true
|
||||
}
|
||||
}
|
||||
|
||||
result = multierror.Append(result, p.Validate())
|
||||
}
|
||||
|
||||
// error on unreferenced archives
|
||||
for name, referenced := range archives {
|
||||
if !referenced {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: archive '%v' is not used in any package",
|
||||
fr.SourceMap.Locations["ArchiveUploadSpec"][""][name],
|
||||
name))
|
||||
}
|
||||
}
|
||||
|
||||
// index functions, check function package refs, mark referenced packages
|
||||
functions := make(map[string]bool)
|
||||
for _, f := range fr.Functions {
|
||||
functions[MapKey(&f.Metadata)] = false
|
||||
|
||||
pkgMeta := &metav1.ObjectMeta{
|
||||
Name: f.Spec.Package.PackageRef.Name,
|
||||
Namespace: f.Spec.Package.PackageRef.Namespace,
|
||||
}
|
||||
|
||||
// check package ref from function
|
||||
packageRefExists := func() bool {
|
||||
_, ok := packages[MapKey(pkgMeta)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// check that the package referenced by each function is in the same ns as the function
|
||||
packageRefInFuncNs := func(f *fv1.Function) bool {
|
||||
return f.Spec.Package.PackageRef.Namespace == f.Metadata.Namespace
|
||||
}
|
||||
|
||||
if !packageRefInFuncNs(&f) {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: function '%v' references a package outside of its namespace %v/%v",
|
||||
fr.SourceMap.Locations["Function"][f.Metadata.Namespace][f.Metadata.Name],
|
||||
f.Metadata.Name,
|
||||
f.Spec.Package.PackageRef.Namespace,
|
||||
f.Spec.Package.PackageRef.Name))
|
||||
} else if !packageRefExists() {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: function '%v' references unknown package %v/%v",
|
||||
fr.SourceMap.Locations["Function"][f.Metadata.Namespace][f.Metadata.Name],
|
||||
f.Metadata.Name,
|
||||
pkgMeta.Namespace,
|
||||
pkgMeta.Name))
|
||||
} else {
|
||||
packages[MapKey(pkgMeta)] = true
|
||||
}
|
||||
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
for _, cm := range f.Spec.ConfigMaps {
|
||||
_, err := client.ConfigMapGet(&metav1.ObjectMeta{
|
||||
Name: cm.Name,
|
||||
Namespace: cm.Namespace,
|
||||
})
|
||||
if k8serrors.IsNotFound(err) {
|
||||
log.Warn(fmt.Sprintf("Configmap %s is referred in the spec but not present in the cluster", cm.Name))
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range f.Spec.Secrets {
|
||||
_, err := client.SecretGet(&metav1.ObjectMeta{
|
||||
Name: s.Name,
|
||||
Namespace: s.Namespace,
|
||||
})
|
||||
if k8serrors.IsNotFound(err) {
|
||||
log.Warn(fmt.Sprintf("Secret %s is referred in the spec but not present in the cluster", s.Name))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result = multierror.Append(result, f.Validate())
|
||||
}
|
||||
|
||||
// error on unreferenced packages
|
||||
for key, referenced := range packages {
|
||||
ks := strings.Split(key, ":")
|
||||
namespace, name := ks[0], ks[1]
|
||||
if !referenced {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"%v: package '%v' is not used in any function",
|
||||
fr.SourceMap.Locations["Package"][namespace][name],
|
||||
name))
|
||||
}
|
||||
}
|
||||
|
||||
// check function refs from triggers
|
||||
for _, t := range fr.HttpTriggers {
|
||||
err := fr.validateFunctionReference(functions, t.Kind, &t.Metadata, t.Spec.FunctionReference)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
result = multierror.Append(result, t.Validate())
|
||||
}
|
||||
for _, t := range fr.KubernetesWatchTriggers {
|
||||
err := fr.validateFunctionReference(functions, t.Kind, &t.Metadata, t.Spec.FunctionReference)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
result = multierror.Append(result, t.Validate())
|
||||
}
|
||||
for _, t := range fr.TimeTriggers {
|
||||
err := fr.validateFunctionReference(functions, t.Kind, &t.Metadata, t.Spec.FunctionReference)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
result = multierror.Append(result, t.Validate())
|
||||
}
|
||||
for _, t := range fr.MessageQueueTriggers {
|
||||
err := fr.validateFunctionReference(functions, t.Kind, &t.Metadata, t.Spec.FunctionReference)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
result = multierror.Append(result, t.Validate())
|
||||
}
|
||||
|
||||
// we do not error on unreferenced functions (you can call a function through workflows,
|
||||
// `fission function test`, etc.)
|
||||
|
||||
// Index envs, warn on functions referencing an environment for which spes does not exist
|
||||
environments := make(map[string]struct{})
|
||||
for _, e := range fr.Environments {
|
||||
environments[fmt.Sprintf("%s:%s", e.Metadata.Name, e.Metadata.Namespace)] = struct{}{}
|
||||
if (e.Spec.Runtime.Container != nil) && (e.Spec.Runtime.PodSpec != nil) {
|
||||
log.Warn("You have provided both - container spec and pod spec and while merging the pod spec will take precedence.")
|
||||
}
|
||||
// Unlike CLI can change the environment version silently,
|
||||
// we have to warn the user to modify spec file when this takes place.
|
||||
if e.Spec.Version < 3 && e.Spec.Poolsize != 0 {
|
||||
log.Warn("Poolsize can only be configured when environment version equals to 3, default poolsize 3 will be used for creating environment pool.")
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range fr.Functions {
|
||||
if _, ok := environments[fmt.Sprintf("%s:%s", f.Spec.Environment.Name, f.Spec.Environment.Namespace)]; !ok {
|
||||
log.Warn(fmt.Sprintf("Environment %s is referenced in function %s but not declared in specs", f.Spec.Environment.Name, f.Metadata.Name))
|
||||
}
|
||||
strategy := f.Spec.InvokeStrategy.ExecutionStrategy
|
||||
if strategy.ExecutorType == fv1.ExecutorTypeNewdeploy && strategy.SpecializationTimeout < fv1.DefaultSpecializationTimeOut {
|
||||
log.Warn(fmt.Sprintf("SpecializationTimeout in function spec.InvokeStrategy.ExecutionStrategy should be a value equal to or greater than %v", fv1.DefaultSpecializationTimeOut))
|
||||
}
|
||||
}
|
||||
|
||||
// (ErrorOrNil returns nil if there were no errors appended.)
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Keep track of source location of resources, and track duplicates
|
||||
func (fr *FissionResources) trackSourceMap(kind string, newobj *metav1.ObjectMeta, loc *Location) error {
|
||||
if _, exists := fr.SourceMap.Locations[kind]; !exists {
|
||||
fr.SourceMap.Locations[kind] = make(map[string](map[string]Location))
|
||||
}
|
||||
if _, exists := fr.SourceMap.Locations[kind][newobj.Namespace]; !exists {
|
||||
fr.SourceMap.Locations[kind][newobj.Namespace] = make(map[string]Location)
|
||||
}
|
||||
|
||||
// check for duplicate resources
|
||||
oldloc, exists := fr.SourceMap.Locations[kind][newobj.Namespace][newobj.Name]
|
||||
if exists {
|
||||
return fmt.Errorf("%v: Duplicate %v '%v', first defined in %v", loc, kind, newobj.Name, oldloc)
|
||||
}
|
||||
|
||||
// track new resource
|
||||
fr.SourceMap.Locations[kind][newobj.Namespace][newobj.Name] = *loc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseYaml takes one yaml document, figures out its type, parses it, and puts it in
|
||||
// the right list in the given fission resources set.
|
||||
func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
|
||||
var m *metav1.ObjectMeta
|
||||
|
||||
// Figure out the object type by unmarshaling into the TypeMeta struct; then
|
||||
// unmarshal again into the "real" struct once we know the type.
|
||||
var tm TypeMeta
|
||||
err := yaml.Unmarshal(b, &tm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to decode yaml %v", string(b)))
|
||||
}
|
||||
|
||||
switch tm.Kind {
|
||||
case "Package":
|
||||
var v fv1.Package
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.Packages = append(fr.Packages, v)
|
||||
case "Function":
|
||||
var v fv1.Function
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.Functions = append(fr.Functions, v)
|
||||
case "Environment":
|
||||
var v fv1.Environment
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.Environments = append(fr.Environments, v)
|
||||
case "HTTPTrigger":
|
||||
var v fv1.HTTPTrigger
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
|
||||
// TODO move to validator
|
||||
if !strings.HasPrefix(v.Spec.RelativeURL, "/") {
|
||||
v.Spec.RelativeURL = fmt.Sprintf("/%s", v.Spec.RelativeURL)
|
||||
}
|
||||
|
||||
m = &v.Metadata
|
||||
fr.HttpTriggers = append(fr.HttpTriggers, v)
|
||||
case "KubernetesWatchTrigger":
|
||||
var v fv1.KubernetesWatchTrigger
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.KubernetesWatchTriggers = append(fr.KubernetesWatchTriggers, v)
|
||||
case "TimeTrigger":
|
||||
var v fv1.TimeTrigger
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.TimeTriggers = append(fr.TimeTriggers, v)
|
||||
case "MessageQueueTrigger":
|
||||
var v fv1.MessageQueueTrigger
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &v.Metadata
|
||||
fr.MessageQueueTriggers = append(fr.MessageQueueTriggers, v)
|
||||
|
||||
// The following are not CRDs
|
||||
|
||||
case "DeploymentConfig":
|
||||
var v DeploymentConfig
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
fr.DeploymentConfig = v
|
||||
case "ArchiveUploadSpec":
|
||||
var v ArchiveUploadSpec
|
||||
err = yaml.Unmarshal(b, &v)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
|
||||
}
|
||||
m = &metav1.ObjectMeta{
|
||||
Name: v.Name,
|
||||
Namespace: "",
|
||||
}
|
||||
fr.ArchiveUploadSpecs = append(fr.ArchiveUploadSpecs, v)
|
||||
default:
|
||||
// no need to error out just because there's some extra files around;
|
||||
// also good for compatibility.
|
||||
log.Warn(fmt.Sprintf("Ignoring unknown type %v in %v", tm.Kind, loc))
|
||||
}
|
||||
|
||||
// add to source map, check for duplicates
|
||||
if m != nil {
|
||||
err = fr.trackSourceMap(tm.Kind, m, loc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns metadata if the given resource exists in the specs, nil
|
||||
// otherwise. compareMetadata and compareSpec control how the
|
||||
// equality check is performed.
|
||||
func (fr *FissionResources) SpecExists(resource interface{}, compareMetadata bool, compareSpec bool) *metav1.ObjectMeta {
|
||||
switch typedres := resource.(type) {
|
||||
case *ArchiveUploadSpec:
|
||||
for _, aus := range fr.ArchiveUploadSpecs {
|
||||
if compareMetadata && aus.Name != typedres.Name {
|
||||
continue
|
||||
}
|
||||
if compareSpec &&
|
||||
!(reflect.DeepEqual(aus.RootDir, typedres.RootDir) &&
|
||||
reflect.DeepEqual(aus.IncludeGlobs, typedres.IncludeGlobs) &&
|
||||
reflect.DeepEqual(aus.ExcludeGlobs, typedres.ExcludeGlobs)) {
|
||||
continue
|
||||
}
|
||||
return &metav1.ObjectMeta{Name: aus.Name}
|
||||
}
|
||||
return nil
|
||||
case *fv1.Package:
|
||||
for _, p := range fr.Packages {
|
||||
if compareMetadata && !reflect.DeepEqual(p.Metadata, typedres.Metadata) {
|
||||
continue
|
||||
}
|
||||
if compareSpec && !reflect.DeepEqual(p.Spec, typedres.Spec) {
|
||||
continue
|
||||
}
|
||||
return &p.Metadata
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
// XXX not implemented
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (loc Location) String() string {
|
||||
return fmt.Sprintf("%v:%v", loc.Path, loc.Line)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
func GetServer(flags cli.Input) *client.Client {
|
||||
return util.GetApiClient(flags.GlobalString(FISSION_SERVER))
|
||||
}
|
||||
|
||||
func GetResourceReqs(flags cli.Input, resReqs *v1.ResourceRequirements) (*v1.ResourceRequirements, error) {
|
||||
r := &v1.ResourceRequirements{}
|
||||
|
||||
if resReqs != nil {
|
||||
r.Requests = resReqs.Requests
|
||||
r.Limits = resReqs.Limits
|
||||
}
|
||||
|
||||
if len(r.Requests) == 0 {
|
||||
r.Requests = make(map[v1.ResourceName]resource.Quantity)
|
||||
}
|
||||
|
||||
if len(r.Limits) == 0 {
|
||||
r.Limits = make(map[v1.ResourceName]resource.Quantity)
|
||||
}
|
||||
|
||||
e := &multierror.Error{}
|
||||
|
||||
if flags.IsSet(RUNTIME_MINCPU) {
|
||||
mincpu := flags.Int(RUNTIME_MINCPU)
|
||||
cpuRequest, err := resource.ParseQuantity(strconv.Itoa(mincpu) + "m")
|
||||
if err != nil {
|
||||
e = multierror.Append(e, errors.Wrap(err, "Failed to parse mincpu"))
|
||||
}
|
||||
r.Requests[v1.ResourceCPU] = cpuRequest
|
||||
}
|
||||
|
||||
if flags.IsSet(RUNTIME_MINMEMORY) {
|
||||
minmem := flags.Int(RUNTIME_MINMEMORY)
|
||||
memRequest, err := resource.ParseQuantity(strconv.Itoa(minmem) + "Mi")
|
||||
if err != nil {
|
||||
e = multierror.Append(e, errors.Wrap(err, "Failed to parse minmemory"))
|
||||
}
|
||||
r.Requests[v1.ResourceMemory] = memRequest
|
||||
}
|
||||
|
||||
if flags.IsSet(RUNTIME_MAXCPU) {
|
||||
maxcpu := flags.Int(RUNTIME_MAXCPU)
|
||||
cpuLimit, err := resource.ParseQuantity(strconv.Itoa(maxcpu) + "m")
|
||||
if err != nil {
|
||||
e = multierror.Append(e, errors.Wrap(err, "Failed to parse maxcpu"))
|
||||
}
|
||||
r.Limits[v1.ResourceCPU] = cpuLimit
|
||||
}
|
||||
|
||||
if flags.IsSet(RUNTIME_MAXMEMORY) {
|
||||
maxmem := flags.Int(RUNTIME_MAXMEMORY)
|
||||
memLimit, err := resource.ParseQuantity(strconv.Itoa(maxmem) + "Mi")
|
||||
if err != nil {
|
||||
e = multierror.Append(e, errors.Wrap(err, "Failed to parse maxmemory"))
|
||||
}
|
||||
r.Limits[v1.ResourceMemory] = memLimit
|
||||
}
|
||||
|
||||
limitCPU := r.Limits[v1.ResourceCPU]
|
||||
requestCPU := r.Requests[v1.ResourceCPU]
|
||||
|
||||
if limitCPU.IsZero() && !requestCPU.IsZero() {
|
||||
r.Limits[v1.ResourceCPU] = requestCPU
|
||||
} else if limitCPU.Cmp(requestCPU) < 0 {
|
||||
e = multierror.Append(e, fmt.Errorf("MinCPU (%v) cannot be greater than MaxCPU (%v)", requestCPU.String(), limitCPU.String()))
|
||||
}
|
||||
|
||||
limitMem := r.Limits[v1.ResourceMemory]
|
||||
requestMem := r.Requests[v1.ResourceMemory]
|
||||
|
||||
if limitMem.IsZero() && !requestMem.IsZero() {
|
||||
r.Limits[v1.ResourceMemory] = requestMem
|
||||
} else if limitMem.Cmp(requestMem) < 0 {
|
||||
e = multierror.Append(e, fmt.Errorf("MinMemory (%v) cannot be greater than MaxMemory (%v)", requestMem.String(), limitMem.String()))
|
||||
}
|
||||
|
||||
if e.ErrorOrNil() != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &v1.ResourceRequirements{
|
||||
Requests: r.Requests,
|
||||
Limits: r.Limits,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetSpecDir(flags cli.Input) string {
|
||||
specDir := flags.String(SPEC_SPECDIR)
|
||||
if len(specDir) == 0 {
|
||||
specDir = "specs"
|
||||
}
|
||||
return specDir
|
||||
}
|
||||
|
||||
// GetMetadata returns a pointer to ObjectMeta which initialized with command line input.
|
||||
func GetMetadata(flags cli.Input) (*metav1.ObjectMeta, error) {
|
||||
name := flags.String(RESOURCE_NAME)
|
||||
if len(name) == 0 {
|
||||
return nil, errors.New("Need a resource name, use --name.")
|
||||
}
|
||||
|
||||
ns := flags.String(ENVIRONMENT_NAMESPACE)
|
||||
|
||||
m := &metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Fission 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 fission_cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
|
||||
func envCreate(c *cli.Context) error {
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
log.Fatal("Need a name, use --name.")
|
||||
}
|
||||
envNamespace := c.String("envNamespace")
|
||||
|
||||
envList, err := client.EnvironmentList(envNamespace)
|
||||
if err == nil && len(envList) > 0 {
|
||||
log.Verbose(2, "%d environment(s) are present in the %s namespace. "+
|
||||
"These environments are not isolated from each other; use separate namespaces if you need isolation.",
|
||||
len(envList), envNamespace)
|
||||
}
|
||||
|
||||
var poolsize int
|
||||
if c.IsSet("poolsize") {
|
||||
poolsize = c.Int("poolsize")
|
||||
} else {
|
||||
poolsize = 3
|
||||
}
|
||||
|
||||
envImg := c.String("image")
|
||||
if len(envImg) == 0 {
|
||||
log.Fatal("Need an image, use --image.")
|
||||
}
|
||||
|
||||
envVersion := c.Int("version")
|
||||
envBuilderImg := c.String("builder")
|
||||
envBuildCmd := c.String("buildcmd")
|
||||
envExternalNetwork := c.Bool("externalnetwork")
|
||||
envGracePeriod := c.Int64("period")
|
||||
if envGracePeriod <= 0 {
|
||||
envGracePeriod = 360
|
||||
}
|
||||
|
||||
if len(envBuilderImg) > 0 {
|
||||
if !c.IsSet("version") {
|
||||
envVersion = 2
|
||||
}
|
||||
if len(envBuildCmd) == 0 {
|
||||
envBuildCmd = "build"
|
||||
}
|
||||
}
|
||||
if c.IsSet("poolsize") {
|
||||
envVersion = 3
|
||||
}
|
||||
|
||||
keepArchive := c.Bool("keeparchive")
|
||||
|
||||
// Environment API interface version is not specified and
|
||||
// builder image is empty, set default interface version
|
||||
if envVersion == 0 {
|
||||
envVersion = 1
|
||||
}
|
||||
|
||||
resourceReq := getResourceReq(c, v1.ResourceRequirements{})
|
||||
|
||||
env := &fv1.Environment{
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: envName,
|
||||
Namespace: envNamespace,
|
||||
},
|
||||
Spec: fv1.EnvironmentSpec{
|
||||
Version: envVersion,
|
||||
Runtime: fv1.Runtime{
|
||||
Image: envImg,
|
||||
},
|
||||
Builder: fv1.Builder{
|
||||
Image: envBuilderImg,
|
||||
Command: envBuildCmd,
|
||||
},
|
||||
Poolsize: poolsize,
|
||||
Resources: resourceReq,
|
||||
AllowAccessToExternalNetwork: envExternalNetwork,
|
||||
TerminationGracePeriod: envGracePeriod,
|
||||
KeepArchive: keepArchive,
|
||||
},
|
||||
}
|
||||
|
||||
// if we're writing a spec, don't call the API
|
||||
if c.Bool("spec") {
|
||||
specFile := fmt.Sprintf("env-%v.yaml", envName)
|
||||
err := specSave(*env, specFile)
|
||||
util.CheckErr(err, "create environment spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = client.EnvironmentCreate(env)
|
||||
util.CheckErr(err, "create environment")
|
||||
|
||||
fmt.Printf("environment '%v' created\n", envName)
|
||||
return err
|
||||
}
|
||||
|
||||
func envGet(c *cli.Context) error {
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
log.Fatal("Need a name, use --name.")
|
||||
}
|
||||
envNamespace := c.String("envNamespace")
|
||||
|
||||
m := &metav1.ObjectMeta{
|
||||
Name: envName,
|
||||
Namespace: envNamespace,
|
||||
}
|
||||
env, err := client.EnvironmentGet(m)
|
||||
util.CheckErr(err, "get environment")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\n", "NAME", "UID", "IMAGE")
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\n",
|
||||
env.Metadata.Name, env.Metadata.UID, env.Spec.Runtime.Image)
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func envUpdate(c *cli.Context) error {
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
log.Fatal("Need a name, use --name.")
|
||||
}
|
||||
envNamespace := c.String("envNamespace")
|
||||
|
||||
envImg := c.String("image")
|
||||
envBuilderImg := c.String("builder")
|
||||
envBuildCmd := c.String("buildcmd")
|
||||
envExternalNetwork := c.Bool("externalnetwork")
|
||||
|
||||
if len(envImg) == 0 && len(envBuilderImg) == 0 && len(envBuildCmd) == 0 {
|
||||
log.Fatal("Need --image to specify env image, or use --builder to specify env builder, or use --buildcmd to specify new build command.")
|
||||
}
|
||||
|
||||
env, err := client.EnvironmentGet(&metav1.ObjectMeta{
|
||||
Name: envName,
|
||||
Namespace: envNamespace,
|
||||
})
|
||||
util.CheckErr(err, "find environment")
|
||||
|
||||
if len(envImg) > 0 {
|
||||
env.Spec.Runtime.Image = envImg
|
||||
}
|
||||
|
||||
if env.Spec.Version == 1 && (len(envBuilderImg) > 0 || len(envBuildCmd) > 0) {
|
||||
log.Fatal("Version 1 Environments do not support builders. Must specify --version=2.")
|
||||
}
|
||||
|
||||
if len(envBuilderImg) > 0 {
|
||||
env.Spec.Builder.Image = envBuilderImg
|
||||
}
|
||||
if len(envBuildCmd) > 0 {
|
||||
env.Spec.Builder.Command = envBuildCmd
|
||||
}
|
||||
|
||||
if c.IsSet("poolsize") {
|
||||
env.Spec.Poolsize = c.Int("poolsize")
|
||||
}
|
||||
|
||||
if c.IsSet("period") {
|
||||
env.Spec.TerminationGracePeriod = c.Int64("period")
|
||||
}
|
||||
|
||||
if c.IsSet("keeparchive") {
|
||||
env.Spec.KeepArchive = c.Bool("keeparchive")
|
||||
}
|
||||
|
||||
env.Spec.AllowAccessToExternalNetwork = envExternalNetwork
|
||||
|
||||
if c.IsSet("mincpu") || c.IsSet("maxcpu") || c.IsSet("minmemory") || c.IsSet("maxmemory") || c.IsSet("minscale") || c.IsSet("maxscale") {
|
||||
log.Fatal("Updating resource limits/requests for existing environments is currently unsupported; re-create the environment instead.")
|
||||
}
|
||||
|
||||
_, err = client.EnvironmentUpdate(env)
|
||||
util.CheckErr(err, "update environment")
|
||||
|
||||
fmt.Printf("environment '%v' updated\n", envName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func envDelete(c *cli.Context) error {
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
log.Fatal("Need a name , use --name.")
|
||||
}
|
||||
envNamespace := c.String("envNamespace")
|
||||
|
||||
m := &metav1.ObjectMeta{
|
||||
Name: envName,
|
||||
Namespace: envNamespace,
|
||||
}
|
||||
err := client.EnvironmentDelete(m)
|
||||
util.CheckErr(err, "delete environment")
|
||||
|
||||
fmt.Printf("environment '%v' deleted\n", envName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func envList(c *cli.Context) error {
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
envNamespace := c.String("envNamespace")
|
||||
|
||||
envs, err := client.EnvironmentList(envNamespace)
|
||||
util.CheckErr(err, "list environments")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", "NAME", "UID", "IMAGE", "BUILDER_IMAGE", "POOLSIZE", "MINCPU", "MAXCPU", "MINMEMORY", "MAXMEMORY", "EXTNET", "GRACETIME")
|
||||
for _, env := range envs {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n",
|
||||
env.Metadata.Name, env.Metadata.UID, env.Spec.Runtime.Image, env.Spec.Builder.Image, env.Spec.Poolsize,
|
||||
env.Spec.Resources.Requests.Cpu(), env.Spec.Resources.Limits.Cpu(),
|
||||
env.Spec.Resources.Requests.Memory(), env.Spec.Resources.Limits.Memory(),
|
||||
env.Spec.AllowAccessToExternalNetwork, env.Spec.TerminationGracePeriod)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getResourceReq(c *cli.Context, resources v1.ResourceRequirements) v1.ResourceRequirements {
|
||||
|
||||
var requestResources map[v1.ResourceName]resource.Quantity
|
||||
|
||||
if len(resources.Requests) == 0 {
|
||||
requestResources = make(map[v1.ResourceName]resource.Quantity)
|
||||
} else {
|
||||
requestResources = resources.Requests
|
||||
}
|
||||
|
||||
if c.IsSet("mincpu") {
|
||||
mincpu := c.Int("mincpu")
|
||||
cpuRequest, err := resource.ParseQuantity(strconv.Itoa(mincpu) + "m")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse mincpu")
|
||||
}
|
||||
requestResources[v1.ResourceCPU] = cpuRequest
|
||||
}
|
||||
|
||||
if c.IsSet("minmemory") {
|
||||
minmem := c.Int("minmemory")
|
||||
memRequest, err := resource.ParseQuantity(strconv.Itoa(minmem) + "Mi")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse minmemory")
|
||||
}
|
||||
requestResources[v1.ResourceMemory] = memRequest
|
||||
}
|
||||
|
||||
var limitResources map[v1.ResourceName]resource.Quantity
|
||||
|
||||
if len(resources.Limits) == 0 {
|
||||
limitResources = make(map[v1.ResourceName]resource.Quantity)
|
||||
} else {
|
||||
limitResources = resources.Limits
|
||||
}
|
||||
|
||||
if c.IsSet("maxcpu") {
|
||||
maxcpu := c.Int("maxcpu")
|
||||
cpuLimit, err := resource.ParseQuantity(strconv.Itoa(maxcpu) + "m")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse maxcpu")
|
||||
}
|
||||
limitResources[v1.ResourceCPU] = cpuLimit
|
||||
}
|
||||
|
||||
if c.IsSet("maxmemory") {
|
||||
maxmem := c.Int("maxmemory")
|
||||
memLimit, err := resource.ParseQuantity(strconv.Itoa(maxmem) + "Mi")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse maxmemory")
|
||||
}
|
||||
limitResources[v1.ResourceMemory] = memLimit
|
||||
}
|
||||
|
||||
limitCPU := limitResources[v1.ResourceCPU]
|
||||
requestCPU := requestResources[v1.ResourceCPU]
|
||||
|
||||
if limitCPU.IsZero() && !requestCPU.IsZero() {
|
||||
limitResources[v1.ResourceCPU] = requestCPU
|
||||
} else if limitCPU.Cmp(requestCPU) < 0 {
|
||||
log.Fatal(fmt.Sprintf("MinCPU (%v) cannot be greater than MaxCPU (%v)", requestCPU.String(), limitCPU.String()))
|
||||
}
|
||||
|
||||
limitMem := limitResources[v1.ResourceMemory]
|
||||
requestMem := requestResources[v1.ResourceMemory]
|
||||
|
||||
if limitMem.IsZero() && !requestMem.IsZero() {
|
||||
limitResources[v1.ResourceMemory] = requestMem
|
||||
} else if limitMem.Cmp(requestMem) < 0 {
|
||||
log.Fatal(fmt.Sprintf("MinMemory (%v) cannot be greater than MaxMemory (%v)", requestMem.String(), limitMem.String()))
|
||||
}
|
||||
|
||||
resources = v1.ResourceRequirements{
|
||||
Requests: requestResources,
|
||||
Limits: limitResources,
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
+28
-17
@@ -28,7 +28,6 @@ import (
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/fission/fission/pkg/types"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/urfave/cli"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
@@ -37,15 +36,19 @@ import (
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
ferror "github.com/fission/fission/pkg/error"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/urfavecli"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
cmdutils "github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/logdb"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
"github.com/fission/fission/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_MIN_SCALE = 1
|
||||
DEFAULT_TARGET_CPU_PERCENTAGE = 80
|
||||
DEFAULT_SPECIALIZATION_TIMEOUT = 120
|
||||
DEFAULT_MIN_SCALE = 1
|
||||
DEFAULT_TARGET_CPU_PERCENTAGE = 80
|
||||
)
|
||||
|
||||
func printPodLogs(c *cli.Context) error {
|
||||
@@ -125,7 +128,7 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
|
||||
targetCPU := DEFAULT_TARGET_CPU_PERCENTAGE
|
||||
minScale := DEFAULT_MIN_SCALE
|
||||
maxScale := minScale
|
||||
specializationTimeout := DEFAULT_SPECIALIZATION_TIMEOUT
|
||||
specializationTimeout := fv1.DefaultSpecializationTimeOut
|
||||
|
||||
if existingInvokeStrategy != nil && existingInvokeStrategy.ExecutionStrategy.ExecutorType == types.ExecutorTypeNewdeploy {
|
||||
minScale = existingInvokeStrategy.ExecutionStrategy.MinScale
|
||||
@@ -151,7 +154,7 @@ func getInvokeStrategy(c *cli.Context, existingInvokeStrategy *fv1.InvokeStrateg
|
||||
|
||||
if c.IsSet("specializationtimeout") {
|
||||
specializationTimeout = c.Int("specializationtimeout")
|
||||
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
|
||||
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
|
||||
return nil, errors.New("specializationtimeout must be greater than or equal to 120 seconds")
|
||||
}
|
||||
}
|
||||
@@ -203,13 +206,13 @@ func fnCreate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// user wants a spec, create a yaml file with package and function
|
||||
spec := false
|
||||
toSpec := false
|
||||
specFile := ""
|
||||
if c.Bool("spec") {
|
||||
spec = true
|
||||
toSpec = true
|
||||
specFile = fmt.Sprintf("function-%v.yaml", fnName)
|
||||
}
|
||||
specDir := getSpecDir(c)
|
||||
specDir := cmdutils.GetSpecDir(urfavecli.Parse(c))
|
||||
|
||||
// check for unique function names within a namespace
|
||||
fnList, err := client.FunctionList(fnNamespace)
|
||||
@@ -230,7 +233,10 @@ func fnCreate(c *cli.Context) error {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resourceReq := getResourceReq(c, apiv1.ResourceRequirements{})
|
||||
resourceReq, err := cmd.GetResourceReqs(urfavecli.Parse(c), &apiv1.ResourceRequirements{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var pkgMetadata *metav1.ObjectMeta
|
||||
var envName string
|
||||
@@ -255,7 +261,7 @@ func fnCreate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// examine existence of given environment. If specs - then spec validate will do it, don't check here.
|
||||
if !spec {
|
||||
if !toSpec {
|
||||
_, err := client.EnvironmentGet(&metav1.ObjectMeta{
|
||||
Namespace: envNamespace,
|
||||
Name: envName,
|
||||
@@ -287,7 +293,7 @@ func fnCreate(c *cli.Context) error {
|
||||
buildcmd := c.String("buildcmd")
|
||||
|
||||
// create new package in the same namespace as the function.
|
||||
pkgMetadata = createPackage(client, fnNamespace, envName, envNamespace, srcArchiveFiles, deployArchiveFiles, buildcmd, specDir, specFile, noZip)
|
||||
pkgMetadata = createPackage(c, client, fnNamespace, envName, envNamespace, srcArchiveFiles, deployArchiveFiles, buildcmd, specDir, specFile, noZip)
|
||||
}
|
||||
|
||||
var secrets []fv1.SecretReference
|
||||
@@ -347,14 +353,14 @@ func fnCreate(c *cli.Context) error {
|
||||
},
|
||||
Secrets: secrets,
|
||||
ConfigMaps: cfgmaps,
|
||||
Resources: resourceReq,
|
||||
Resources: *resourceReq,
|
||||
InvokeStrategy: *invokeStrategy,
|
||||
},
|
||||
}
|
||||
|
||||
// if we're writing a spec, don't create the function
|
||||
if spec {
|
||||
err = specSave(*function, specFile)
|
||||
if toSpec {
|
||||
err = spec.SpecSave(*function, specFile)
|
||||
util.CheckErr(err, "create function spec")
|
||||
return nil
|
||||
|
||||
@@ -578,14 +584,19 @@ func fnUpdate(c *cli.Context) error {
|
||||
log.Fatal("specializationtimeout flag is only applicable for newdeploy type of executor")
|
||||
}
|
||||
|
||||
if specializationTimeout < DEFAULT_SPECIALIZATION_TIMEOUT {
|
||||
if specializationTimeout < fv1.DefaultSpecializationTimeOut {
|
||||
log.Fatal("specializationtimeout must be greater than or equal to 120 seconds")
|
||||
} else {
|
||||
function.Spec.InvokeStrategy.ExecutionStrategy.SpecializationTimeout = specializationTimeout
|
||||
}
|
||||
}
|
||||
|
||||
function.Spec.Resources = getResourceReq(c, function.Spec.Resources)
|
||||
resReqs, err := cmd.GetResourceReqs(urfavecli.Parse(c), &function.Spec.Resources)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
function.Spec.Resources = *resReqs
|
||||
|
||||
pkg, err := client.PackageGet(&metav1.ObjectMeta{
|
||||
Namespace: fnNamespace,
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: DEFAULT_MIN_SCALE,
|
||||
MaxScale: DEFAULT_MIN_SCALE,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -74,7 +74,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: DEFAULT_MIN_SCALE,
|
||||
MaxScale: DEFAULT_MIN_SCALE,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -89,7 +89,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: DEFAULT_MIN_SCALE,
|
||||
MaxScale: DEFAULT_MIN_SCALE,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectedResult: &fv1.InvokeStrategy{
|
||||
@@ -115,7 +115,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 3,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -155,7 +155,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: DEFAULT_MIN_SCALE,
|
||||
MaxScale: 3,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -183,7 +183,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 5,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectedResult: &fv1.InvokeStrategy{
|
||||
@@ -193,7 +193,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 9,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -210,7 +210,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 5,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectedResult: &fv1.InvokeStrategy{
|
||||
@@ -220,7 +220,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 5,
|
||||
TargetCPUPercent: DEFAULT_TARGET_CPU_PERCENTAGE,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -239,7 +239,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: DEFAULT_MIN_SCALE,
|
||||
MaxScale: DEFAULT_MIN_SCALE,
|
||||
TargetCPUPercent: 50,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
@@ -257,7 +257,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 5,
|
||||
TargetCPUPercent: 88,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectedResult: &fv1.InvokeStrategy{
|
||||
@@ -267,7 +267,7 @@ func TestGetInvokeStrategy(t *testing.T) {
|
||||
MinScale: 2,
|
||||
MaxScale: 5,
|
||||
TargetCPUPercent: 20,
|
||||
SpecializationTimeout: DEFAULT_SPECIALIZATION_TIMEOUT,
|
||||
SpecializationTimeout: fv1.DefaultSpecializationTimeOut,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
ferror "github.com/fission/fission/pkg/error"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
@@ -106,7 +107,7 @@ func htCreate(c *cli.Context) error {
|
||||
|
||||
triggerName := c.String("name")
|
||||
fnNamespace := c.String("fnNamespace")
|
||||
spec := c.Bool("spec")
|
||||
toSpec := c.Bool("spec")
|
||||
|
||||
m := &metav1.ObjectMeta{
|
||||
Name: triggerName,
|
||||
@@ -135,7 +136,7 @@ func htCreate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// For Specs, the spec validate checks for function reference
|
||||
if !spec {
|
||||
if !toSpec {
|
||||
err = util.CheckFunctionExistence(client, functionList, fnNamespace)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
@@ -169,9 +170,9 @@ func htCreate(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// if we're writing a spec, don't call the API
|
||||
if spec {
|
||||
if toSpec {
|
||||
specFile := fmt.Sprintf("route-%v.yaml", triggerName)
|
||||
err := specSave(*ht, specFile)
|
||||
err := spec.SpecSave(*ht, specFile)
|
||||
util.CheckErr(err, "create HTTP trigger spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
+28
-33
@@ -27,6 +27,9 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/urfavecli"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/environment"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/plugin"
|
||||
"github.com/fission/fission/pkg/fission-cli/support"
|
||||
@@ -66,9 +69,9 @@ func NewCliApp() *cli.App {
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{Name: "server", Value: "", Usage: "Fission server URL"},
|
||||
cli.IntFlag{Name: "verbosity", Value: 1, Usage: "CLI verbosity (0 is quiet, 1 is the default, 2 is verbose.)"},
|
||||
cli.BoolFlag{Name: "plugin", Hidden: true},
|
||||
cli.StringFlag{Name: cmd.FISSION_SERVER, Value: "", Usage: "Fission server URL"},
|
||||
cli.IntFlag{Name: cmd.GLOBAL_VERBOSITY, Value: 1, Usage: "CLI verbosity (0 is quiet, 1 is the default, 2 is verbose.)"},
|
||||
cli.BoolFlag{Name: cmd.GLOBAL_PLUGIN, Hidden: true},
|
||||
}
|
||||
|
||||
// all resource create commands accept --spec
|
||||
@@ -76,7 +79,7 @@ func NewCliApp() *cli.App {
|
||||
|
||||
// namespace reference for all objects
|
||||
fnNamespaceFlag := cli.StringFlag{Name: "fnNamespace, fns", Value: metav1.NamespaceDefault, Usage: "Namespace for function object"}
|
||||
envNamespaceFlag := cli.StringFlag{Name: "envNamespace, envns", Value: metav1.NamespaceDefault, Usage: "Namespace for environment object"}
|
||||
envNamespaceFlag := cli.StringFlag{Name: cmd.GetCliFlagName(cmd.ENVIRONMENT_NAMESPACE, cmd.ENVIRONMENT_NAMESPACE_ALIAS), Value: metav1.NamespaceDefault, Usage: "Namespace for environment object"}
|
||||
pkgNamespaceFlag := cli.StringFlag{Name: "pkgNamespace, pkgns", Value: metav1.NamespaceDefault, Usage: "Namespace for package object"}
|
||||
triggerNamespaceFlag := cli.StringFlag{Name: "triggerNamespace, triggerns", Value: metav1.NamespaceDefault, Usage: "Namespace for trigger object"}
|
||||
recorderNamespaceFlag := cli.StringFlag{Name: "recorderNamespace, recorderns", Value: metav1.NamespaceDefault, Usage: "Namespace for recorder object"}
|
||||
@@ -87,13 +90,13 @@ func NewCliApp() *cli.App {
|
||||
htUrlFlag := cli.StringFlag{Name: "url", Usage: "URL pattern (See gorilla/mux supported patterns)"}
|
||||
|
||||
// Resource & scale related flags (Used in env and function)
|
||||
minCpu := cli.IntFlag{Name: "mincpu", Usage: "Minimum CPU to be assigned to pod (In millicore, minimum 1)"}
|
||||
maxCpu := cli.IntFlag{Name: "maxcpu", Usage: "Maximum CPU to be assigned to pod (In millicore, minimum 1)"}
|
||||
minMem := cli.IntFlag{Name: "minmemory", Usage: "Minimum memory to be assigned to pod (In megabyte)"}
|
||||
maxMem := cli.IntFlag{Name: "maxmemory", Usage: "Maximum memory to be assigned to pod (In megabyte)"}
|
||||
minScale := cli.IntFlag{Name: "minscale", Usage: "Minimum number of pods (Uses resource inputs to configure HPA)"}
|
||||
maxScale := cli.IntFlag{Name: "maxscale", Usage: "Maximum number of pods (Uses resource inputs to configure HPA)"}
|
||||
targetcpu := cli.IntFlag{Name: "targetcpu", Usage: "Target average CPU usage percentage across pods for scaling"}
|
||||
minCpu := cli.IntFlag{Name: cmd.RUNTIME_MINCPU, Usage: "Minimum CPU to be assigned to pod (In millicore, minimum 1)"}
|
||||
maxCpu := cli.IntFlag{Name: cmd.RUNTIME_MAXCPU, Usage: "Maximum CPU to be assigned to pod (In millicore, minimum 1)"}
|
||||
minMem := cli.IntFlag{Name: cmd.RUNTIME_MINMEMORY, Usage: "Minimum memory to be assigned to pod (In megabyte)"}
|
||||
maxMem := cli.IntFlag{Name: cmd.RUNTIME_MAXMEMORY, Usage: "Maximum memory to be assigned to pod (In megabyte)"}
|
||||
minScale := cli.IntFlag{Name: cmd.RUNTIME_MINSCALE, Usage: "Minimum number of pods (Uses resource inputs to configure HPA)"}
|
||||
maxScale := cli.IntFlag{Name: cmd.RUNTIME_MAXSCALE, Usage: "Maximum number of pods (Uses resource inputs to configure HPA)"}
|
||||
targetcpu := cli.IntFlag{Name: cmd.RUNTIME_TARGETCPU, Usage: "Target average CPU usage percentage across pods for scaling"}
|
||||
specializationTimeoutFlag := cli.IntFlag{Name: "specializationtimeout, st", Usage: "Timeout for newdeploy to wait for function pod creation"}
|
||||
|
||||
// functions
|
||||
@@ -212,21 +215,21 @@ func NewCliApp() *cli.App {
|
||||
reqIDFlag := cli.StringFlag{Name: "reqUID", Usage: "Replay a particular request by providing the reqUID (to view reqUIDs, do 'fission records view')"}
|
||||
|
||||
// environments
|
||||
envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"}
|
||||
envPoolsizeFlag := cli.IntFlag{Name: "poolsize", Value: 3, Usage: "Size of the pool"}
|
||||
envImageFlag := cli.StringFlag{Name: "image", Usage: "Environment image URL"}
|
||||
envBuilderImageFlag := cli.StringFlag{Name: "builder", Usage: "Environment builder image URL (optional)"}
|
||||
envBuildCmdFlag := cli.StringFlag{Name: "buildcmd", Usage: "Build command for environment builder to build source package (optional)"}
|
||||
envKeepArchiveFlag := cli.BoolFlag{Name: "keeparchive", Usage: "Keep the archive instead of extracting it into a directory (optional, defaults to false)"}
|
||||
envExternalNetworkFlag := cli.BoolFlag{Name: "externalnetwork", Usage: "Allow environment access external network when istio feature enabled (optional, defaults to false)"}
|
||||
envTerminationGracePeriodFlag := cli.Int64Flag{Name: "graceperiod, period", Value: 360, Usage: "The grace time (in seconds) for pod to perform connection draining before termination (optional)"}
|
||||
envVersionFlag := cli.IntFlag{Name: "version", Value: 1, Usage: "Environment API version (1 means v1 interface)"}
|
||||
envNameFlag := cli.StringFlag{Name: cmd.RESOURCE_NAME, Usage: "Environment name"}
|
||||
envPoolsizeFlag := cli.IntFlag{Name: cmd.ENVIRONMENT_POOLSIZE, Value: 3, Usage: "Size of the pool"}
|
||||
envImageFlag := cli.StringFlag{Name: cmd.ENVIRONMENT_IMAGE, Usage: "Environment image URL"}
|
||||
envBuilderImageFlag := cli.StringFlag{Name: cmd.ENVIRONMENT_BUILDER, Usage: "Environment builder image URL (optional)"}
|
||||
envBuildCmdFlag := cli.StringFlag{Name: cmd.ENVIRONMENT_BUILDCOMMAND, Usage: "Build command for environment builder to build source package (optional)"}
|
||||
envKeepArchiveFlag := cli.BoolFlag{Name: cmd.ENVIRONMENT_KEEPARCHIVE, Usage: "Keep the archive instead of extracting it into a directory (optional, defaults to false)"}
|
||||
envExternalNetworkFlag := cli.BoolFlag{Name: cmd.ENVIRONMENT_EXTERNAL_NETWORK, Usage: "Allow environment access external network when istio feature enabled (optional, defaults to false)"}
|
||||
envTerminationGracePeriodFlag := cli.Int64Flag{Name: cmd.GetCliFlagName(cmd.ENVIRONMENT_GRACE_PERIOD, cmd.ENVIRONMENT_GRACE_PERIOD_ALIAS), Value: 360, Usage: "The grace time (in seconds) for pod to perform connection draining before termination (optional)"}
|
||||
envVersionFlag := cli.IntFlag{Name: cmd.ENVIRONMENT_VERSION, Value: 1, Usage: "Environment API version (1 means v1 interface)"}
|
||||
envSubcommands := []cli.Command{
|
||||
{Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, envKeepArchiveFlag, minCpu, maxCpu, minMem, maxMem, envVersionFlag, envExternalNetworkFlag, envTerminationGracePeriodFlag, specSaveFlag}, Action: envCreate},
|
||||
{Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: envGet},
|
||||
{Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, envKeepArchiveFlag, minCpu, maxCpu, minMem, maxMem, envExternalNetworkFlag, envTerminationGracePeriodFlag}, Action: envUpdate},
|
||||
{Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: envDelete},
|
||||
{Name: "list", Usage: "List all environments", Flags: []cli.Flag{envNamespaceFlag}, Action: envList},
|
||||
{Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, envKeepArchiveFlag, minCpu, maxCpu, minMem, maxMem, envVersionFlag, envExternalNetworkFlag, envTerminationGracePeriodFlag, specSaveFlag}, Action: urfavecli.Wrapper(environment.Create)},
|
||||
{Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: urfavecli.Wrapper(environment.Get)},
|
||||
{Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, envKeepArchiveFlag, minCpu, maxCpu, minMem, maxMem, envExternalNetworkFlag, envTerminationGracePeriodFlag}, Action: urfavecli.Wrapper(environment.Update)},
|
||||
{Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: urfavecli.Wrapper(environment.Delete)},
|
||||
{Name: "list", Usage: "List all environments", Flags: []cli.Flag{envNamespaceFlag}, Action: urfavecli.Wrapper(environment.List)},
|
||||
}
|
||||
|
||||
// watches
|
||||
@@ -263,13 +266,6 @@ func NewCliApp() *cli.App {
|
||||
{Name: "delete", Usage: "Delete package", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag, pkgForceFlag, pkgOrphanFlag}, Action: pkgDelete},
|
||||
}
|
||||
|
||||
// upgrades, data migrations
|
||||
upgradeFileFlag := cli.StringFlag{Name: "file", Usage: "JSON file containing all fission state"}
|
||||
upgradeSubCommands := []cli.Command{
|
||||
{Name: "dump", Usage: "Dump all state from a v0.1 fission installation", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeDumpState},
|
||||
{Name: "restore", Usage: "Restore state dumped from a v0.1 install into a v0.2+ install", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeRestoreState},
|
||||
}
|
||||
|
||||
// specs
|
||||
specDirFlag := cli.StringFlag{Name: "specdir", Usage: "Directory to store specs, defaults to ./specs"}
|
||||
specNameFlag := cli.StringFlag{Name: "name", Usage: "(optional) Name for the app, applied to resources as a Kubernetes annotation"}
|
||||
@@ -320,7 +316,6 @@ func NewCliApp() *cli.App {
|
||||
{Name: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands},
|
||||
{Name: "package", Aliases: []string{"pkg"}, Usage: "Manage packages", Subcommands: pkgSubCommands},
|
||||
{Name: "spec", Aliases: []string{"specs"}, Usage: "Manage a declarative app specification", Subcommands: specSubCommands},
|
||||
{Name: "upgrade", Aliases: []string{}, Usage: "Upgrade tool from fission v0.1", Subcommands: upgradeSubCommands},
|
||||
{Name: "support", Usage: "Collect an archive of diagnostic information for support", Subcommands: supportSubCommands},
|
||||
cmdPlugin,
|
||||
{Name: "canary-config", Aliases: []string{}, Usage: "Create, Update and manage Canary Configs", Subcommands: canarySubCommands},
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
"github.com/fission/fission/pkg/types"
|
||||
@@ -110,7 +111,7 @@ func mqtCreate(c *cli.Context) error {
|
||||
// if we're writing a spec, don't call the API
|
||||
if c.Bool("spec") {
|
||||
specFile := fmt.Sprintf("mqtrigger-%v.yaml", mqtName)
|
||||
err := specSave(*mqt, specFile)
|
||||
err := spec.SpecSave(*mqt, specFile)
|
||||
util.CheckErr(err, "create message queue trigger spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ import (
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/urfavecli"
|
||||
cmdutils "github.com/fission/fission/pkg/fission-cli/cmd"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
storageSvcClient "github.com/fission/fission/pkg/storagesvc/client"
|
||||
@@ -95,7 +98,7 @@ func pkgCreate(c *cli.Context) error {
|
||||
log.Fatal("Need --src to specify source archive, or use --deploy to specify deployment archive.")
|
||||
}
|
||||
|
||||
createPackage(client, pkgNamespace, envName, envNamespace, srcArchiveFiles, deployArchiveFiles, buildcmd, "", "", false)
|
||||
createPackage(c, client, pkgNamespace, envName, envNamespace, srcArchiveFiles, deployArchiveFiles, buildcmd, "", "", false)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -511,7 +514,7 @@ func createArchive(client *client.Client, includeFiles []string, noZip bool, spe
|
||||
|
||||
if len(specFile) > 0 {
|
||||
// create an ArchiveUploadSpec and reference it from the archive
|
||||
aus := &ArchiveUploadSpec{
|
||||
aus := &spec.ArchiveUploadSpec{
|
||||
Name: archiveName("", includeFiles),
|
||||
IncludeGlobs: includeFiles,
|
||||
}
|
||||
@@ -519,19 +522,19 @@ func createArchive(client *client.Client, includeFiles []string, noZip bool, spe
|
||||
// check if this AUS exists in the specs; if so, don't create a new one
|
||||
fr, err := readSpecs(specDir)
|
||||
util.CheckErr(err, "read specs")
|
||||
if m := fr.specExists(aus, false, true); m != nil {
|
||||
if m := fr.SpecExists(aus, false, true); m != nil {
|
||||
fmt.Printf("Re-using previously created archive %v\n", m.Name)
|
||||
aus.Name = m.Name
|
||||
} else {
|
||||
// save the uploadspec
|
||||
err := specSave(*aus, specFile)
|
||||
err := spec.SpecSave(*aus, specFile)
|
||||
util.CheckErr(err, fmt.Sprintf("write spec file %v", specFile))
|
||||
}
|
||||
|
||||
// create the archive object
|
||||
ar := &fv1.Archive{
|
||||
Type: fv1.ArchiveTypeUrl,
|
||||
URL: fmt.Sprintf("%v%v", ARCHIVE_URL_PREFIX, aus.Name),
|
||||
URL: fmt.Sprintf("%v%v", spec.ARCHIVE_URL_PREFIX, aus.Name),
|
||||
}
|
||||
return ar
|
||||
}
|
||||
@@ -581,7 +584,7 @@ func uploadArchive(ctx context.Context, client *client.Client, fileName string)
|
||||
return &archive
|
||||
}
|
||||
|
||||
func createPackage(client *client.Client, pkgNamespace string, envName string, envNamespace string, srcArchiveFiles []string, deployArchiveFiles []string, buildcmd string, specDir string, specFile string, noZip bool) *metav1.ObjectMeta {
|
||||
func createPackage(c *cli.Context, client *client.Client, pkgNamespace string, envName string, envNamespace string, srcArchiveFiles []string, deployArchiveFiles []string, buildcmd string, specDir string, specFile string, noZip bool) *metav1.ObjectMeta {
|
||||
pkgSpec := fv1.PackageSpec{
|
||||
Environment: fv1.EnvironmentReference{
|
||||
Namespace: envNamespace,
|
||||
@@ -624,14 +627,14 @@ func createPackage(client *client.Client, pkgNamespace string, envName string, e
|
||||
|
||||
if len(specFile) > 0 {
|
||||
// if a package sith the same spec exists, don't create a new spec file
|
||||
fr, err := readSpecs(getSpecDir(nil))
|
||||
fr, err := readSpecs(cmdutils.GetSpecDir(urfavecli.Parse(c)))
|
||||
util.CheckErr(err, "read specs")
|
||||
if m := fr.specExists(pkg, false, true); m != nil {
|
||||
if m := fr.SpecExists(pkg, false, true); m != nil {
|
||||
fmt.Printf("Re-using previously created package %v\n", m.Name)
|
||||
return m
|
||||
}
|
||||
|
||||
err = specSave(*pkg, specFile)
|
||||
err = spec.SpecSave(*pkg, specFile)
|
||||
util.CheckErr(err, "save package spec")
|
||||
return &pkg.Metadata
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
@@ -82,7 +83,7 @@ func recorderCreate(c *cli.Context) error {
|
||||
// If we're writing a spec, don't call the API
|
||||
if c.Bool("spec") {
|
||||
specFile := fmt.Sprintf("recorder-%v.yaml", recName)
|
||||
err := specSave(*recorder, specFile)
|
||||
err := spec.SpecSave(*recorder, specFile)
|
||||
util.CheckErr(err, "create recorder spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
+98
-670
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/controller/client"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
@@ -93,7 +94,7 @@ func ttCreate(c *cli.Context) error {
|
||||
// if we're writing a spec, don't call the API
|
||||
if c.Bool("spec") {
|
||||
specFile := fmt.Sprintf("timetrigger-%v.yaml", name)
|
||||
err := specSave(*tt, specFile)
|
||||
err := spec.SpecSave(*tt, specFile)
|
||||
util.CheckErr(err, "create time trigger spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Fission 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 fission_cli
|
||||
|
||||
const (
|
||||
FISSION_DEPLOYMENT_NAME_KEY = "fission-name"
|
||||
FISSION_DEPLOYMENT_UID_KEY = "fission-uid"
|
||||
)
|
||||
|
||||
// CLI spec types
|
||||
type (
|
||||
// DeploymentConfig is the global configuration for a set of Fission specs.
|
||||
DeploymentConfig struct {
|
||||
// TypeMeta describes the type of this object. It is inlined. The Kind
|
||||
// field should always be "DeploymentConfig".
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Name is a user-friendly name for the deployment. It is also stored in
|
||||
// all uploaded resources as an annotation.
|
||||
Name string `json:"name"`
|
||||
|
||||
// UID uniquely identifies the deployment. It is stored as a label and
|
||||
// used to find resources to clean up when local specs are changed.
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
|
||||
// ArchiveUploadSpec specifies a set of files to be archived and uploaded.
|
||||
//
|
||||
// The resulting archive can be referenced as archive://<Name> in PackageSpecs,
|
||||
// using the name specified in the archive. The fission spec applier will
|
||||
// replace the archive:// URL with a real HTTP URL after uploading the file.
|
||||
ArchiveUploadSpec struct {
|
||||
// TypeMeta describes the type of this object. It is inlined. The Kind
|
||||
// field should always be "ArchiveUploadSpec".
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Name is a local name that can be used to reference this archive. It
|
||||
// must be unique; duplicate names will cause an error while handling
|
||||
// specs.
|
||||
Name string `json:"name"`
|
||||
|
||||
// RootDir specifies the root that the globs below are relative to. It
|
||||
// is optional and defaults to the parent directory of the spec
|
||||
// directory: for example, if the deployment config is at
|
||||
// /path/to/project/specs/config.yaml, the RootDir is /path/to/project.
|
||||
RootDir string `json:"rootdir,omitempty"`
|
||||
|
||||
// IncludeGlobs is a list of Unix shell globs to include
|
||||
IncludeGlobs []string `json:"include,omitempty"`
|
||||
|
||||
// ExcludeGlobs is a list of globs to exclude from the set specified by
|
||||
// IncludeGlobs.
|
||||
ExcludeGlobs []string `json:"exclude,omitempty"`
|
||||
}
|
||||
|
||||
// TypeMeta is the same as Kubernetes' TypeMeta, and allows us to version and
|
||||
// unmarshal local-only objects (like ArchiveUploadSpec) the same way that
|
||||
// Kubernetes does.
|
||||
TypeMeta struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
}
|
||||
)
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Fission 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 fission_cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dchest/uniuri"
|
||||
"github.com/urfave/cli"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
v1 "github.com/fission/fission/pkg/v1"
|
||||
)
|
||||
|
||||
type (
|
||||
V1FissionState struct {
|
||||
Functions []v1.Function `json:"functions"`
|
||||
Environments []v1.Environment `json:"environments"`
|
||||
HTTPTriggers []v1.HTTPTrigger `json:"httptriggers"`
|
||||
Mqtriggers []v1.MessageQueueTrigger `json:"mqtriggers"`
|
||||
TimeTriggers []v1.TimeTrigger `json:"timetriggers"`
|
||||
Watches []v1.Watch `json:"watches"`
|
||||
NameChanges map[string]string `json:"namechanges"`
|
||||
}
|
||||
nameRemapper struct {
|
||||
oldToNew map[string]string
|
||||
newNames map[string]bool
|
||||
}
|
||||
)
|
||||
|
||||
func getV1URL(serverUrl string) string {
|
||||
if len(serverUrl) == 0 {
|
||||
log.Fatal("Need --server or FISSION_URL set to your fission server.")
|
||||
}
|
||||
isHTTPS := strings.Index(serverUrl, "https://") == 0
|
||||
isHTTP := strings.Index(serverUrl, "http://") == 0
|
||||
if !(isHTTP || isHTTPS) {
|
||||
serverUrl = "http://" + serverUrl
|
||||
}
|
||||
v1url := strings.TrimSuffix(serverUrl, "/") + "/v1"
|
||||
return v1url
|
||||
}
|
||||
|
||||
func get(url string) []byte {
|
||||
resp, err := http.Get(url)
|
||||
util.CheckErr(err, "get fission v0.1 state")
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
util.CheckErr(err, "reading server response")
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Fatal(fmt.Sprintf("Failed to fetch fission v0.1 state: %v", string(body)))
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// track a name in the remapper, creating a new name if needed
|
||||
func (nr *nameRemapper) trackName(old string) {
|
||||
// all kubernetes names must match this regex
|
||||
kubeNameRegex := "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
|
||||
maxLen := 63
|
||||
|
||||
ok, err := regexp.MatchString(kubeNameRegex, old)
|
||||
util.CheckErr(err, "match name regexp")
|
||||
if ok && len(old) < maxLen {
|
||||
// no rename
|
||||
nr.oldToNew[old] = old
|
||||
nr.newNames[old] = true
|
||||
return
|
||||
}
|
||||
|
||||
newName := strings.ToLower(old)
|
||||
|
||||
// remove disallowed
|
||||
inv, err := regexp.Compile("[^-a-z0-9]")
|
||||
util.CheckErr(err, "compile regexp")
|
||||
newName = string(inv.ReplaceAll([]byte(newName), []byte("-")))
|
||||
|
||||
// trim leading non-alphabetic
|
||||
leadingnonalpha, err := regexp.Compile("^[^a-z]+")
|
||||
util.CheckErr(err, "compile regexp")
|
||||
newName = string(leadingnonalpha.ReplaceAll([]byte(newName), []byte{}))
|
||||
|
||||
// trim trailing
|
||||
trailing, err := regexp.Compile("[^a-z0-9]+$")
|
||||
util.CheckErr(err, "compile regexp")
|
||||
newName = string(trailing.ReplaceAll([]byte(newName), []byte{}))
|
||||
|
||||
// truncate to length
|
||||
if len(newName) > maxLen-4 {
|
||||
newName = newName[0:(maxLen - 4)]
|
||||
}
|
||||
|
||||
// uniqueness
|
||||
n := newName
|
||||
i := 0
|
||||
for {
|
||||
_, exists := nr.newNames[n]
|
||||
if !exists {
|
||||
break
|
||||
} else {
|
||||
i++
|
||||
n = fmt.Sprintf("%v-%v", newName, i)
|
||||
}
|
||||
}
|
||||
newName = n
|
||||
|
||||
// track
|
||||
nr.oldToNew[old] = newName
|
||||
nr.newNames[newName] = true
|
||||
}
|
||||
|
||||
func upgradeDumpV1State(v1url string, filename string) {
|
||||
var v1state V1FissionState
|
||||
|
||||
fmt.Println("Getting environments")
|
||||
resp := get(v1url + "/environments")
|
||||
err := json.Unmarshal(resp, &v1state.Environments)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
fmt.Println("Getting watches")
|
||||
resp = get(v1url + "/watches")
|
||||
err = json.Unmarshal(resp, &v1state.Watches)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
fmt.Println("Getting routes")
|
||||
resp = get(v1url + "/triggers/http")
|
||||
err = json.Unmarshal(resp, &v1state.HTTPTriggers)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
fmt.Println("Getting message queue triggers")
|
||||
resp = get(v1url + "/triggers/messagequeue")
|
||||
err = json.Unmarshal(resp, &v1state.Mqtriggers)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
fmt.Println("Getting time triggers")
|
||||
resp = get(v1url + "/triggers/time")
|
||||
err = json.Unmarshal(resp, &v1state.TimeTriggers)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
fmt.Println("Getting function list")
|
||||
resp = get(v1url + "/functions")
|
||||
err = json.Unmarshal(resp, &v1state.Functions)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
// we have to change names that are disallowed in kubernetes
|
||||
nr := nameRemapper{
|
||||
oldToNew: make(map[string]string),
|
||||
newNames: make(map[string]bool),
|
||||
}
|
||||
|
||||
// get all referenced function metadata
|
||||
funcMetaSet := make(map[v1.Metadata]bool)
|
||||
for _, f := range v1state.Functions {
|
||||
funcMetaSet[f.Metadata] = true
|
||||
nr.trackName(f.Metadata.Name)
|
||||
}
|
||||
for _, t := range v1state.HTTPTriggers {
|
||||
funcMetaSet[t.Function] = true
|
||||
nr.trackName(t.Metadata.Name)
|
||||
}
|
||||
for _, t := range v1state.Mqtriggers {
|
||||
funcMetaSet[t.Function] = true
|
||||
nr.trackName(t.Metadata.Name)
|
||||
}
|
||||
for _, t := range v1state.Watches {
|
||||
funcMetaSet[t.Function] = true
|
||||
nr.trackName(t.Metadata.Name)
|
||||
}
|
||||
for _, t := range v1state.TimeTriggers {
|
||||
funcMetaSet[t.Function] = true
|
||||
nr.trackName(t.Metadata.Name)
|
||||
}
|
||||
|
||||
for _, e := range v1state.Environments {
|
||||
nr.trackName(e.Metadata.Name)
|
||||
}
|
||||
|
||||
fmt.Println("Getting functions")
|
||||
// get each function
|
||||
funcs := make(map[v1.Metadata]v1.Function)
|
||||
for m := range funcMetaSet {
|
||||
if len(m.Uid) != 0 {
|
||||
resp = get(fmt.Sprintf("%v/functions/%v?uid=%v", v1url, m.Name, m.Uid))
|
||||
} else {
|
||||
resp = get(fmt.Sprintf("%v/functions/%v", v1url, m.Name))
|
||||
}
|
||||
|
||||
var f v1.Function
|
||||
|
||||
// unmarshal
|
||||
err = json.Unmarshal(resp, &f)
|
||||
util.CheckErr(err, "parse server response")
|
||||
|
||||
// load into a map to remove duplicates
|
||||
funcs[f.Metadata] = f
|
||||
}
|
||||
|
||||
// add list of unique functions to v1state from map
|
||||
v1state.Functions = make([]v1.Function, 0)
|
||||
for _, f := range funcs {
|
||||
v1state.Functions = append(v1state.Functions, f)
|
||||
}
|
||||
|
||||
// dump name changes
|
||||
v1state.NameChanges = nr.oldToNew
|
||||
|
||||
// serialize v1state
|
||||
out, err := json.MarshalIndent(v1state, "", " ")
|
||||
util.CheckErr(err, "serialize v0.1 state")
|
||||
|
||||
// dump to file fission-v01-state.json
|
||||
if len(filename) == 0 {
|
||||
filename = "fission-v01-state.json"
|
||||
}
|
||||
err = ioutil.WriteFile(filename, out, 0644)
|
||||
util.CheckErr(err, "write file")
|
||||
|
||||
fmt.Printf("Done: Saved %v functions, %v HTTP triggers, %v watches, %v message queue triggers, %v time triggers.\n",
|
||||
len(v1state.Functions), len(v1state.HTTPTriggers), len(v1state.Watches), len(v1state.Mqtriggers), len(v1state.TimeTriggers))
|
||||
}
|
||||
|
||||
func functionRefFromV1Metadata(m *v1.Metadata, nameRemap map[string]string) *fv1.FunctionReference {
|
||||
return &fv1.FunctionReference{
|
||||
Type: fv1.FunctionReferenceTypeFunctionName,
|
||||
Name: nameRemap[m.Name],
|
||||
}
|
||||
}
|
||||
|
||||
func crdMetadataFromV1Metadata(m *v1.Metadata, nameRemap map[string]string) *metav1.ObjectMeta {
|
||||
return &metav1.ObjectMeta{
|
||||
Name: nameRemap[m.Name],
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func upgradeDumpState(c *cli.Context) error {
|
||||
u := getV1URL(c.GlobalString("server"))
|
||||
filename := c.String("file")
|
||||
|
||||
// check v1
|
||||
resp, err := http.Get(u + "/environments")
|
||||
util.CheckErr(err, "reach fission server")
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
msg := fmt.Sprintf("Server %v isn't a v1 Fission server. Use --server to point at a pre-0.2.x Fission server.", u)
|
||||
log.Fatal(msg)
|
||||
}
|
||||
|
||||
upgradeDumpV1State(u, filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
func upgradeRestoreState(c *cli.Context) error {
|
||||
filename := c.String("file")
|
||||
if len(filename) == 0 {
|
||||
filename = "fission-v01-state.json"
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(filename)
|
||||
util.CheckErr(err, fmt.Sprintf("open file %v", filename))
|
||||
|
||||
var v1state V1FissionState
|
||||
err = json.Unmarshal(contents, &v1state)
|
||||
util.CheckErr(err, "parse dumped v1 state")
|
||||
|
||||
// create a regular v2 client
|
||||
client := util.GetApiClient(c.GlobalString("server"))
|
||||
|
||||
// create functions
|
||||
for _, f := range v1state.Functions {
|
||||
|
||||
// get post-rename function name, derive pkg name from it
|
||||
fnName := v1state.NameChanges[f.Metadata.Name]
|
||||
pkgName := fmt.Sprintf("%v-%v", fnName, strings.ToLower(uniuri.NewLen(6)))
|
||||
|
||||
// write function to file
|
||||
tmpfile, err := ioutil.TempFile("", pkgName)
|
||||
util.CheckErr(err, "create temporary file")
|
||||
code, err := base64.StdEncoding.DecodeString(f.Code)
|
||||
util.CheckErr(err, "decode base64 function contents")
|
||||
tmpfile.Write(code)
|
||||
tmpfile.Sync()
|
||||
tmpfile.Close()
|
||||
|
||||
// upload
|
||||
ctx := context.Background()
|
||||
archive := uploadArchive(ctx, client, tmpfile.Name())
|
||||
os.Remove(tmpfile.Name())
|
||||
|
||||
// create pkg
|
||||
pkgSpec := fv1.PackageSpec{
|
||||
Environment: fv1.EnvironmentReference{
|
||||
Name: v1state.NameChanges[f.Environment.Name],
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Deployment: *archive,
|
||||
}
|
||||
pkg, err := client.PackageCreate(&fv1.Package{
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: pkgName,
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: pkgSpec,
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create package %v", pkgName))
|
||||
_, err = client.FunctionCreate(&fv1.Function{
|
||||
Metadata: *crdMetadataFromV1Metadata(&f.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.FunctionSpec{
|
||||
Environment: pkgSpec.Environment,
|
||||
Package: fv1.FunctionPackageRef{
|
||||
PackageRef: fv1.PackageRef{
|
||||
Name: pkg.Name,
|
||||
Namespace: pkg.Namespace,
|
||||
ResourceVersion: pkg.ResourceVersion,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create function %v", v1state.NameChanges[f.Metadata.Name]))
|
||||
|
||||
}
|
||||
|
||||
// create envs
|
||||
for _, e := range v1state.Environments {
|
||||
_, err = client.EnvironmentCreate(&fv1.Environment{
|
||||
Metadata: *crdMetadataFromV1Metadata(&e.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.EnvironmentSpec{
|
||||
Version: 1,
|
||||
Runtime: fv1.Runtime{
|
||||
Image: e.RunContainerImageUrl,
|
||||
},
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create environment %v", e.Metadata.Name))
|
||||
}
|
||||
|
||||
// create httptriggers
|
||||
for _, t := range v1state.HTTPTriggers {
|
||||
_, err = client.HTTPTriggerCreate(&fv1.HTTPTrigger{
|
||||
Metadata: *crdMetadataFromV1Metadata(&t.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.HTTPTriggerSpec{
|
||||
RelativeURL: t.UrlPattern,
|
||||
Method: t.Method,
|
||||
FunctionReference: *functionRefFromV1Metadata(&t.Function, v1state.NameChanges),
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create http trigger %v", t.Metadata.Name))
|
||||
}
|
||||
|
||||
// create mqtriggers
|
||||
for _, t := range v1state.Mqtriggers {
|
||||
_, err = client.MessageQueueTriggerCreate(&fv1.MessageQueueTrigger{
|
||||
Metadata: *crdMetadataFromV1Metadata(&t.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.MessageQueueTriggerSpec{
|
||||
FunctionReference: *functionRefFromV1Metadata(&t.Function, v1state.NameChanges),
|
||||
MessageQueueType: fv1.MessageQueueTypeNats, // only NATS is supported at that time (v1 types)
|
||||
Topic: t.Topic,
|
||||
ResponseTopic: t.ResponseTopic,
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create http trigger %v", t.Metadata.Name))
|
||||
}
|
||||
|
||||
// create time triggers
|
||||
for _, t := range v1state.TimeTriggers {
|
||||
_, err = client.TimeTriggerCreate(&fv1.TimeTrigger{
|
||||
Metadata: *crdMetadataFromV1Metadata(&t.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.TimeTriggerSpec{
|
||||
FunctionReference: *functionRefFromV1Metadata(&t.Function, v1state.NameChanges),
|
||||
Cron: t.Cron,
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create time trigger %v", t.Metadata.Name))
|
||||
}
|
||||
|
||||
// create watches
|
||||
for _, t := range v1state.Watches {
|
||||
_, err = client.WatchCreate(&fv1.KubernetesWatchTrigger{
|
||||
Metadata: *crdMetadataFromV1Metadata(&t.Metadata, v1state.NameChanges),
|
||||
Spec: fv1.KubernetesWatchTriggerSpec{
|
||||
Namespace: t.Namespace,
|
||||
Type: t.ObjType,
|
||||
FunctionReference: *functionRefFromV1Metadata(&t.Function, v1state.NameChanges),
|
||||
},
|
||||
})
|
||||
util.CheckErr(err, fmt.Sprintf("create kubernetes watch trigger %v", t.Metadata.Name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/fission-cli/cmd/spec"
|
||||
"github.com/fission/fission/pkg/fission-cli/log"
|
||||
"github.com/fission/fission/pkg/fission-cli/util"
|
||||
)
|
||||
@@ -82,7 +83,7 @@ func wCreate(c *cli.Context) error {
|
||||
// if we're writing a spec, don't call the API
|
||||
if c.Bool("spec") {
|
||||
specFile := fmt.Sprintf("kubewatch-%v.yaml", watchName)
|
||||
err := specSave(*w, specFile)
|
||||
err := spec.SpecSave(*w, specFile)
|
||||
util.CheckErr(err, "create kubernetes watch spec")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 generator is a generator that helps to convert a resource object
|
||||
// into target encoding format like JSON, YAML ...etc.
|
||||
package generator
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 encoder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
type (
|
||||
EncodeCodec string
|
||||
|
||||
Encoder interface {
|
||||
Marshal(v interface{}) ([]byte, error)
|
||||
Unmarshal(data []byte, v interface{}) error
|
||||
}
|
||||
|
||||
JSONEncoder struct{}
|
||||
YAMLEncoder struct{}
|
||||
)
|
||||
|
||||
var _ Encoder = DefaultJSONEncoder()
|
||||
|
||||
func DefaultJSONEncoder() Encoder {
|
||||
return JSONEncoder{}
|
||||
}
|
||||
|
||||
func (encoder JSONEncoder) Marshal(v interface{}) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (encoder JSONEncoder) Unmarshal(data []byte, v interface{}) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
var _ Encoder = DefaultYAMLEncoder()
|
||||
|
||||
func DefaultYAMLEncoder() Encoder {
|
||||
return YAMLEncoder{}
|
||||
}
|
||||
|
||||
func (encoder YAMLEncoder) Marshal(v interface{}) ([]byte, error) {
|
||||
return yaml.Marshal(v)
|
||||
}
|
||||
|
||||
func (encoder YAMLEncoder) Unmarshal(data []byte, v interface{}) error {
|
||||
return yaml.Unmarshal(data, v)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 generator
|
||||
|
||||
import (
|
||||
"github.com/fission/fission/pkg/generator/encoder"
|
||||
)
|
||||
|
||||
type (
|
||||
StructuredGenerator interface {
|
||||
StructuredGenerate(enc encoder.Encoder) ([]byte, error)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2019 The Fission 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 v1
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
|
||||
"github.com/fission/fission/pkg/generator"
|
||||
"github.com/fission/fission/pkg/generator/encoder"
|
||||
)
|
||||
|
||||
// ensure environment generator fits the generator interface
|
||||
var _ generator.StructuredGenerator = &EnvironmentGenerator{}
|
||||
|
||||
type (
|
||||
EnvironmentGenerator struct {
|
||||
obj *fv1.Environment
|
||||
}
|
||||
)
|
||||
|
||||
// CreateEnvironmentGeneratorFromObj creates environment generator initialized with pass-in environment object.
|
||||
func CreateEnvironmentGeneratorFromObj(env *fv1.Environment) (*EnvironmentGenerator, error) {
|
||||
if env == nil {
|
||||
return nil, errors.New("cannot create environment generator with nil pointer")
|
||||
}
|
||||
|
||||
if len(env.TypeMeta.Kind) == 0 {
|
||||
env.TypeMeta.Kind = fv1.CRD_NAME_ENVIRONMENT
|
||||
}
|
||||
|
||||
if len(env.TypeMeta.APIVersion) == 0 {
|
||||
env.TypeMeta.APIVersion = fv1.CRD_VERSION
|
||||
}
|
||||
|
||||
err := env.Validate()
|
||||
if err != nil {
|
||||
return nil, fv1.AggregateValidationErrors("Environment", err)
|
||||
}
|
||||
|
||||
return &EnvironmentGenerator{
|
||||
obj: env,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (generator EnvironmentGenerator) StructuredGenerate(enc encoder.Encoder) ([]byte, error) {
|
||||
return enc.Marshal(generator.obj)
|
||||
}
|
||||
Reference in New Issue
Block a user