[{"data":1,"prerenderedAt":721},["ShallowReactive",2],{"/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint/":3,"navigation-en-us":37,"banner-en-us":466,"footer-en-us":483,"Michael Friedrich":693,"next-steps-en-us":706},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":27,"_id":30,"_type":31,"title":32,"_source":33,"_file":34,"_stem":35,"_extension":36},"/en-us/blog/devops-workflows-json-format-jq-ci-cd-lint","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"JSON formatting and CI/CD linting tips for DevOps workflows","Learn how to filter in JSON data structures and interact with the REST API. Use the GitLab API to lint your CI/CD configuration and dive into Git hooks speeding up your workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681979/Blog/Hero%20Images/gert-boers-unsplash.jpg","https://about.gitlab.com/blog/devops-workflows-json-format-jq-ci-cd-lint","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2021-04-21\",\n      }",{"title":17,"description":10,"authors":18,"heroImage":11,"date":20,"body":21,"category":22,"tags":23},"Tips for productive DevOps workflows: JSON formatting with jq and CI/CD linting automation",[19],"Michael Friedrich","2021-04-21","## What is JSON linting?\n\n\nTo understand JSON linting, let’s quickly break down the two concepts of\nJSON and linting. \n\n\n***JSON*** is an acronym for JavaScript Object Notation, which is a\nlightweight, text-based, open standard format designed specifically for\nrepresenting structured data based on the JavaScript object syntax. It is\nmost commonly used for transmitting data in web applications. It parses data\nfaster than XML and is easy for humans to read and write.\n\n\n***Linting*** is a process that automatically checks and analyzes static\nsource code for programming and stylistic errors, bugs and suspicious\nconstructs. \n\n\nJSON has become popular because it is human-readable and doesn’t require a\ncomplete markup structure like XML. It is easy to analyze into logical\nsyntactic components, especially in JavaScript. It also has many JSON\nlibraries for most programming languages.\n\n\n### Benefits of JSON linting\n\n\nFinding an error in JSON code can be challenging and time-consuming. The\nbest way to find and correct errors while simultaneously saving time is to\nuse a linting tool. When Json code is copied and pasted into the linting\neditor, it validates and reformats Json. It is easy to use and supports a\nwide range of browsers, so applications development with Json coding don’t\nrequire a lot of effort to make them browser-compatible.\n\n\nJSON linting is an efficient way to reduce errors and it improves the\noverall quality of the JSON code. This can help accelerate development and\nreduce costs because errors are discovered earlier.\n\n\n### Some common JSON linting errors\n\n\nIn instances where a JSON transaction fails, the error information is\nconveyed to the user by the API gateway. By default, the API gateway returns\na very basic fault to the client when a message filter has failed.\n\n\nOne common JSON linting error is parsing. A “parse: unexpected character\"\nerror occurs when passing a value that is not a valid JSON string to the\nJSON. parse method, for example, a native JavaScript object. To solve the\nerror, make sure to only pass valid JSON strings to the JSON.\n\n\nAnother common error is NULL or inaccurate data errors, not using the right\ndata type per column or extension for JSON files, and not ensuring every row\nin the JSON table is in the JSON format.\n\n\n### How to fix JSON linting errors\n\n\nIf you encounter a NULL or inaccurate data error in parsing, the first step\nis to make sure you use the right data type per column. For example, in the\ncase of “age,” use 12 instead of twelve.\n\n\nAlso make sure you are using the right extension for JSON files. When using\na compressed JSON file, it must end with “json” followed by the extension of\nthe format, such as “.gz.”\n\n\nNext, make sure the JSON format is used for every row in the JSON table.\nCreate a table with a delimiter that is not in the input files. Then, run a\nquery equivalent to the return name of the file, row points and the file\npath for the null NSON rows.\n\n\nSometimes you may find files that are not your source code files, but ones\ngenerated by the system when compiling your project. In that instance, when\nthe file has a .js extension, the ESLint needs to exclude that file when\nsearching for errors. One method of doing this is by using ‘IgnorePatterns:’\nin .eslintrc.json file either after or before the “rules” tag.\n\n\n“ignorePatterns”: [“temp.js”, “**/vendor/*.js”],\n\n\n“rules”: {\n\n\nAlternatively, you can create a separate file named‘.eslintignore’ and\nincorporate the files to be excluded as shown below :\n\n**/*.js\n\nIf you opt to correct instead of ignore, look for the error code in the last\ncolumn. Correct all the errors in one fule and rerun ‘npx eslint . >errfile’\nand ensure all the errors of that type are cleared. Then look for the next\nerror code and repeat the procedure until all errors are cleared.\n\n\nOf course, there will be instances when you won’t understand an error, so in\nthat case, open\n[https://eslint.org/docs/user-guide/getting-started](https://eslint.org/docs/user-guide/getting-started)\nand type the error code in the ‘Search’ field on the top of the document.\nThere you will find very detailed instructions as to why that error is\nraised and how to fix it.\n\n\nFinally, you can forcibly fix errors automatically while generating the\nerror list using:\n\n\nNpx eslintrc . — fix \n\n\nThis is not recommended until you become more well-versed with lint errors\nand how to fix them. Also, you should keep a backup of the files you are\nlinting because while fixing errors, certain code may get overwritten, which\ncould cause your program to fail.\n\n\n## JSON linting best practices\n\n\nHere are some tips for helping your consumers use your output:\n\n\nFirst, always enclose the **Key** **:** **Value** pair within **double\nquotes**. It may be convenient (not sure how) to generate with Single\nquotes, but JSON parser don’t like to parse JSON objects with single quotes.\n\n\nFor numerical values, quotes are optional but it is a good idea to enclose\nthem in double quotes.\n\n\nNext, don’t ever use hyphens in your key fields because it breaks python and\nscala parser. Instead use underscores (_). \n\n\nIt’s a good idea to always create a root element, especially when you’re\ncreating a complicated JSON.\n\n\n\nModern web applications come with a REST API which returns JSON. The format\nneeds to be parsed, and often feeds into scripts and service daemons polling\nthe API for automation.\n\n\nStarting with a new REST API and its endpoints can often be overwhelming.\nDocumentation may suggest looking into a set of SDKs and libraries for\nvarious languages, or instruct you to use `curl` or `wget` on the CLI to\nsend a request. Both CLI tools come with a variety of parameters which help\nto download and print the response string, for example in JSON format.\n\n\nThe response string retrieved from `curl` may get long and confusing. It can\nrequire parsing the JSON format and filtering for a smaller subset of\nresults. This helps with viewing the results on the CLI, and minimizes the\ndata to process in scripts. The following example retrieves all projects\nfrom GitLab and returns a paginated result set with the first 20 projects:\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\"\n\n```\n\n\n![Raw JSON as API\nresponse](https://about.gitlab.com/images/blogimages/devops-workflows-json-format-jq-ci-cd-lint/gitlab_api_response_raw_json.png){:\n.shadow}\n\n\nThe [GitLab REST API\ndocumentation](https://docs.gitlab.com/ee/api/#how-to-use-the-api) guides\nyou through the first steps with error handling and authentication. In this\nblog post, we will be using the [Personal Access\nToken](https://docs.gitlab.com/ee/api/#personalproject-access-tokens) as the\nauthentication method. Alternatively, you can use [project access\ntokens](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html)\nfor [automated\nauthentication](https://docs.gitlab.com/ee/api/#authentication) that avoids\nthe use of personal credentials.\n\n\n### REST API authentication\n\n\nSince not all endpoints are accessible with anonymous access they might\nrequire authentication. Try fetching user profile data with this request:\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/user\"\n\n{\"message\":\"401 Unauthorized\"}\n\n```\n\n\nThe API request against the `/user` endpoint requires to pass the personal\naccess token into the request, for example, as a request header. To avoid\nexposing credentials on the terminal, you can export the token and its value\ninto the user's environment. You can automate the variable export with ZSH\nand the [.env\nplugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/dotenv) in\nyour shell environment. You can also source the `.env` once in the existing\nshell environment.\n\n\n```shell\n\n$ vim ~/.env\n\n\nexport GITLAB_TOKEN=”...”\n\n\n$ source ~/.env\n\n```\n\n\nScripts and commands being run in your shell environment can reference the\n`$GITLAB_TOKEN` variable. Try querying the user API endpoint again, with\nadding the authorization header into the request:\n\n\n```shell\n\n$ curl -H \"Authorization: Bearer $GITLAB_TOKEN\"\n\"https://gitlab.com/api/v4/user\"\n\n```\n\n\nA reminder that only administrators can see the attributes of all users, and\nthe individual can only see their user profile – for example, `email` is\nhidden from the public domain.\n\n\n### How to request responses in JSON\n\n\nThe [GitLab API provides many\nresources](https://docs.gitlab.com/ee/api/api_resources.html) and URL\nendpoints. You can manage almost anything with the API that you’d otherwise\nconfigure using the graphic user interface.\n\n\nAfter sending the [API\nrequest](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_message),\nthe [response\nmessage](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message)\ncontains the body as string, for example as a [JSON content\ntype](https://docs.gitlab.com/ee/api/#content-type). `curl` can provide more\ninformation about the response headers which is helpful for debugging.\nMultiple verbose levels enable the full debug output with `-vvv`:\n\n\n```shell\n\n$ curl -vvv \"https://gitlab.com/api/v4/projects\"\n\n[...]\n\n* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305\n\n* ALPN, server accepted to use h2\n\n* Server certificate:\n\n*  subject: CN=gitlab.com\n\n*  start date: Jan 21 00:00:00 2021 GMT\n\n*  expire date: May 11 23:59:59 2021 GMT\n\n*  subjectAltName: host \"gitlab.com\" matched cert's \"gitlab.com\"\n\n*  issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited;\nCN=Sectigo RSA Domain Validation Secure Server CA\n\n*  SSL certificate verify ok.\n\n[...]\n\n> GET /api/v4/projects HTTP/2\n\n> Host: gitlab.com\n\n> User-Agent: curl/7.64.1\n\n> Accept: */*\n\n[...]\n\n\u003C HTTP/2 200\n\n\u003C date: Mon, 19 Apr 2021 11:25:31 GMT\n\n\u003C content-type: application/json\n\n[...]\n\n[{\"id\":25993690,\"description\":\"project for adding\nissues\",\"name\":\"project-for-issues-1e1b6d5f938fb240\",\"name_with_namespace\":\"gitlab-qa-sandbox-group\n/ qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6 /\nproject-for-issues-1e1b6d5f938fb240\",\"path\":\"project-for-issues-1e1b6d5f938fb240\",\"path_with_namespace\":\"gitlab-qa-sandbox-group/qa-test-2021-04-19-11-13-01-d7d873fd43cd34b6/project-for-issues-1e1b6d5f938fb240\"\n\n\n[... JSON content ...]\n\n\n\"avatar_url\":null,\"web_url\":\"https://gitlab.com/groups/gitlab-qa-sandbox-group/qa-test-2021-04-19-11-12-56-7f3128bd0e41b92f\"}}]\n\n* Closing connection 0\n\n```\n\n\nThe `curl` command output provides helpful insights into TLS ciphers and\nversions, the request lines starting with `>` and response lines starting\nwith `\u003C`. The response body string is encoded as JSON.\n\n\n### How to see the structure of the returned JSON\n\n\nTo get a quick look at the structure of the returned JSON file, try these\ntips:\n\n\n* Enclose square brackets to identify an array `[ …. ]`.\n\n* Enclose curly brackets identify a\n[dictionary](https://en.wikipedia.org/wiki/Associative_array) `{ … }`.\nDictionaries are also called associative arrays, maps, etc.\n\n* `”key”: value` indicates a key-value pair in a dictionary, which is\nidentified by curly brackets enclosing the key-value pairs.\n\n\nThe values in [JSON](https://en.wikipedia.org/wiki/JSON) consist of specific\ntypes - a string value is put in double-quotes. Boolean true/false, numbers,\nand floating-point numbers are also present as types. If a key exists but\nits value is not set, REST APIs often return `null`.\n\n\nVerify the data structure by running \"linters\". Python's JSON module can\nparse and lint JSON strings. The example below misses a closing square\nbracket to showcase the error:\n\n\n```shell\n\n$ echo '[{\"key\": \"broken\"}' | python -m json.tool\n\nExpecting object: line 1 column 19 (char 18)\n\n```\n\n\n[jq](https://stedolan.github.io/jq/) – a lightweight and flexible CLI\nprocessor – can be used as a standalone tool to parse and validate JSON\ndata.\n\n\n```shell\n\n$ echo '[{\"key\": \"broken\"}' | jq\n\nparse error: Unfinished JSON term at EOF at line 2, column 0\n\n```\n\n\n[`jq` is available](https://stedolan.github.io/jq/download/) in the package\nmanagers of most operating systems.\n\n\n```shell\n\n$ brew install jq\n\n$ apt install jq\n\n$ dnf install jq\n\n$ zypper in jq\n\n$ pacman -S jq\n\n$ apk add jq\n\n```\n\n\n### Dive deep into JSON data structures\n\n\nThe true power of `jq` lies in how it can be used to parse JSON data:\n\n\n> `jq` is like `sed` for JSON data. It can be used to slice, filter, map,\nand transform structured data with the same ease that `sed`, `awk`, `grep`\netc., let you manipulate text.\n\n\nThe output below shows how it looks to run the request against the project\nAPI again, but this time, the output is piped to `jq`.\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\" | jq\n\n[\n  {\n    \"id\": 25994891,\n    \"description\": \"...\",\n    \"name\": \"...\",\n\n[...]\n\n    \"forks_count\": 0,\n    \"star_count\": 0,\n    \"last_activity_at\": \"2021-04-19T11:50:24.292Z\",\n    \"namespace\": {\n      \"id\": 11528141,\n      \"name\": \"...\",\n\n[...]\n\n    }\n  }\n]\n\n```\n\n\nThe first difference is the format of the JSON data structure, so-called\n[pretty-printed](https://en.wikipedia.org/wiki/Prettyprint). New lines and\nindents in data structure scopes help your eyes and allow you to identify\nthe inner and outer data structures involved. This format is needed to\ndetermine which `jq` filters and methods you want to apply next.\n\n\n#### About arrays and dictionaries\n\n\nThe set of results from an API often is returned as a list (or \"array\") of\nitems. An item itself can be a single value or a JSON object. The following\nexample mimics the response from the GitLab API and creates an array of\ndictionaries as a nested result set.\n\n\n```shell\n\n$ vim result.json\n\n[\n  {\n    \"id\": 1,\n    \"name\": \"project1\"\n  },\n  {\n    \"id\": 2,\n    \"name\": \"project2\"\n  },\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n]\n\n```\n\n\nUse `cat` to print the file content on stdout and pipe it into `jq`. The\nouter data structure is an array – use `-c .[]` to access and print all\nitems.\n\n\n```shell\n\n$ cat result.json | jq -c '.[]'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n```\n\n\n### How to filter data structures with `jq`\n\n\nFilter items by passing `| select (...)` to `jq`. The filter takes a lambda\ncallback function as a comparator condition. When the item matches the\ncondition, it is returned to the caller.\n\n\nUse the dot indexer `.` to access dictionary keys and their values. Try to\nfilter for all items where the name is `project2`:\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name == \"project2\")'\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nPractice this example by selecting the `id` with the value `2` instead of\nthe `name`.\n\n\n#### Filter with matching a string\n\n\nDuring tests, you may need to match different patterns instead of knowing\nthe full name. Think of projects that match a specific path or are located\nin a group where you only know the prefix. Simple string matches can be\nachieved with the `| contains (...)` function. It allows you to check\nwhether the given string is inside the target string – which requires the\nselected attribute to be of the string type.\n\n\nFor a filter with the select chain, the comparison condition needs to be\nchanged from the equal operator `==` to checking the attribute `.name` with\n`| contains (\"dev\")`.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name | contains (\"dev\") )'\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n```\n\n\nSimple matches can be achieved with the `contains` function.\n\n\n#### Filter with matching regular expressions\n\n\nFor advanced string pattern matching, it is recommended to use regular\nexpressions. `jq` provides the [test function for this use\ncase](https://stedolan.github.io/jq/manual/#RegularexpressionsPCRE). Try to\nfilter for all projects which end with a number, represented by `\\d+`. Note\nthat the backslash `\\` needs to be escaped as `\\\\` for shell execution. `^`\ntests for beginning of the string, `$` is the ending check.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.name | test (\"^project\\\\d+$\") )'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nTip: You can [test and build the regular expression with\nregex101](https://regex101.com/) before test-driving it with `jq`.\n\n\n#### Access nested values\n\n\nKey value pairs in a dictionary may have a dictionary or array as a value.\n`jq` filters need to take this factor into account when filtering or\ntransforming the result. The example data structure provides\n`project-internal-dev` which has the key `namespace` and a value of a\ndictionary type.\n\n\n```shell\n  {\n    \"id\": 3,\n    \"name\": \"project-internal-dev\",\n    \"namespace\": {\n      \"name\": \"🦊\"\n    }\n  }\n```\n\n\n`jq` allows the user to specify the [array and dictionary\ntypes](https://stedolan.github.io/jq/manual/#TypesandValues) as `[]` and\n`{}` to be used in select chains with greater and less than comparisons. The\n`[]` brackets select filters for non-empty dictionaries for the `namespace`\nattribute, while the `{}` brackets select for all `null` (raw JSON) values.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.namespace >={} )'\n\n{\"id\":3,\"name\":\"project-internal-dev\",\"namespace\":{\"name\":\"🦊\"}}\n\n\n$ cat result.json | jq -c '.[] | select (.namespace \u003C={} )'\n\n{\"id\":1,\"name\":\"project1\"}\n\n{\"id\":2,\"name\":\"project2\"}\n\n```\n\n\nThese methods can be used to access the name attribute of the namespace, but\nonly if the namespace contains values. Tip: You can chain multiple `jq`\ncalls by piping the result into another `jq` call. `.name` is a subkey of\nthe primary `.namespace` key.\n\n\n```shell\n\n$ cat result.json | jq -c '.[] | select (.namespace >={} )' | jq -c\n'.namespace.name'\n\n\"🦊\"\n\n```\n\n\nThe additional select command with non-empty namespaces ensures that only\ninitialized values for `.namespace.name` are returned. This is a safety\ncheck, and avoids receiving `null` values in the result you would need to\nfilter again.\n\n\n```shell\n\n$ cat result.json| jq -c '.[]' | jq -c '.namespace.name'\n\nnull\n\nnull\n\n\"🦊\"\n\n```\n\n\nBy using the additional check with `| select (.namespace >={} )`, you only\nget the expected results and do not have to filter empty `null` values.\n\n\n### How to expand the GitLab endpoint response\n\n\nSave the result from the API projects call and retry the examples above with\n`jq`.\n\n\n```shell\n\n$ curl \"https://gitlab.com/api/v4/projects\" -o result.json 2&>1 >/dev/null\n\n```\n\n\n### Validate CI/CD YAML with `jq` for Git hooks\n\n\nWhile writing this blog post, I learned that you can [escape and encode YAML\ninto JSON with\n`jq`](https://docs.gitlab.com/ee/api/lint.html#escape-yaml-for-json-encoding).\nThis trick comes in handy when automating YAML linting on the CLI, for\nexample as a Git pre-commit hook.\n\n\nLet’s take a look at the simplest way to test GitLab CI/CD from our\n[community meetup\nworkshops](https://gitlab.com/gitlab-de/swiss-meetup-2021-jan#resources). A\ncommon mistake with the first steps of the process can be missing the two\nspaces indent or missing whitespace between the dash and following command.\nThe following examples use `.gitlab-ci.error.yml` as a filename to showcase\nerrors and `.gitlab-ci.main.yml` for working examples.\n\n\n```shell\n\n$ vim .gitlab-ci.error.yml\n\n\nimage: alpine:latest\n\n\ntest:\n\nscript:\n  -exit 1\n```\n\n\nCommitting the change and waiting for the CI/CD pipeline to validate at\nruntime can be time-consuming. The [GitLab API provides a resource endpoint\n/ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration).\nA POST request with JSON-encoded YAML content will return a linting result\nfaster.\n\n\n#### Parse CI/CD YAML into JSON with jq\n\n\nYou can use jq to parse the raw YAML string into JSON:\n\n\n```shell\n\n$ jq --raw-input --slurp \u003C .gitlab-ci.error.yml\n\n\"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\\n\"\n\n```\n\n\nThe `/ci/lint` API endpoint requires a JSON dictionary with `content` as\nkey, and the raw YAML string as a value. You can use `jq` to format the\ninput by using the arg parser:\n\n\n```shell\n\n§ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml'\n\n{\n  \"content\": \"image: alpine:latest\\n\\ntest:\\nscript:\\n  -exit 1\"\n}\n\n```\n\n\n#### Send POST request to /ci/lint\n\n\nThe next building block is to [send a POST request to the\n/ci/lint](https://docs.gitlab.com/ee/api/lint.html#validate-the-ci-yaml-configuration).\nThe request needs to specify the `Content-Type` header for the body. With\nusing the pipe `|` character, the JSON-encoded YAML configuration is fed\ninto the curl command call.\n\n\n```shell\n\n$ jq --null-input --arg yaml \"$(\u003C.gitlab-ci.error.yml)\" '.content=$yaml' \\\n\n| curl \"https://gitlab.com/api/v4/ci/lint?include_merged_yaml=true\" \\\n\n--header 'Content-Type: application/json' --data @-\n\n{\"status\":\"invalid\",\"errors\":[\"jobs test config should implement a script:\nor a trigger: keyword\",\"jobs script config should implement a script: or a\ntrigger: keyword\",\"jobs config should contain at least one visible\njob\"],\"warnings\":[],\"merged_yaml\":\"\n","engineering",[24,25,26],"CI","DevOps","tutorial",{"slug":28,"featured":6,"template":29},"devops-workflows-json-format-jq-ci-cd-lint","BlogPost","content:en-us:blog:devops-workflows-json-format-jq-ci-cd-lint.yml","yaml","Devops Workflows Json Format Jq Ci Cd Lint","content","en-us/blog/devops-workflows-json-format-jq-ci-cd-lint.yml","en-us/blog/devops-workflows-json-format-jq-ci-cd-lint","yml",{"_path":38,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":40,"_id":462,"_type":31,"title":463,"_source":33,"_file":464,"_stem":465,"_extension":36},"/shared/en-us/main-navigation","en-us",{"logo":41,"freeTrial":46,"sales":51,"login":56,"items":61,"search":393,"minimal":424,"duo":443,"pricingDeployment":452},{"config":42},{"href":43,"dataGaName":44,"dataGaLocation":45},"/","gitlab logo","header",{"text":47,"config":48},"Get free trial",{"href":49,"dataGaName":50,"dataGaLocation":45},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":52,"config":53},"Talk to sales",{"href":54,"dataGaName":55,"dataGaLocation":45},"/sales/","sales",{"text":57,"config":58},"Sign in",{"href":59,"dataGaName":60,"dataGaLocation":45},"https://gitlab.com/users/sign_in/","sign in",[62,106,204,209,314,374],{"text":63,"config":64,"cards":66,"footer":89},"Platform",{"dataNavLevelOne":65},"platform",[67,73,81],{"title":63,"description":68,"link":69},"The most comprehensive AI-powered DevSecOps Platform",{"text":70,"config":71},"Explore our Platform",{"href":72,"dataGaName":65,"dataGaLocation":45},"/platform/",{"title":74,"description":75,"link":76},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":77,"config":78},"Meet GitLab Duo",{"href":79,"dataGaName":80,"dataGaLocation":45},"/gitlab-duo/","gitlab duo ai",{"title":82,"description":83,"link":84},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":85,"config":86},"Learn more",{"href":87,"dataGaName":88,"dataGaLocation":45},"/why-gitlab/","why gitlab",{"title":90,"items":91},"Get started with",[92,97,102],{"text":93,"config":94},"Platform Engineering",{"href":95,"dataGaName":96,"dataGaLocation":45},"/solutions/platform-engineering/","platform engineering",{"text":98,"config":99},"Developer Experience",{"href":100,"dataGaName":101,"dataGaLocation":45},"/developer-experience/","Developer experience",{"text":103,"config":104},"MLOps",{"href":105,"dataGaName":103,"dataGaLocation":45},"/topics/devops/the-role-of-ai-in-devops/",{"text":107,"left":108,"config":109,"link":111,"lists":115,"footer":186},"Product",true,{"dataNavLevelOne":110},"solutions",{"text":112,"config":113},"View all Solutions",{"href":114,"dataGaName":110,"dataGaLocation":45},"/solutions/",[116,141,165],{"title":117,"description":118,"link":119,"items":124},"Automation","CI/CD and automation to accelerate deployment",{"config":120},{"icon":121,"href":122,"dataGaName":123,"dataGaLocation":45},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[125,129,133,137],{"text":126,"config":127},"CI/CD",{"href":128,"dataGaLocation":45,"dataGaName":126},"/solutions/continuous-integration/",{"text":130,"config":131},"AI-Assisted Development",{"href":79,"dataGaLocation":45,"dataGaName":132},"AI assisted development",{"text":134,"config":135},"Source Code Management",{"href":136,"dataGaLocation":45,"dataGaName":134},"/solutions/source-code-management/",{"text":138,"config":139},"Automated Software Delivery",{"href":122,"dataGaLocation":45,"dataGaName":140},"Automated software delivery",{"title":142,"description":143,"link":144,"items":149},"Security","Deliver code faster without compromising security",{"config":145},{"href":146,"dataGaName":147,"dataGaLocation":45,"icon":148},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[150,155,160],{"text":151,"config":152},"Application Security Testing",{"href":153,"dataGaName":154,"dataGaLocation":45},"/solutions/application-security-testing/","Application security testing",{"text":156,"config":157},"Software Supply Chain Security",{"href":158,"dataGaLocation":45,"dataGaName":159},"/solutions/supply-chain/","Software supply chain security",{"text":161,"config":162},"Software Compliance",{"href":163,"dataGaName":164,"dataGaLocation":45},"/solutions/software-compliance/","software compliance",{"title":166,"link":167,"items":172},"Measurement",{"config":168},{"icon":169,"href":170,"dataGaName":171,"dataGaLocation":45},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[173,177,181],{"text":174,"config":175},"Visibility & Measurement",{"href":170,"dataGaLocation":45,"dataGaName":176},"Visibility and Measurement",{"text":178,"config":179},"Value Stream Management",{"href":180,"dataGaLocation":45,"dataGaName":178},"/solutions/value-stream-management/",{"text":182,"config":183},"Analytics & Insights",{"href":184,"dataGaLocation":45,"dataGaName":185},"/solutions/analytics-and-insights/","Analytics and insights",{"title":187,"items":188},"GitLab for",[189,194,199],{"text":190,"config":191},"Enterprise",{"href":192,"dataGaLocation":45,"dataGaName":193},"/enterprise/","enterprise",{"text":195,"config":196},"Small Business",{"href":197,"dataGaLocation":45,"dataGaName":198},"/small-business/","small business",{"text":200,"config":201},"Public Sector",{"href":202,"dataGaLocation":45,"dataGaName":203},"/solutions/public-sector/","public sector",{"text":205,"config":206},"Pricing",{"href":207,"dataGaName":208,"dataGaLocation":45,"dataNavLevelOne":208},"/pricing/","pricing",{"text":210,"config":211,"link":213,"lists":217,"feature":301},"Resources",{"dataNavLevelOne":212},"resources",{"text":214,"config":215},"View all resources",{"href":216,"dataGaName":212,"dataGaLocation":45},"/resources/",[218,251,273],{"title":219,"items":220},"Getting started",[221,226,231,236,241,246],{"text":222,"config":223},"Install",{"href":224,"dataGaName":225,"dataGaLocation":45},"/install/","install",{"text":227,"config":228},"Quick start guides",{"href":229,"dataGaName":230,"dataGaLocation":45},"/get-started/","quick setup checklists",{"text":232,"config":233},"Learn",{"href":234,"dataGaLocation":45,"dataGaName":235},"https://university.gitlab.com/","learn",{"text":237,"config":238},"Product documentation",{"href":239,"dataGaName":240,"dataGaLocation":45},"https://docs.gitlab.com/","product documentation",{"text":242,"config":243},"Best practice videos",{"href":244,"dataGaName":245,"dataGaLocation":45},"/getting-started-videos/","best practice videos",{"text":247,"config":248},"Integrations",{"href":249,"dataGaName":250,"dataGaLocation":45},"/integrations/","integrations",{"title":252,"items":253},"Discover",[254,259,263,268],{"text":255,"config":256},"Customer success stories",{"href":257,"dataGaName":258,"dataGaLocation":45},"/customers/","customer success stories",{"text":260,"config":261},"Blog",{"href":262,"dataGaName":5,"dataGaLocation":45},"/blog/",{"text":264,"config":265},"Remote",{"href":266,"dataGaName":267,"dataGaLocation":45},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":269,"config":270},"TeamOps",{"href":271,"dataGaName":272,"dataGaLocation":45},"/teamops/","teamops",{"title":274,"items":275},"Connect",[276,281,286,291,296],{"text":277,"config":278},"GitLab Services",{"href":279,"dataGaName":280,"dataGaLocation":45},"/services/","services",{"text":282,"config":283},"Community",{"href":284,"dataGaName":285,"dataGaLocation":45},"/community/","community",{"text":287,"config":288},"Forum",{"href":289,"dataGaName":290,"dataGaLocation":45},"https://forum.gitlab.com/","forum",{"text":292,"config":293},"Events",{"href":294,"dataGaName":295,"dataGaLocation":45},"/events/","events",{"text":297,"config":298},"Partners",{"href":299,"dataGaName":300,"dataGaLocation":45},"/partners/","partners",{"backgroundColor":302,"textColor":303,"text":304,"image":305,"link":309},"#2f2a6b","#fff","Insights for the future of software development",{"altText":306,"config":307},"the source promo card",{"src":308},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":310,"config":311},"Read the latest",{"href":312,"dataGaName":313,"dataGaLocation":45},"/the-source/","the source",{"text":315,"config":316,"lists":318},"Company",{"dataNavLevelOne":317},"company",[319],{"items":320},[321,326,332,334,339,344,349,354,359,364,369],{"text":322,"config":323},"About",{"href":324,"dataGaName":325,"dataGaLocation":45},"/company/","about",{"text":327,"config":328,"footerGa":331},"Jobs",{"href":329,"dataGaName":330,"dataGaLocation":45},"/jobs/","jobs",{"dataGaName":330},{"text":292,"config":333},{"href":294,"dataGaName":295,"dataGaLocation":45},{"text":335,"config":336},"Leadership",{"href":337,"dataGaName":338,"dataGaLocation":45},"/company/team/e-group/","leadership",{"text":340,"config":341},"Team",{"href":342,"dataGaName":343,"dataGaLocation":45},"/company/team/","team",{"text":345,"config":346},"Handbook",{"href":347,"dataGaName":348,"dataGaLocation":45},"https://handbook.gitlab.com/","handbook",{"text":350,"config":351},"Investor relations",{"href":352,"dataGaName":353,"dataGaLocation":45},"https://ir.gitlab.com/","investor relations",{"text":355,"config":356},"Trust Center",{"href":357,"dataGaName":358,"dataGaLocation":45},"/security/","trust center",{"text":360,"config":361},"AI Transparency Center",{"href":362,"dataGaName":363,"dataGaLocation":45},"/ai-transparency-center/","ai transparency center",{"text":365,"config":366},"Newsletter",{"href":367,"dataGaName":368,"dataGaLocation":45},"/company/contact/","newsletter",{"text":370,"config":371},"Press",{"href":372,"dataGaName":373,"dataGaLocation":45},"/press/","press",{"text":375,"config":376,"lists":377},"Contact us",{"dataNavLevelOne":317},[378],{"items":379},[380,383,388],{"text":52,"config":381},{"href":54,"dataGaName":382,"dataGaLocation":45},"talk to sales",{"text":384,"config":385},"Get help",{"href":386,"dataGaName":387,"dataGaLocation":45},"/support/","get help",{"text":389,"config":390},"Customer portal",{"href":391,"dataGaName":392,"dataGaLocation":45},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":394,"login":395,"suggestions":402},"Close",{"text":396,"link":397},"To search repositories and projects, login to",{"text":398,"config":399},"gitlab.com",{"href":59,"dataGaName":400,"dataGaLocation":401},"search login","search",{"text":403,"default":404},"Suggestions",[405,407,411,413,417,421],{"text":74,"config":406},{"href":79,"dataGaName":74,"dataGaLocation":401},{"text":408,"config":409},"Code Suggestions (AI)",{"href":410,"dataGaName":408,"dataGaLocation":401},"/solutions/code-suggestions/",{"text":126,"config":412},{"href":128,"dataGaName":126,"dataGaLocation":401},{"text":414,"config":415},"GitLab on AWS",{"href":416,"dataGaName":414,"dataGaLocation":401},"/partners/technology-partners/aws/",{"text":418,"config":419},"GitLab on Google Cloud",{"href":420,"dataGaName":418,"dataGaLocation":401},"/partners/technology-partners/google-cloud-platform/",{"text":422,"config":423},"Why GitLab?",{"href":87,"dataGaName":422,"dataGaLocation":401},{"freeTrial":425,"mobileIcon":430,"desktopIcon":435,"secondaryButton":438},{"text":426,"config":427},"Start free trial",{"href":428,"dataGaName":50,"dataGaLocation":429},"https://gitlab.com/-/trials/new/","nav",{"altText":431,"config":432},"Gitlab Icon",{"src":433,"dataGaName":434,"dataGaLocation":429},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":431,"config":436},{"src":437,"dataGaName":434,"dataGaLocation":429},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":439,"config":440},"Get Started",{"href":441,"dataGaName":442,"dataGaLocation":429},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":444,"mobileIcon":448,"desktopIcon":450},{"text":445,"config":446},"Learn more about GitLab Duo",{"href":79,"dataGaName":447,"dataGaLocation":429},"gitlab duo",{"altText":431,"config":449},{"src":433,"dataGaName":434,"dataGaLocation":429},{"altText":431,"config":451},{"src":437,"dataGaName":434,"dataGaLocation":429},{"freeTrial":453,"mobileIcon":458,"desktopIcon":460},{"text":454,"config":455},"Back to pricing",{"href":207,"dataGaName":456,"dataGaLocation":429,"icon":457},"back to pricing","GoBack",{"altText":431,"config":459},{"src":433,"dataGaName":434,"dataGaLocation":429},{"altText":431,"config":461},{"src":437,"dataGaName":434,"dataGaLocation":429},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":467,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"title":468,"button":469,"image":474,"config":478,"_id":480,"_type":31,"_source":33,"_file":481,"_stem":482,"_extension":36},"/shared/en-us/banner","is now in public beta!",{"text":470,"config":471},"Try the Beta",{"href":472,"dataGaName":473,"dataGaLocation":45},"/gitlab-duo/agent-platform/","duo banner",{"altText":475,"config":476},"GitLab Duo Agent Platform",{"src":477},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":479},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":484,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":485,"_id":689,"_type":31,"title":690,"_source":33,"_file":691,"_stem":692,"_extension":36},"/shared/en-us/main-footer",{"text":486,"source":487,"edit":493,"contribute":498,"config":503,"items":508,"minimal":681},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":488,"config":489},"View page source",{"href":490,"dataGaName":491,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":494,"config":495},"Edit this page",{"href":496,"dataGaName":497,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":499,"config":500},"Please contribute",{"href":501,"dataGaName":502,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":504,"facebook":505,"youtube":506,"linkedin":507},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[509,532,588,617,651],{"title":63,"links":510,"subMenu":515},[511],{"text":512,"config":513},"DevSecOps platform",{"href":72,"dataGaName":514,"dataGaLocation":492},"devsecops platform",[516],{"title":205,"links":517},[518,522,527],{"text":519,"config":520},"View plans",{"href":207,"dataGaName":521,"dataGaLocation":492},"view plans",{"text":523,"config":524},"Why Premium?",{"href":525,"dataGaName":526,"dataGaLocation":492},"/pricing/premium/","why premium",{"text":528,"config":529},"Why Ultimate?",{"href":530,"dataGaName":531,"dataGaLocation":492},"/pricing/ultimate/","why ultimate",{"title":533,"links":534},"Solutions",[535,540,542,544,549,554,558,561,565,570,572,575,578,583],{"text":536,"config":537},"Digital transformation",{"href":538,"dataGaName":539,"dataGaLocation":492},"/topics/digital-transformation/","digital transformation",{"text":151,"config":541},{"href":153,"dataGaName":151,"dataGaLocation":492},{"text":140,"config":543},{"href":122,"dataGaName":123,"dataGaLocation":492},{"text":545,"config":546},"Agile development",{"href":547,"dataGaName":548,"dataGaLocation":492},"/solutions/agile-delivery/","agile delivery",{"text":550,"config":551},"Cloud transformation",{"href":552,"dataGaName":553,"dataGaLocation":492},"/topics/cloud-native/","cloud transformation",{"text":555,"config":556},"SCM",{"href":136,"dataGaName":557,"dataGaLocation":492},"source code management",{"text":126,"config":559},{"href":128,"dataGaName":560,"dataGaLocation":492},"continuous integration & delivery",{"text":562,"config":563},"Value stream management",{"href":180,"dataGaName":564,"dataGaLocation":492},"value stream management",{"text":566,"config":567},"GitOps",{"href":568,"dataGaName":569,"dataGaLocation":492},"/solutions/gitops/","gitops",{"text":190,"config":571},{"href":192,"dataGaName":193,"dataGaLocation":492},{"text":573,"config":574},"Small business",{"href":197,"dataGaName":198,"dataGaLocation":492},{"text":576,"config":577},"Public sector",{"href":202,"dataGaName":203,"dataGaLocation":492},{"text":579,"config":580},"Education",{"href":581,"dataGaName":582,"dataGaLocation":492},"/solutions/education/","education",{"text":584,"config":585},"Financial services",{"href":586,"dataGaName":587,"dataGaLocation":492},"/solutions/finance/","financial services",{"title":210,"links":589},[590,592,594,596,599,601,603,605,607,609,611,613,615],{"text":222,"config":591},{"href":224,"dataGaName":225,"dataGaLocation":492},{"text":227,"config":593},{"href":229,"dataGaName":230,"dataGaLocation":492},{"text":232,"config":595},{"href":234,"dataGaName":235,"dataGaLocation":492},{"text":237,"config":597},{"href":239,"dataGaName":598,"dataGaLocation":492},"docs",{"text":260,"config":600},{"href":262,"dataGaName":5,"dataGaLocation":492},{"text":255,"config":602},{"href":257,"dataGaName":258,"dataGaLocation":492},{"text":264,"config":604},{"href":266,"dataGaName":267,"dataGaLocation":492},{"text":277,"config":606},{"href":279,"dataGaName":280,"dataGaLocation":492},{"text":269,"config":608},{"href":271,"dataGaName":272,"dataGaLocation":492},{"text":282,"config":610},{"href":284,"dataGaName":285,"dataGaLocation":492},{"text":287,"config":612},{"href":289,"dataGaName":290,"dataGaLocation":492},{"text":292,"config":614},{"href":294,"dataGaName":295,"dataGaLocation":492},{"text":297,"config":616},{"href":299,"dataGaName":300,"dataGaLocation":492},{"title":315,"links":618},[619,621,623,625,627,629,631,635,640,642,644,646],{"text":322,"config":620},{"href":324,"dataGaName":317,"dataGaLocation":492},{"text":327,"config":622},{"href":329,"dataGaName":330,"dataGaLocation":492},{"text":335,"config":624},{"href":337,"dataGaName":338,"dataGaLocation":492},{"text":340,"config":626},{"href":342,"dataGaName":343,"dataGaLocation":492},{"text":345,"config":628},{"href":347,"dataGaName":348,"dataGaLocation":492},{"text":350,"config":630},{"href":352,"dataGaName":353,"dataGaLocation":492},{"text":632,"config":633},"Sustainability",{"href":634,"dataGaName":632,"dataGaLocation":492},"/sustainability/",{"text":636,"config":637},"Diversity, inclusion and belonging (DIB)",{"href":638,"dataGaName":639,"dataGaLocation":492},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":355,"config":641},{"href":357,"dataGaName":358,"dataGaLocation":492},{"text":365,"config":643},{"href":367,"dataGaName":368,"dataGaLocation":492},{"text":370,"config":645},{"href":372,"dataGaName":373,"dataGaLocation":492},{"text":647,"config":648},"Modern Slavery Transparency Statement",{"href":649,"dataGaName":650,"dataGaLocation":492},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":652,"links":653},"Contact Us",[654,657,659,661,666,671,676],{"text":655,"config":656},"Contact an expert",{"href":54,"dataGaName":55,"dataGaLocation":492},{"text":384,"config":658},{"href":386,"dataGaName":387,"dataGaLocation":492},{"text":389,"config":660},{"href":391,"dataGaName":392,"dataGaLocation":492},{"text":662,"config":663},"Status",{"href":664,"dataGaName":665,"dataGaLocation":492},"https://status.gitlab.com/","status",{"text":667,"config":668},"Terms of use",{"href":669,"dataGaName":670,"dataGaLocation":492},"/terms/","terms of use",{"text":672,"config":673},"Privacy statement",{"href":674,"dataGaName":675,"dataGaLocation":492},"/privacy/","privacy statement",{"text":677,"config":678},"Cookie preferences",{"dataGaName":679,"dataGaLocation":492,"id":680,"isOneTrustButton":108},"cookie preferences","ot-sdk-btn",{"items":682},[683,685,687],{"text":667,"config":684},{"href":669,"dataGaName":670,"dataGaLocation":492},{"text":672,"config":686},{"href":674,"dataGaName":675,"dataGaLocation":492},{"text":677,"config":688},{"dataGaName":679,"dataGaLocation":492,"id":680,"isOneTrustButton":108},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[694],{"_path":695,"_dir":696,"_draft":6,"_partial":6,"_locale":7,"content":697,"config":701,"_id":703,"_type":31,"title":19,"_source":33,"_file":704,"_stem":705,"_extension":36},"/en-us/blog/authors/michael-friedrich","authors",{"name":19,"config":698},{"headshot":699,"ctfId":700},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{"template":702},"BlogAuthor","content:en-us:blog:authors:michael-friedrich.yml","en-us/blog/authors/michael-friedrich.yml","en-us/blog/authors/michael-friedrich",{"_path":707,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"header":708,"eyebrow":709,"blurb":710,"button":711,"secondaryButton":715,"_id":717,"_type":31,"title":718,"_source":33,"_file":719,"_stem":720,"_extension":36},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":47,"config":712},{"href":713,"dataGaName":50,"dataGaLocation":714},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":52,"config":716},{"href":54,"dataGaName":55,"dataGaLocation":714},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326217461]