Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: cmd preview and apply support new flag '--ignore-fields' #138

Merged
merged 5 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/engine/models/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ package models
type Spec struct {
Resources Resources `json:"resources" yaml:"resources"`
}

// ParseCluster try to parse Cluster from resource extensions.
// All resources in one compile MUST have the same Cluster and this constraint will be guaranteed by KCL compile logic
func (s *Spec) ParseCluster() string {
resources := s.Resources
var cluster string
if len(resources) != 0 && resources[0].Extensions != nil && resources[0].Extensions["Cluster"] != nil {
cluster = resources[0].Extensions["Cluster"].(string)
}
return cluster
}
8 changes: 8 additions & 0 deletions pkg/engine/operation/graph/resource_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"reflect"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"kusionstack.io/kusion/pkg/engine/models"
opsmodels "kusionstack.io/kusion/pkg/engine/operation/models"
"kusionstack.io/kusion/pkg/engine/operation/types"
Expand Down Expand Up @@ -83,6 +85,12 @@ func (rn *ResourceNode) Execute(operation *opsmodels.Operation) status.Status {
return dryRunResp.Status
}
predictableState = dryRunResp.Resource
// Ignore differences of target fields
howieyuen marked this conversation as resolved.
Show resolved Hide resolved
for _, field := range operation.IgnoreFields {
splits := strings.Split(field, ".")
unstructured.RemoveNestedField(liveState.Attributes, splits...)
unstructured.RemoveNestedField(predictableState.Attributes, splits...)
}
report, err := diff.ToReport(liveState, predictableState)
if err != nil {
return status.NewErrorStatus(err)
Expand Down
1 change: 1 addition & 0 deletions pkg/engine/operation/graph/resource_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func TestResourceNode_Execute(t *testing.T) {
CtxResourceIndex: priorStateResourceIndex,
PriorStateResourceIndex: priorStateResourceIndex,
StateResourceIndex: priorStateResourceIndex,
IgnoreFields: []string{"not_exist_field"},
MsgCh: make(chan opsmodels.Message),
ResultState: states.NewState(),
Lock: &sync.Mutex{},
Expand Down
14 changes: 13 additions & 1 deletion pkg/engine/operation/models/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models
import (
"bytes"
"fmt"
"io"
"strings"

"github.com/AlecAivazis/survey/v2"
Expand Down Expand Up @@ -148,7 +149,17 @@ func (o *ChangeOrder) Diffs() string {
return buf.String()
}

func (p *Changes) Summary() {
func (p *Changes) AllUnChange() bool {
for _, v := range p.ChangeSteps {
if v.Action != types.UnChange {
return false
}
}

return true
}

func (p *Changes) Summary(writer io.Writer) {
// Create a fork of the default table, fill it with data and print it.
// Data can also be generated and inserted later.
tableHeader := []string{fmt.Sprintf("Stack: %s", p.stack.Name), "ID", "Action"}
Expand All @@ -169,6 +180,7 @@ func (p *Changes) Summary() {
WithLeftAlignment(true).
WithSeparator(" ").
WithData(tableData).
WithWriter(writer).
Render()
pterm.Println() // Blank line
}
Expand Down
35 changes: 34 additions & 1 deletion pkg/engine/operation/models/change_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package models

import (
"os"
"reflect"
"testing"

"github.com/stretchr/testify/assert"

"kusionstack.io/kusion/pkg/engine/models"
"kusionstack.io/kusion/pkg/engine/operation/types"
"kusionstack.io/kusion/pkg/projectstack"
Expand Down Expand Up @@ -432,7 +435,7 @@ func TestChanges_Preview(t *testing.T) {
project: tt.fields.project,
stack: tt.fields.stack,
}
p.Summary()
p.Summary(os.Stdout)
})
}
}
Expand Down Expand Up @@ -462,3 +465,33 @@ func Test_buildResourceStateMap(t *testing.T) {
})
}
}

func TestChanges_AllUnChange(t *testing.T) {
t.Run("changed", func(t *testing.T) {
changes := &Changes{
ChangeOrder: &ChangeOrder{
ChangeSteps: map[string]*ChangeStep{
"foo": {
Action: types.Update,
},
},
},
}
flag := changes.AllUnChange()
assert.False(t, flag)
})

t.Run("unchanged", func(t *testing.T) {
changes := &Changes{
ChangeOrder: &ChangeOrder{
ChangeSteps: map[string]*ChangeStep{
"bar": {
Action: types.UnChange,
},
},
},
}
flag := changes.AllUnChange()
assert.True(t, flag)
})
}
3 changes: 3 additions & 0 deletions pkg/engine/operation/models/operation_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type Operation struct {
// StateResourceIndex represents resources that will be saved in states.StateStorage
StateResourceIndex map[string]*models.Resource

// IgnoreFields will be ignored in preview stage
IgnoreFields []string

// ChangeOrder is resources' change order during this operation
ChangeOrder *ChangeOrder

Expand Down
1 change: 1 addition & 0 deletions pkg/engine/operation/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (po *PreviewOperation) Preview(request *PreviewRequest) (rsp *PreviewRespon
CtxResourceIndex: map[string]*models.Resource{},
PriorStateResourceIndex: priorStateResourceIndex,
StateResourceIndex: priorStateResourceIndex,
IgnoreFields: o.IgnoreFields,
ChangeOrder: o.ChangeOrder,
Runtime: o.Runtime, // preview need get the latest spec from runtime
ResultState: resultState,
Expand Down
8 changes: 3 additions & 5 deletions pkg/kusionctl/cmd/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,15 @@ func NewCmdApply() *cobra.Command {
}

o.AddCompileFlags(cmd)
cmd.Flags().StringVarP(&o.Operator, "operator", "", "",
i18n.T("Specify the operator"))
o.AddPreviewFlags(cmd)
o.AddBackendFlags(cmd)

cmd.Flags().BoolVarP(&o.Yes, "yes", "y", false,
i18n.T("Automatically approve and perform the update after previewing it"))
cmd.Flags().BoolVarP(&o.Detail, "detail", "d", false,
i18n.T("Automatically show plan details after previewing it"))
cmd.Flags().BoolVarP(&o.NoStyle, "no-style", "", false,
i18n.T("no-style sets to RawOutput mode and disables all of styling"))
cmd.Flags().BoolVarP(&o.DryRun, "dry-run", "", false,
i18n.T("dry-run to preview the execution effect (always successful) without actually applying the changes"))
o.AddBackendFlags(cmd)

return cmd
}
105 changes: 13 additions & 92 deletions pkg/kusionctl/cmd/apply/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,29 @@ import (
"kusionstack.io/kusion/pkg/engine/runtime"
runtimeInit "kusionstack.io/kusion/pkg/engine/runtime/init"
"kusionstack.io/kusion/pkg/engine/states"
compilecmd "kusionstack.io/kusion/pkg/kusionctl/cmd/compile"
previewcmd "kusionstack.io/kusion/pkg/kusionctl/cmd/preview"
"kusionstack.io/kusion/pkg/log"
"kusionstack.io/kusion/pkg/projectstack"
"kusionstack.io/kusion/pkg/status"
)

// ApplyOptions defines flags for the `apply` command
type ApplyOptions struct {
compilecmd.CompileOptions
Operator string
previewcmd.PreviewOptions
ApplyFlag
}

type ApplyFlag struct {
Yes bool
Detail bool
NoStyle bool
DryRun bool
OnlyPreview bool
backend.BackendOps
}

// NewApplyOptions returns a new ApplyOptions instance
func NewApplyOptions() *ApplyOptions {
return &ApplyOptions{
CompileOptions: compilecmd.CompileOptions{
Filenames: []string{},
Arguments: []string{},
Settings: []string{},
Overrides: []string{},
},
PreviewOptions: *previewcmd.NewPreviewOptions(),
}
}

Expand Down Expand Up @@ -80,20 +76,20 @@ func (o *ApplyOptions) Run() error {
sp.Success() // Resolve spinner with success message.
pterm.Println()

// Get stateStroage from backend config to manage state
// Get state storage from backend config to manage state
stateStorage, err := backend.BackendFromConfig(project.Backend, o.BackendOps, o.WorkDir)
if err != nil {
return err
}

// Compute changes for preview
runtimes := runtimeInit.InitRuntime()
runtime, err := runtimes[planResources.Resources[0].Type]()
r, err := runtimes[planResources.Resources[0].Type]()
if err != nil {
return err
}

changes, err := Preview(o, runtime, stateStorage, planResources, project, stack, os.Stdout)
changes, err := previewcmd.Preview(&o.PreviewOptions, r, stateStorage, planResources, project, stack)
if err != nil {
return err
}
Expand All @@ -104,7 +100,7 @@ func (o *ApplyOptions) Run() error {
}

// Summary preview table
changes.Summary()
changes.Summary(os.Stdout)

// Detail detection
if o.Detail && !o.Yes {
Expand Down Expand Up @@ -136,7 +132,7 @@ func (o *ApplyOptions) Run() error {

if !o.OnlyPreview {
fmt.Println("Start applying diffs ...")
if err := Apply(o, runtime, stateStorage, planResources, changes, os.Stdout); err != nil {
if err := Apply(o, r, stateStorage, planResources, changes, os.Stdout); err != nil {
return err
}

Expand All @@ -149,81 +145,6 @@ func (o *ApplyOptions) Run() error {
return nil
}

// The Preview function calculates the upcoming actions of each resource
// through the execution Kusion Engine, and you can customize the
// runtime of engine and the state storage through `runtime` and
// `storage` parameters.
//
// Example:
//
// o := NewApplyOptions()
// stateStorage := &states.FileSystemState{
// Path: filepath.Join(o.WorkDir, states.KusionState)
// }
// kubernetesRuntime, err := runtime.NewKubernetesRuntime()
// if err != nil {
// return err
// }
//
// changes, err := Preview(o, kubernetesRuntime, stateStorage,
// planResources, project, stack, os.Stdout)
// if err != nil {
// return err
// }
//
// todo @elliotxx io.Writer is not used now
func Preview(
o *ApplyOptions,
runtime runtime.Runtime,
storage states.StateStorage,
planResources *models.Spec,
project *projectstack.Project,
stack *projectstack.Stack,
out io.Writer,
) (*opsmodels.Changes, error) {
log.Info("Start compute preview changes ...")

// Construct the preview operation
pc := &operation.PreviewOperation{
Operation: opsmodels.Operation{
OperationType: types.ApplyPreview,
Runtime: runtime,
StateStorage: storage,
ChangeOrder: &opsmodels.ChangeOrder{StepKeys: []string{}, ChangeSteps: map[string]*opsmodels.ChangeStep{}},
},
}

log.Info("Start call pc.Preview() ...")

cluster := parseCluster(planResources)
rsp, s := pc.Preview(&operation.PreviewRequest{
Request: opsmodels.Request{
Tenant: project.Tenant,
Project: project.Name,
Stack: stack.Name,
Operator: o.Operator,
Spec: planResources,
Cluster: cluster,
},
})
if status.IsErr(s) {
return nil, fmt.Errorf("preview failed.\n%s", s.String())
}

return opsmodels.NewChanges(project, stack, rsp.Order), nil
}

// parseCluster try to parse Cluster from resource extensions.
// All resources in one compile MUST have the same Cluster and this constraint will be guaranteed by KCL compile logic
func parseCluster(planResources *models.Spec) string {
resources := planResources.Resources
var cluster string
if len(resources) != 0 && resources[0].Extensions != nil && resources[0].Extensions["Cluster"] != nil {
cluster = resources[0].Extensions["Cluster"].(string)
}
return cluster
}

// The Apply function will apply the resources changes
// through the execution Kusion Engine, and will save
// the state to specified storage.
Expand Down Expand Up @@ -344,7 +265,7 @@ func Apply(
}
close(ac.MsgCh)
} else {
cluster := parseCluster(planResources)
cluster := planResources.ParseCluster()
_, st := ac.Apply(&operation.ApplyRequest{
Request: opsmodels.Request{
Tenant: changes.Project().Tenant,
Expand Down
12 changes: 0 additions & 12 deletions pkg/kusionctl/cmd/apply/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,6 @@ func mockCompileWithSpinner() {
})
}

func Test_preview(t *testing.T) {
stateStorage := &local.FileSystemState{Path: filepath.Join("", local.KusionState)}
t.Run("preview success", func(t *testing.T) {
defer monkey.UnpatchAll()
mockOperationPreview()

o := NewApplyOptions()
_, err := Preview(o, &fakerRuntime{}, stateStorage, &models.Spec{Resources: []models.Resource{sa1, sa2, sa3}}, project, stack, os.Stdout)
assert.Nil(t, err)
})
}

func mockNewKubernetesRuntime() {
monkey.Patch(runtime.NewKubernetesRuntime, func() (runtime.Runtime, error) {
return &fakerRuntime{}, nil
Expand Down
Loading