mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-20 08:13:23 +00:00
Some checks failed
CodeQL / Analyze (go) (push) Failing after 1m43s
Definition-Lint / definition-doc (push) Failing after 6m13s
E2E MultiCluster Test / detect-noop (push) Successful in 24s
E2E Test / detect-noop (push) Successful in 17s
Go / detect-noop (push) Successful in 21s
license / Check for unapproved licenses (push) Failing after 2m38s
Registry / publish-core-images (push) Failing after 40s
Unit-Test / detect-noop (push) Successful in 20s
E2E MultiCluster Test / e2e-multi-cluster-tests (v1.29) (push) Failing after 1m55s
E2E Test / e2e-tests (v1.29) (push) Failing after 1m18s
Go / staticcheck (push) Successful in 18m35s
Go / lint (push) Failing after 19m38s
Go / check-diff (push) Failing after 15m7s
Go / check-core-image-build (push) Failing after 3m45s
Go / check-cli-image-build (push) Failing after 2m23s
Unit-Test / unit-tests (push) Failing after 12m43s
Go / check-windows (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Failing after 48s
* feature: Add Semantic versioning to KubeVela Definitions Fixes https://github.com/kubevela/kubevela/issues/6435 Fixes https://github.com/kubevela/kubevela/issues/6534 Changes: - Adds an optional "Version" field for all Definition Specs. - Adds the following new validations to Webhooks for Definitions: - Validate the "Version" field follows Semantic versioning. - Dis-allow conflicting versioning fields ( Name annotation, Spec.Version) - Adds the following new validations to Webhooks for Application: - Dis-allow the use of both the "publishVersion" & "autoUpdate" annotations. - Enahnce "multiStageComponentApply" feature to support auto updates. Boy Scout Changes: - Fixes Plugin e2e tests broken by the fix for 6534. - Fixes the dryRun and livediff commands to respect the "-n" namespace flag. - Fixes the Application ValidationWebhook to respect the "-n" namespace flag. Co-authored-by: Rahul Kumar <35751394+bugbounce@users.noreply.github.com> Co-authored-by: Chaitanya Reddy <chaitanyareddy0702@gmail.com> Co-authored-by: Vibhor Chinda <vibhorchinda@gmail.com> Co-authored-by: Shivin Gopalani <gopalanishivin@gmail.com> Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * feature: Add KEP to define the proposal Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * fix: Rebase and fix merge conflicts Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> * Fix: Adds unit test cases Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> --------- Signed-off-by: kanchan-dhamane <74534570+kanchan-dhamane@users.noreply.github.com> Co-authored-by: bugbounce <35751394+bugbounce@users.noreply.github.com>
240 lines
8.6 KiB
Go
240 lines
8.6 KiB
Go
/*
|
|
Copyright 2021 The KubeVela 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
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
"github.com/oam-dev/kubevela/apis/types"
|
|
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
|
|
"github.com/oam-dev/kubevela/pkg/utils/common"
|
|
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
|
)
|
|
|
|
// LiveDiffCmdOptions contains the live-diff cmd options
|
|
type LiveDiffCmdOptions struct {
|
|
cmdutil.IOStreams
|
|
ApplicationFile string
|
|
DefinitionFile string
|
|
AppName string
|
|
Namespace string
|
|
Revision string
|
|
SecondaryRevision string
|
|
Context int
|
|
}
|
|
|
|
// NewLiveDiffCommand creates `live-diff` command
|
|
func NewLiveDiffCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
|
o := &LiveDiffCmdOptions{IOStreams: ioStreams}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "live-diff",
|
|
DisableFlagsInUseLine: true,
|
|
Short: "Compare application and revisions.",
|
|
Long: "Compare application and revisions.",
|
|
Example: "# compare the current application and the running revision\n" +
|
|
"> vela live-diff my-app\n" +
|
|
"# compare the current application and the specified revision\n" +
|
|
"> vela live-diff my-app --revision my-app-v1\n" +
|
|
"# compare two application revisions\n" +
|
|
"> vela live-diff --revision my-app-v1,my-app-v2\n" +
|
|
"# compare the application file and the specified revision\n" +
|
|
"> vela live-diff -f my-app.yaml -r my-app-v1 --context 10",
|
|
Annotations: map[string]string{
|
|
types.TagCommandOrder: order,
|
|
types.TagCommandType: types.TypeApp,
|
|
},
|
|
Args: cobra.RangeArgs(0, 1),
|
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
|
o.Namespace, err = GetFlagNamespaceOrEnv(cmd, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = o.loadAndValidate(args); err != nil {
|
|
return err
|
|
}
|
|
buff, err := LiveDiffApplication(o, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.Println(buff.String())
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&o.ApplicationFile, "file", "f", "", "application file name")
|
|
cmd.Flags().StringVarP(&o.DefinitionFile, "definition", "d", "", "specify a file or directory containing capability definitions, they will only be used in dry-run rather than applied to K8s cluster")
|
|
cmd.Flags().StringVarP(&o.Revision, "revision", "r", "", "specify one or two application revision name(s), by default, it will compare with the latest revision")
|
|
cmd.Flags().IntVarP(&o.Context, "context", "c", -1, "output number lines of context around changes, by default show all unchanged lines")
|
|
addNamespaceAndEnvArg(cmd)
|
|
return cmd
|
|
}
|
|
|
|
// LiveDiffApplication can return user what would change if upgrade an application.
|
|
func LiveDiffApplication(cmdOption *LiveDiffCmdOptions, c common.Args) (bytes.Buffer, error) {
|
|
var buff = bytes.Buffer{}
|
|
|
|
newClient, err := c.GetClient()
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
var objs []*unstructured.Unstructured
|
|
if cmdOption.DefinitionFile != "" {
|
|
objs, err = ReadDefinitionsFromFile(cmdOption.DefinitionFile, cmdOption.IOStreams)
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
}
|
|
config, err := c.GetConfig()
|
|
if err != nil {
|
|
return buff, err
|
|
}
|
|
liveDiffOption := dryrun.NewLiveDiffOption(newClient, config, objs)
|
|
if cmdOption.ApplicationFile == "" {
|
|
return cmdOption.renderlessDiff(newClient, liveDiffOption)
|
|
}
|
|
|
|
app, err := readApplicationFromFile(cmdOption.ApplicationFile)
|
|
if err != nil {
|
|
return buff, errors.WithMessagef(err, "read application file: %s", cmdOption.ApplicationFile)
|
|
}
|
|
if app.Namespace == "" {
|
|
app.SetNamespace(cmdOption.Namespace)
|
|
}
|
|
|
|
appRevision := &v1beta1.ApplicationRevision{}
|
|
if cmdOption.Revision != "" {
|
|
// get the Revision if user specifies
|
|
if err := newClient.Get(context.Background(),
|
|
client.ObjectKey{Name: cmdOption.Revision, Namespace: app.Namespace}, appRevision); err != nil {
|
|
return buff, errors.Wrapf(err, "cannot get application Revision %q", cmdOption.Revision)
|
|
}
|
|
} else {
|
|
// get the latest Revision of the application
|
|
livingApp := &v1beta1.Application{}
|
|
if err := newClient.Get(context.Background(),
|
|
client.ObjectKey{Name: app.Name, Namespace: app.Namespace}, livingApp); err != nil {
|
|
return buff, errors.Wrapf(err, "cannot get application %q", app.Name)
|
|
}
|
|
if livingApp.Status.LatestRevision != nil {
|
|
latestRevName := livingApp.Status.LatestRevision.Name
|
|
if err := newClient.Get(context.Background(),
|
|
client.ObjectKey{Name: latestRevName, Namespace: app.Namespace}, appRevision); err != nil {
|
|
return buff, errors.Wrapf(err, "cannot get application Revision %q", cmdOption.Revision)
|
|
}
|
|
} else {
|
|
// .status.latestRevision is nil, that means the app has not
|
|
// been rendered yet
|
|
return buff, fmt.Errorf("the application %q has no Revision in the cluster", app.Name)
|
|
}
|
|
}
|
|
|
|
diffResult, err := liveDiffOption.Diff(context.Background(), app, appRevision)
|
|
if err != nil {
|
|
return buff, errors.WithMessage(err, "cannot calculate diff")
|
|
}
|
|
|
|
reportDiffOpt := dryrun.NewReportDiffOption(cmdOption.Context, &buff)
|
|
reportDiffOpt.PrintDiffReport(diffResult)
|
|
|
|
return buff, nil
|
|
}
|
|
|
|
func (o *LiveDiffCmdOptions) loadAndValidate(args []string) error {
|
|
if len(args) > 0 {
|
|
o.AppName = args[0]
|
|
}
|
|
revisions := strings.Split(o.Revision, ",")
|
|
if len(revisions) > 2 {
|
|
return errors.Errorf("cannot use more than 2 revisions")
|
|
}
|
|
o.Revision = revisions[0]
|
|
if len(revisions) == 2 {
|
|
o.SecondaryRevision = revisions[1]
|
|
}
|
|
if (o.AppName == "" && len(revisions) == 1) && o.ApplicationFile == "" {
|
|
return errors.Errorf("either application name or application file must be set")
|
|
}
|
|
if (o.AppName != "" || len(revisions) > 1) && o.ApplicationFile != "" {
|
|
return errors.Errorf("cannot set application name and application file at the same time")
|
|
}
|
|
if o.AppName != "" && o.SecondaryRevision != "" {
|
|
return errors.Errorf("cannot use application name and two revisions at the same time")
|
|
}
|
|
if o.SecondaryRevision != "" && o.ApplicationFile != "" {
|
|
return errors.Errorf("cannot use application file and two revisions at the same time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *LiveDiffCmdOptions) renderlessDiff(cli client.Client, option *dryrun.LiveDiffOption) (bytes.Buffer, error) {
|
|
var base, comparor dryrun.LiveDiffObject
|
|
ctx := context.Background()
|
|
var buf bytes.Buffer
|
|
if o.AppName != "" {
|
|
app := &v1beta1.Application{}
|
|
if err := cli.Get(ctx, client.ObjectKey{Name: o.AppName, Namespace: o.Namespace}, app); err != nil {
|
|
return buf, errors.Wrapf(err, "cannot get application %s/%s", o.Namespace, o.AppName)
|
|
}
|
|
if app.Namespace == "" {
|
|
app.SetNamespace(o.Namespace)
|
|
}
|
|
base = dryrun.LiveDiffObject{Application: app}
|
|
if o.Revision == "" {
|
|
if app.Status.LatestRevision == nil {
|
|
return buf, errors.Errorf("no latest application revision available for application %s/%s", o.Namespace, o.AppName)
|
|
}
|
|
o.Revision = app.Status.LatestRevision.Name
|
|
}
|
|
}
|
|
rev, secondaryRev := &v1beta1.ApplicationRevision{}, &v1beta1.ApplicationRevision{}
|
|
if err := cli.Get(ctx, client.ObjectKey{Name: o.Revision, Namespace: o.Namespace}, rev); err != nil {
|
|
return buf, errors.Wrapf(err, "cannot get application revision %s/%s", o.Namespace, o.Revision)
|
|
}
|
|
if rev.Namespace == "" {
|
|
rev.SetNamespace(o.Namespace)
|
|
}
|
|
if o.SecondaryRevision == "" {
|
|
comparor = dryrun.LiveDiffObject{ApplicationRevision: rev}
|
|
} else {
|
|
if err := cli.Get(ctx, client.ObjectKey{Name: o.SecondaryRevision, Namespace: o.Namespace}, secondaryRev); err != nil {
|
|
return buf, errors.Wrapf(err, "cannot get application revision %s/%s", o.Namespace, o.SecondaryRevision)
|
|
}
|
|
if secondaryRev.Namespace == "" {
|
|
secondaryRev.SetNamespace(o.Namespace)
|
|
}
|
|
base = dryrun.LiveDiffObject{ApplicationRevision: rev}
|
|
comparor = dryrun.LiveDiffObject{ApplicationRevision: secondaryRev}
|
|
}
|
|
diffResult, err := option.RenderlessDiff(ctx, base, comparor)
|
|
if err != nil {
|
|
return buf, errors.WithMessage(err, "cannot calculate diff")
|
|
}
|
|
reportDiffOpt := dryrun.NewReportDiffOption(o.Context, &buf)
|
|
reportDiffOpt.PrintDiffReport(diffResult)
|
|
return buf, nil
|
|
}
|