
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
This guide introduces a repeatable, audit-friendly methodology for managing your AIC pipelines in tandem with GitLab, bridging the gap between your workspace configurations in AIC and your version-controlled artifacts in Git.
By leveraging the aic_utils package and the GitLabManager helper library, you gain end-to-end control over:
This integration ensures that every pipeline change—from minor SQL tweak to full-scale workflow overhaul—is captured as code, reviewed via merge requests, and traceable through Git history. It enforces a strict 1:1 relationship between Git branches and AIC workspaces, preventing accidental or out-of-sync deployments. Whether you’re onboarding a new environment, back-syncing an existing workspace, or building feature-specific sandbox branches, this approach provides the guardrails and automation you need to move fast without sacrificing governance or reliability.
In the sections that follow, you’ll find:
config in JSON formcode folders for corporate code scanningThe AIC class wraps the AIC REST API, exposing five main capability areas:
AIC(api_key: str, project: str, workspace: str, pipelines: list[str] = [], qa: bool = False)
What it does
Attributes
.pop_pipelines() → list[{'name': str, 'id': str}]
.pop_pipeline_config(pipelines: list[str]) → list[{'name', 'jobConfig', 'id'}]
.fetch_pipeline_config(pipeline: dict, direct: bool=False) → dict
.write_config_to_pipeline(config: dict) → None
.create_pipeline_branch(config: dict) → Response
.create_or_update_pipeline(workspace_id: str, pipeline_config: dict) → None
.update_pipeline(workspace_id: str, pipeline_id: str, pipeline_config: dict) → None
Datasets & Tables
.get_datasets() → list[dict]
Interactive Branches
.delete_branches(job_names: list[str]) → None
Backups
.backup_pipelines(pipelines: list[Union[str, dict]], base_folder: str='.', drive_name: str='backups') → None
The GitLabManager class uses your AIC instance and the GitLab API to synchronize pipeline definitions, enforce branch/workspace governance, and automate promotions.
GitLabManager( aic_instance: AIC, gitlab_token: str, gitlab_namespace: str, repo_folder: str, gitlab_base_url: str = "https://git.autodatacorp.org/api/v4", use_hash_comparison: bool = True, email_recipients: list = None, email_sender: str = None, mapping_file: str = "branch_to_workspace.yaml" )
What it does
project_path, retrieves default_branch, and loads branch→workspace mapping.self.branch) from AIC workspace and ensures it exists.Key attributes
self.aic, self.gitlab_base, self.gitlab_token, self.headersself.project_path, self.default_branch, self.branch, self.mapperself.email_recipients, self.email_sender, self.use_hash_comparison.repository_exists(repo_path: str) → bool
GET /projects/{repo_path}.create_repository(repo_name: str) → dict or None
POST /projects under specified subgroup..get_subgroup_id() → int
GET /groups/{namespace} to find subgroup ID..ensure_branch_exists(branch_name: str, ref: str) → None
GET /repository/branches/{branch_name}POST /repository/branches?branch={branch_name}&ref={ref}._slugify(text: str) → str
.check_token_expiration(warning_threshold_days: int = 30) → None
GET /personal_access_tokens.get_existing_file_content(repo_name: str, file_name: str) → Optional[str]
GET /repository/files/{file_name} on self.branch or default_branch..fetch_pipeline_file(full_path: str, ref: str = None) → str
GET /repository/files/{full_path}/raw?ref={ref}.list_pipeline_files(subpath: str = None) → list[str]
GET /repository/tree (per_page=100), filters for .json files..list_all_blobs(subpath: str) → list[str]
subpath via GET /repository/tree?recursive=true..delete_file(file_name: str, branch: str = None) → None
DELETE /repository/files/{file_name}?branch={branch}&commit_message=...._commit_deletion_actions(paths_to_delete: list[str], branch: str, commit_msg_prefix: str) → None
POST /repository/commits with actions=[{action: 'delete', file_path: ...}].._push_file(proj_encoded: str, file_name: str, file_content: str, branch: str) → None
POST /repository/files/{file_name} for new files.PUT /repository/files/{file_name} on 400/409 responses.._extract_and_push_code(pipeline_name: str, job_config: dict) → None
PYSPARK or SQL..py scripts and extracted SQL blocks as .sql files..push_to_git() → None
self.aic.pipeline_configs, pushes JSON into config/{name}.json._extract_and_push_code to sync code under code/{pipeline}._format_push_summary and _send_email.._format_push_summary(branch: str, results: list[tuple]) → (subject: str, body: str)
(pipeline, status, message) rows..deploy_pipelines(force_sync: bool = False) → None
self.mapper.config/*.json and upserts into AIC._format_deploy_summary..create_feature_branch(feature_name: str) → None
sandbox.feature/{feature_name} off sandbox, switches self.branch..create_release_branch(branch_name: str) → None
develop.release/{branch_name}, deletes any config/ or code/ files not in self.aic.pipeline_configs..create_hotfix_branch(branch_name: str) → None
hotfix/{branch_name} off prod, deletes pipelines not allowed.BranchWorkspaceMapper(mapping_file: str = "branch_to_workspace.yaml")
default_branch..lookup(branch: str) → str
raw_mapping./*.feature/ → develop, release/ & hotfix/ → main..workspace_to_branch(workspace: str) → str
response.raise_for_status(), catch HTTPError around critical calls.print() statements for visibility during operations.pin/pin-analytics/pin-fusion-2.0).main as the default and protected branch in Settings → Repository → Branches. This serves as the Prod branch in our mapping.branch_to_workspace.yaml on main to create a 1:1 mapping of desired workspace to designated git branchesmapping:
main: PIN FUSION 2.0
develop: PIN FUSION 2.0 QA
sandbox: PIN FUSION 2.0 DEV
mapping: exact branch names.To leverage this GitLabManager, a proper AIC object needs to be created that will encapsulate the workspace in question as well as the pipelines required in the git operations.
To obtain AIC API key, log into AIC and in the top right corner, navigate to User Profile -> Manage API Keys
aic_prod = AIC(
api_key = "<API-KEY>",
project = "dylan.doyle@jdpa.com",
workspace = "PROD TEST",
pipelines = ['test_job']
)
The correct branch will be selected or created based on the workspace passed and the branch_to_workspace.yaml file. If the file is not found or workspace is not mapped, the branch will default to a formatted string from the workspace name,
NOTE: Operations will be completely limited to this workspace and only the pipelines passed. Add more pipelines as desired, or pass ['*'] to load ALL pipelines.
Create a GitLabManager instance and pass the previously created AIC object so that GitLab API can speak directly to the objects in the AIC workspace.
mgr = GitLabManager(
aic_instance = aic_prod,
gitlab_token = "<GIT_TOKEN>",
gitlab_namespace = "pin/pin-analytics",
repo_folder = "aic-git-integration"
)
TIP: Multiple instances can be created for multiple workspaces (i.e for both Prod and QA instances). Each AIC workspace must have its own AIC instance and respective GitLabManager instance.
mgr.push_to_git()
When:
develop or sandbox branchesrelease or feature branchWhat happens:
config/<pipeline>.json files are upserted via the GitLab API based on the AIC.pipelines list that was passed earlier.code/<pipeline>/.Restrictions:
main branch unless force_push=True is passed. This is to restrict production branches to release and hotfix merges primarily.Create sandbox objects
aic_sandbox = AIC(workspace='sandbox_workspace'...)
mgr = GitLabManager(aic_instance=aic_sandbox, ...)
Sync changes to sandbox
mgr.push_to_git() # push current AIC state so sandbox branch captures the changes
Cut a feature branch
mgr.create_feature_branch("PINOPS-1831") # sets mgr.branch to feature branch
Create merge request and resolve
mgr.create_merge_request(title="Demographics Module", description='Push for QA Testing', target_branch='develop')
Deploy
mgr.deploy_pipelines() # deploys merged pipelines to develop workspace
sandbox or other similar environments. This is not a feature release to production.feature/PINOPS-1831 off sandbox.config/ and code/ for the specified pipelines into that branch.main or develop branches; feature branch creation is only for promoting code from sandbox-like environments.Create develop objects
aic_qa = AIC(workspace='develop_workspace'...)
mgr = GitLabManager(aic_instance=aic_qa, ...)
Prepare QA
# Starting from develop branch/workspace, ensure AIC has updated code
mgr.push_to_git() # sync develop environment to capture new updates
Cut Release Branch
mgr.create_release_branch("PINOPS-1832") # sets mgr.branch to 'release/v2.1.0'
mgr.aic.pipeline_configs.Create Merge Request and Resolve
mgr.create_merge_request(
title="Release v2.1.0",
description="QA-approved changes for selected pipelines"
)
release/v2.1.0 → main.Deploy
mgr.deploy_pipelines() # deploys merged pipelines to production workspace
release/PINOPS-1831 off develop.config/ and code/ for the specified pipelines into that branch.Diagnose & Plan
Create main objects
aic_prod = AIC(workspace='prod_workspace', pipelines=['<PIPELINE_FROM_STEP1>'], ...)
mgr = GitLabManager(aic_instance=aic_prod, ...)
Create Hotfix Branch
mgr.create_hotfix_branch("PINOPS-1831")
# At this point, AIC prod environment should have the fix in place.
mgr.push_to_git() # Sync the updated code to the hotfix branch and NOT the production branch
hotfix/PINOPS-1831 off main and pushes the fix to hotfix branch.Prepare Hotfix branch
# Starting from develop branch/workspace, ensure AIC has updated code
mgr.push_to_git() # sync hotfix branch to current prod workspace
NOTE The workflow for a Hotfix is the opposite of feature/release branches. Because the change will be made in the AIC production workspace prior, the hotfix branch will be updatec with push_to_git() while the main branch on the repository remains 1 commit behind. This is to ensure that fixes exclusively in production can be captured by a merge request and appropriately audited.
Open & Merge MR
mgr.create_merge_request(
title="Hotfix PINOPS-1831",
description="Emergency fix: correct streaming timeout",
target_branch="main"
)
Deploy & Back-merge
hotfix/PINOPS-1831 off develop.
The production branch will not contain the hotfix at this point.main branchbranch_to_workspace.yaml. No cross‑territory deployments.main, develop, sandbox are protected; all changes flow via feature/*, release/*, or hotfix/*.FAQs
AIC API wrapper and GitLab integration framework for pipeline management
We found that aic-utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.