-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
649 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package templates | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/parameters" | ||
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types" | ||
"github.com/kyma-project/cli.v3/internal/kube" | ||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
) | ||
|
||
type KubeClientGetter interface { | ||
GetKubeClientWithClierr() (kube.Client, clierror.Error) | ||
} | ||
|
||
type CreateOptions struct { | ||
types.CreateCommand | ||
ResourceInfo types.ResourceInfo | ||
} | ||
|
||
func BuildCreateCommand(clientGetter KubeClientGetter, createOptions *CreateOptions) *cobra.Command { | ||
return buildCreateCommand(os.Stdout, clientGetter, createOptions) | ||
} | ||
|
||
func buildCreateCommand(out io.Writer, clientGetter KubeClientGetter, createOptions *CreateOptions) *cobra.Command { | ||
extraValues := []parameters.Value{} | ||
cmd := &cobra.Command{ | ||
Use: "create", | ||
Short: createOptions.Description, | ||
Long: createOptions.DescriptionLong, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
clierror.Check(createResource(&createArgs{ | ||
out: out, | ||
ctx: cmd.Context(), | ||
clientGetter: clientGetter, | ||
createOptions: createOptions, | ||
extraValues: extraValues, | ||
})) | ||
}, | ||
} | ||
|
||
flags := append(createOptions.CustomFlags, buildDefaultFlags(createOptions.ResourceInfo.Scope)...) | ||
for _, flag := range flags { | ||
value := parameters.NewTyped(flag.Type, flag.Path, flag.DefaultValue) | ||
cmd.Flags().VarP(value, flag.Name, flag.Shorthand, flag.Description) | ||
if flag.Required { | ||
_ = cmd.MarkFlagRequired(flag.Name) | ||
} | ||
extraValues = append(extraValues, value) | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
type createArgs struct { | ||
out io.Writer | ||
ctx context.Context | ||
clientGetter KubeClientGetter | ||
createOptions *CreateOptions | ||
extraValues []parameters.Value | ||
} | ||
|
||
func createResource(args *createArgs) clierror.Error { | ||
u := &unstructured.Unstructured{} | ||
u.SetGroupVersionKind(schema.GroupVersionKind{ | ||
Group: args.createOptions.ResourceInfo.Group, | ||
Version: args.createOptions.ResourceInfo.Version, | ||
Kind: args.createOptions.ResourceInfo.Kind, | ||
}) | ||
|
||
client, clierr := args.clientGetter.GetKubeClientWithClierr() | ||
if clierr != nil { | ||
return clierr | ||
} | ||
|
||
for _, extraValue := range args.extraValues { | ||
value := extraValue.GetValue() | ||
if value == nil { | ||
// value is not set and has no default value | ||
continue | ||
} | ||
|
||
fields := strings.Split( | ||
// remove optional dot at the beginning of the path | ||
strings.TrimPrefix(extraValue.GetPath(), "."), | ||
".", | ||
) | ||
|
||
err := unstructured.SetNestedField(u.Object, value, fields...) | ||
if err != nil { | ||
return clierror.Wrap(err, clierror.New( | ||
fmt.Sprintf("failed to set value %v for path %s", value, extraValue.GetPath()), | ||
)) | ||
} | ||
} | ||
|
||
err := client.RootlessDynamic().Apply(args.ctx, u) | ||
if err != nil { | ||
return clierror.Wrap(err, clierror.New("failed to create resource")) | ||
} | ||
|
||
fmt.Fprintf(args.out, "resource %s applied\n", getResourceName(args.createOptions.ResourceInfo.Scope, u)) | ||
return nil | ||
} | ||
|
||
func buildDefaultFlags(resourceScope types.Scope) []types.CreateCustomFlag { | ||
params := []types.CreateCustomFlag{ | ||
{ | ||
Name: "name", | ||
Type: types.StringCustomFlagType, | ||
Description: "name of the resource", | ||
Path: ".metadata.name", | ||
Required: true, | ||
}, | ||
} | ||
if resourceScope == types.NamespaceScope { | ||
params = append(params, types.CreateCustomFlag{ | ||
Name: "namespace", | ||
Type: types.StringCustomFlagType, | ||
Description: "resource namespace", | ||
Path: ".metadata.namespace", | ||
DefaultValue: "default", | ||
}) | ||
} | ||
|
||
return params | ||
} | ||
|
||
func getResourceName(scope types.Scope, u *unstructured.Unstructured) string { | ||
if scope == types.NamespaceScope { | ||
return fmt.Sprintf("%s/%s", u.GetNamespace(), u.GetName()) | ||
} | ||
|
||
return u.GetName() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package templates | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"io" | ||
"testing" | ||
|
||
"github.com/kyma-project/cli.v3/internal/clierror" | ||
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/parameters" | ||
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types" | ||
"github.com/kyma-project/cli.v3/internal/kube" | ||
"github.com/kyma-project/cli.v3/internal/kube/fake" | ||
"github.com/spf13/cobra" | ||
"github.com/stretchr/testify/require" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
) | ||
|
||
func Test_create(t *testing.T) { | ||
t.Run("build proper command", func(t *testing.T) { | ||
cmd := fixCreateCommand(bytes.NewBuffer([]byte{}), &mockGetter{}) | ||
|
||
require.Equal(t, "create", cmd.Use) | ||
require.Equal(t, "create test deploy", cmd.Short) | ||
require.Equal(t, "use this to create test deploy", cmd.Long) | ||
|
||
require.NotNil(t, cmd.Flag("name")) | ||
require.NotNil(t, cmd.Flag("namespace")) | ||
require.NotNil(t, cmd.Flag("replicas")) | ||
|
||
}) | ||
|
||
t.Run("create custom resource", func(t *testing.T) { | ||
buf := bytes.NewBuffer([]byte{}) | ||
fakeClient := &fake.RootlessDynamicClient{} | ||
mock := mockGetter{ | ||
client: &fake.KubeClient{ | ||
TestRootlessDynamicInterface: fakeClient, | ||
}, | ||
} | ||
cmd := fixCreateCommand(buf, &mock) | ||
|
||
cmd.SetArgs([]string{"--name", "test-deploy", "--namespace", "test-namespace", "--replicas", "2"}) | ||
err := cmd.Execute() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, "resource test-namespace/test-deploy applied\n", buf.String()) | ||
|
||
require.Len(t, fakeClient.ApplyObjs, 1) | ||
require.Equal(t, fixUnstructuredDeployment(), fakeClient.ApplyObjs[0]) | ||
}) | ||
|
||
t.Run("failed to get client from getter", func(t *testing.T) { | ||
buf := bytes.NewBuffer([]byte{}) | ||
mock := mockGetter{ | ||
clierror: clierror.New("test error"), | ||
client: nil, | ||
} | ||
|
||
err := createResource(&createArgs{ | ||
out: buf, | ||
ctx: context.Background(), | ||
clientGetter: &mock, | ||
createOptions: fixCreateOptions(), | ||
}) | ||
require.Equal(t, clierror.New("test error"), err) | ||
}) | ||
|
||
t.Run("failed to apply resource", func(t *testing.T) { | ||
buf := bytes.NewBuffer([]byte{}) | ||
fakeClient := &fake.RootlessDynamicClient{ | ||
ReturnErr: errors.New("test error"), | ||
} | ||
mock := mockGetter{ | ||
client: &fake.KubeClient{ | ||
TestRootlessDynamicInterface: fakeClient, | ||
}, | ||
} | ||
|
||
err := createResource(&createArgs{ | ||
out: buf, | ||
ctx: context.Background(), | ||
clientGetter: &mock, | ||
createOptions: fixCreateOptions(), | ||
extraValues: []parameters.Value{ | ||
parameters.NewTyped(types.StringCustomFlagType, ".metadata.name", "test-name"), | ||
parameters.NewTyped(types.StringCustomFlagType, ".metadata.namespace", "test-namespace"), | ||
parameters.NewTyped(types.IntCustomFlagType, ".spec.replicas", 1), | ||
}, | ||
}) | ||
require.Equal(t, clierror.Wrap(errors.New("test error"), clierror.New("failed to create resource")), err) | ||
}) | ||
} | ||
|
||
type mockGetter struct { | ||
clierror clierror.Error | ||
client kube.Client | ||
} | ||
|
||
func (m *mockGetter) GetKubeClientWithClierr() (kube.Client, clierror.Error) { | ||
return m.client, m.clierror | ||
} | ||
|
||
func fixCreateCommand(writer io.Writer, clietGetter KubeClientGetter) *cobra.Command { | ||
return buildCreateCommand(writer, clietGetter, fixCreateOptions()) | ||
} | ||
|
||
func fixCreateOptions() *CreateOptions { | ||
return &CreateOptions{ | ||
ResourceInfo: types.ResourceInfo{ | ||
Scope: types.NamespaceScope, | ||
Kind: "Deployment", | ||
Group: "apps", | ||
Version: "v1", | ||
}, | ||
CreateCommand: types.CreateCommand{ | ||
Description: "create test deploy", | ||
DescriptionLong: "use this to create test deploy", | ||
CustomFlags: []types.CreateCustomFlag{ | ||
{ | ||
Type: types.IntCustomFlagType, | ||
Name: "replicas", | ||
Description: "test flag", | ||
Shorthand: "r", | ||
Path: ".spec.replicas", | ||
DefaultValue: 3, | ||
Required: false, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func fixUnstructuredDeployment() unstructured.Unstructured { | ||
return unstructured.Unstructured{ | ||
Object: map[string]interface{}{ | ||
"apiVersion": "apps/v1", | ||
"kind": "Deployment", | ||
"metadata": map[string]interface{}{ | ||
"name": "test-deploy", | ||
"namespace": "test-namespace", | ||
}, | ||
"spec": map[string]interface{}{ | ||
"replicas": int64(2), | ||
}, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package templates | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_explain(t *testing.T) { | ||
t.Run("print output string", func(t *testing.T) { | ||
buf := bytes.NewBuffer([]byte{}) | ||
cmd := buildExplainCommand(buf, &ExplainOptions{ | ||
ExplainCommand: types.ExplainCommand{ | ||
Description: "test explain command", | ||
DescriptionLong: "this is test explain command", | ||
Output: "test output", | ||
}, | ||
}) | ||
|
||
require.Equal(t, "explain", cmd.Use) | ||
require.Equal(t, "test explain command", cmd.Short) | ||
require.Equal(t, "this is test explain command", cmd.Long) | ||
|
||
err := cmd.Execute() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, "test output\n", buf.String()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package parameters | ||
|
||
// this func makes sure that types used as an default follows YAML standards | ||
// converts: | ||
// int8,int16,int32,int64,int to int64 | ||
// string, []byte to string | ||
func sanitizeDefaultValue(defaultValue interface{}) interface{} { | ||
switch value := defaultValue.(type) { | ||
case int8: | ||
return int64(value) | ||
case int16: | ||
return int64(value) | ||
case int32: | ||
return int64(value) | ||
case int64: | ||
return value | ||
case int: | ||
return int64(value) | ||
case string: | ||
return value | ||
case []byte: | ||
return string(value) | ||
default: | ||
return nil | ||
} | ||
} |
Oops, something went wrong.