Skip to content

Commit

Permalink
Merge branch 'main' into refactor/change_owner
Browse files Browse the repository at this point in the history
  • Loading branch information
JulesFaucherre authored Jan 24, 2023
2 parents 9a336a4 + 99feff3 commit c675453
Show file tree
Hide file tree
Showing 19 changed files with 517 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,4 @@ workflows:
- shellcheck/check
filters:
branches:
only: master
only: main
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have checked for similar issues and haven't found anything relevant.
- [ ] This is not a security issue (which should be reported here: https://circleci.com/security/)
- [ ] I have read [Contribution Guidelines](https://github.com/CircleCI-Public/circleci-cli/blob/master/CONTRIBUTING.md).
- [ ] I have read [Contribution Guidelines](https://github.com/CircleCI-Public/circleci-cli/blob/main/CONTRIBUTING.md).

### Internal Checklist
- [ ] I am requesting a review from my own team as well as the owning team
Expand Down Expand Up @@ -45,7 +45,7 @@ Image or gif where change can be clearly seen

## **Here are some helpful tips you can follow when submitting a pull request:**

1. Fork [the repository](https://github.com/CircleCI-Public/circleci-cli) and create your branch from `master`.
1. Fork [the repository](https://github.com/CircleCI-Public/circleci-cli) and create your branch from `main`.
2. Run `make build` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`make test`).
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,25 @@ choco install circleci-cli -y
You can also install the CLI binary by running our install script on most Unix platforms:

```
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | bash
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | bash
```

By default, the `circleci` app will be installed to the ``/usr/local/bin`` directory. If you do not have write permissions to `/usr/local/bin`, you may need to run the above command with `sudo`:

```
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | sudo bash
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | sudo bash
```

Alternatively, you can install to an alternate location by defining the `DESTDIR` environment variable when invoking `bash`:

```
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | DESTDIR=/opt/bin bash
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | DESTDIR=/opt/bin bash
```

You can also set a specific version of the CLI to install with the `VERSION` environment variable:

```
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | VERSION=0.1.5222 sudo bash
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | VERSION=0.1.5222 sudo bash
```

### Updating
Expand Down Expand Up @@ -135,17 +135,17 @@ The following commands are affected:

## Platforms, Deployment and Package Managers

The tool is deployed through a number of channels. The primary release channel is through [GitHub Releases](https://github.com/CircleCI-Public/circleci-cli/releases). Green builds on the `master` branch will publish a new GitHub release. These releases contain binaries for macOS, Linux and Windows. These releases are published from (CircleCI)[https://app.circleci.com/pipelines/github/CircleCI-Public/circleci-cli] using (GoReleaser)[https://goreleaser.com/].
The tool is deployed through a number of channels. The primary release channel is through [GitHub Releases](https://github.com/CircleCI-Public/circleci-cli/releases). Green builds on the `main` branch will publish a new GitHub release. These releases contain binaries for macOS, Linux and Windows. These releases are published from (CircleCI)[https://app.circleci.com/pipelines/github/CircleCI-Public/circleci-cli] using (GoReleaser)[https://goreleaser.com/].

### Homebrew

We publish the tool to [Homebrew](https://brew.sh/). The tool is [part of `homebrew-core`](https://github.com/Homebrew/homebrew-core/blob/master/Formula/circleci.rb), and therefore the maintainers of the tool are obligated to follow the guidelines for acceptable Homebrew formulae. You should [familairise yourself with the guidelines](https://docs.brew.sh/Acceptable-Formulae#we-dont-like-tools-that-upgrade-themselves) before making changes to the Homebrew deployment system.
We publish the tool to [Homebrew](https://brew.sh/). The tool is [part of `homebrew-core`](https://github.com/Homebrew/homebrew-core/blob/main/Formula/circleci.rb), and therefore the maintainers of the tool are obligated to follow the guidelines for acceptable Homebrew formulae. You should [familairise yourself with the guidelines](https://docs.brew.sh/Acceptable-Formulae#we-dont-like-tools-that-upgrade-themselves) before making changes to the Homebrew deployment system.

The particular considerations that we make are:


1. Since Homebrew [doesn't "like tools that upgrade themselves"](https://docs.brew.sh/Acceptable-Formulae#we-dont-like-tools-that-upgrade-themselves), we disable the `circleci update` command when the tool is released through homebrew. We do this by [defining the PackageManager](https://github.com/Homebrew/homebrew-core/blob/eb1fdb84e2924289bcc8c85ee45081bf83dc024d/Formula/circleci.rb#L28) constant to `homebrew`, which allows us to [disable the `update` command at runtime](https://github.com/CircleCI-Public/circleci-cli/blob/67c7d52bace63846f87a1ed79f67f257c94a55b4/cmd/root.go#L119-L123).
1. We want to avoid every push to `master` from creating a Pull Request to the `circleci` formula on Homebrew. We want to avoid overloading the Homebrew team with pull requests to update our formula for small changes (changes to docs or other files that don't change functionality in the tool).
1. We want to avoid every push to `main` from creating a Pull Request to the `circleci` formula on Homebrew. We want to avoid overloading the Homebrew team with pull requests to update our formula for small changes (changes to docs or other files that don't change functionality in the tool).

### Snap

Expand Down
12 changes: 10 additions & 2 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@ import (
)

func newLocalExecuteCommand(config *settings.Config) *cobra.Command {
var args []string
buildCommand := &cobra.Command{
Use: "execute",
Use: "execute <job-name>",
Short: "Run a job in a container on the local machine",
PreRunE: func(cmd *cobra.Command, _args []string) error {
args = _args
return nil
},
RunE: func(cmd *cobra.Command, _ []string) error {
return local.Execute(cmd.Flags(), config)
return local.Execute(cmd.Flags(), config, args)
},
Args: cobra.MinimumNArgs(1),
}

local.AddFlagsForDocumentation(buildCommand.Flags())
buildAgentVersionUsage := `The version of the build agent image you want to use. This can be configured by writing in $HOME/.circleci/build_agent_settings.json: '{"LatestSha256":"<version-of-build-agent>"}'`
buildCommand.Flags().String("build-agent-version", "", buildAgentVersionUsage)
buildCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")
buildCommand.Flags().String("org-id", "", "organization id, used when a config depends on private orbs belonging to that org")

Expand Down
99 changes: 99 additions & 0 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,30 @@ import (
"github.com/CircleCI-Public/circleci-cli/references"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/CircleCI-Public/circleci-cli/version"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"

"github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"

"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
)

type orbOptions struct {
cfg *settings.Config
cl *graphql.Client
args []string

color string

listUncertified bool
listJSON bool
listDetails bool
Expand Down Expand Up @@ -320,6 +328,18 @@ Please note that at this time all orbs created in the registry are world-readabl
}
orbInit.PersistentFlags().BoolVarP(&opts.private, "private", "", false, "initialize a private orb")

orbDiff := &cobra.Command{
Use: "diff <orb> <version1> <version2>",
Short: "Shows the difference between two versions of the same orb",
RunE: func(_ *cobra.Command, _ []string) error {
return orbDiff(opts)
},
Args: cobra.ExactArgs(3),
Annotations: make(map[string]string),
}
orbDiff.Annotations["<orb>"] = "An orb with only a namespace and a name. This takes this form namespace/orb"
orbDiff.PersistentFlags().StringVar(&opts.color, "color", "auto", "Show colored diff. Can be one of \"always\", \"never\", or \"auto\"")

orbCreate.Flags().BoolVar(&opts.integrationTesting, "integration-testing", false, "Enable test mode to bypass interactive UI.")
if err := orbCreate.Flags().MarkHidden("integration-testing"); err != nil {
panic(err)
Expand Down Expand Up @@ -354,6 +374,7 @@ Please note that at this time all orbs created in the registry are world-readabl
orbCommand.AddCommand(removeCategorizationFromOrbCommand)
orbCommand.AddCommand(listCategoriesCommand)
orbCommand.AddCommand(orbInit)
orbCommand.AddCommand(orbDiff)

return orbCommand
}
Expand Down Expand Up @@ -1609,3 +1630,81 @@ func orbTemplate(fileContents string, projectName string, orgName string, orbNam

return x
}

func orbDiff(opts orbOptions) error {
colorOpt := opts.color
allowedColorOpts := []string{"auto", "always", "never"}
if !slices.Contains(allowedColorOpts, colorOpt) {
return fmt.Errorf("option `color' expects \"always\", \"auto\", or \"never\"")
}

orbName := opts.args[0]
version1 := opts.args[1]
version2 := opts.args[2]
orb1 := fmt.Sprintf("%s@%s", orbName, version1)
orb2 := fmt.Sprintf("%s@%s", orbName, version2)

orb1Source, err := api.OrbSource(opts.cl, orb1)
if err != nil {
return errors.Wrapf(err, "Failed to get source for '%s'", orb1)
}
orb2Source, err := api.OrbSource(opts.cl, orb2)
if err != nil {
return errors.Wrapf(err, "Failed to get source for '%s'", orb2)
}

edits := myers.ComputeEdits(span.URIFromPath(orb1), orb1Source, orb2Source)
unified := gotextdiff.ToUnified(orb1, orb2, orb1Source, edits)
diff := stringifyDiff(unified, colorOpt)
if diff == "" {
fmt.Println("No diff found")
} else {
fmt.Println(diff)
}

return nil
}

// Stringifies the unified diff passed as argument, and colorize it depending on the colorOpt value
func stringifyDiff(diff gotextdiff.Unified, colorOpt string) string {
if len(diff.Hunks) == 0 {
return ""
}

headerColor := color.New(color.BgYellow, color.FgBlack)
diffStartColor := color.New(color.BgBlue, color.FgWhite)
deleteColor := color.New(color.FgRed)
insertColor := color.New(color.FgGreen)
untouchedColor := color.New(color.Reset)

// The color library already takes care of disabling the color when stdout is redirected so we
// just enforce the color behavior for "never" and "always" and let the library handle the 'auto'
// case
oldNoColor := color.NoColor
if colorOpt == "never" {
color.NoColor = true
}
if colorOpt == "always" {
color.NoColor = false
}

diffString := fmt.Sprintf("%s", diff)
lines := strings.Split(diffString, "\n")

for i, line := range lines {
if strings.HasPrefix(line, "--- ") || strings.HasPrefix(line, "+++ ") {
lines[i] = headerColor.Sprint(line)
} else if strings.HasPrefix(line, "@@ ") {
lines[i] = diffStartColor.Sprint(line)
} else if strings.HasPrefix(line, "-") {
lines[i] = deleteColor.Sprint(line)
} else if strings.HasPrefix(line, "+") {
lines[i] = insertColor.Sprint(line)
} else {
lines[i] = untouchedColor.Sprint(line)
}
}

color.NoColor = oldNoColor
return strings.Join(lines, "\n")
}
88 changes: 88 additions & 0 deletions cmd/orb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os/exec"
"path/filepath"
"strconv"
"time"

"gotest.tools/v3/golden"

Expand Down Expand Up @@ -3338,4 +3339,91 @@ Windows Server 2010
})

})

Describe("Orb diff", func() {
var (
token string
tempSettings *clitest.TempSettings
command *exec.Cmd
)

BeforeEach(func() {
token = "testtoken"
tempSettings = clitest.WithTempSettings()
})

AfterEach(func() {
tempSettings.Close()
})

DescribeTable("Shows the expected diff", func(source1, source2, expected, color string) {
orbName := "somenamespace/someorb"
version1 := "1.0.0"
orb1 := fmt.Sprintf("%s@%s", orbName, version1)
version2 := "2.0.0"
orb2 := fmt.Sprintf("%s@%s", orbName, version2)
command = exec.Command(pathCLI, "orb", "diff", orbName, version1, version2,
"--token", token,
"--host", tempSettings.TestServer.URL())

mockOrbSource(source1, orb1, token, tempSettings)
mockOrbSource(source2, orb2, token, tempSettings)

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())

Eventually(session.Out).WithTimeout(5 * time.Second).Should(gbytes.Say(expected))
Eventually(session).Should(gexec.Exit(0))
},
Entry("Detect identical sources", "orb-source", "orb-source", "No diff found", "auto"),
Entry(
"Detect difference",
"line1\\nline3\\n",
"line1\\nline2\\n",
`--- somenamespace/[email protected]
\+\+\+ somenamespace/[email protected]
@@ -1,2 \+1,2 @@
line1
-line3
\+line2`,
"auto",
),
)
})
})

func mockOrbSource(source, orbVersion, token string, tempSettings *clitest.TempSettings) {
requestStruct := struct {
Query string `json:"query"`
Variables struct {
OrbVersionRef string `json:"orbVersionRef"`
} `json:"variables"`
}{
Query: `query($orbVersionRef: String!) {
orbVersion(orbVersionRef: $orbVersionRef) {
id
version
orb { id }
source
}
}`,
Variables: struct {
OrbVersionRef string `json:"orbVersionRef"`
}{OrbVersionRef: orbVersion},
}
request, err := json.Marshal(requestStruct)
Expect(err).ToNot(HaveOccurred())
response := fmt.Sprintf(`{
"orbVersion": {
"id": "some-id",
"version": "some-version",
"orb": { "id": "some-id" },
"source": "%s"
}
}`, source)
tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status: http.StatusOK,
Request: string(request),
Response: response,
})
}
Loading

0 comments on commit c675453

Please sign in to comment.