Skip to content
Notifications
Clear all

Walkthrough: Replicating complex Jenkins shared libraries in GitLab CI.

3 Posts
3 Users
0 Reactions
36 Views
(@markomancer)
Trusted Member
Joined: 5 months ago
Posts: 44
Topic starter   [#5415]

So, you’re moving from Jenkins to GitLab CI and staring down those *sprawling* shared libraries? Yeah, that was me last quarter. The logic, the global vars, the custom steps—it feels like you have to rebuild a whole engine just to run your car.

Here’s how I tackled it without losing my mind (or breaking every pipeline).

First, I mapped the mental model:
* **Jenkins Shared Libraries** = Centralized logic in a separate repo, imported via `@Library`.
* **GitLab CI Equivalent** = You've got a few paths: custom CI templates, `include:` from other projects, or the new-ish `reusable` workflows.

For us, the sweet spot was **CI Templates in a dedicated "Automation" project**. We created a project full of `.gitlab-ci.yml` job definitions (like `deploy-to-stage.yml`, `notify-slack.yml`). Then, any pipeline can pull them in with `include: project: 'our-org/ci-templates'`. It’s not a 1:1 match for Groovy, but it gets you the reusability.

The real pain points?
1. **Parameter translation:** Jenkins libraries often use structured args. We switched to using CI variables and `rules:` or `if:` statements for control. For complex inputs, we’d define the entire job config as a reusable template and pass variables into it.
2. **State & Caching:** Shared libs sometimes hold state. We had to refactor that into GitLab's cache or artifacts, or use the new `needs:` keyword for dependency chains.
3. **Secrets migration:** This was actually smoother. Jenkins credentials → GitLab CI variables (masked, protected). Used the Project-level or Group-level variables. The audit log is nicer, IMO.

Biggest "aha" moment? We didn't need to replicate *everything*. A lot of those shared library steps were bandaids for Jenkins' quirks. GitLab CI’s `image:`, `services:`, and `artifacts:` made some custom steps completely obsolete.

The whole migration for our mid-sized setup (about 15 pipelines, 4 "core" shared libs) took about 3 weeks of part-time work. The final week was just testing in a staging environment.

Anyone else been through this? How did you handle the more complex, multi-branch logic from Jenkins? Did you go the `reusable` workflow route, or stick with project includes?


It's not marketing, it's logic.


   
Quote
(@lucas)
Eminent Member
Joined: 3 months ago
Posts: 24
 

Templates work, but you're missing the main event for complex logic: custom CI components. It's the closest thing to a proper shared library. You define inputs, validation, and scripts in a component YAML, then reference it.

Your parameter pain disappears. Instead of juggling variables and rules, you get a clean interface. Example component spec:

```yaml
inputs:
environment:
default: staging
deployment_file:
required: true
```

Reference it with `include: component: ...`. It's far more maintainable than a pile of templates.


Benchmarks > marketing.


   
ReplyQuote
(@devops_journeyman)
Reputable Member
Joined: 5 months ago
Posts: 216
 

Templates got us started too, but we hit a snag with complex parameter flow. Your point about structured args is key. In Jenkins, you'd call `deployApp(env: 'prod', force: true)`. In a plain GitLab template, that becomes a mess of `DEPLOY_ENV` and `FORCE_DEPLOY` variables you have to coordinate.

We landed on a hybrid approach: each logical "function" gets its own template file, and inside we use `extends:` to pull in a base job that sets up the Docker image and common scripts. Then the specific template just defines the variables it expects and the script that uses them. It looks like this:

```yaml
# Base job in `base.yml`
.deploy-base:
image: our-runner:latest
before_script:
- source /scripts/lib.sh

# Actual template in `deploy.yml`
deploy-to-env:
extends: .deploy-base
variables:
ENVIRONMENT: ''
FORCE: 'false'
script:
- deploy --env "$ENVIRONMENT" --force "$FORCE"
```

Caller projects `include:` the template and set the variables. It's not as clean as a function call, but it keeps the contract visible.

How did you handle validation of those input variables? We ended up adding a lot of upfront `if [ -z "$ENVIRONMENT" ]; then exit 1; fi` checks.



   
ReplyQuote