Skip to content

Commit

Permalink
Merging in from develop
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotforbes committed Mar 24, 2023
1 parent 1d4e972 commit 62deb45
Show file tree
Hide file tree
Showing 20 changed files with 740 additions and 314 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,8 @@ Development instructions for the CircleCI CLI can be found in [HACKING.md](HACKI
## More

Please see the [documentation](https://circleci-public.github.io/circleci-cli) or `circleci help` for more.


## Version Compatibility

As of homebrew version `0.1.24705` - we no longer support Server 3.x instances. In order to upgrade the CLI to the latest version, you'll need to update your instance of server to 4.x.
12 changes: 6 additions & 6 deletions api/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import (
)

type Client struct {
baseURL *url.URL
BaseURL *url.URL
circleToken string
client *http.Client
}

func New(baseURL *url.URL, token string, httpClient *http.Client) *Client {
func New(BaseURL *url.URL, token string, httpClient *http.Client) *Client {
return &Client{
baseURL: baseURL,
BaseURL: BaseURL,
circleToken: token,
client: httpClient,
}
Expand All @@ -37,13 +37,13 @@ func NewFromConfig(host string, config *settings.Config) *Client {
endpoint += "/"
}

baseURL, _ := url.Parse(host)
BaseURL, _ := url.Parse(host)

client := config.HTTPClient
client.Timeout = 10 * time.Second

return New(
baseURL.ResolveReference(&url.URL{Path: endpoint}),
BaseURL.ResolveReference(&url.URL{Path: endpoint}),
config.Token,
client,
)
Expand All @@ -60,7 +60,7 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req
}
}

req, err = http.NewRequest(method, c.baseURL.ResolveReference(u).String(), r)
req, err = http.NewRequest(method, c.BaseURL.ResolveReference(u).String(), r)
if err != nil {
return nil, err
}
Expand Down
247 changes: 45 additions & 202 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,16 @@ package cmd

import (
"fmt"
"net/url"
"os"
"strings"

"github.com/CircleCI-Public/circleci-cli/api/rest"
"github.com/CircleCI-Public/circleci-cli/config"
"github.com/CircleCI-Public/circleci-cli/filetree"
"github.com/CircleCI-Public/circleci-cli/local"
"github.com/CircleCI-Public/circleci-cli/pipeline"
"github.com/CircleCI-Public/circleci-cli/proxy"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)

var (
CollaborationsPath = "me/collaborations"
)

type configOptions struct {
cfg *settings.Config
rest *rest.Client
args []string
}

// Path to the config.yml file to operate on.
// Used to for compatibility with `circleci config validate --path`
var configPath string
Expand All @@ -39,19 +22,7 @@ var configAnnotations = map[string]string{
"<path>": "The path to your config (use \"-\" for STDIN)",
}

func GetConfigAPIHost(cfg *settings.Config) string {
if cfg.Host != defaultHost {
return cfg.Host
} else {
return cfg.ConfigAPIHost
}
}

func newConfigCommand(config *settings.Config) *cobra.Command {
opts := configOptions{
cfg: config,
}

func newConfigCommand(globalConfig *settings.Config) *cobra.Command {
configCmd := &cobra.Command{
Use: "config",
Short: "Operate on build config files",
Expand All @@ -60,11 +31,8 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
packCommand := &cobra.Command{
Use: "pack <path>",
Short: "Pack up your CircleCI configuration into a single file.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
},
RunE: func(_ *cobra.Command, _ []string) error {
return packConfig(opts)
RunE: func(_ *cobra.Command, args []string) error {
return packConfig(args)
},
Args: cobra.ExactArgs(1),
Annotations: make(map[string]string),
Expand All @@ -75,12 +43,24 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
Use: "validate <path>",
Aliases: []string{"check"},
Short: "Check that the config file is well formed.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.rest = rest.NewFromConfig(GetConfigAPIHost(opts.cfg), config)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return validateConfig(opts, cmd.Flags())
RunE: func(cmd *cobra.Command, args []string) error {
compiler := config.New(globalConfig)
orgID, _ := cmd.Flags().GetString("org-id")
orgSlug, _ := cmd.Flags().GetString("org-slug")
path := config.DefaultConfigPath
if configPath != "" {
path = configPath
}
if len(args) == 1 {
path = args[0]
}
return compiler.ValidateConfig(config.ValidateConfigOpts{
ConfigPath: path,
OrgID: orgID,
OrgSlug: orgSlug,
IgnoreDeprecatedImages: ignoreDeprecatedImages,
VerboseOutput: verboseOutput,
})
},
Args: cobra.MaximumNArgs(1),
Annotations: make(map[string]string),
Expand All @@ -89,6 +69,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
validateCommand.PersistentFlags().StringVarP(&configPath, "config", "c", ".circleci/config.yml", "path to config file")
validateCommand.PersistentFlags().BoolVarP(&verboseOutput, "verbose", "v", false, "Enable verbose output")
validateCommand.PersistentFlags().BoolVar(&ignoreDeprecatedImages, "ignore-deprecated-images", false, "ignores the deprecated images error")

if err := validateCommand.PersistentFlags().MarkHidden("config"); err != nil {
panic(err)
}
Expand All @@ -98,12 +79,22 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
processCommand := &cobra.Command{
Use: "process <path>",
Short: "Validate config and display expanded configuration.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.rest = rest.NewFromConfig(GetConfigAPIHost(opts.cfg), config)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return processConfig(opts, cmd.Flags())
RunE: func(cmd *cobra.Command, args []string) error {
compiler := config.New(globalConfig)
pipelineParamsFilePath, _ := cmd.Flags().GetString("pipeline-parameters")
orgID, _ := cmd.Flags().GetString("org-id")
orgSlug, _ := cmd.Flags().GetString("org-slug")
configPath := config.DefaultConfigPath
if len(args) >= 1 {
configPath = args[0]
}
return compiler.ProcessConfig(config.ProcessConfigOpts{
ConfigPath: configPath,
OrgID: orgID,
OrgSlug: orgSlug,
PipelineParamsFilePath: pipelineParamsFilePath,
VerboseOutput: verboseOutput,
})
},
Args: cobra.ExactArgs(1),
Annotations: make(map[string]string),
Expand All @@ -112,16 +103,13 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
processCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")
processCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org")
processCommand.Flags().StringP("pipeline-parameters", "", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)")
processCommand.Flags().StringP("circleci-api-host", "", "", "the api-host you want to use for config processing and validation")
processCommand.PersistentFlags().BoolVar(&verboseOutput, "verbose", false, "adds verbose output to the command")

migrateCommand := &cobra.Command{
Use: "migrate",
Short: "Migrate a pre-release 2.0 config to the official release version",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
},
RunE: func(_ *cobra.Command, _ []string) error {
return migrateConfig(opts)
RunE: func(_ *cobra.Command, args []string) error {
return migrateConfig(args)
},
Hidden: true,
DisableFlagParsing: true,
Expand All @@ -138,115 +126,8 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
return configCmd
}

// The <path> arg is actually optional, in order to support compatibility with the --path flag.
func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
var err error
var response *config.ConfigResponse
path := local.DefaultConfigPath
// First, set the path to configPath set by --path flag for compatibility
if configPath != "" {
path = configPath
}

// Then, if an arg is passed in, choose that instead
if len(opts.args) == 1 {
path = opts.args[0]
}

//if no orgId provided use org slug
values := pipeline.LocalPipelineValues()
if verboseOutput {
printValues(values)
}

var orgID string
orgID, _ = flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = config.ConfigQuery(opts.rest, path, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
orgs, err := GetOrgCollaborations(opts.cfg)
if err != nil {
fmt.Println(err.Error())
}
orgID = GetOrgIdFromSlug(orgSlug, orgs)
response, err = config.ConfigQuery(opts.rest, path, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}

// check if a deprecated Linux VM image is being used
// link here to blog post when available
// returns an error if a deprecated image is used
if !ignoreDeprecatedImages {
err := config.DeprecatedImageCheck(response)
if err != nil {
return err
}
}

if path == "-" {
fmt.Printf("\nConfig input is valid.\n")
} else {
fmt.Printf("\nConfig file at %s is valid.\n", path)
}

return nil
}

func processConfig(opts configOptions, flags *pflag.FlagSet) error {
paramsYaml, _ := flags.GetString("pipeline-parameters")
var response *config.ConfigResponse
var params pipeline.Parameters
var err error

if len(paramsYaml) > 0 {
// The 'src' value can be a filepath, or a yaml string. If the file cannot be read successfully,
// proceed with the assumption that the value is already valid yaml.
raw, err := os.ReadFile(paramsYaml)
if err != nil {
raw = []byte(paramsYaml)
}

err = yaml.Unmarshal(raw, &params)
if err != nil {
return fmt.Errorf("invalid 'pipeline-parameters' provided: %s", err.Error())
}
}

//if no orgId provided use org slug
values := pipeline.LocalPipelineValues()
printValues(values)

orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = config.ConfigQuery(opts.rest, opts.args[0], orgID, params, values)
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
orgs, err := GetOrgCollaborations(opts.cfg)
if err != nil {
fmt.Println(err.Error())
}
orgID = GetOrgIdFromSlug(orgSlug, orgs)
response, err = config.ConfigQuery(opts.rest, opts.args[0], orgID, params, values)
if err != nil {
return err
}
}

fmt.Print(response.OutputYaml)
return nil
}

func packConfig(opts configOptions) error {
tree, err := filetree.NewTree(opts.args[0])
func packConfig(args []string) error {
tree, err := filetree.NewTree(args[0])
if err != nil {
return errors.Wrap(err, "An error occurred trying to build the tree")
}
Expand All @@ -259,44 +140,6 @@ func packConfig(opts configOptions) error {
return nil
}

func migrateConfig(opts configOptions) error {
return proxy.Exec([]string{"config", "migrate"}, opts.args)
}

func printValues(values pipeline.Values) {
fmt.Fprintln(os.Stderr, "Processing config with following values:")
for key, value := range values {
fmt.Fprintf(os.Stderr, "%-18s %s\n", key+":", value)
}
}

type CollaborationResult struct {
VcsTye string `json:"vcs_type"`
OrgSlug string `json:"slug"`
OrgName string `json:"name"`
OrgId string `json:"id"`
AvatarUrl string `json:"avatar_url"`
}

// GetOrgCollaborations - fetches all the collaborations for a given user.
func GetOrgCollaborations(cfg *settings.Config) ([]CollaborationResult, error) {
baseClient := rest.NewFromConfig(cfg.Host, cfg)
req, err := baseClient.NewRequest("GET", &url.URL{Path: CollaborationsPath}, nil)
if err != nil {
return nil, err
}

var resp []CollaborationResult
_, err = baseClient.DoRequest(req, &resp)
return resp, err
}

// GetOrgIdFromSlug - converts a slug into an orgID.
func GetOrgIdFromSlug(slug string, collaborations []CollaborationResult) string {
for _, v := range collaborations {
if v.OrgSlug == slug {
return v.OrgId
}
}
return ""
func migrateConfig(args []string) error {
return proxy.Exec([]string{"config", "migrate"}, args)
}
Loading

0 comments on commit 62deb45

Please sign in to comment.