Skip to content

Commit

Permalink
Feature: Make it clearer why you need to save or force set targets by…
Browse files Browse the repository at this point in the history
… displaying a unified diff. (#4)

* Using "github.com/go-corelibs/diff" diff package

* private package for unified diff copied from
https://github.com/golang/tools/tree/v0.24.0

* 1. Show an unified diff of what changed based upon the work
 from diogomatsubrata on
[PR-20](guidowb#20)
2. I changed his code to redact tokens and secrets.  A sha256
   sum is used to show there are differences without exposing
   the actual values.
3. Changed the unified diff code to not be dependent on an archived
   read-only directoy.

* Added new dependencies

* go fmt cleanup

* I could not figure out how to make a local module/package withou
using a replace line.

* 1. resolve <nil> printing after the diff
2. Make the code more idomatic go

* Shorten up the required copyright notice

* Only call unified diff printing if there are changes
  • Loading branch information
norman-abramovitz authored Aug 28, 2024
1 parent e541c10 commit 316ee82
Show file tree
Hide file tree
Showing 14 changed files with 1,888 additions and 2 deletions.
67 changes: 67 additions & 0 deletions cf_targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"bytes"
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"path/filepath"
Expand All @@ -15,6 +17,8 @@ import (
"code.cloudfoundry.org/cli/cf/configuration/confighelpers"
"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
"code.cloudfoundry.org/cli/plugin"
"github.com/norman-abramovitz/cf-targets-plugin/diff"
"github.com/norman-abramovitz/cf-targets-plugin/diff/myers"
)

type TargetsPlugin struct {
Expand Down Expand Up @@ -178,6 +182,21 @@ func main() {
fmt.Printf(f, "VCS Url:", BuildVcsUrl)
fmt.Printf(f, "VCS Id:", BuildVcsId)
fmt.Printf(f, "VCS Id Date:", BuildVcsIdDate)

fmt.Printf("\nCopyright 2009 The Go Authors. (diff directory tree only)\n\n")

fmt.Printf("Redistribution and use in source and binary forms, with or without\n")
fmt.Printf("modification, are permitted provided that the following conditions are met:\n\n")

fmt.Printf(" * Redistributions of source code must retain the above copyright\n")
fmt.Printf(" notice, this list of conditions and the following disclaimer.\n")
fmt.Printf(" * Redistributions in binary form must reproduce the above\n")
fmt.Printf(" copyright notice, this list of conditions and the following disclaimer\n")
fmt.Printf(" in the documentation and/or other materials provided with the\n")
fmt.Printf(" distribution.\n")
fmt.Printf(" * Neither the name of Google LLC nor the names of its\n")
fmt.Printf(" contributors may be used to endorse or promote products derived from\n")
fmt.Printf(" this software without specific prior written permission.\n")
}
os = &RealOS{}
plugin.Start(newTargetsPlugin())
Expand Down Expand Up @@ -205,6 +224,52 @@ func (c *TargetsPlugin) Run(cliConnection plugin.CliConnection, args []string) {
}
}

func createRedaction(jsonMap map[string]interface{}, key string) string {
var valueAssertion interface{}
valueAssertion = jsonMap[key]
currentSum := sha256.Sum256([]byte(valueAssertion.(string)))
return fmt.Sprintf("REDACTED sha256(%x)", currentSum)
}

func (c *TargetsPlugin) showDiff(targetPath string) {
var json_data_current map[string]interface{}
var json_data_target map[string]interface{}
var err error

currentContent, err := os.ReadFile(c.currentPath)
c.checkError(err)
targetContent, err := os.ReadFile(targetPath)
c.checkError(err)
err = json.Unmarshal(currentContent, &json_data_current)
c.checkError(err)
err = json.Unmarshal(targetContent, &json_data_target)
c.checkError(err)

json_data_current["AccessToken"] = createRedaction(json_data_current, "AccessToken")
json_data_target["AccessToken"] = createRedaction(json_data_target, "AccessToken")

json_data_current["RefreshToken"] = createRedaction(json_data_current, "RefreshToken")
json_data_target["RefreshToken"] = createRedaction(json_data_target, "RefreshToken")

json_data_current["UAAOAuthClientSecret"] = createRedaction(json_data_current, "UAAOAuthClientSecret")
json_data_target["UAAOAuthClientSecret"] = createRedaction(json_data_target, "UAAOAuthClientSecret")

current, err := json.MarshalIndent(json_data_current, "", " ")
c.checkError(err)
target, err := json.MarshalIndent(json_data_target, "", " ")
c.checkError(err)

edits := myers.ComputeEdits(string(current), string(target))
if len(edits) != 0 {
udiff, err := diff.ToUnified("Current", "Target", string(current), edits, 0)
c.checkError(err)
fmt.Println(udiff)
} else {
fmt.Println("hmmm no differences")
}

}

func (c *TargetsPlugin) TargetsCommand(args []string) {
if len(args) != 1 {
c.exitWithUsage("targets")
Expand Down Expand Up @@ -248,6 +313,7 @@ func (c *TargetsPlugin) SetTargetCommand(args []string) {
c.linkCurrent(targetPath)
} else {
fmt.Println("Your current target has not been saved. Use save-target first, or use -f to discard your changes.")
c.showDiff(targetPath)
panic(1)
}
fmt.Println("Set target to", targetName)
Expand Down Expand Up @@ -289,6 +355,7 @@ func (c *TargetsPlugin) SaveCurrentTargetCommand(force bool) {
if c.status.currentNeedsSaving && !force {
fmt.Println("You've made substantial changes to the current target.")
fmt.Println("Use -f if you intend to overwrite the target named", targetName, "or provide an alternate name")
c.showDiff(c.configPath)
panic(1)
}
c.copyContents(c.configPath, targetPath)
Expand Down
27 changes: 27 additions & 0 deletions diff/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright 2009 The Go Authors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
176 changes: 176 additions & 0 deletions diff/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package diff computes differences between text files or strings.
package diff

import (
"fmt"
"sort"
"strings"
)

// An Edit describes the replacement of a portion of a text file.
type Edit struct {
Start, End int // byte offsets of the region to replace
New string // the replacement
}

func (e Edit) String() string {
return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New)
}

// Apply applies a sequence of edits to the src buffer and returns the
// result. Edits are applied in order of start offset; edits with the
// same start offset are applied in they order they were provided.
//
// Apply returns an error if any edit is out of bounds,
// or if any pair of edits is overlapping.
func Apply(src string, edits []Edit) (string, error) {
edits, size, err := validate(src, edits)
if err != nil {
return "", err
}

// Apply edits.
out := make([]byte, 0, size)
lastEnd := 0
for _, edit := range edits {
if lastEnd < edit.Start {
out = append(out, src[lastEnd:edit.Start]...)
}
out = append(out, edit.New...)
lastEnd = edit.End
}
out = append(out, src[lastEnd:]...)

if len(out) != size {
panic("wrong size")
}

return string(out), nil
}

// ApplyBytes is like Apply, but it accepts a byte slice.
// The result is always a new array.
func ApplyBytes(src []byte, edits []Edit) ([]byte, error) {
res, err := Apply(string(src), edits)
return []byte(res), err
}

// validate checks that edits are consistent with src,
// and returns the size of the patched output.
// It may return a different slice.
func validate(src string, edits []Edit) ([]Edit, int, error) {
if !sort.IsSorted(editsSort(edits)) {
edits = append([]Edit(nil), edits...)
SortEdits(edits)
}

// Check validity of edits and compute final size.
size := len(src)
lastEnd := 0
for _, edit := range edits {
if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) {
return nil, 0, fmt.Errorf("diff has out-of-bounds edits")
}
if edit.Start < lastEnd {
return nil, 0, fmt.Errorf("diff has overlapping edits")
}
size += len(edit.New) + edit.Start - edit.End
lastEnd = edit.End
}

return edits, size, nil
}

// SortEdits orders a slice of Edits by (start, end) offset.
// This ordering puts insertions (end = start) before deletions
// (end > start) at the same point, but uses a stable sort to preserve
// the order of multiple insertions at the same point.
// (Apply detects multiple deletions at the same point as an error.)
func SortEdits(edits []Edit) {
sort.Stable(editsSort(edits))
}

type editsSort []Edit

func (a editsSort) Len() int { return len(a) }
func (a editsSort) Less(i, j int) bool {
if cmp := a[i].Start - a[j].Start; cmp != 0 {
return cmp < 0
}
return a[i].End < a[j].End
}
func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

// lineEdits expands and merges a sequence of edits so that each
// resulting edit replaces one or more complete lines.
// See ApplyEdits for preconditions.
func lineEdits(src string, edits []Edit) ([]Edit, error) {
edits, _, err := validate(src, edits)
if err != nil {
return nil, err
}

// Do all deletions begin and end at the start of a line,
// and all insertions end with a newline?
// (This is merely a fast path.)
for _, edit := range edits {
if edit.Start >= len(src) || // insertion at EOF
edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start
edit.End > 0 && src[edit.End-1] != '\n' || // not at line start
edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert
goto expand // slow path
}
}
return edits, nil // aligned

expand:
if len(edits) == 0 {
return edits, nil // no edits (unreachable due to fast path)
}
expanded := make([]Edit, 0, len(edits)) // a guess
prev := edits[0]
// TODO(adonovan): opt: start from the first misaligned edit.
// TODO(adonovan): opt: avoid quadratic cost of string += string.
for _, edit := range edits[1:] {
between := src[prev.End:edit.Start]
if !strings.Contains(between, "\n") {
// overlapping lines: combine with previous edit.
prev.New += between + edit.New
prev.End = edit.End
} else {
// non-overlapping lines: flush previous edit.
expanded = append(expanded, expandEdit(prev, src))
prev = edit
}
}
return append(expanded, expandEdit(prev, src)), nil // flush final edit
}

// expandEdit returns edit expanded to complete whole lines.
func expandEdit(edit Edit, src string) Edit {
// Expand start left to start of line.
// (delta is the zero-based column number of start.)
start := edit.Start
if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 {
edit.Start -= delta
edit.New = src[start-delta:start] + edit.New
}

// Expand end right to end of line.
end := edit.End
if end > 0 && src[end-1] != '\n' ||
edit.New != "" && edit.New[len(edit.New)-1] != '\n' {
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
edit.End = len(src) // extend to EOF
} else {
edit.End = end + nl + 1 // extend beyond \n
}
}
edit.New += src[end:edit.End]

return edit
}
3 changes: 3 additions & 0 deletions diff/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module diff

go 1.23.0
Loading

0 comments on commit 316ee82

Please sign in to comment.