commands/variable/export/export.go (256 lines of code) (raw):
package export
import (
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"strings"
"github.com/MakeNowJust/heredoc/v2"
"github.com/spf13/cobra"
gitlab "gitlab.com/gitlab-org/api/client-go"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/commands/flag"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
)
type ExportOpts struct {
HTTPClient func() (*gitlab.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
ValueSet bool
Group string
OutputFormat string
Scope string
Page int
PerPage int
}
func marshalJson(variables any) ([]byte, error) {
res, err := json.MarshalIndent(variables, "", " ")
if err != nil {
return nil, err
}
return res, nil
}
func NewCmdExport(f *cmdutils.Factory, runE func(opts *ExportOpts) error) *cobra.Command {
opts := &ExportOpts{
IO: f.IO,
}
cmd := &cobra.Command{
Use: "export",
Short: "Export variables from a project or group.",
Aliases: []string{"ex"},
Args: cobra.ExactArgs(0),
Example: heredoc.Doc(`
$ glab variable export
$ glab variable export --per-page 1000 --page 1
$ glab variable export --group gitlab-org
$ glab variable export --group gitlab-org --per-page 1000 --page 1
`),
RunE: func(cmd *cobra.Command, args []string) (err error) {
// Supports repo override
opts.HTTPClient = f.HttpClient
opts.BaseRepo = f.BaseRepo
group, err := flag.GroupOverride(cmd)
if err != nil {
return err
}
opts.Group = group
if runE != nil {
err = runE(opts)
return
}
err = exportRun(opts)
return
},
}
cmdutils.EnableRepoOverride(cmd, f)
cmd.PersistentFlags().StringP("group", "g", "", "Select a group or subgroup. Ignored if a repository argument is set.")
cmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number.")
cmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 100, "Number of items to list per page.")
cmd.Flags().StringVarP(&opts.OutputFormat, "format", "F", "json", "Format of output: json, export, env.")
cmd.Flags().StringVarP(&opts.Scope, "scope", "s", "*", "The environment_scope of the variables. Values: '*' (default), or specific environments.")
return cmd
}
func exportRun(opts *ExportOpts) error {
var out io.Writer = os.Stdout
if opts.IO != nil && opts.IO.StdOut != nil {
out = opts.IO.StdOut
}
httpClient, err := opts.HTTPClient()
if err != nil {
return err
}
repo, err := opts.BaseRepo()
if err != nil {
return err
}
if opts.Group != "" {
createVarOpts := &gitlab.ListGroupVariablesOptions{Page: opts.Page, PerPage: opts.PerPage}
groupVariables, err := api.ListGroupVariables(httpClient, opts.Group, createVarOpts)
if err != nil {
return err
}
opts.IO.Logf("Exporting variables from the %s group:\n", opts.Group)
if len(groupVariables) == 0 {
return nil
}
return printGroupVariables(groupVariables, opts, out)
} else {
createVarOpts := &gitlab.ListProjectVariablesOptions{Page: opts.Page, PerPage: opts.PerPage}
projectVariables, err := api.ListProjectVariables(httpClient, repo.FullName(), createVarOpts)
if err != nil {
return err
}
opts.IO.Logf("Exporting variables from the %s project:\n", repo.FullName())
if len(projectVariables) == 0 {
return nil
}
return printProjectVariables(projectVariables, opts, out)
}
}
func matchesScope(varScope, optScope string) bool {
if varScope == "*" || optScope == "*" {
return true
}
if varScope == optScope {
return true
}
if strings.Contains(varScope, "*") {
varPattern := "^" + regexp.QuoteMeta(varScope) + "$"
optPattern := "^" + regexp.QuoteMeta(optScope) + "$"
varPattern = strings.ReplaceAll(varPattern, `\*`, ".*")
optPattern = strings.ReplaceAll(optPattern, `\*`, ".*")
matchesVar, _ := regexp.MatchString(varPattern, optScope)
matchesOpt, _ := regexp.MatchString(optPattern, varScope)
return matchesVar || matchesOpt
}
return false
}
func isValidEnvironmentScope(optScope string) bool {
pattern := `^[a-zA-Z0-9\s\-_/${}\x20]+$`
re, _ := regexp.Compile(pattern)
matched := re.MatchString(optScope)
return matched || optScope == "*"
}
func printGroupVariables(variables []*gitlab.GroupVariable, opts *ExportOpts, out io.Writer) error {
if !isValidEnvironmentScope((opts.Scope)) {
return fmt.Errorf("invalid environment scope: %s", opts.Scope)
}
writtenKeys := make([]string, 0)
switch opts.OutputFormat {
case "env":
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !strings.Contains(variable.EnvironmentScope, "*") {
fmt.Fprintf(out, "%s=%s\n", variable.Key, variable.Value)
writtenKeys = append(writtenKeys, variable.Key)
}
}
}
keysMap := CreateWrittenKeysMap(writtenKeys)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !(keysMap[variable.Key]) && (strings.Contains(variable.EnvironmentScope, "*")) {
fmt.Fprintf(out, "%s=%s\n", variable.Key, variable.Value)
}
}
}
case "export":
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !strings.Contains(variable.EnvironmentScope, "*") {
fmt.Fprintf(out, "export %s=%s\n", variable.Key, variable.Value)
writtenKeys = append(writtenKeys, variable.Key)
}
}
}
keysMap := CreateWrittenKeysMap(writtenKeys)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !(keysMap[variable.Key]) && (strings.Contains(variable.EnvironmentScope, "*")) {
fmt.Fprintf(out, "export %s=%s\n", variable.Key, variable.Value)
}
}
}
case "json":
filteredVariables := make([]*gitlab.GroupVariable, 0)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
filteredVariables = append(filteredVariables, variable)
}
}
res, err := marshalJson(filteredVariables)
if err != nil {
return err
}
fmt.Fprintln(out, string(res))
default:
return fmt.Errorf("unsupported output format: %s", opts.OutputFormat)
}
return nil
}
func printProjectVariables(variables []*gitlab.ProjectVariable, opts *ExportOpts, out io.Writer) error {
if !isValidEnvironmentScope((opts.Scope)) {
return fmt.Errorf("invalid environment scope: %s", opts.Scope)
}
writtenKeys := make([]string, 0)
switch opts.OutputFormat {
case "env":
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !strings.Contains(variable.EnvironmentScope, "*") {
fmt.Fprintf(out, "%s=\"%s\"\n", variable.Key, variable.Value)
writtenKeys = append(writtenKeys, variable.Key)
}
}
}
keysMap := CreateWrittenKeysMap(writtenKeys)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !(keysMap[variable.Key]) && (strings.Contains(variable.EnvironmentScope, "*")) {
fmt.Fprintf(out, "%s=\"%s\"\n", variable.Key, variable.Value)
}
}
}
case "export":
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !strings.Contains(variable.EnvironmentScope, "*") {
fmt.Fprintf(out, "export %s=\"%s\"\n", variable.Key, variable.Value)
writtenKeys = append(writtenKeys, variable.Key)
}
}
}
keysMap := CreateWrittenKeysMap(writtenKeys)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
if !(keysMap[variable.Key]) && (strings.Contains(variable.EnvironmentScope, "*")) {
fmt.Fprintf(out, "export %s=\"%s\"\n", variable.Key, variable.Value)
}
}
}
case "json":
filteredVariables := make([]*gitlab.ProjectVariable, 0)
for _, variable := range variables {
if matchesScope(variable.EnvironmentScope, opts.Scope) {
filteredVariables = append(filteredVariables, variable)
}
}
res, err := marshalJson(filteredVariables)
if err != nil {
return err
}
fmt.Fprintln(out, string(res))
default:
return fmt.Errorf("unsupported output format: %s", opts.OutputFormat)
}
return nil
}
func CreateWrittenKeysMap(writtenKeys []string) map[string]bool {
keysMap := make(map[string]bool)
for _, key := range writtenKeys {
keysMap[key] = true
}
return keysMap
}