summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml8
-rw-r--r--.gitlab/ci/notifications.gitlab-ci.yml18
-rw-r--r--app/assets/javascripts/frequent_items/store/mutations.js2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js16
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js2
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--changelogs/unreleased/27630-specify-kubernetes-namespace-in-ci-template.yml5
-rw-r--r--changelogs/unreleased/fix-groups-search-dropdown.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-11-0.yml5
-rw-r--r--doc/ci/yaml/README.md1
-rw-r--r--doc/development/pipelines.md6
-rw-r--r--lib/gitlab/ci/config/entry/default.rb8
-rw-r--r--lib/gitlab/ci/config/entry/environment.rb15
-rw-r--r--lib/gitlab/ci/config/entry/job.rb8
-rw-r--r--lib/gitlab/ci/config/entry/kubernetes.rb25
-rwxr-xr-xscripts/notify-slack14
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js24
-rw-r--r--spec/javascripts/frequent_items/mock_data.js2
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb24
-rw-r--r--spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb22
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb26
24 files changed, 280 insertions, 18 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 36108d04e9c..6b76853f56f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@ stages:
- review
- qa
- post-test
+ - notification
- pages
variables:
@@ -33,6 +34,7 @@ include:
- local: .gitlab/ci/frontend.gitlab-ci.yml
- local: .gitlab/ci/global.gitlab-ci.yml
- local: .gitlab/ci/memory.gitlab-ci.yml
+ - local: .gitlab/ci/notifications.gitlab-ci.yml
- local: .gitlab/ci/pages.gitlab-ci.yml
- local: .gitlab/ci/qa.gitlab-ci.yml
- local: .gitlab/ci/reports.gitlab-ci.yml
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index d746d8fe030..c1cab844ca6 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -93,7 +93,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- - "doc/api/graphql/**/*"
+ - "doc/api/graphql/reference/*" # Files in this folder are auto-generated
.backstage-patterns: &backstage-patterns
- "Dangerfile"
@@ -139,7 +139,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- - "doc/api/graphql/**/*"
+ - "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# Backstage changes
- "Dangerfile"
- "danger/**/*"
@@ -163,7 +163,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- - "doc/api/graphql/**/*"
+ - "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# QA changes
- ".dockerignore"
- "qa/**/*"
@@ -183,7 +183,7 @@
- "config.ru"
- "{package.json,yarn.lock}"
- "{,ee/}{app,bin,config,db,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- - "doc/api/graphql/**/*"
+ - "doc/api/graphql/reference/*" # Files in this folder are auto-generated
# Backstage changes
- "Dangerfile"
- "danger/**/*"
diff --git a/.gitlab/ci/notifications.gitlab-ci.yml b/.gitlab/ci/notifications.gitlab-ci.yml
new file mode 100644
index 00000000000..8e2574fad4c
--- /dev/null
+++ b/.gitlab/ci/notifications.gitlab-ci.yml
@@ -0,0 +1,18 @@
+.notify:
+ image: alpine
+ stage: notification
+ dependencies: []
+ cache: {}
+ before_script:
+ - apk update && apk add git curl bash
+ variables:
+ COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
+
+schedule:package-and-qa:notify-failure:
+ extends:
+ - .only:variables_refs-canonical-dot-com-schedules
+ - .notify
+ script:
+ - 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_failing'
+ needs: ["schedule:package-and-qa"]
+ when: on_failure
diff --git a/app/assets/javascripts/frequent_items/store/mutations.js b/app/assets/javascripts/frequent_items/store/mutations.js
index 92ac3a2c94d..78ccef7f253 100644
--- a/app/assets/javascripts/frequent_items/store/mutations.js
+++ b/app/assets/javascripts/frequent_items/store/mutations.js
@@ -48,7 +48,7 @@ export default {
});
},
[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, results) {
- const rawItems = results.data;
+ const rawItems = results.data ? results.data : results; // Api.groups returns array, Api.projects returns object
Object.assign(state, {
items: rawItems.map(rawItem => ({
id: rawItem.id,
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 28143859e4c..1ab11892f61 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -602,3 +602,19 @@ export const getDatesInRange = (d1, d2, formatter = x => x) => {
* @return {Number} number of milliseconds
*/
export const secondsToMilliseconds = seconds => seconds * 1000;
+
+/**
+ * Converts the supplied number of seconds to days.
+ *
+ * @param {Number} seconds
+ * @return {Number} number of days
+ */
+export const secondsToDays = seconds => Math.round(seconds / 86400);
+
+/**
+ * Returns the date after the date provided
+ *
+ * @param {Date} date the initial date
+ * @return {Date} the date following the date provided
+ */
+export const dayAfter = date => new Date(newDate(date).setDate(date.getDate() + 1));
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 87e94311176..e3300967022 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -1,8 +1,6 @@
import invalidUrl from '~/lib/utils/invalid_url';
export default () => ({
- hasMetrics: false,
- showPanels: true,
metricsEndpoint: null,
environmentsEndpoint: null,
deploymentsEndpoint: null,
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 37ba8a7c97e..fd05fd6bab9 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.10.1'
+ VERSION = '0.11.0'
self.table_name = 'clusters_applications_runners'
diff --git a/changelogs/unreleased/27630-specify-kubernetes-namespace-in-ci-template.yml b/changelogs/unreleased/27630-specify-kubernetes-namespace-in-ci-template.yml
new file mode 100644
index 00000000000..5ec5cb2015d
--- /dev/null
+++ b/changelogs/unreleased/27630-specify-kubernetes-namespace-in-ci-template.yml
@@ -0,0 +1,5 @@
+---
+title: Allow specifying Kubernetes namespace for an environment in gitlab-ci.yml
+merge_request: 20270
+author:
+type: added
diff --git a/changelogs/unreleased/fix-groups-search-dropdown.yml b/changelogs/unreleased/fix-groups-search-dropdown.yml
new file mode 100644
index 00000000000..7dd1a5a1d4f
--- /dev/null
+++ b/changelogs/unreleased/fix-groups-search-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Fix group search in groups dropdown
+merge_request: 20535
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-11-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-11-0.yml
new file mode 100644
index 00000000000..a6a22b8f55f
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-11-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.11.0
+merge_request: 20461
+author:
+type: other
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index ca04bbd0444..05b660ccdf6 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -135,6 +135,7 @@ The following job parameters can be defined inside a `default:` block:
- [`before_script`](#before_script-and-after_script)
- [`after_script`](#before_script-and-after_script)
- [`cache`](#cache)
+- [`retry`](#retry)
- [`timeout`](#timeout)
- [`interruptible`](#interruptible)
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 7ec76f61e42..39384f9bcbe 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -29,6 +29,7 @@ The current stages are:
- `review`: This stage includes jobs that deploy the GitLab and Docs Review Apps.
- `qa`: This stage includes jobs that perform QA tasks against the Review App
that is deployed in the previous stage.
+- `notification`: This stage includes jobs that sends notifications about pipeline status.
- `post-test`: This stage includes jobs that build reports or gather data from
the previous stages' jobs (e.g. coverage, Knapsack metadata etc.).
- `pages`: This stage includes a job that deploys the various reports as
@@ -209,6 +210,11 @@ subgraph "`qa` stage"
dast -.-> |needs and depends on| G;
end
+subgraph "`notification` stage"
+ NOTIFICATION1["schedule:package-and-qa:notify-success<br>(on_success)"] -.-> |needs| P;
+ NOTIFICATION2["schedule:package-and-qa:notify-failure<br>(on_failure)"] -.-> |needs| P;
+ end
+
subgraph "`post-test` stage"
M
end
diff --git a/lib/gitlab/ci/config/entry/default.rb b/lib/gitlab/ci/config/entry/default.rb
index 646f06a60a9..b84ae53a514 100644
--- a/lib/gitlab/ci/config/entry/default.rb
+++ b/lib/gitlab/ci/config/entry/default.rb
@@ -15,7 +15,7 @@ module Gitlab
ALLOWED_KEYS = %i[before_script image services
after_script cache interruptible
- timeout].freeze
+ timeout retry].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -49,7 +49,11 @@ module Gitlab
description: 'Set jobs default timeout.',
inherit: false
- helpers :before_script, :image, :services, :after_script, :cache, :interruptible, :timeout
+ entry :retry, Entry::Retry,
+ description: 'Set retry default value.',
+ inherit: false
+
+ helpers :before_script, :image, :services, :after_script, :cache, :interruptible, :timeout, :retry
private
diff --git a/lib/gitlab/ci/config/entry/environment.rb b/lib/gitlab/ci/config/entry/environment.rb
index 5a13fd18504..68254552f82 100644
--- a/lib/gitlab/ci/config/entry/environment.rb
+++ b/lib/gitlab/ci/config/entry/environment.rb
@@ -8,9 +8,11 @@ module Gitlab
# Entry that represents an environment.
#
class Environment < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Validatable
+ include ::Gitlab::Config::Entry::Configurable
- ALLOWED_KEYS = %i[name url action on_stop].freeze
+ ALLOWED_KEYS = %i[name url action on_stop kubernetes].freeze
+
+ entry :kubernetes, Entry::Kubernetes, description: 'Kubernetes deployment configuration.'
validations do
validate do
@@ -46,6 +48,7 @@ module Gitlab
allow_nil: true
validates :on_stop, type: String, allow_nil: true
+ validates :kubernetes, type: Hash, allow_nil: true
end
end
@@ -73,6 +76,10 @@ module Gitlab
value[:on_stop]
end
+ def kubernetes
+ value[:kubernetes]
+ end
+
def value
case @config
when String then { name: @config, action: 'start' }
@@ -80,6 +87,10 @@ module Gitlab
else {}
end
end
+
+ def skip_config_hash_validation?
+ true
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index a109265f2a7..7c01e6ffbe8 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -105,6 +105,10 @@ module Gitlab
description: 'Timeout duration of this job.',
inherit: true
+ entry :retry, Entry::Retry,
+ description: 'Retry configuration for this job.',
+ inherit: true
+
entry :only, Entry::Policy,
description: 'Refs policy this job will be executed for.',
default: Entry::Policy::DEFAULT_ONLY,
@@ -142,10 +146,6 @@ module Gitlab
description: 'Coverage configuration for this job.',
inherit: false
- entry :retry, Entry::Retry,
- description: 'Retry configuration for this job.',
- inherit: false
-
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
:artifacts, :environment, :coverage, :retry, :rules,
diff --git a/lib/gitlab/ci/config/entry/kubernetes.rb b/lib/gitlab/ci/config/entry/kubernetes.rb
new file mode 100644
index 00000000000..2f1595d4437
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/kubernetes.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ class Kubernetes < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+ include ::Gitlab::Config::Entry::Attributable
+
+ ALLOWED_KEYS = %i[namespace].freeze
+
+ attributes ALLOWED_KEYS
+
+ validations do
+ validates :config, type: Hash
+ validates :config, allowed_keys: ALLOWED_KEYS
+
+ validates :namespace, type: String, presence: true
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/scripts/notify-slack b/scripts/notify-slack
new file mode 100755
index 00000000000..5907fd8b986
--- /dev/null
+++ b/scripts/notify-slack
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Sends Slack notification MSG to CI_SLACK_WEBHOOK_URL (which needs to be set).
+# ICON_EMOJI needs to be set to an icon emoji name (without the `:` around it).
+
+CHANNEL=$1
+MSG=$2
+ICON_EMOJI=$3
+
+if [ -z "$CHANNEL" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ] || [ -z "$MSG" ] || [ -z "$ICON_EMOJI" ]; then
+ echo "Missing argument(s) - Use: $0 channel message icon_emoji"
+ echo "and set CI_SLACK_WEBHOOK_URL environment variable."
+else
+ curl -X POST --data-urlencode 'payload={"channel": "#'"$CHANNEL"'", "username": "GitLab QA Bot", "text": "'"$MSG"'", "icon_emoji": "'":$ICON_EMOJI:"'"}' "$CI_SLACK_WEBHOOK_URL"
+fi
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index ee27789b6b9..fd75c9aa0cd 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -482,3 +482,27 @@ describe('secondsToMilliseconds', () => {
expect(datetimeUtility.secondsToMilliseconds(123)).toBe(123000);
});
});
+
+describe('dayAfter', () => {
+ const date = new Date('2019-07-16T00:00:00.000Z');
+
+ it('returns the following date', () => {
+ const nextDay = datetimeUtility.dayAfter(date);
+ const expectedNextDate = new Date('2019-07-17T00:00:00.000Z');
+
+ expect(nextDay).toStrictEqual(expectedNextDate);
+ });
+
+ it('does not modifiy the original date', () => {
+ datetimeUtility.dayAfter(date);
+ expect(date).toStrictEqual(new Date('2019-07-16T00:00:00.000Z'));
+ });
+});
+
+describe('secondsToDays', () => {
+ it('converts seconds to days correctly', () => {
+ expect(datetimeUtility.secondsToDays(0)).toBe(0);
+ expect(datetimeUtility.secondsToDays(90000)).toBe(1);
+ expect(datetimeUtility.secondsToDays(270000)).toBe(3);
+ });
+});
diff --git a/spec/javascripts/frequent_items/mock_data.js b/spec/javascripts/frequent_items/mock_data.js
index 7f7d7b1cdbf..419f70e41af 100644
--- a/spec/javascripts/frequent_items/mock_data.js
+++ b/spec/javascripts/frequent_items/mock_data.js
@@ -68,7 +68,7 @@ export const mockFrequentGroups = [
},
];
-export const mockSearchedGroups = { data: [mockRawGroup] };
+export const mockSearchedGroups = [mockRawGroup];
export const mockProcessedSearchedGroups = [mockGroup];
export const mockProject = {
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index 0366a63ef05..ffd24aa56b9 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::Ci::Config::Entry::Default do
expect(described_class.nodes.keys)
.to match_array(%i[before_script image services
after_script cache interruptible
- timeout])
+ timeout retry])
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 7b72b45fd8d..c80b54bd6be 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -241,4 +241,28 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
end
+
+ describe 'kubernetes' do
+ let(:config) do
+ { name: 'production', kubernetes: kubernetes_config }
+ end
+
+ context 'is a string' do
+ let(:kubernetes_config) { 'production' }
+
+ it { expect(entry).not_to be_valid }
+ end
+
+ context 'is a hash' do
+ let(:kubernetes_config) { Hash(namespace: 'production') }
+
+ it { expect(entry).to be_valid }
+ end
+
+ context 'is nil' do
+ let(:kubernetes_config) { nil }
+
+ it { expect(entry).to be_valid }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
new file mode 100644
index 00000000000..468e83ec506
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/kubernetes_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Kubernetes do
+ subject { described_class.new(config) }
+
+ describe 'attributes' do
+ it { is_expected.to respond_to(:namespace) }
+ it { is_expected.to respond_to(:has_namespace?) }
+ end
+
+ describe 'validations' do
+ describe 'config' do
+ context 'is a hash containing known keys' do
+ let(:config) { Hash(namespace: 'namespace') }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'is a hash containing an unknown key' do
+ let(:config) { Hash(unknown: 'attribute') }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'is a string' do
+ let(:config) { 'config' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe 'namespace' do
+ let(:config) { Hash(namespace: namespace) }
+
+ context 'is a string' do
+ let(:namespace) { 'namespace' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'is a hash' do
+ let(:namespace) { Hash(key: 'namespace') }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'is not present' do
+ let(:namespace) { '' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 66f6402b9a2..5dc51f83b3c 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -149,6 +149,28 @@ module Gitlab
expect(subject[:options]).not_to have_key(:retry)
end
end
+
+ context 'when retry count is specified by default' do
+ let(:config) do
+ YAML.dump(default: { retry: { max: 1 } },
+ rspec: { script: 'rspec' })
+ end
+
+ it 'does use the default value' do
+ expect(subject[:options]).to include(retry: { max: 1 })
+ end
+ end
+
+ context 'when retry count default value is overridden' do
+ let(:config) do
+ YAML.dump(default: { retry: { max: 1 } },
+ rspec: { script: 'rspec', retry: { max: 2 } })
+ end
+
+ it 'does use the job value' do
+ expect(subject[:options]).to include(retry: { max: 2 })
+ end
+ end
end
describe 'allow failure entry' do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index de0f4841215..8f16d375f03 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -801,6 +801,32 @@ describe Ci::CreatePipelineService do
end
end
+ context 'environment with Kubernetes configuration' do
+ let(:kubernetes_namespace) { 'custom-namespace' }
+
+ before do
+ config = YAML.dump(
+ deploy: {
+ environment: {
+ name: "environment-name",
+ kubernetes: { namespace: kubernetes_namespace }
+ },
+ script: 'ls'
+ }
+ )
+
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'stores the requested namespace' do
+ result = execute_service
+ build = result.builds.first
+
+ expect(result).to be_persisted
+ expect(build.options.dig(:environment, :kubernetes, :namespace)).to eq(kubernetes_namespace)
+ end
+ end
+
context 'when environment with invalid name' do
before do
config = YAML.dump(deploy: { environment: { name: 'name,with,commas' }, script: 'ls' })