Allow to perform phase render using document entrypoint

In current setup, we rely on executor to perform render operation,
however there are many usecases, when we want to have a simple
`kustomize build` performed against document entrypoint in phase.
Now this will be a default behaviour and if a user would like
executor to perform rendering to actually understand what it's
doing, he will be able to specify --executor boolean flag.

Relates-To: #403

Change-Id: I181830f9814ff48a19ba0a1284e89900187bc7d8
This commit is contained in:
Kostiantyn Kalynovskyi 2020-11-11 19:20:28 -06:00
parent f2e305a56d
commit a6987107bd
7 changed files with 56 additions and 19 deletions

View File

@ -35,14 +35,14 @@ airshipctl phase render initinfra -l app=helm,service=tiller -k Deployment
// NewRenderCommand create a new command for document rendering // NewRenderCommand create a new command for document rendering
func NewRenderCommand(cfgFactory config.Factory) *cobra.Command { func NewRenderCommand(cfgFactory config.Factory) *cobra.Command {
filterOptions := &phase.FilterOptions{} filterOptions := &phase.RenderCommand{}
renderCmd := &cobra.Command{ renderCmd := &cobra.Command{
Use: "render PHASE_NAME", Use: "render PHASE_NAME",
Short: "Render phase documents from model", Short: "Render phase documents from model",
Example: renderExample, Example: renderExample,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return filterOptions.Render(cfgFactory, args[0], cmd.OutOrStdout()) return filterOptions.RunE(cfgFactory, args[0], cmd.OutOrStdout())
}, },
} }
@ -51,7 +51,7 @@ func NewRenderCommand(cfgFactory config.Factory) *cobra.Command {
} }
// addRenderFlags adds flags for document render sub-command // addRenderFlags adds flags for document render sub-command
func addRenderFlags(filterOptions *phase.FilterOptions, cmd *cobra.Command) { func addRenderFlags(filterOptions *phase.RenderCommand, cmd *cobra.Command) {
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVarP( flags.StringVarP(
@ -81,4 +81,12 @@ func addRenderFlags(filterOptions *phase.FilterOptions, cmd *cobra.Command) {
"k", "k",
"", "",
"filter documents by Kinds") "filter documents by Kinds")
flags.BoolVarP(
&filterOptions.Executor,
"executor",
"e",
false,
"if set to true rendering will be performed by executor "+
"otherwise phase entrypoint will be rendered by kustomize, if entrypoint is not specified "+
"error will be returned")
} }

View File

@ -17,6 +17,7 @@ airshipctl phase render initinfra -l app=helm,service=tiller -k Deployment
Flags: Flags:
-a, --annotation string filter documents by Annotations -a, --annotation string filter documents by Annotations
-g, --apiversion string filter documents by API version -g, --apiversion string filter documents by API version
-e, --executor if set to true rendering will be performed by executor otherwise phase entrypoint will be rendered by kustomize, if entrypoint is not specified error will be returned
-h, --help help for render -h, --help help for render
-k, --kind string filter documents by Kinds -k, --kind string filter documents by Kinds
-l, --label string filter documents by Labels -l, --label string filter documents by Labels

View File

@ -29,6 +29,7 @@ airshipctl phase render initinfra -l app=helm,service=tiller -k Deployment
``` ```
-a, --annotation string filter documents by Annotations -a, --annotation string filter documents by Annotations
-g, --apiversion string filter documents by API version -g, --apiversion string filter documents by API version
-e, --executor if set to true rendering will be performed by executor otherwise phase entrypoint will be rendered by kustomize, if entrypoint is not specified error will be returned
-h, --help help for render -h, --help help for render
-k, --kind string filter documents by Kinds -k, --kind string filter documents by Kinds
-l, --label string filter documents by Labels -l, --label string filter documents by Labels

View File

@ -134,13 +134,30 @@ func (p *phase) Validate() error {
} }
// Render executor documents // Render executor documents
func (p *phase) Render(w io.Writer, options ifc.RenderOptions) error { func (p *phase) Render(w io.Writer, executorRender bool, options ifc.RenderOptions) error {
executor, err := p.Executor() if executorRender {
executor, err := p.Executor()
if err != nil {
return err
}
return executor.Render(w, options)
}
root, err := p.DocumentRoot()
if err != nil { if err != nil {
return err return err
} }
return executor.Render(w, options) bundle, err := document.NewBundleByPath(root)
if err != nil {
return err
}
rendered, err := bundle.SelectBundle(options.FilterSelector)
if err != nil {
return err
}
return rendered.Write(w)
} }
// DocumentRoot root that holds all the documents associated with the phase // DocumentRoot root that holds all the documents associated with the phase

View File

@ -28,7 +28,7 @@ type Phase interface {
DocumentRoot() (string, error) DocumentRoot() (string, error)
Details() (string, error) Details() (string, error)
Executor() (Executor, error) Executor() (Executor, error)
Render(io.Writer, RenderOptions) error Render(io.Writer, bool, RenderOptions) error
} }
// ID uniquely identifies the phase // ID uniquely identifies the phase

View File

@ -23,8 +23,8 @@ import (
"opendev.org/airship/airshipctl/pkg/phase/ifc" "opendev.org/airship/airshipctl/pkg/phase/ifc"
) )
// FilterOptions holds filters for selector // RenderCommand holds filters for selector
type FilterOptions struct { type RenderCommand struct {
// Label filters documents by label string // Label filters documents by label string
Label string Label string
// Annotation filters documents by annotation string // Annotation filters documents by annotation string
@ -33,10 +33,12 @@ type FilterOptions struct {
APIVersion string APIVersion string
// Kind filters documents by document kind // Kind filters documents by document kind
Kind string Kind string
// Executor identifies if executor should perform rendering
Executor bool
} }
// Render prints out filtered documents // RunE prints out filtered documents
func (fo *FilterOptions) Render(cfgFactory config.Factory, phaseName string, out io.Writer) error { func (fo *RenderCommand) RunE(cfgFactory config.Factory, phaseName string, out io.Writer) error {
cfg, err := cfgFactory() cfg, err := cfgFactory()
if err != nil { if err != nil {
return err return err
@ -62,6 +64,5 @@ func (fo *FilterOptions) Render(cfgFactory config.Factory, phaseName string, out
} }
sel := document.NewSelector().ByLabel(fo.Label).ByAnnotation(fo.Annotation).ByGvk(group, version, fo.Kind) sel := document.NewSelector().ByLabel(fo.Label).ByAnnotation(fo.Annotation).ByGvk(group, version, fo.Kind)
return phase.Render(out, fo.Executor, ifc.RenderOptions{FilterSelector: sel})
return phase.Render(out, ifc.RenderOptions{FilterSelector: sel})
} }

View File

@ -43,19 +43,19 @@ func TestRender(t *testing.T) {
fixturePath := "phase" fixturePath := "phase"
tests := []struct { tests := []struct {
name string name string
settings *phase.FilterOptions settings *phase.RenderCommand
expResFile string expResFile string
expErr error expErr error
}{ }{
{ {
name: "No Filters", name: "No Filters",
settings: &phase.FilterOptions{}, settings: &phase.RenderCommand{},
expResFile: "noFilter.yaml", expResFile: "noFilter.yaml",
expErr: nil, expErr: nil,
}, },
{ {
name: "All Filters", name: "All Filters",
settings: &phase.FilterOptions{ settings: &phase.RenderCommand{
Label: "airshipit.org/deploy-k8s=false", Label: "airshipit.org/deploy-k8s=false",
Annotation: "airshipit.org/clustertype=ephemeral", Annotation: "airshipit.org/clustertype=ephemeral",
APIVersion: "metal3.io/v1alpha1", APIVersion: "metal3.io/v1alpha1",
@ -66,7 +66,7 @@ func TestRender(t *testing.T) {
}, },
{ {
name: "Multiple Labels", name: "Multiple Labels",
settings: &phase.FilterOptions{ settings: &phase.RenderCommand{
Label: "airshipit.org/deploy-k8s=false, airshipit.org/ephemeral-node=true", Label: "airshipit.org/deploy-k8s=false, airshipit.org/ephemeral-node=true",
}, },
expResFile: "multiLabels.yaml", expResFile: "multiLabels.yaml",
@ -74,12 +74,21 @@ func TestRender(t *testing.T) {
}, },
{ {
name: "Malformed Label", name: "Malformed Label",
settings: &phase.FilterOptions{ settings: &phase.RenderCommand{
Label: "app=(", Label: "app=(",
}, },
expResFile: "", expResFile: "",
expErr: fmt.Errorf("unable to parse requirement: found '(', expected: identifier"), expErr: fmt.Errorf("unable to parse requirement: found '(', expected: identifier"),
}, },
{
name: "Malformed Label",
settings: &phase.RenderCommand{
Label: "app=(",
Executor: true,
},
expResFile: "",
expErr: fmt.Errorf("unable to parse requirement: found '(', expected: identifier"),
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -92,7 +101,7 @@ func TestRender(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
out := &bytes.Buffer{} out := &bytes.Buffer{}
err = tt.settings.Render(func() (*config.Config, error) { err = tt.settings.RunE(func() (*config.Config, error) {
return rs, nil return rs, nil
}, fixturePath, out) }, fixturePath, out)
assert.Equal(t, tt.expErr, err) assert.Equal(t, tt.expErr, err)