From 39df53bf26b005635d032aa4faa6b0f5e4954716 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Wed, 8 Oct 2025 00:41:23 +0530 Subject: [PATCH 01/12] feat(cmd): add import command --- internal/commands/variable/import/import.go | 194 ++++++++++++++++++++ internal/commands/variable/variable.go | 2 + 2 files changed, 196 insertions(+) create mode 100644 internal/commands/variable/import/import.go diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go new file mode 100644 index 000000000..66f47618d --- /dev/null +++ b/internal/commands/variable/import/import.go @@ -0,0 +1,194 @@ +package importCmd + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/MakeNowJust/heredoc/v2" + "github.com/spf13/cobra" + clientgo "gitlab.com/gitlab-org/api/client-go" + "gitlab.com/gitlab-org/cli/internal/api" + "gitlab.com/gitlab-org/cli/internal/cmdutils" + "gitlab.com/gitlab-org/cli/internal/glrepo" + "gitlab.com/gitlab-org/cli/internal/iostreams" + "gitlab.com/gitlab-org/cli/internal/mcpannotations" +) + +type options struct { + apiClient func(repoHost string) (*api.Client, error) + io *iostreams.IOStreams + baseRepo func() (glrepo.Interface, error) + + group string + filePath string + fromStdin bool + update bool // true => update existing vars, false => error if exists +} + +func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Command { + opts := &options{ + io: f.IO(), + apiClient: f.ApiClient, + baseRepo: f.BaseRepo, + } + + cmd := &cobra.Command{ + Use: "import", + Short: "Import variables from JSON into a project or group.", + Aliases: []string{"im"}, + Example: heredoc.Doc(` + $ glab variable import -f variables.json + $ cat variables.json | glab variable import --stdin + $ glab variable import -g mygroup -f group_vars.json + $ glab variable import -f vars.json --update + `), + Annotations: map[string]string{ + mcpannotations.Safe: "true", + }, + RunE: func(cmd *cobra.Command, args []string) error { + if runE != nil { + return runE(opts) + } + return opts.run() + }, + } + + cmd.PersistentFlags().StringP("group", "g", "", "Select a group or subgroup. Ignored if a repository argument is set.") + cmd.Flags().StringVarP(&opts.filePath, "file", "f", "", "Path to JSON file containing variables.") + cmd.Flags().BoolVar(&opts.fromStdin, "stdin", false, "Read JSON from standard input.") + cmd.Flags().BoolVar(&opts.update, "update", false, "Update existing variables instead of throwing an error.") + + return cmd +} + +func (o *options) run() error { + var input []byte + var err error + + if o.filePath != "" { + input, err = os.ReadFile(o.filePath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + } else if o.fromStdin { + input, err = io.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("failed to read from stdin: %w", err) + } + } else { + return fmt.Errorf("no input source provided: use --file or --stdin") + } + + var variables []clientgo.ProjectVariable + if err := json.Unmarshal(input, &variables); err != nil { + return fmt.Errorf("failed to parse JSON: %w", err) + } + + // Setup API client + var repoHost string + if baseRepo, err := o.baseRepo(); err == nil { + repoHost = baseRepo.RepoHost() + } + apiClient, err := o.apiClient(repoHost) + if err != nil { + return err + } + client := apiClient.Lab() + + // Import for group or project + if o.group != "" { + return o.importGroupVariables(client, variables) + } + + repo, err := o.baseRepo() + if err != nil { + return err + } + return o.importProjectVariables(client, repo.FullName(), variables) +} + +func (o *options) importProjectVariables(client *clientgo.Client, project string, vars []clientgo.ProjectVariable) error { + for _, v := range vars { + _, resp, err := client.ProjectVariables.GetVariable(project, v.Key, nil) + if err == nil && resp.StatusCode == 200 { + if !o.update { + return fmt.Errorf("variable %q already exists", v.Key) + } + // Update existing variable + _, _, err := client.ProjectVariables.UpdateVariable(project, v.Key, &clientgo.UpdateProjectVariableOptions{ + Value: &v.Value, + Protected: &v.Protected, + Masked: &v.Masked, + EnvironmentScope: &v.EnvironmentScope, + VariableType: &v.VariableType, + Description: &v.Description, + Raw: &v.Raw, + }) + if err != nil { + return fmt.Errorf("failed to update variable %s: %w", v.Key, err) + } + fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) + } else { + // Create new variable + _, _, err := client.ProjectVariables.CreateVariable(project, &clientgo.CreateProjectVariableOptions{ + Key: &v.Key, + Value: &v.Value, + Protected: &v.Protected, + Masked: &v.Masked, + EnvironmentScope: &v.EnvironmentScope, + VariableType: &v.VariableType, + Description: &v.Description, + Raw: &v.Raw, + }) + if err != nil { + return fmt.Errorf("failed to create variable %s: %w", v.Key, err) + } + fmt.Fprintf(o.io.StdOut, "Created variable: %s\n", v.Key) + } + } + return nil +} + +func (o *options) importGroupVariables(client *clientgo.Client, vars []clientgo.ProjectVariable) error { + for _, v := range vars { + _, resp, err := client.GroupVariables.GetVariable(o.group, v.Key, nil) + if err == nil && resp.StatusCode == 200 { + if !o.update { + return fmt.Errorf("variable %q already exists", v.Key) + } + // Update existing variable + _, _, err := client.GroupVariables.UpdateVariable(o.group, v.Key, &clientgo.UpdateGroupVariableOptions{ + Value: &v.Value, + Protected: &v.Protected, + Masked: &v.Masked, + EnvironmentScope: &v.EnvironmentScope, + VariableType: &v.VariableType, + Description: &v.Description, + Raw: &v.Raw, + }) + if err != nil { + return fmt.Errorf("failed to update variable %s: %w", v.Key, err) + } + fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) + } else { + // Create new variable + _, _, err := client.GroupVariables.CreateVariable(o.group, &clientgo.CreateGroupVariableOptions{ + Key: &v.Key, + Value: &v.Value, + Protected: &v.Protected, + Masked: &v.Masked, + EnvironmentScope: &v.EnvironmentScope, + VariableType: &v.VariableType, + Description: &v.Description, + Raw: &v.Raw, + }) + if err != nil { + return fmt.Errorf("failed to create variable %s: %w", v.Key, err) + } + fmt.Fprintf(o.io.StdOut, "Created variable: %s\n", v.Key) + } + } + return nil +} diff --git a/internal/commands/variable/variable.go b/internal/commands/variable/variable.go index fa2d6646c..8c422c6dd 100644 --- a/internal/commands/variable/variable.go +++ b/internal/commands/variable/variable.go @@ -9,6 +9,7 @@ import ( listCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/list" setCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/set" updateCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/update" + importCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/import" ) func NewVariableCmd(f cmdutils.Factory) *cobra.Command { @@ -26,5 +27,6 @@ func NewVariableCmd(f cmdutils.Factory) *cobra.Command { cmd.AddCommand(updateCmd.NewCmdUpdate(f, nil)) cmd.AddCommand(getCmd.NewCmdGet(f, nil)) cmd.AddCommand(exportCmd.NewCmdExport(f, nil)) + cmd.AddCommand(importCmd.NewCmdImport(f, nil)) return cmd } -- GitLab From b52edb06dda494f4e4739863e07e30e88b3adab7 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Thu, 9 Oct 2025 02:57:43 +0530 Subject: [PATCH 02/12] refactor: updated proper error message --- internal/commands/variable/import/import.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go index 66f47618d..3737bcf05 100644 --- a/internal/commands/variable/import/import.go +++ b/internal/commands/variable/import/import.go @@ -86,7 +86,6 @@ func (o *options) run() error { return fmt.Errorf("failed to parse JSON: %w", err) } - // Setup API client var repoHost string if baseRepo, err := o.baseRepo(); err == nil { repoHost = baseRepo.RepoHost() @@ -97,7 +96,6 @@ func (o *options) run() error { } client := apiClient.Lab() - // Import for group or project if o.group != "" { return o.importGroupVariables(client, variables) } @@ -114,9 +112,8 @@ func (o *options) importProjectVariables(client *clientgo.Client, project string _, resp, err := client.ProjectVariables.GetVariable(project, v.Key, nil) if err == nil && resp.StatusCode == 200 { if !o.update { - return fmt.Errorf("variable %q already exists", v.Key) + return fmt.Errorf("variable %q already exists. use --update if you wish to override it", v.Key) } - // Update existing variable _, _, err := client.ProjectVariables.UpdateVariable(project, v.Key, &clientgo.UpdateProjectVariableOptions{ Value: &v.Value, Protected: &v.Protected, @@ -131,7 +128,6 @@ func (o *options) importProjectVariables(client *clientgo.Client, project string } fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) } else { - // Create new variable _, _, err := client.ProjectVariables.CreateVariable(project, &clientgo.CreateProjectVariableOptions{ Key: &v.Key, Value: &v.Value, @@ -158,7 +154,6 @@ func (o *options) importGroupVariables(client *clientgo.Client, vars []clientgo. if !o.update { return fmt.Errorf("variable %q already exists", v.Key) } - // Update existing variable _, _, err := client.GroupVariables.UpdateVariable(o.group, v.Key, &clientgo.UpdateGroupVariableOptions{ Value: &v.Value, Protected: &v.Protected, @@ -173,7 +168,6 @@ func (o *options) importGroupVariables(client *clientgo.Client, vars []clientgo. } fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) } else { - // Create new variable _, _, err := client.GroupVariables.CreateVariable(o.group, &clientgo.CreateGroupVariableOptions{ Key: &v.Key, Value: &v.Value, -- GitLab From 22a0d9615ededfb15af49cf00066e3cd230b2828 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Mon, 20 Oct 2025 03:32:27 +0530 Subject: [PATCH 03/12] add: testcases for import.go --- internal/commands/variable/import/import.go | 11 +- .../commands/variable/import/import_test.go | 259 ++++++++++++++++++ 2 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 internal/commands/variable/import/import_test.go diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go index 3737bcf05..e048f8807 100644 --- a/internal/commands/variable/import/import.go +++ b/internal/commands/variable/import/import.go @@ -39,10 +39,10 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com Short: "Import variables from JSON into a project or group.", Aliases: []string{"im"}, Example: heredoc.Doc(` - $ glab variable import -f variables.json + $ glab variable import --file variables.json + $ glab variable import --file vars.json --update $ cat variables.json | glab variable import --stdin - $ glab variable import -g mygroup -f group_vars.json - $ glab variable import -f vars.json --update + $ glab variable import --group mygroup --file group_vars.json `), Annotations: map[string]string{ mcpannotations.Safe: "true", @@ -55,7 +55,7 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com }, } - cmd.PersistentFlags().StringP("group", "g", "", "Select a group or subgroup. Ignored if a repository argument is set.") + cmd.Flags().StringVarP(&opts.group, "group", "g", "", "Select a group or subgroup. Ignored if a repository argument is set.") cmd.Flags().StringVarP(&opts.filePath, "file", "f", "", "Path to JSON file containing variables.") cmd.Flags().BoolVar(&opts.fromStdin, "stdin", false, "Read JSON from standard input.") cmd.Flags().BoolVar(&opts.update, "update", false, "Update existing variables instead of throwing an error.") @@ -77,6 +77,9 @@ func (o *options) run() error { if err != nil { return fmt.Errorf("failed to read from stdin: %w", err) } + if len(input) == 0 { + return fmt.Errorf("failed to read from stdin: no data") + } } else { return fmt.Errorf("no input source provided: use --file or --stdin") } diff --git a/internal/commands/variable/import/import_test.go b/internal/commands/variable/import/import_test.go new file mode 100644 index 000000000..fb1b35547 --- /dev/null +++ b/internal/commands/variable/import/import_test.go @@ -0,0 +1,259 @@ +package importCmd + +import ( + "bytes" + "encoding/json" + "net/http" + "os" + "testing" + + "github.com/google/shlex" + "github.com/stretchr/testify/assert" + gitlab "gitlab.com/gitlab-org/api/client-go" + "gitlab.com/gitlab-org/cli/internal/api" + "gitlab.com/gitlab-org/cli/internal/glinstance" + "gitlab.com/gitlab-org/cli/internal/glrepo" + "gitlab.com/gitlab-org/cli/internal/testing/cmdtest" + "gitlab.com/gitlab-org/cli/internal/testing/httpmock" +) + +func Test_NewCmdImport(t *testing.T) { + tests := []struct { + name string + cli string + wants options + wantsErr bool + }{ + { + name: "no args", + cli: "", + wantsErr: false, + }, + { + name: "with group", + cli: "--group mygroup", + wants: options{group: "mygroup"}, + wantsErr: false, + }, + { + name: "missing file path", + cli: "--file", + wantsErr: true, + }, + { + name: "with update flag", + cli: "--file vars.json --update", + wants: options{filePath: "vars.json", update: true}, + wantsErr: false, + }, + { + name: "with stdin flag", + cli: "--stdin", + wants: options{fromStdin: true}, + wantsErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + io, _, _, _ := cmdtest.TestIOStreams() + f := cmdtest.NewTestFactory(io) + + argv, err := shlex.Split(test.cli) + assert.NoError(t, err) + + var gotOpts *options + cmd := NewCmdImport(f, func(opts *options) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + + _, err = cmd.ExecuteC() + if test.wantsErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + if gotOpts != nil { + assert.Equal(t, test.wants.group, gotOpts.group) + assert.Equal(t, test.wants.filePath, gotOpts.filePath) + assert.Equal(t, test.wants.fromStdin, gotOpts.fromStdin) + assert.Equal(t, test.wants.update, gotOpts.update) + } + }) + } +} + +func Test_run_FileAndStdin(t *testing.T) { + io, _, _, _ := cmdtest.TestIOStreams() + f := cmdtest.NewTestFactory(io) + + tmpFile, err := os.CreateTemp("", "vars.json") + assert.NoError(t, err) + defer os.Remove(tmpFile.Name()) + + variables := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + data, _ := json.Marshal(variables) + _, _ = tmpFile.Write(data) + tmpFile.Close() + + tests := []struct { + name string + opts *options + expectError string + }{ + { + name: "no input", + opts: &options{ + io: io, + apiClient: f.ApiClient, + baseRepo: f.BaseRepo, + }, + expectError: "no input source provided", + }, + { + name: "invalid file path", + opts: &options{ + io: io, + apiClient: f.ApiClient, + baseRepo: f.BaseRepo, + filePath: "missing.json", + }, + expectError: "failed to read file", + }, + { + name: "invalid stdin read", + opts: &options{ + io: io, + apiClient: f.ApiClient, + baseRepo: f.BaseRepo, + fromStdin: true, + }, + expectError: "failed to read from stdin: no data", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.opts.run() + assert.ErrorContains(t, err, test.expectError) + }) + } +} + +func Test_importProjectVariables_CreateAndUpdate(t *testing.T) { + reg := &httpmock.Mocker{MatchURL: httpmock.FullURL} + defer reg.Verify(t) + + io, _, stdout, _ := cmdtest.TestIOStreams() + reg.RegisterResponder(http.MethodGet, "https://gitlab.com/api/v4/projects/owner%2Frepo/variables/VAR1", + httpmock.NewStringResponse(http.StatusNotFound, `{"code":"404","status":"Not Found"}`)) + + reg.RegisterResponder(http.MethodPost, "https://gitlab.com/api/v4/projects/owner%2Frepo/variables", + httpmock.NewStringResponse(http.StatusCreated, `{"key": "VAR1", "value": "value1"}`)) + + opts := &options{ + apiClient: func(repoHost string) (*api.Client, error) { + return cmdtest.NewTestApiClient(t, &http.Client{Transport: reg}, "", "gitlab.com"), nil + }, + baseRepo: func() (glrepo.Interface, error) { + return glrepo.FromFullName("owner/repo", glinstance.DefaultHostname) + }, + io: io, + } + + vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + client, _ := opts.apiClient("gitlab.com") + + err := opts.importProjectVariables(client.Lab(), "owner/repo", vars) + assert.NoError(t, err) + assert.Contains(t, stdout.String(), "Created variable: VAR1") +} + +func Test_importProjectVariables_UpdateExisting(t *testing.T) { + reg := &httpmock.Mocker{MatchURL: httpmock.FullURL} + defer reg.Verify(t) + + io, _, stdout, _ := cmdtest.TestIOStreams() + + reg.RegisterResponder(http.MethodGet, "https://gitlab.com/api/v4/projects/owner%2Frepo/variables/VAR1", + httpmock.NewStringResponse(http.StatusOK, `{"key":"VAR1","value":"value1"}`)) + + reg.RegisterResponder(http.MethodPut, "https://gitlab.com/api/v4/projects/owner%2Frepo/variables/VAR1", + httpmock.NewStringResponse(http.StatusOK, `{"key":"VAR1","value":"value1"}`)) + + opts := &options{ + update: true, + apiClient: func(repoHost string) (*api.Client, error) { + return cmdtest.NewTestApiClient(t, &http.Client{Transport: reg}, "", "gitlab.com"), nil + }, + baseRepo: func() (glrepo.Interface, error) { + return glrepo.FromFullName("owner/repo", glinstance.DefaultHostname) + }, + io: io, + } + + client, _ := opts.apiClient("gitlab.com") + vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + + err := opts.importProjectVariables(client.Lab(), "owner/repo", vars) + assert.NoError(t, err) + assert.Contains(t, stdout.String(), "Updated variable: VAR1") +} + +func Test_importGroupVariables_CreateAndUpdate(t *testing.T) { + reg := &httpmock.Mocker{MatchURL: httpmock.FullURL} + defer reg.Verify(t) + + io, _, stdout, _ := cmdtest.TestIOStreams() + + reg.RegisterResponder(http.MethodGet, "https://gitlab.com/api/v4/groups/group/variables/VAR1", + httpmock.NewStringResponse(http.StatusNotFound, `{"code":"404","status":"Not Found"}`)) + + reg.RegisterResponder(http.MethodPost, "https://gitlab.com/api/v4/groups/group/variables", + httpmock.NewStringResponse(http.StatusCreated, `{"key":"VAR1","value":"value1"}`)) + + opts := &options{ + group: "group", + apiClient: func(repoHost string) (*api.Client, error) { + return cmdtest.NewTestApiClient(t, &http.Client{Transport: reg}, "", "gitlab.com"), nil + }, + io: io, + } + + client, _ := opts.apiClient("gitlab.com") + vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + + err := opts.importGroupVariables(client.Lab(), vars) + assert.NoError(t, err) + assert.Contains(t, stdout.String(), "Created variable: VAR1") +} + +func Test_importGroupVariables_Existing_NoUpdate(t *testing.T) { + reg := &httpmock.Mocker{MatchURL: httpmock.FullURL} + defer reg.Verify(t) + + io, _, _, _ := cmdtest.TestIOStreams() + + reg.RegisterResponder(http.MethodGet, + "https://gitlab.com/api/v4/groups/group/variables/VAR1", + httpmock.NewStringResponse(http.StatusOK, `{"key": "VAR1", "value": "oldvalue"}`)) + + opts := &options{ + group: "group", + apiClient: func(repoHost string) (*api.Client, error) { + return cmdtest.NewTestApiClient(t, &http.Client{Transport: reg}, "", "gitlab.com"), nil + }, + io: io, + } + + client, _ := opts.apiClient("gitlab.com") + vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + + err := opts.importGroupVariables(client.Lab(), vars) + assert.ErrorContains(t, err, "variable \"VAR1\" already exists") +} -- GitLab From a7d377414401afe0fe781cc9be313961ba17acc9 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Mon, 20 Oct 2025 03:42:34 +0530 Subject: [PATCH 04/12] refactor: added suggestions from duo --- internal/commands/variable/import/import.go | 4 +++- internal/commands/variable/import/import_test.go | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go index e048f8807..f361686cc 100644 --- a/internal/commands/variable/import/import.go +++ b/internal/commands/variable/import/import.go @@ -60,6 +60,8 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com cmd.Flags().BoolVar(&opts.fromStdin, "stdin", false, "Read JSON from standard input.") cmd.Flags().BoolVar(&opts.update, "update", false, "Update existing variables instead of throwing an error.") + cmd.MarkFlagsMutuallyExclusive("file", "stdin") + return cmd } @@ -73,7 +75,7 @@ func (o *options) run() error { return fmt.Errorf("failed to read file: %w", err) } } else if o.fromStdin { - input, err = io.ReadAll(os.Stdin) + input, err = io.ReadAll(o.io.In) if err != nil { return fmt.Errorf("failed to read from stdin: %w", err) } diff --git a/internal/commands/variable/import/import_test.go b/internal/commands/variable/import/import_test.go index fb1b35547..264815648 100644 --- a/internal/commands/variable/import/import_test.go +++ b/internal/commands/variable/import/import_test.go @@ -56,6 +56,7 @@ func Test_NewCmdImport(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() io, _, _, _ := cmdtest.TestIOStreams() f := cmdtest.NewTestFactory(io) @@ -139,6 +140,7 @@ func Test_run_FileAndStdin(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() err := test.opts.run() assert.ErrorContains(t, err, test.expectError) }) -- GitLab From 023a95eb6fe15d5ecca1f460c6923e00d34b96a6 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Mon, 20 Oct 2025 04:03:06 +0530 Subject: [PATCH 05/12] fix(pipeline): add doc for import and resolve pipeline issues --- docs/source/variable/import.md | 46 +++++++++++++++++++ .../commands/variable/import/import_test.go | 8 ++-- internal/commands/variable/variable.go | 2 +- 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 docs/source/variable/import.md diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md new file mode 100644 index 000000000..91589a477 --- /dev/null +++ b/docs/source/variable/import.md @@ -0,0 +1,46 @@ +--- +title: glab variable import +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +Import variables from JSON or STDIN into a project or group. + +```plaintext +glab variable import [flags] +``` + +## Aliases + +```plaintext +im +``` + + +```console +$ glab variable import --file variables.json +$ glab variable import --file vars.json --update +$ cat variables.json | glab variable import --stdin +$ glab variable import --group mygroup --file group_vars.json +``` + +## Options + +```plaintext + -f, --file string Path to JSON file containing variables. + --stdin Read JSON from standard input. + -g, --group string Select a group or subgroup. Ignored if a repository argument is set. + --update Update existing variables instead of throwing an error. +``` + +## Options inherited from parent commands + +```plaintext + -h, --help Show help for this command. +``` diff --git a/internal/commands/variable/import/import_test.go b/internal/commands/variable/import/import_test.go index 264815648..7b9547289 100644 --- a/internal/commands/variable/import/import_test.go +++ b/internal/commands/variable/import/import_test.go @@ -18,6 +18,7 @@ import ( ) func Test_NewCmdImport(t *testing.T) { + t.Parallel() tests := []struct { name string cli string @@ -56,7 +57,6 @@ func Test_NewCmdImport(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - t.Parallel() io, _, _, _ := cmdtest.TestIOStreams() f := cmdtest.NewTestFactory(io) @@ -90,12 +90,13 @@ func Test_NewCmdImport(t *testing.T) { } func Test_run_FileAndStdin(t *testing.T) { + t.Parallel() io, _, _, _ := cmdtest.TestIOStreams() f := cmdtest.NewTestFactory(io) - tmpFile, err := os.CreateTemp("", "vars.json") + tmpFile, err := os.CreateTemp(t.TempDir(), "vars.json") assert.NoError(t, err) - defer os.Remove(tmpFile.Name()) + t.Cleanup(func() { os.Remove(tmpFile.Name()) }) variables := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} data, _ := json.Marshal(variables) @@ -140,7 +141,6 @@ func Test_run_FileAndStdin(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - t.Parallel() err := test.opts.run() assert.ErrorContains(t, err, test.expectError) }) diff --git a/internal/commands/variable/variable.go b/internal/commands/variable/variable.go index 8c422c6dd..4b29f7071 100644 --- a/internal/commands/variable/variable.go +++ b/internal/commands/variable/variable.go @@ -6,10 +6,10 @@ import ( deleteCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/delete" exportCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/export" getCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/get" + importCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/import" listCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/list" setCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/set" updateCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/update" - importCmd "gitlab.com/gitlab-org/cli/internal/commands/variable/import" ) func NewVariableCmd(f cmdutils.Factory) *cobra.Command { -- GitLab From 3acf76f32879a6897e1efbb4e283462c586f033d Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Mon, 20 Oct 2025 13:52:50 +0530 Subject: [PATCH 06/12] fix(pipeline): resolve code related with job failure --- docs/source/variable/_index.md | 1 + docs/source/variable/import.md | 13 ++++++++----- internal/commands/variable/import/import.go | 2 +- internal/commands/variable/import/import_test.go | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/source/variable/_index.md b/docs/source/variable/_index.md index 4b4910be4..efae5f64b 100644 --- a/docs/source/variable/_index.md +++ b/docs/source/variable/_index.md @@ -35,6 +35,7 @@ var - [`delete`](delete.md) - [`export`](export.md) - [`get`](get.md) +- [`import`](import.md) - [`list`](list.md) - [`set`](set.md) - [`update`](update.md) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index 91589a477..b5752316d 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -22,25 +22,28 @@ glab variable import [flags] im ``` +## Examples ```console $ glab variable import --file variables.json $ glab variable import --file vars.json --update $ cat variables.json | glab variable import --stdin $ glab variable import --group mygroup --file group_vars.json + ``` ## Options ```plaintext - -f, --file string Path to JSON file containing variables. - --stdin Read JSON from standard input. - -g, --group string Select a group or subgroup. Ignored if a repository argument is set. - --update Update existing variables instead of throwing an error. + -f, --file string Path to JSON file containing variables. + -g, --group string Select a group or subgroup. Ignored if a repository argument is set. + --stdin Read JSON from standard input. + --update Update existing variables instead of throwing an error. ``` ## Options inherited from parent commands ```plaintext - -h, --help Show help for this command. + -h, --help Show help for this command. + -R, --repo OWNER/REPO Select another repository. Can use either OWNER/REPO or `GROUP/NAMESPACE/REPO` format. Also accepts full URL or Git URL. ``` diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go index f361686cc..08ab90869 100644 --- a/internal/commands/variable/import/import.go +++ b/internal/commands/variable/import/import.go @@ -36,7 +36,7 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com cmd := &cobra.Command{ Use: "import", - Short: "Import variables from JSON into a project or group.", + Short: "Import variables from JSON or STDIN into a project or group.", Aliases: []string{"im"}, Example: heredoc.Doc(` $ glab variable import --file variables.json diff --git a/internal/commands/variable/import/import_test.go b/internal/commands/variable/import/import_test.go index 7b9547289..bfc02614c 100644 --- a/internal/commands/variable/import/import_test.go +++ b/internal/commands/variable/import/import_test.go @@ -57,6 +57,7 @@ func Test_NewCmdImport(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() io, _, _, _ := cmdtest.TestIOStreams() f := cmdtest.NewTestFactory(io) @@ -141,6 +142,7 @@ func Test_run_FileAndStdin(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + t.Parallel() err := test.opts.run() assert.ErrorContains(t, err, test.expectError) }) -- GitLab From 34f59cf300ea73f26438834759079c495d8618c8 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 02:50:49 +0530 Subject: [PATCH 07/12] refactor: replaced if/else with switch and add new function and examples --- internal/commands/variable/import/import.go | 153 +++++++++++------- .../commands/variable/import/import_test.go | 89 +--------- 2 files changed, 106 insertions(+), 136 deletions(-) diff --git a/internal/commands/variable/import/import.go b/internal/commands/variable/import/import.go index 08ab90869..89046d5df 100644 --- a/internal/commands/variable/import/import.go +++ b/internal/commands/variable/import/import.go @@ -4,11 +4,12 @@ import ( "encoding/json" "fmt" "io" + "net/http" "os" "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" - clientgo "gitlab.com/gitlab-org/api/client-go" + gitlab "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/cli/internal/api" "gitlab.com/gitlab-org/cli/internal/cmdutils" "gitlab.com/gitlab-org/cli/internal/glrepo" @@ -25,6 +26,8 @@ type options struct { filePath string fromStdin bool update bool // true => update existing vars, false => error if exists + + variables []gitlab.ProjectVariable } func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Command { @@ -39,17 +42,47 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com Short: "Import variables from JSON or STDIN into a project or group.", Aliases: []string{"im"}, Example: heredoc.Doc(` + # Example JSON file format (variables.json) + [ + { + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" + }, + { + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, + "masked_and_hidden": true, + "protected": false, + "environment_scope": "production", + "variable_type": "env_var", + "description": "API key for production services" + } + ] + + # Import variables from a JSON file into the current project $ glab variable import --file variables.json + + # Import and update existing variables if they already exist $ glab variable import --file vars.json --update + + # Import variables from standard input $ cat variables.json | glab variable import --stdin + + # Import variables into a specific group or subgroup $ glab variable import --group mygroup --file group_vars.json `), Annotations: map[string]string{ mcpannotations.Safe: "true", }, RunE: func(cmd *cobra.Command, args []string) error { - if runE != nil { - return runE(opts) + if err := opts.complete(); err != nil { + return err } return opts.run() }, @@ -65,16 +98,18 @@ func NewCmdImport(f cmdutils.Factory, runE func(opts *options) error) *cobra.Com return cmd } -func (o *options) run() error { +func (o *options) complete() error { var input []byte var err error - if o.filePath != "" { + switch { + case o.filePath != "": input, err = os.ReadFile(o.filePath) if err != nil { return fmt.Errorf("failed to read file: %w", err) } - } else if o.fromStdin { + + case o.fromStdin: input, err = io.ReadAll(o.io.In) if err != nil { return fmt.Errorf("failed to read from stdin: %w", err) @@ -82,15 +117,21 @@ func (o *options) run() error { if len(input) == 0 { return fmt.Errorf("failed to read from stdin: no data") } - } else { + + default: return fmt.Errorf("no input source provided: use --file or --stdin") } - var variables []clientgo.ProjectVariable - if err := json.Unmarshal(input, &variables); err != nil { + if err := json.Unmarshal(input, &o.variables); err != nil { return fmt.Errorf("failed to parse JSON: %w", err) } + return nil +} + +func (o *options) run() error { + var err error + var repoHost string if baseRepo, err := o.baseRepo(); err == nil { repoHost = baseRepo.RepoHost() @@ -101,47 +142,50 @@ func (o *options) run() error { } client := apiClient.Lab() - if o.group != "" { - return o.importGroupVariables(client, variables) - } + switch { + case o.group != "": + return o.importGroupVariables(client, o.variables) - repo, err := o.baseRepo() - if err != nil { - return err + default: + repo, err := o.baseRepo() + if err != nil { + return err + } + return o.importProjectVariables(client, repo.FullName(), o.variables) } - return o.importProjectVariables(client, repo.FullName(), variables) } -func (o *options) importProjectVariables(client *clientgo.Client, project string, vars []clientgo.ProjectVariable) error { +func (o *options) importProjectVariables(client *gitlab.Client, project string, vars []gitlab.ProjectVariable) error { for _, v := range vars { _, resp, err := client.ProjectVariables.GetVariable(project, v.Key, nil) - if err == nil && resp.StatusCode == 200 { + if err == nil && resp.StatusCode == http.StatusOK { if !o.update { return fmt.Errorf("variable %q already exists. use --update if you wish to override it", v.Key) } - _, _, err := client.ProjectVariables.UpdateVariable(project, v.Key, &clientgo.UpdateProjectVariableOptions{ - Value: &v.Value, - Protected: &v.Protected, - Masked: &v.Masked, - EnvironmentScope: &v.EnvironmentScope, - VariableType: &v.VariableType, - Description: &v.Description, - Raw: &v.Raw, + _, _, err := client.ProjectVariables.UpdateVariable(project, v.Key, &gitlab.UpdateProjectVariableOptions{ + Value: gitlab.Ptr(v.Value), + Description: gitlab.Ptr(v.Description), + EnvironmentScope: gitlab.Ptr(v.EnvironmentScope), + Masked: gitlab.Ptr(v.Masked), + Protected: gitlab.Ptr(v.Protected), + Raw: gitlab.Ptr(v.Raw), + VariableType: gitlab.Ptr(v.VariableType), }) if err != nil { return fmt.Errorf("failed to update variable %s: %w", v.Key, err) } fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) } else { - _, _, err := client.ProjectVariables.CreateVariable(project, &clientgo.CreateProjectVariableOptions{ - Key: &v.Key, - Value: &v.Value, - Protected: &v.Protected, - Masked: &v.Masked, - EnvironmentScope: &v.EnvironmentScope, - VariableType: &v.VariableType, - Description: &v.Description, - Raw: &v.Raw, + _, _, err := client.ProjectVariables.CreateVariable(project, &gitlab.CreateProjectVariableOptions{ + Key: gitlab.Ptr(v.Key), + Value: gitlab.Ptr(v.Value), + Description: gitlab.Ptr(v.Description), + EnvironmentScope: gitlab.Ptr(v.EnvironmentScope), + Masked: gitlab.Ptr(v.Masked), + MaskedAndHidden: gitlab.Ptr(v.Hidden), + Protected: gitlab.Ptr(v.Protected), + Raw: gitlab.Ptr(v.Raw), + VariableType: gitlab.Ptr(v.VariableType), }) if err != nil { return fmt.Errorf("failed to create variable %s: %w", v.Key, err) @@ -152,36 +196,37 @@ func (o *options) importProjectVariables(client *clientgo.Client, project string return nil } -func (o *options) importGroupVariables(client *clientgo.Client, vars []clientgo.ProjectVariable) error { +func (o *options) importGroupVariables(client *gitlab.Client, vars []gitlab.ProjectVariable) error { for _, v := range vars { _, resp, err := client.GroupVariables.GetVariable(o.group, v.Key, nil) - if err == nil && resp.StatusCode == 200 { + if err == nil && resp.StatusCode == http.StatusOK { if !o.update { return fmt.Errorf("variable %q already exists", v.Key) } - _, _, err := client.GroupVariables.UpdateVariable(o.group, v.Key, &clientgo.UpdateGroupVariableOptions{ - Value: &v.Value, - Protected: &v.Protected, - Masked: &v.Masked, - EnvironmentScope: &v.EnvironmentScope, - VariableType: &v.VariableType, - Description: &v.Description, - Raw: &v.Raw, + _, _, err := client.GroupVariables.UpdateVariable(o.group, v.Key, &gitlab.UpdateGroupVariableOptions{ + Value: gitlab.Ptr(v.Value), + Description: gitlab.Ptr(v.Description), + EnvironmentScope: gitlab.Ptr(v.EnvironmentScope), + Masked: gitlab.Ptr(v.Masked), + Protected: gitlab.Ptr(v.Protected), + Raw: gitlab.Ptr(v.Raw), + VariableType: gitlab.Ptr(v.VariableType), }) if err != nil { return fmt.Errorf("failed to update variable %s: %w", v.Key, err) } fmt.Fprintf(o.io.StdOut, "Updated variable: %s\n", v.Key) } else { - _, _, err := client.GroupVariables.CreateVariable(o.group, &clientgo.CreateGroupVariableOptions{ - Key: &v.Key, - Value: &v.Value, - Protected: &v.Protected, - Masked: &v.Masked, - EnvironmentScope: &v.EnvironmentScope, - VariableType: &v.VariableType, - Description: &v.Description, - Raw: &v.Raw, + _, _, err := client.GroupVariables.CreateVariable(o.group, &gitlab.CreateGroupVariableOptions{ + Key: gitlab.Ptr(v.Key), + Value: gitlab.Ptr(v.Value), + Description: gitlab.Ptr(v.Description), + EnvironmentScope: gitlab.Ptr(v.EnvironmentScope), + Masked: gitlab.Ptr(v.Masked), + MaskedAndHidden: gitlab.Ptr(v.Hidden), + Protected: gitlab.Ptr(v.Protected), + VariableType: gitlab.Ptr(v.VariableType), + Raw: gitlab.Ptr(v.Raw), }) if err != nil { return fmt.Errorf("failed to create variable %s: %w", v.Key, err) diff --git a/internal/commands/variable/import/import_test.go b/internal/commands/variable/import/import_test.go index bfc02614c..08c42633b 100644 --- a/internal/commands/variable/import/import_test.go +++ b/internal/commands/variable/import/import_test.go @@ -1,15 +1,13 @@ package importCmd import ( - "bytes" "encoding/json" "net/http" "os" "testing" - "github.com/google/shlex" "github.com/stretchr/testify/assert" - gitlab "gitlab.com/gitlab-org/api/client-go" + gitlabtesting "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/cli/internal/api" "gitlab.com/gitlab-org/cli/internal/glinstance" "gitlab.com/gitlab-org/cli/internal/glrepo" @@ -17,79 +15,6 @@ import ( "gitlab.com/gitlab-org/cli/internal/testing/httpmock" ) -func Test_NewCmdImport(t *testing.T) { - t.Parallel() - tests := []struct { - name string - cli string - wants options - wantsErr bool - }{ - { - name: "no args", - cli: "", - wantsErr: false, - }, - { - name: "with group", - cli: "--group mygroup", - wants: options{group: "mygroup"}, - wantsErr: false, - }, - { - name: "missing file path", - cli: "--file", - wantsErr: true, - }, - { - name: "with update flag", - cli: "--file vars.json --update", - wants: options{filePath: "vars.json", update: true}, - wantsErr: false, - }, - { - name: "with stdin flag", - cli: "--stdin", - wants: options{fromStdin: true}, - wantsErr: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - io, _, _, _ := cmdtest.TestIOStreams() - f := cmdtest.NewTestFactory(io) - - argv, err := shlex.Split(test.cli) - assert.NoError(t, err) - - var gotOpts *options - cmd := NewCmdImport(f, func(opts *options) error { - gotOpts = opts - return nil - }) - cmd.SetArgs(argv) - cmd.SetIn(&bytes.Buffer{}) - cmd.SetOut(&bytes.Buffer{}) - cmd.SetErr(&bytes.Buffer{}) - - _, err = cmd.ExecuteC() - if test.wantsErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - if gotOpts != nil { - assert.Equal(t, test.wants.group, gotOpts.group) - assert.Equal(t, test.wants.filePath, gotOpts.filePath) - assert.Equal(t, test.wants.fromStdin, gotOpts.fromStdin) - assert.Equal(t, test.wants.update, gotOpts.update) - } - }) - } -} - func Test_run_FileAndStdin(t *testing.T) { t.Parallel() io, _, _, _ := cmdtest.TestIOStreams() @@ -99,7 +24,7 @@ func Test_run_FileAndStdin(t *testing.T) { assert.NoError(t, err) t.Cleanup(func() { os.Remove(tmpFile.Name()) }) - variables := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + variables := []gitlabtesting.ProjectVariable{{Key: "VAR1", Value: "value1"}} data, _ := json.Marshal(variables) _, _ = tmpFile.Write(data) tmpFile.Close() @@ -143,7 +68,7 @@ func Test_run_FileAndStdin(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - err := test.opts.run() + err := test.opts.complete() assert.ErrorContains(t, err, test.expectError) }) } @@ -170,7 +95,7 @@ func Test_importProjectVariables_CreateAndUpdate(t *testing.T) { io: io, } - vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + vars := []gitlabtesting.ProjectVariable{{Key: "VAR1", Value: "value1"}} client, _ := opts.apiClient("gitlab.com") err := opts.importProjectVariables(client.Lab(), "owner/repo", vars) @@ -202,7 +127,7 @@ func Test_importProjectVariables_UpdateExisting(t *testing.T) { } client, _ := opts.apiClient("gitlab.com") - vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + vars := []gitlabtesting.ProjectVariable{{Key: "VAR1", Value: "value1"}} err := opts.importProjectVariables(client.Lab(), "owner/repo", vars) assert.NoError(t, err) @@ -230,7 +155,7 @@ func Test_importGroupVariables_CreateAndUpdate(t *testing.T) { } client, _ := opts.apiClient("gitlab.com") - vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + vars := []gitlabtesting.ProjectVariable{{Key: "VAR1", Value: "value1"}} err := opts.importGroupVariables(client.Lab(), vars) assert.NoError(t, err) @@ -256,7 +181,7 @@ func Test_importGroupVariables_Existing_NoUpdate(t *testing.T) { } client, _ := opts.apiClient("gitlab.com") - vars := []gitlab.ProjectVariable{{Key: "VAR1", Value: "value1"}} + vars := []gitlabtesting.ProjectVariable{{Key: "VAR1", Value: "value1"}} err := opts.importGroupVariables(client.Lab(), vars) assert.ErrorContains(t, err, "variable \"VAR1\" already exists") -- GitLab From 372ff83a13efacecc3bfb75b3a74a31174317a44 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 02:57:55 +0530 Subject: [PATCH 08/12] refactor: update doc --- docs/source/variable/import.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index b5752316d..e34d61d68 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -25,9 +25,39 @@ im ## Examples ```console +# Example JSON file format (variables.json) +[ + { + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" + }, + { + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, + "masked_and_hidden": true, + "protected": false, + "environment_scope": "production", + "variable_type": "env_var", + "description": "API key for production services" + } +] + +# Import variables from a JSON file into the current project $ glab variable import --file variables.json + +# Import and update existing variables if they already exist $ glab variable import --file vars.json --update + +# Import variables from standard input $ cat variables.json | glab variable import --stdin + +# Import variables into a specific group or subgroup $ glab variable import --group mygroup --file group_vars.json ``` -- GitLab From 1dd6438d15284f3fded4dde27bd93a00e2abb39a Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 03:24:40 +0530 Subject: [PATCH 09/12] chore: doc update --- docs/source/variable/import.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index e34d61d68..1f3cca5cb 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -28,22 +28,22 @@ im # Example JSON file format (variables.json) [ { - "key": "DATABASE_URL", - "value": "postgres://user:password@host/db", - "protected": true, - "masked": false, - "environment_scope": "*", - "variable_type": "env_var", - "description": "Database connection string" + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" }, { - "key": "API_KEY", - "value": "secret_key_here", - "masked": true, + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, "masked_and_hidden": true, "protected": false, "environment_scope": "production", - "variable_type": "env_var", + "variable_type": "env_var", "description": "API key for production services" } ] -- GitLab From aa9da2c7ecbf52d62f3e1402437ddb085b998cb6 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 03:31:51 +0530 Subject: [PATCH 10/12] doc: update --- docs/source/variable/import.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index 1f3cca5cb..12e4754c8 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -28,23 +28,23 @@ im # Example JSON file format (variables.json) [ { - "key": "DATABASE_URL", - "value": "postgres://user:password@host/db", - "protected": true, - "masked": false, - "environment_scope": "*", - "variable_type": "env_var", - "description": "Database connection string" + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" }, { - "key": "API_KEY", - "value": "secret_key_here", - "masked": true, - "masked_and_hidden": true, - "protected": false, - "environment_scope": "production", - "variable_type": "env_var", - "description": "API key for production services" + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, + "masked_and_hidden": true, + "protected": false, + "environment_scope": "production", + "variable_type": "env_var", + "description": "API key for production services" } ] -- GitLab From 31be552d72e248685f298f2902d8f0c450d76c68 Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 03:41:42 +0530 Subject: [PATCH 11/12] doc: update --- docs/source/variable/import.md | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index 12e4754c8..0016b7eaa 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -27,25 +27,25 @@ im ```console # Example JSON file format (variables.json) [ - { - "key": "DATABASE_URL", - "value": "postgres://user:password@host/db", - "protected": true, - "masked": false, - "environment_scope": "*", - "variable_type": "env_var", - "description": "Database connection string" - }, - { - "key": "API_KEY", - "value": "secret_key_here", - "masked": true, - "masked_and_hidden": true, - "protected": false, - "environment_scope": "production", - "variable_type": "env_var", - "description": "API key for production services" - } + { + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" + }, + { + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, + "masked_and_hidden": true, + "protected": false, + "environment_scope": "production", + "variable_type": "env_var", + "description": "API key for production services" + } ] # Import variables from a JSON file into the current project -- GitLab From b2b593983886842f20ceba18f84fec8a4b9f470d Mon Sep 17 00:00:00 2001 From: ajeymuthiah Date: Sun, 26 Oct 2025 03:53:02 +0530 Subject: [PATCH 12/12] doc: generated doc --- docs/source/variable/import.md | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/source/variable/import.md b/docs/source/variable/import.md index 0016b7eaa..12e4754c8 100644 --- a/docs/source/variable/import.md +++ b/docs/source/variable/import.md @@ -27,25 +27,25 @@ im ```console # Example JSON file format (variables.json) [ - { - "key": "DATABASE_URL", - "value": "postgres://user:password@host/db", - "protected": true, - "masked": false, - "environment_scope": "*", - "variable_type": "env_var", - "description": "Database connection string" - }, - { - "key": "API_KEY", - "value": "secret_key_here", - "masked": true, - "masked_and_hidden": true, - "protected": false, - "environment_scope": "production", - "variable_type": "env_var", - "description": "API key for production services" - } + { + "key": "DATABASE_URL", + "value": "postgres://user:password@host/db", + "protected": true, + "masked": false, + "environment_scope": "*", + "variable_type": "env_var", + "description": "Database connection string" + }, + { + "key": "API_KEY", + "value": "secret_key_here", + "masked": true, + "masked_and_hidden": true, + "protected": false, + "environment_scope": "production", + "variable_type": "env_var", + "description": "API key for production services" + } ] # Import variables from a JSON file into the current project -- GitLab