From 89f80c6f25148ca534a6a4650b60c99dfab0d5ca Mon Sep 17 00:00:00 2001 From: Pradeep Lakshmi Narasimha Date: Tue, 11 Jan 2022 12:31:59 +0530 Subject: [PATCH] Apply commit label on resources created/updated when '--commitlabel' flag is passed (#2279) --- pkg/fission-cli/cmd/function/create.go | 4 +- pkg/fission-cli/cmd/httptrigger/create.go | 2 +- pkg/fission-cli/cmd/kubewatch/create.go | 2 +- pkg/fission-cli/cmd/mqtrigger/create.go | 2 +- pkg/fission-cli/cmd/package/create.go | 4 +- pkg/fission-cli/cmd/package/package.go | 2 +- pkg/fission-cli/cmd/spec/apply.go | 42 ++++-- pkg/fission-cli/cmd/spec/command.go | 2 +- pkg/fission-cli/cmd/spec/destroy.go | 2 +- pkg/fission-cli/cmd/spec/list.go | 2 +- pkg/fission-cli/cmd/spec/spec.go | 25 +++- pkg/fission-cli/cmd/spec/validate.go | 30 ++++- pkg/fission-cli/cmd/timetrigger/create.go | 2 +- pkg/fission-cli/flag/flag.go | 21 +-- pkg/fission-cli/flag/key/key.go | 21 +-- pkg/fission-cli/util/constants.go | 1 + pkg/utils/gitrepo/gitrepo.go | 157 ++++++++++++++++++++++ 17 files changed, 274 insertions(+), 47 deletions(-) create mode 100644 pkg/utils/gitrepo/gitrepo.go diff --git a/pkg/fission-cli/cmd/function/create.go b/pkg/fission-cli/cmd/function/create.go index d3c5ccdd..0ba21799 100644 --- a/pkg/fission-cli/cmd/function/create.go +++ b/pkg/fission-cli/cmd/function/create.go @@ -128,7 +128,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if toSpec { - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } @@ -169,7 +169,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if toSpec { - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } diff --git a/pkg/fission-cli/cmd/httptrigger/create.go b/pkg/fission-cli/cmd/httptrigger/create.go index c466b5f4..c4f87271 100644 --- a/pkg/fission-cli/cmd/httptrigger/create.go +++ b/pkg/fission-cli/cmd/httptrigger/create.go @@ -135,7 +135,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if input.Bool(flagkey.SpecSave) { specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } diff --git a/pkg/fission-cli/cmd/kubewatch/create.go b/pkg/fission-cli/cmd/kubewatch/create.go index 3b802e2e..7cac953b 100644 --- a/pkg/fission-cli/cmd/kubewatch/create.go +++ b/pkg/fission-cli/cmd/kubewatch/create.go @@ -67,7 +67,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if input.Bool(flagkey.SpecSave) { specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } diff --git a/pkg/fission-cli/cmd/mqtrigger/create.go b/pkg/fission-cli/cmd/mqtrigger/create.go index 1f688ce7..a7ce5646 100644 --- a/pkg/fission-cli/cmd/mqtrigger/create.go +++ b/pkg/fission-cli/cmd/mqtrigger/create.go @@ -128,7 +128,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if input.Bool(flagkey.SpecSave) { specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } diff --git a/pkg/fission-cli/cmd/package/create.go b/pkg/fission-cli/cmd/package/create.go index a0c47dbf..833f7ac0 100644 --- a/pkg/fission-cli/cmd/package/create.go +++ b/pkg/fission-cli/cmd/package/create.go @@ -87,7 +87,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error { if input.Bool(flagkey.SpecSave) { specDir = util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } @@ -186,7 +186,7 @@ func CreatePackage(input cli.Input, client client.Interface, pkgName string, pkg if input.Bool(flagkey.SpecSave) { // if a package with the same spec exists, don't create a new spec file - fr, err := spec.ReadSpecs(util.GetSpecDir(input), util.GetSpecIgnore(input)) + fr, err := spec.ReadSpecs(util.GetSpecDir(input), util.GetSpecIgnore(input), false) if err != nil { return nil, errors.Wrap(err, "error reading specs") } diff --git a/pkg/fission-cli/cmd/package/package.go b/pkg/fission-cli/cmd/package/package.go index 91aaa0fd..e711ed2e 100644 --- a/pkg/fission-cli/cmd/package/package.go +++ b/pkg/fission-cli/cmd/package/package.go @@ -162,7 +162,7 @@ func CreateArchive(client client.Interface, input cli.Input, includeFiles []stri } else if input.Bool(flagkey.SpecSave) { // check if this AUS exists in the specs; if so, don't create a new one specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return nil, errors.Wrap(err, "error reading specs") } diff --git a/pkg/fission-cli/cmd/spec/apply.go b/pkg/fission-cli/cmd/spec/apply.go index dcc83557..8bdd1779 100644 --- a/pkg/fission-cli/cmd/spec/apply.go +++ b/pkg/fission-cli/cmd/spec/apply.go @@ -67,7 +67,7 @@ func (opts *ApplySubCommand) do(input cli.Input) error { func (opts *ApplySubCommand) run(input cli.Input) error { specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - + applyCommitLabel := input.Bool(flagkey.SpecApplyCommitLabel) deleteResources := input.Bool(flagkey.SpecDelete) watchResources := input.Bool(flagkey.SpecWatch) waitForBuild := input.Bool(flagkey.SpecWait) @@ -112,7 +112,7 @@ func (opts *ApplySubCommand) run(input cli.Input) error { for { // read all specs - fr, err := ReadSpecs(specDir, specIgnore) + fr, err := ReadSpecs(specDir, specIgnore, applyCommitLabel) if err != nil { return errors.Wrap(err, "error reading specs") } @@ -626,7 +626,7 @@ func applyPackages(fclient client.Interface, fr *FissionResources, delete bool) keep = true } - if keep && existingObj.Status.BuildStatus == fv1.BuildStatusSucceeded { + if keep && isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && existingObj.Status.BuildStatus == fv1.BuildStatusSucceeded { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -726,7 +726,7 @@ func applyFunctions(fclient client.Interface, fr *FissionResources, delete bool) existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -809,7 +809,7 @@ func applyEnvironments(fclient client.Interface, fr *FissionResources, delete bo existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -892,7 +892,7 @@ func applyHTTPTriggers(fclient client.Interface, fr *FissionResources, delete bo existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -975,7 +975,7 @@ func applyKubernetesWatchTriggers(fclient client.Interface, fr *FissionResources existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -1058,7 +1058,7 @@ func applyTimeTriggers(fclient client.Interface, fr *FissionResources, delete bo existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -1141,7 +1141,7 @@ func applyMessageQueueTriggers(fclient client.Interface, fr *FissionResources, d existingObj, ok := existent[mapKey(&o.ObjectMeta)] if ok { // ok, a resource with the same name exists, is it the same? - if reflect.DeepEqual(existingObj.Spec, o.Spec) { + if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) { // nothing to do on the server metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta } else { @@ -1184,3 +1184,27 @@ func applyMessageQueueTriggers(fclient client.Interface, fr *FissionResources, d return metadataMap, &ras, nil } + +func isObjectMetaEqual(existingObj, newObj metav1.ObjectMeta) bool { + + if !reflect.DeepEqual(existingObj.Labels, newObj.Labels) { + return false + } + + existingAnnotations := make(map[string]string) + newAnnotations := make(map[string]string) + + for existingObjKey, existingObjVal := range existingObj.Annotations { + if existingObjKey != FISSION_DEPLOYMENT_NAME_KEY && existingObjKey != FISSION_DEPLOYMENT_UID_KEY { + existingAnnotations[existingObjKey] = existingObjVal + } + } + + for newObjKey, newObjVal := range newObj.Annotations { + if newObjKey != FISSION_DEPLOYMENT_NAME_KEY && newObjKey != FISSION_DEPLOYMENT_UID_KEY { + newAnnotations[newObjKey] = newObjVal + } + } + + return reflect.DeepEqual(existingAnnotations, newAnnotations) +} diff --git a/pkg/fission-cli/cmd/spec/command.go b/pkg/fission-cli/cmd/spec/command.go index c1fafcd5..b7966ab5 100644 --- a/pkg/fission-cli/cmd/spec/command.go +++ b/pkg/fission-cli/cmd/spec/command.go @@ -48,7 +48,7 @@ func Commands() *cobra.Command { RunE: wrapper.Wrapper(Apply), } wrapper.SetFlags(applyCmd, flag.FlagSet{ - Optional: []flag.Flag{flag.SpecDir, flag.SpecIgnore, flag.SpecDelete, flag.SpecWait, flag.SpecWatch, flag.SpecValidation}, + Optional: []flag.Flag{flag.SpecDir, flag.SpecIgnore, flag.SpecDelete, flag.SpecWait, flag.SpecWatch, flag.SpecValidation, flag.SpecApplyCommitLabel}, }) destroyCmd := &cobra.Command{ diff --git a/pkg/fission-cli/cmd/spec/destroy.go b/pkg/fission-cli/cmd/spec/destroy.go index 7c5d955f..dd394c26 100644 --- a/pkg/fission-cli/cmd/spec/destroy.go +++ b/pkg/fission-cli/cmd/spec/destroy.go @@ -43,7 +43,7 @@ func (opts *DestroySubCommand) run(input cli.Input) error { specIgnore := util.GetSpecIgnore(input) // read everything - fr, err := ReadSpecs(specDir, specIgnore) + fr, err := ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, "error reading specs") } diff --git a/pkg/fission-cli/cmd/spec/list.go b/pkg/fission-cli/cmd/spec/list.go index f752a983..378aac5d 100644 --- a/pkg/fission-cli/cmd/spec/list.go +++ b/pkg/fission-cli/cmd/spec/list.go @@ -53,7 +53,7 @@ func (opts *ListSubCommand) run(input cli.Input) error { // get specdir, specignore and read the deployID specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := ReadSpecs(specDir, specIgnore) + fr, err := ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, "error reading specs") } diff --git a/pkg/fission-cli/cmd/spec/spec.go b/pkg/fission-cli/cmd/spec/spec.go index 48229b7c..12619b66 100644 --- a/pkg/fission-cli/cmd/spec/spec.go +++ b/pkg/fission-cli/cmd/spec/spec.go @@ -171,7 +171,7 @@ func SpecSave(resource interface{}, specFile string) error { return err } - fr, err := ReadSpecs(specDir, util.SPEC_IGNORE_FILE) + fr, err := ReadSpecs(specDir, util.SPEC_IGNORE_FILE, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } @@ -526,9 +526,19 @@ func (fr *FissionResources) trackSourceMap(kind string, newobj *metav1.ObjectMet return nil } +// Apply commit label to the object metadata +func applyCommitLabel(commitLabelVal string, m *metav1.ObjectMeta) { + if len(commitLabelVal) != 0 { + if m.Labels == nil { + m.Labels = make(map[string]string) + } + m.Labels[util.COMMIT_LABEL] = commitLabelVal + } +} + // 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 { +func (fr *FissionResources) ParseYaml(b []byte, loc *Location, commitLabelVal string) error { var m *metav1.ObjectMeta // Figure out the object type by unmarshaling into the TypeMeta struct; then @@ -547,6 +557,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.Packages = append(fr.Packages, v) case "Function": var v fv1.Function @@ -555,6 +566,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.Functions = append(fr.Functions, v) case "Environment": var v fv1.Environment @@ -563,6 +575,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.Environments = append(fr.Environments, v) case "HTTPTrigger": var v fv1.HTTPTrigger @@ -570,8 +583,8 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { if err != nil { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } - m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.HttpTriggers = append(fr.HttpTriggers, v) case "KubernetesWatchTrigger": var v fv1.KubernetesWatchTrigger @@ -580,6 +593,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.KubernetesWatchTriggers = append(fr.KubernetesWatchTriggers, v) case "TimeTrigger": var v fv1.TimeTrigger @@ -588,6 +602,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.TimeTriggers = append(fr.TimeTriggers, v) case "MessageQueueTrigger": var v fv1.MessageQueueTrigger @@ -596,6 +611,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc)) } m = &v.ObjectMeta + applyCommitLabel(commitLabelVal, m) fr.MessageQueueTriggers = append(fr.MessageQueueTriggers, v) // The following are not CRDs @@ -606,6 +622,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { 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 types.ArchiveUploadSpec @@ -613,10 +630,12 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error { 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: "", } + applyCommitLabel(commitLabelVal, m) fr.ArchiveUploadSpecs = append(fr.ArchiveUploadSpecs, v) default: // no need to error out just because there's some extra files around; diff --git a/pkg/fission-cli/cmd/spec/validate.go b/pkg/fission-cli/cmd/spec/validate.go index eb87e551..14133f6b 100644 --- a/pkg/fission-cli/cmd/spec/validate.go +++ b/pkg/fission-cli/cmd/spec/validate.go @@ -33,6 +33,7 @@ import ( "github.com/fission/fission/pkg/fission-cli/console" "github.com/fission/fission/pkg/fission-cli/util" "github.com/fission/fission/pkg/utils" + "github.com/fission/fission/pkg/utils/gitrepo" ) type ValidateSubCommand struct { @@ -54,7 +55,7 @@ func (opts *ValidateSubCommand) run(input cli.Input) error { // this will error on parse errors and on duplicates specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := ReadSpecs(specDir, specIgnore) + fr, err := ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, "error reading specs") } @@ -198,7 +199,7 @@ func isResourceConflicts(deployUID string, specObj fv1.MetadataAccessor, cluster // ReadSpecs reads all specs in the specified directory and returns a parsed set of // fission resources. -func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) { +func ReadSpecs(specDir, specIgnore string, applyCommitLabel bool) (*FissionResources, error) { // make sure spec directory exists before continue if _, err := os.Stat(specDir); os.IsNotExist(err) { @@ -225,6 +226,21 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) { }, } + // get absolute path of specdir + if !filepath.IsAbs(specDir) { + cwd, err := filepath.Abs("./") + if err != nil { + return nil, err + } + specDir = filepath.Join(cwd, specDir) + } + + var gr *gitrepo.GitRepo + // check if applyCommitLabel flag is true + if applyCommitLabel { + gr = gitrepo.NewGitRepo(specDir) + } + var result *multierror.Error // Users can organize the specdir into subdirs if they want to. @@ -239,16 +255,24 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) { return nil } + // check if file matches any path in .specignore file if ignoreParser.MatchesPath(path) { return nil } + var fileCommitLabelVal string + // check if applyCommitLabel is true and specdir is tracked by git repo + if applyCommitLabel { + fileCommitLabelVal, _ = gr.GetFileCommitLabel(path) + } + // read b, err := os.ReadFile(path) if err != nil { result = multierror.Append(result, err) return nil } + // handle the case where there are multiple YAML docs per file. go-yaml // doesn't support this directly, yet. docs := bytes.Split(b, []byte("\n---")) @@ -260,7 +284,7 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) { err = fr.ParseYaml(d, &Location{ Path: path, Line: lines, - }) + }, fileCommitLabelVal) if err != nil { // collect all errors so user can fix them all result = multierror.Append(result, err) diff --git a/pkg/fission-cli/cmd/timetrigger/create.go b/pkg/fission-cli/cmd/timetrigger/create.go index 07b4f69d..6c15bacc 100644 --- a/pkg/fission-cli/cmd/timetrigger/create.go +++ b/pkg/fission-cli/cmd/timetrigger/create.go @@ -78,7 +78,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error { if input.Bool(flagkey.SpecSave) { specDir := util.GetSpecDir(input) specIgnore := util.GetSpecIgnore(input) - fr, err := spec.ReadSpecs(specDir, specIgnore) + fr, err := spec.ReadSpecs(specDir, specIgnore, false) if err != nil { return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir)) } diff --git a/pkg/fission-cli/flag/flag.go b/pkg/fission-cli/flag/flag.go index 70545f9d..773251fc 100644 --- a/pkg/fission-cli/flag/flag.go +++ b/pkg/fission-cli/flag/flag.go @@ -193,16 +193,17 @@ var ( PkgSrcChecksum = Flag{Type: String, Name: flagkey.PkgSrcChecksum, Usage: "SHA256 checksum of source archive when providing URL"} PkgInsecure = Flag{Type: Bool, Name: flagkey.PkgInsecure, Usage: "Skip generating SHA256 checksum for file integrity validation"} - SpecSave = Flag{Type: Bool, Name: flagkey.SpecSave, Usage: "Save to the spec directory instead of creating on cluster"} - SpecDir = Flag{Type: String, Name: flagkey.SpecDir, Usage: "Directory to store specs, defaults to ./specs"} - SpecName = Flag{Type: String, Name: flagkey.SpecName, Usage: "Name for the app, applied to resources as a Kubernetes annotation"} - SpecDeployID = Flag{Type: String, Name: flagkey.SpecDeployID, Aliases: []string{"id"}, Usage: "Deployment ID for the spec deployment config"} - SpecWait = Flag{Type: Bool, Name: flagkey.SpecWait, Usage: "Wait for package builds"} - SpecWatch = Flag{Type: Bool, Name: flagkey.SpecWatch, Usage: "Watch local files for change, and re-apply specs as necessary"} - SpecDelete = Flag{Type: Bool, Name: flagkey.SpecDelete, Usage: "Allow apply to delete resources that no longer exist in the specification"} - SpecDry = Flag{Type: Bool, Name: flagkey.SpecDry, Usage: "View the generated specs"} - SpecValidation = Flag{Type: String, Name: flagkey.SpecValidate, Usage: "Turns server side validations of Fission objects on/off"} - SpecIgnore = Flag{Type: String, Name: flagkey.SpecIgnore, Usage: fmt.Sprintf("File containing specs to be ignored inside --specdir, defaults to %v", util.SPEC_IGNORE_FILE)} + SpecSave = Flag{Type: Bool, Name: flagkey.SpecSave, Usage: "Save to the spec directory instead of creating on cluster"} + SpecDir = Flag{Type: String, Name: flagkey.SpecDir, Usage: "Directory to store specs, defaults to ./specs"} + SpecName = Flag{Type: String, Name: flagkey.SpecName, Usage: "Name for the app, applied to resources as a Kubernetes annotation"} + SpecDeployID = Flag{Type: String, Name: flagkey.SpecDeployID, Aliases: []string{"id"}, Usage: "Deployment ID for the spec deployment config"} + SpecWait = Flag{Type: Bool, Name: flagkey.SpecWait, Usage: "Wait for package builds"} + SpecWatch = Flag{Type: Bool, Name: flagkey.SpecWatch, Usage: "Watch local files for change, and re-apply specs as necessary"} + SpecDelete = Flag{Type: Bool, Name: flagkey.SpecDelete, Usage: "Allow apply to delete resources that no longer exist in the specification"} + SpecDry = Flag{Type: Bool, Name: flagkey.SpecDry, Usage: "View the generated specs"} + SpecValidation = Flag{Type: String, Name: flagkey.SpecValidate, Usage: "Turns server side validations of Fission objects on/off"} + SpecIgnore = Flag{Type: String, Name: flagkey.SpecIgnore, Usage: fmt.Sprintf("File containing specs to be ingored inside --specdir, defaults to %v", util.SPEC_IGNORE_FILE)} + SpecApplyCommitLabel = Flag{Type: Bool, Name: flagkey.SpecApplyCommitLabel, Usage: "Apply commit label to the resources"} SupportOutput = Flag{Type: String, Name: flagkey.SupportOutput, Short: "o", Usage: "Output directory to save dump archive/files", DefaultValue: flagkey.DefaultSpecOutputDir} SupportNoZip = Flag{Type: Bool, Name: flagkey.SupportNoZip, Usage: "Save dump information into multiple files instead of single zip file"} diff --git a/pkg/fission-cli/flag/key/key.go b/pkg/fission-cli/flag/key/key.go index e3c477f2..2c856f3b 100644 --- a/pkg/fission-cli/flag/key/key.go +++ b/pkg/fission-cli/flag/key/key.go @@ -144,16 +144,17 @@ const ( PkgStatus = "status" PkgOrphan = "orphan" - SpecSave = "spec" - SpecDir = "specdir" - SpecName = resourceName - SpecDeployID = "deployid" - SpecWait = "wait" - SpecWatch = "watch" - SpecDelete = "delete" - SpecDry = "dry" - SpecValidate = "validation" - SpecIgnore = "specignore" + SpecSave = "spec" + SpecDir = "specdir" + SpecName = resourceName + SpecDeployID = "deployid" + SpecWait = "wait" + SpecWatch = "watch" + SpecDelete = "delete" + SpecDry = "dry" + SpecValidate = "validation" + SpecIgnore = "specignore" + SpecApplyCommitLabel = "commitlabel" SupportOutput = Output SupportNoZip = "nozip" diff --git a/pkg/fission-cli/util/constants.go b/pkg/fission-cli/util/constants.go index fb070c44..2b386e5b 100644 --- a/pkg/fission-cli/util/constants.go +++ b/pkg/fission-cli/util/constants.go @@ -19,4 +19,5 @@ package util // fission-cli options const ( SPEC_IGNORE_FILE = ".specignore" + COMMIT_LABEL = "commit" ) diff --git a/pkg/utils/gitrepo/gitrepo.go b/pkg/utils/gitrepo/gitrepo.go new file mode 100644 index 00000000..cdbec7fa --- /dev/null +++ b/pkg/utils/gitrepo/gitrepo.go @@ -0,0 +1,157 @@ +/* +Copyright 2022 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 gitrepo + +import ( + "fmt" + "os" + "strings" + + "github.com/go-git/go-git/v5" + "github.com/pkg/errors" +) + +type GitRepo struct { + setupDone bool + isGitRepo bool + repo *git.Repository + status git.Status + dirPath string + gitRepoRootPath string // absolute path of the root of the git repository + commitID string // commit ID the HEAD of the repository is poitning to +} + +// NewGitRepo creates new GitRepo struct +// checks if the given directory path is part of git repository +// accordingly updates the fields of GitRepo struct and returns it +func NewGitRepo(dirPath string) *GitRepo { + + var g *GitRepo = &GitRepo{} + var err error + + g.dirPath = dirPath + g.setupDone = true + + // check if directory is tracked by git repo + g.repo, err = git.PlainOpenWithOptions(dirPath, &git.PlainOpenOptions{DetectDotGit: true}) + if err != nil { + return g + } + + workTree, err := g.repo.Worktree() + if err != nil { + return g + } + + g.status, err = workTree.Status() + if err != nil { + return g + } + + if g.status == nil { + return g + } + + plumbRef, err := g.repo.Head() + if err != nil { + return g + } + + // extract short commit ID + g.commitID = plumbRef.Hash().String()[:7] + + // get the root path of git repository + g.gitRepoRootPath = workTree.Filesystem.Root() + + g.isGitRepo = true + return g +} + +// IsGitRepo returns if the initialized directory path is tracked by git repo +func (g *GitRepo) IsGitRepo() bool { + return g.setupDone && g.isGitRepo +} + +// getFileCommitLabel returns the value of 'commit' label +// for the resources present in the file +/* +The value of the `commit` label for different status of the file is as follows: +| Git File Status | Label Value | +|---------------------------------------|----------------------| +| New untracked file | untracked | +| New staged file | staged | +| Tracked file with changes in worktree | -unstaged | +| Tracked file with changes staged | -staged | +| Tracked file with clean commit | | +*/ +func (g *GitRepo) GetFileCommitLabel(filePath string) (string, error) { + + if !g.setupDone { + return "", errors.New(`GitRepo is not setup. It has to be created by calling 'NewGitRepo' function, + then this function has to be called.`) + } + + if !g.isGitRepo { + return "", errors.Errorf(`directory: %s doesn't belong to git repository.`, g.dirPath) + } + + // filepath in the git repository + splitPathList := strings.Split(filePath, g.gitRepoRootPath+string(os.PathSeparator)) + if len(splitPathList) != 2 { + return "", errors.Errorf("error finding the git repository path of %s", filePath) + } + gitFilePath := splitPathList[1] + gitFileStatus := g.status.File(gitFilePath) + + // check if file is tracked + if !g.status.IsUntracked(gitFilePath) { + + // unstaged file + if gitFileStatus.Worktree == git.Modified { + return fmt.Sprintf("%s-unstaged", g.commitID), nil + } + + // newly staged file + if gitFileStatus.Staging == git.Added { + return "staged", nil + } + + // staged file + if gitFileStatus.Staging == git.Modified { + return fmt.Sprintf("%s-staged", g.commitID), nil + } + + } + + // check if file is committed already + oc, err := g.repo.Log(&git.LogOptions{FileName: &gitFilePath}) + if err != nil { + return "", nil + } + + _, err = oc.Next() + defer oc.Close() + + if err != nil { + // File not tracked by git and/or not committed + return "untracked", nil + + } + + // File tracked by git and has been committed + return g.commitID, nil +}